数据结构——顺序表(c语言笔记)
1.线性结构的特点
在数据元素的非空有限集中,(1)存在惟一的一个被称做“第一个”的数据元素;(2)存在惟一的一个被称做“最后一个”的数据元素;(3)除第一个之外,集合中的每个数据元素均只有一个前驱;(4)除最后一个之外,集合中每个数据元素均只有一个后继。
2.顺序表定义
抽象数据类型(ADT)线性表定义:
ADT List {
数据对象:D={ aᵢ | aᵢ ∈ ElemSet,i=1,2,…,n,n≥0 }
数据关系:R1={ <aᵢ₋₁,aᵢ> |aᵢ₋₁,aᵢ ∈ D,i=2,…,n }
基本操作:插入、删除等(以下会详细说明)}ADT List
特点 | 说明 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
存储方式 | 使用 一组连续的存储单元(数组) 按顺序存放元素 | 结构简单,容易实现 | 容量固定(静态),动态扩容需要复制数据,代价较大 | 数据量较小或变化不大 |
存取方式 | 支持 随机存取,可通过下标 O(1) 时间访问任意元素 | 查找第 i 个元素效率高 | 无法快速找到“值”所在位置(要顺序遍历 O(n)) | 频繁需要按下标访问元素 |
存储密度 | 每个元素都紧密存放,没有额外存储开销 | 空间利用率高 | 插入、删除需要移动大量元素,效率低 | 元素变动不频繁的场景 |
容量限制 | 静态:编译时确定大小;动态:需手动扩容(如 realloc) | 静态:内存管理简单 | 静态:可能浪费或不够用;动态:扩容代价高 | 元素数目可预测,或允许偶尔扩容 |
内存分布 | 连续存储,局部性好,CPU 缓存命中率高 | 访问速度快 | 必须有足够大的连续内存空间 | 内存较充足时 |
3.顺序表代码
main.cpp
int main()
{SeqList mylist; // 定义顺序表变量(具体结构体在其它文件中定义,通常包含数组指针、容量、当前长度等)InitSeqList(&mylist); // 初始化顺序表,分配内存或设置初始长度为0、容量等ELemType Item; // 用于存放用户输入的数据元素。int pos; // 用于存放位置(索引)输入int select = 1; // 菜单选项;初始化为1,使得第一次进入 while 循环。选择 0 则退出while (select) // 主循环:当 select 为 0 时退出(因为 while(0) 为假){/* 打印菜单(多行输出) */printf("**************************************\n");printf("* [1] push_back [2] push_front *\n"); printf("* [3] show_list [4] pop_back *\n");printf("* [5] pop_front [6] insert_pos *\n");printf("* [7] find [8] length *\n"); printf("* [9] delete_pos [10] delete_val *\n");printf("* [11] sort [12] resver *\n"); printf("* [13] clear [14*] destroy *\n"); /printf("* [0] quit_system *\n");printf("**************************************\n");printf("请选择:>");scanf_s("%d", &select); // 从 stdin 读取用户选择(注意:scanf_s 在 Visual C 环境下可用;最好检查返回值)if (select == 0) // 若用户输入 0,退出循环(跳出主菜单)break;switch (select) // 根据用户输入的选项进入不同分支{case 1:/* push_back:在表尾插入若干元素,直到输入 -1 结束 */printf("请输入要插入的数据(-1结束):>");/* 这里使用了逗号运算符:while (scanf_s("%d", &Item), Item != -1)等价于:scanf_s("%d", &Item); // 调用并丢弃返回值while (Item != -1) { ... }*/while (scanf_s("%d", &Item), Item != -1){push_back(&mylist, Item); // 将 Item 插入到顺序表末尾}break;case 2:/* push_front:在表头插入若干元素,直到输入 -1 结束 */printf("请输入要插入的数据(-1结束):>");while (scanf_s("%d", &Item), Item != -1){push_front(&mylist, Item); // 将 Item 插入到顺序表开头}break;case 3:/* show_list:显示顺序表中的所有元素 */show_list(&mylist);break;case 4:/* pop_back:删除表尾元素 */pop_back(&mylist);break;case 5:/* pop_front:删除表头元素 */pop_front(&mylist);break;case 6:/* insert_pos:在指定位置插入单个元素 */printf("请输入要插入的数据:>");scanf_s("%d", &Item); // 读取要插入的数据printf("请输入要插入的位置:>");scanf_s("%d", &pos); insert_pos(&mylist, pos, Item); // 插入函数应包含越界校验(例如 pos 范围)break;case 7:/* find:查找元素并返回下标(没找到可能返回 -1) */printf("请输入要查找的数据:>");scanf_s("%d", &Item);pos = find(&mylist, Item); // find 应返回索引或 -1if (pos == -1){printf("查找的数据%d在顺序表中不存在.\n", Item);}else{printf("查找的数据%d在顺序表中的%d下标位置.\n", Item, pos);}break;case 8:/* length:返回顺序表长度(元素个数) */printf("顺序表长度为:>%d\n", length(&mylist));break;case 9:/* delete_pos:按位置删除元素 */printf("请输入要删除数据的位置:>");scanf_s("%d", &pos);delete_pos(&mylist, pos);break;case 10:/* delete_val:按值删除 */printf("请输入要删除的数据:>");scanf_s("%d", &Item);delete_val(&mylist, Item);break;case 11:/* sort:对顺序表排序 */sort(&mylist);break;case 12:/* resver: 将顺序表反转 */resver(&mylist);break;case 13:/* clear:清空顺序表(置长度为0,但保留分配的内存) */clear(&mylist);break;// case 14:// // destroy(&mylist);// // break;/* 注意:case 14 被注释掉了,不能让用户去摧毁顺序表,因此放在最后,当用户退出时候直接自动摧毁 */default:printf("输入的选项错误,请重新输入。\n");break;}}destroy(&mylist); // 程序退出前释放顺序表占用的资源(例如 free 动态数组),以防内存泄漏
}
Seqlist.h
#ifndef __SEQLIST_H__
#define __SEQLIST_H__#include<stdio.h> // 用于 printf/scanf 等 I/O(在头文件中包含 stdio 通常没问题,但建议在实现文件里包含)
#include <malloc.h> // 提供 malloc/realloc/free 声明
#include <cassert> // C++ 里的断言头#define SEQLIST_INIT_SIZE 8 /* 初始分配容量(元素个数) */
#define INC_SIZE 3 /* 每次扩容时增加的容量(固定增量策略) */typedef int ELemType; /* 元素类型定义 */typedef struct SeqList
{ELemType* base; /* 指向动态数组的指针(由 InitSeqList 分配) */int capacity;/* 当前数组的总容量(能装多少元素) */int size; /* 当前已存放的元素数量(有效元素个数) */
}SeqList;/* 扩容函数功能:确保顺序表至少还能容纳更多元素,通常在 push 时调用。返回:bool(true 表示扩容成功或已有足够空间;false 表示分配失败)
*/
bool Inc(SeqList* list);/* 初始化顺序表*/
void InitSeqList(SeqList* list);/* 在尾部插入元素 x*/
void push_back(SeqList* list, ELemType x);/* 在头部插入元素 x*/
void push_front(SeqList* list, ELemType x);/* 遍历并显示顺序表*/
void show_list(SeqList* list);/* 删除并丢弃尾部元素(pop)*/
void pop_back(SeqList* list);/* 删除并丢弃头部元素*/
void pop_front(SeqList* list);/* 在指定位置 pos 插入元素 x*/
void insert_pos(SeqList* list,int pos, ELemType x);/* 查找值为 key 的元素,返回下标(约定:找不到返回 -1)*/
int find(SeqList* list, ELemType key);/* 返回当前顺序表的长度(元素个数)*/
int length(SeqList* list);/* 按位置删除元素(pos 索引约定同 insert_pos)*/
void delete_pos(SeqList* list,int pos);/* 按值删除(常见有两种实现)*/
void delete_val(SeqList* list, ELemType key);/* 排序(sort)*/
void sort(SeqList* list);/* 反转顺序表(resver)*/
void resver(SeqList* list);/* 清空顺序表(clear)*/
void clear(SeqList* list);/* 销毁顺序表(destroy)- 功能:释放动态分配的内存(free(list->base)),并把 base 置 NULL,capacity 和 size 置 0(或其它哨值)。- 销毁后不要再使用该 SeqList 除非再次 Init。
*/
void destroy(SeqList* list);/* 合并两个顺序表(merge)*/
void merge(SeqList* lt, SeqList* la, SeqList* lb);#endif // __SEQLIST_H__
SeqList.cpp
bool Inc(SeqList* list)
{ELemType* newbase = (ELemType*)realloc(list->base, sizeof(ELemType) * (list->capacity + INC_SIZE));if (newbase == NULL){printf("增配空间失败,内存不足.\n");return false;}list->base = newbase;list->capacity += INC_SIZE;return true;
}
目的
为顺序表增加容量(当前实现为 capacity += INC_SIZE)。
参数
list:目标顺序表指针(要求非 NULL)。
前置条件
list != NULL。list->base 可为 NULL(realloc(NULL, size) 等同 malloc)。
后置条件
若返回 true:list->base 已更新到新地址(可能移动),list->capacity 增加;若 false:内存未改变,原 list->base 仍有效。
实现步骤(当前代码)
调用 realloc(list->base, sizeof(ELemType)*(list->capacity+INC_SIZE)),把返回值存在 newbase。
若 newbase == NULL,打印错误并返回 false(此时原内存未被释放)。
否则将 list->base = newbase 并 list->capacity += INC_SIZE,返回 true。
void InitSeqList(SeqList* list)
{list->base = (ELemType*)malloc(sizeof(ELemType) * SEQLIST_INIT_SIZE);assert(list->base != NULL);list->capacity = SEQLIST_INIT_SIZE;list->size = 0;
}
目的
初始化顺序表结构:分配初始内存、设置 capacity 和 size。
实现步骤
malloc 分配 SEQLIST_INIT_SIZE 个元素的内存。
assert(list->base != NULL)(当前实现)——断言失败会终止程序(仅在调试有效)。
设置 list->capacity = SEQLIST_INIT_SIZE,list->size = 0。
void push_back(SeqList* list, ELemType x)
{if (list->size >= list->capacity && !Inc(list)){printf("顺序表空间已满,不能尾部插入数据.\n");return;}list->base[list->size] = x;list->size++;
}
目的
在顺序表尾部插入元素 x。
实现步骤
检查 list->size >= list->capacity,若是则调用 Inc(list) 扩容;扩容失败时打印错误并返回。
将 list->base[list->size] = x,然后 list->size++。
void push_front(SeqList* list, ELemType x)
{if (list->size >= list->capacity && !Inc(list)){printf("顺序表空间已满,不能尾部插入数据.\n");return;}for (int i = list->size; i > 0; --i){list->base[i] = list->base[i - 1];}list->base[0] = x;list->size++;
}
目的
在顺序表头部插入元素(把现有元素整体右移一位)。
实现步骤
若空间不足,调用 Inc(list) 扩容。
从 i = size 到 1 逐项右移:base[i] = base[i-1]。
base[0] = x,size++。
void show_list(SeqList* list)
{for (int i = 0; i < list->size; ++i){printf("%d", list->base[i]);}printf("\n");
}
目的
把顺序表元素打印到标准输出,供调试或查看用。
实现步骤
遍历 i=0..size-1,printf("%d", base[i]),最后换行。
void pop_back(SeqList *list)
{if (list->size==0){printf("顺序表已空,不能尾部删除数据.\n");return;}list->size--;
}
目的
删除尾元素(不返回该元素)。
实现步骤
若 size == 0,打印“顺序表已空”并返回。
否则 size--(可选择清零 base[size])。
void pop_front(SeqList* list)
{if (list->size == 0){printf("顺序表已空,不能尾部删除数据.\n");return;}for (int i = 0; i < list->size-1; ++i){list->base[i] = list->base[i + 1];}list->size--;
}
目的
删除头元素并将后续元素前移一位。
实现步骤
检查 size 是否为 0。
将 base[1..size-1] 左移到 base[0..size-2],size--。
void insert_pos(SeqList* list, int pos, ELemType x)
{if (pos<0 || pos>list->size){printf("插入数据的位置非法,不能插入数据.\n");return;}for (int i = list->size; i > pos; --i){list->base[i] = list->base[i - 1];}list->base[pos] = x;list->size++;
}
目的
在下标 pos(约定为 0..size)处插入元素 x,插入后 x 的索引为 pos。
实现步骤
检查 pos 是否在合法范围 0 <= pos <= size,否则打印错误并返回。
从 i = size 到 pos+1 右移元素(base[i] = base[i-1])。
base[pos] = x,size++。
int find(SeqList* list, ELemType key)
{for (int i = 0; i < list->size; ++i){if (list->base[i] == key)return i;}return -1;
}
目的
线性查找第一个等于 key 的元素,下标从 0 开始,找不到返回 -1。
实现步骤
遍历 i=0..size-1,比较 base[i] == key,命中返回 i。
int length(SeqList* list)
{return list->size;
}
目的
返回当前元素个数 size。
实现步骤
直接 return list->size;
void delete_pos(SeqList* list, int pos)
{if (pos<0||pos>=list->size){printf("删除数据的位置非法,不能删除数据.\n"); return;}for (int i = pos; i < list->size - 1; ++i){list->base[i] = list->base[i + 1];}list->size--;
}
目的
删除下标为 pos 的元素并把后面的元素左移填补空位。
实现步骤
检查 pos 是否在 0 <= pos < size,否则打印错误并返回。
从 i = pos 到 size - 2 执行 base[i] = base[i+1]。
size--。
void delete_val(SeqList* list, ELemType key)
{int pos = find(list, key);if (pos==-1){printf("要删除的数据不存在.\n"); return;}delete_pos(list, pos);
}
目的
删除第一次出现的值为 key 的元素(如果存在)。
实现步骤
调用 find(list, key) 获取下标 pos。
若 pos == -1 打印不存在并返回。
调用 delete_pos(list, pos)。
void sort(SeqList* list)
{for (int i = 0; i < list->size - 1; ++i){for (int j = 0; j < list->size - i - 1; ++j){if (list->base[j] > list->base[j + 1]){ELemType tmp = list->base[j];list->base[j] = list->base[j + 1];list->base[j + 1] = tmp;}}}
}
目的
将顺序表元素排序(当前实现为冒泡排序,升序)。
实现步骤(当前代码)
嵌套两层循环(冒泡):交换相邻逆序对直到整体有序。
void resver(SeqList* list)
{if (list->size == 0 || list->size == 1)return;int low = 0;int high = list->size - 1;ELemType tmp;while (low < high){tmp = list->base[low];list->base[low] = list->base[high];list->base[high] = tmp;low++;high--;}
}
目的
就地反转顺序表元素顺序。
实现步骤
若 size <= 1 直接返回。
用 low=0, high=size-1,交换 base[low] 和 base[high],low++,high--,直到 low >= high。
void clear(SeqList* list)
{list->size = 0;
}
目的
逻辑上清空顺序表(把 size 置 0),但保留 base 内存以供复用。
实现步骤
list->size = 0;
void destroy(SeqList* list)
{free(list->base);list->base = NULL;list->capacity = 0;list->size = 0;
}
目的
销毁顺序表:释放 base 指向的动态内存并将结构复位。
实现步骤
free(list->base)(如果 base == NULL,free 是安全的)。
list->base = NULL; list->capacity = 0; list->size = 0;
4..双指针合并两个顺序表
main.cpp
#include"SeqList.h"int main()
{SeqList mylist, youlist, list;InitSeqList(&mylist);InitSeqList(&youlist);push_back(&mylist, 1);push_back(&mylist, 3);push_back(&mylist, 5);push_back(&mylist, 7);push_back(&mylist, 9);push_back(&youlist, 2);push_back(&youlist, 4);//push_back(&youlist,6);push_back(&youlist, 8);//push_back(&youlist,10);merge(&list, &mylist, &youlist);show_list(&list);
}
void merge(SeqList* lt, const SeqList* la, const SeqList* lb) {if (!lt || !la || !lb) return;int total = la->size + lb->size;if (lt->base) free(lt->base); // 先释放已有内存,避免泄露lt->base = malloc(total * sizeof(ELemType));if (!lt->base) { perror("malloc"); lt->capacity = lt->size = 0; return; }lt->capacity = total;int ia=0, ib=0, ic=0;while (ia < la->size && ib < lb->size) {if (la->base[ia] <= lb->base[ib]) lt->base[ic++] = la->base[ia++];else lt->base[ic++] = lb->base[ib++];}while (ia < la->size) lt->base[ic++] = la->base[ia++];while (ib < lb->size) lt->base[ic++] = lb->base[ib++];lt->size = ic;
}
目的
将两个有序顺序表 la、lb 合并为 lt(有序合并),常用于归并排序的合并步骤或合并两个已排序数组。
实现步骤
设 lt->capacity = la->size + lb->size。
lt->base = malloc(...)(没有先释放原 lt->base)。
使用双指针 ia, ib 遍历 la 和 lb,把较小元素依次写入 lt->base[ic++]。
将剩余元素追加。
lt->size = la->size + lb->size;