2026/1/10 13:30:38
网站建设
项目流程
e福州官方网站,网站网络营销方案,wordpress插件汉化包,fevr wordpress一、线性表线性表是n 个数据类型相同的元素组成的有限序列#xff08;n≥0#xff0c;n0 时叫 “空表”#xff09;#xff08;1#xff09;特点有唯一的 “第一个元素” 和 “最后一个元素”除第一个元素外#xff0c;每个元素只有一个前驱#xff1b;除最后一个元素外…一、线性表线性表是n 个数据类型相同的元素组成的有限序列n≥0n0 时叫 “空表”1特点有唯一的 “第一个元素” 和 “最后一个元素”除第一个元素外每个元素只有一个前驱除最后一个元素外每个元素只有一个后继2按存储方式分类1. 顺序存储结构 → 顺序表定义用连续的内存空间存储线性表的元素逻辑上相邻的元素物理地址也相邻。底层实现基于数组实现分为静态顺序表和动态顺序表。静态顺序表使用固定长度的数组容量不可变。动态顺序表使用动态分配的数组如 C 语言的malloc/realloc容量可按需扩容。特点支持随机访问通过下标直接访问时间复杂度 O(1插入、删除需要移动大量元素 时间复杂度 O(n)2. 链式存储结构 → 链表定义用任意的内存空间存储元素元素节点之间通过指针 / 引用链接逻辑相邻的元素物理地址不一定相邻。底层实现基于节点结构体实现节点包含数据域和指针域。特点不支持随机访问只能从头节点顺序遍历 时间复杂度 O(n)插入、删除只需修改指针 时间复杂度 O(1)二、顺序表1. 顺序表的定义顺序表是用连续的内存空间存储线性表元素的结构 —— 逻辑上相邻的元素物理存储位置也相邻。2. 顺序表的存储结构先拆解代码里的关键概念#define MAXSIZE 100定义顺序表的最大容量typedef int ElemType给数据类型起别名这里用int举例可替换为其他类型struct结构体包含两个核心成员data[MAXSIZE]存储元素的数组length记录顺序表当前的实际长度// 定义顺序表的最大容量 #define MAXSIZE 100 // 定义元素的数据类型这里用int可根据需求修改 typedef int ElemType; // 定义顺序表的结构体静态分配版 typedef struct { ElemType data[MAXSIZE]; // 存储元素的数组 int length; // 顺序表当前的实际长度 } SeqList; // SeqList是这个结构体的别名方便后续使用3. 顺序表的相关操作1初始化顺序表静态分配版初始化作用把顺序表初始化为 “空表”长度设为 0// 初始化顺序表静态分配版参数是顺序表的指针 void initList(SeqList *L) { L-length 0; // 空表的长度为0 }动态分配内存初始化作用通过malloc动态申请内存更灵活地管理顺序表空间// 定义顺序表的结构体动态分配版 typedef struct { ElemType *data; // 指向动态分配数组的指针 int length; // 顺序表当前的实际长度 } SeqList; // 初始化顺序表动态分配版返回初始化后的顺序表指针 SeqList* initList(SeqList *L) { // 为顺序表结构体本身分配内存 L (SeqList*)malloc(sizeof(SeqList)); // 为存储元素的数组分配内存容量为MAXSIZE L-data (ElemType*)malloc(sizeof(ElemType) * MAXSIZE); L-length 0; // 初始化为空表 return L; }2在尾部添加元素作用在顺序表的末尾插入新元素要先判断表是否已满// 在顺序表尾部添加元素e成功返回1失败返回0 int appendElem(SeqList *L, ElemType e) { // 先判断如果当前长度≥最大容量表已满 if (L-length MAXSIZE) { printf(顺序表已满\n); return 0; // 失败返回0 } // 把元素e存到数组的“当前长度”位置因为数组从0开始 L-data[L-length] e; L-length; // 长度1 return 1; // 成功返回1 }3遍历顺序表作用打印顺序表中所有元素// 遍历顺序表打印所有元素 void listElem(SeqList *L) { // 循环从0到length-1因为数组下标从0开始 for (int i 0; i L-length; i) { printf(%d , L-data[i]); // 打印第i个元素 } printf(\n); // 换行 }4在指定位置插入元素作用在第pos个位置插入元素 e注意pos 是 “逻辑位置”要转成数组的 “物理下标”// 在第pos个位置插入元素e成功返回1 int insertElem(SeqList *L, int pos, ElemType e) { // 先判断pos的范围是否合法不能超过当前长度 if (pos L-length) { // 从最后一个元素开始向后移动一位给新元素腾位置 for (int i L-length-1; i pos-1; i--) { L-data[i1] L-data[i]; } // 把e存到pos对应的下标pos-1因为数组从0开始 L-data[pos-1] e; L-length; // 长度1 } return 1; }5删除指定位置的元素作用删除第pos个位置的元素并通过指针e返回被删除的元素值// 删除第pos个位置的元素用e返回被删除的值成功返回1 int deleteElem(SeqList *L, int pos, ElemType *e) { // 先把要删除的元素存到e中pos转成数组下标pos-1 *e L-data[pos-1]; // 判断pos是否合法不能超过当前长度 if (pos L-length) { // 从pos位置开始向前移动元素覆盖被删除的位置 for (int i pos; i L-length; i) { L-data[i-1] L-data[i]; } } L-length--; // 长度减1 return 1; }6查找元素作用查找元素e在顺序表中的逻辑位置找不到返回 0// 查找元素e返回其逻辑位置从1开始找不到返回0 int findElem(SeqList *L, ElemType e) { // 遍历顺序表所有元素 for (int i 0; i L-length; i) { if (L-data[i] e) { return i 1; // 逻辑位置数组下标1 } } return 0; // 没找到返回0 }三、顺序表总结顺序表是线性表的基础实现方式优点是随机访问快通过数组下标直接访问缺点是插入 / 删除元素时需要移动数据效率较低四、链表链表是线性表的链式存储结构核心特点是用任意存储单元连续 / 不连续存储元素每个元素称为节点 node包含两部分数据域存储元素本身信息指针域存储下一个节点的地址即 “链”n 个节点通过指针域链接成一个链表代表线性表的逻辑结构五、单向链表的存储结构链表的基本单元是 “节点”用结构体定义// 定义元素的数据类型这里用int举例 typedef int ElemType; // 定义链表节点的结构体 typedef struct node{ ElemType data; // 数据域存储元素值 struct node *next; // 指针域存储下一个节点的地址 }Node; // Node是结构体的别名简化后续使用关键概念struct node自定义结构体类型用于描述链表的单个节点ElemType data数据域存储节点的实际数据可替换为 char、float 等类型struct node *next指针域本质是一个指向struct node类型的指针用于链接下一个节点Node通过typedef给结构体起的别名后续定义节点时可直接用Node代替struct node六、单向链表的相关操作1初始化链表作用创建一个头节点作为链表的起始标识数据域可存空值或链表长度初始化为空链表// 初始化链表返回头节点的指针 Node* initList() { // 1. 为头节点分配内存malloc函数动态申请空间 Node *head (Node*)malloc(sizeof(Node)); // 2. 头节点数据域赋值可存0或链表长度空链表时暂存0 head-data 0; // 3. 空链表时头节点的next指针指向NULL表示无后续节点 head-next NULL; return head; // 返回头节点地址后续操作通过头节点访问链表 } // 调用示例main函数中使用 int main() { Node *list initList(); // 得到初始化后的空链表 return 1; }2头插法在链表头部插入元素作用新元素插入到头节点之后链表的第一个有效节点位置插入顺序与最终存储顺序相反比如先插 10 再插 20链表为头→20→10// 头插法向链表L头节点指针中插入元素e int insertHead(Node* L, ElemType e) { // 1. 为新节点分配内存每个新元素都需要单独的节点空间 Node *p (Node*)malloc(sizeof(Node)); // 2. 给新节点的data域赋值存储要插入的元素 p-data e; // 3. 新节点的next指向头节点原来的next即原第一个有效节点 p-next L-next; // 4. 头节点的next指向新节点完成新节点的插入 L-next p; return 1; // 插入成功返回1 } // 调用示例 int main() { Node *list initList(); insertHead(list, 10); // 插入元素10 insertHead(list, 20); // 插入元素20最终链表头节点→20→10 return 1; }3遍历链表作用打印链表中所有有效元素跳过头节点只输出数据域内容// 遍历链表L打印所有有效元素 void listNode(Node* L) { // p指向第一个有效节点头节点的next跳过头节点本身 Node *p L-next; // 循环条件p不为NULL未到达链表末尾 while(p ! NULL) { printf(%d , p-data); // 打印当前节点的data值 p p-next; // p指向下一个节点继续遍历 } printf(\n); // 换行优化输出格式 } // 调用示例接上面main函数 int main() { Node *list initList(); insertHead(list, 10); insertHead(list, 20); listNode(list); // 输出结果20 10 return 1; }4尾插法更高效的尾部插入实现作用直接传入当前尾节点指针避免重复遍历链表找尾提升插入效率// 尾插法优化版传入当前尾节点tail插入元素e返回新的尾节点 Node* insertTail(Node *tail, ElemType e) { // 1. 为新节点分配内存 Node *p (Node*)malloc(sizeof(Node)); // 2. 新节点数据域赋值 p-data e; // 3. 原尾节点的next指向新节点 tail-next p; // 4. 新节点的next指向NULL作为新的尾节点 p-next NULL; return p; // 返回新的尾节点供下一次插入使用 } // 调用示例 int main() { Node *list initList(); Node *tail list; // 初始尾节点是头节点 tail insertTail(tail, 10); // 插入10更新尾节点 tail insertTail(tail, 20); // 插入20更新尾节点 listNode(list); // 输出结果10 20 return 1; }5指定位置插入元素作用在链表的第pos个有效节点位置插入元素逻辑位置从 1 开始。原理找到目标位置的前驱节点将新节点的next指向前驱节点的原next再将前驱节点的next指向新节点// 在第pos个位置插入元素e成功返回1失败返回0 int insertNode(Node *L, int pos, ElemType e) { Node *p L; // p指向头节点用于找前驱节点 int i 0; // 记录当前位置头节点为0 // 1. 找到第pos-1个节点目标位置的前驱节点 while(i pos-1) { p p-next; i; // 若p为空说明pos超出链表长度 if (p NULL) { return 0; } } // 2. 为新节点分配内存并赋值 Node *q (Node*)malloc(sizeof(Node)); q-data e; // 3. 新节点的next指向前驱节点的原next q-next p-next; // 4. 前驱节点的next指向新节点 p-next q; return 1; } // 调用示例 int main() { Node *list initList(); insertTail(list, 70); insertTail(list, 80); insertNode(list, 2, 75); // 在第2个位置插入75 listNode(list); // 输出结果70 75 80 return 1; }6删除指定位置的节点作用删除第pos个有效节点释放节点内存避免泄漏步骤找到待删除节点的前驱节点用指针记录待删除节点前驱节点的next指向待删除节点的next释放待删除节点的内存// 删除第pos个节点成功返回1失败返回0 int deleteNode(Node *L, int pos) { Node *p L; // p指向头节点找前驱节点 int i 0; // 记录当前位置头节点为0 // 1. 找到第pos-1个节点前驱节点 while(i pos-1) { p p-next; i; if (p NULL) { return 0; } } // 2. 若前驱节点的next为空说明pos超出范围 if(p-next NULL) { printf(要删除的位置错误\n); return 0; } // 3. 记录待删除节点 Node *q p-next; // 4. 前驱节点的next跳过待删除节点 p-next q-next; // 5. 释放待删除节点的内存 free(q); return 1; } // 调用示例 int main() { Node *list initList(); insertTail(list, 70); insertTail(list, 80); deleteNode(list, 1); // 删除第1个节点70 listNode(list); // 输出结果80 return 1; }7释放链表内存作用销毁链表释放所有节点的内存避免内存泄漏// 释放链表所有节点的内存 void freeList(Node *L) { Node *p L-next; // p指向第一个有效节点 Node *q; // 记录下一个节点的地址 // 遍历链表逐个释放节点 while(p ! NULL) { q p-next; // 先记录下一个节点 free(p); // 释放当前节点 p q; // p指向下一个节点 } L-next NULL; // 头节点的next置空链表恢复为空 } // 调用示例 int main() { Node *list initList(); insertTail(list, 70); insertTail(list, 80); freeList(list); // 释放链表 listNode(list); // 无输出链表已空 return 1; }8获取链表长度作用统计链表中有效节点的个数跳过头节点// 获取链表的有效节点个数 int listLength(Node *L) { Node *p L; // p从头节点开始 int len 0; // 记录长度 while(p ! NULL) { p p-next; // 向后移动 len; // 计数1 } return len - 1; // 减去头节点的计数 } // 调用示例 int main() { Node *list initList(); insertTail(list, 70); insertTail(list, 80); printf(链表长度%d\n, listLength(list)); // 输出2 return 1; }七、链表与顺序表的区别对比维度顺序表数组实现链表链式实现存储方式连续的内存空间任意内存空间节点通过指针链接访问效率随机访问快O (1)直接通过下标访问只能顺序访问O (n)需从头遍历插入 / 删除效率需移动大量元素O (n)只需修改指针O (1)无需移动元素空间灵活性固定容量静态/ 需扩容动态按需分配内存空间利用率更高适用场景频繁访问数据、少量插入删除频繁插入删除、数据量不固定概念补充时间复杂度 O (1)操作时间与数据量无关比如顺序表下标访问时间复杂度 O (n)操作时间随数据量增长而线性增长比如链表遍历