当前位置: 首页 > news >正文

C语言——链表的基础操作

引言

链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。链表有一个 头指针 变量,它存放一个地址,该地址指向一个元素,链表中每一个元素称为 结点,每个结点都应包括两个部分,一为用户需要用的实际数据,二为下一个结点的地址。可以看出,头指针 head 指向第一个元素,第一个元素又指向第二个元素…………直到最后一个元素,该元素不再指向其他元素,它称为 表尾,它的地址部分放一个 NULL(表示 空地址)链表到此结束。

基础操作代码结构

  • 链表节点结构体定义
  • 节点创建函数
  • 插入操作(头插法、尾插法、指定位置插入)
  • 删除操作(按值删除)
  • 查找操作(按值查位置、按位置查值)
  • 其他操作(替换元素、链表倒置、打印、清空)
  • 主函数(测试与交互)

1. 链表节点结构体定义

struct Node {int data;struct Node *next;
};
  • 这是链表的基本组成单位 —— 节点的结构体定义
  • 每个节点包含两个成员:
    • data:存储节点的数据
    • next:是指向 struct Node 类型的指针,用于指向链表中的下一个节点

图示逻辑

一个节点就像一节 "火车车厢",data是车厢里的货物,next是连接下一节车厢的挂钩。

+----------+
|  data    |  // 比如存一个整数 10
+----------+
|  next    |  // 指向 NULL(暂时没有下一节车厢)
+----------+
|
v
NULL

2. 节点创建函数

// 创建一个节点函数
Node *createNode(int data) {Node *newNode = (Node *)malloc(sizeof(Node));if (newNode == NULL) {return NULL;}newNode->data = data;newNode->next = NULL;return newNode;
}

  • 功能:创建一个新的链表节点,并初始化其数据
  • 步骤:
    1. 使用malloc函数分配一块大小为Node结构体的内存空间
    2. 检查内存分配是否成功(newNode == NULL表示分配失败)
    3. 给新节点的data成员赋值
    4. 将新节点的next指针初始化为NULL
    5. 返回新创建的节点指针

图示逻辑

eg.创建一个存储数据10的节点:

申请内存前:
newNode -> NULL(空指针)

申请内存并初始化后:
newNode -> +----------+
|  data=5  |
+----------+
|  next=NULL |
+----------+

3. 插入操作

3.1 头插法

// 头插法
Node *addFirst(int data, Node *head) {struct Node *newNode = createNode(data);if (newNode == NULL) {printf("创建失败,内存分配失败\n");return head;}newNode->next = head;return newNode;
}
  • 功能:在链表的头部插入一个新节点
  • 步骤:
    1. 调用createNode函数创建新节点
    2. 检查节点创建是否成功
    3. 将新节点的next指针指向原来的头节点(head
    4. 返回新节点作为链表新的头节点
图示逻辑

原链表:head -> A -> B -> NULL
头插新节点C后:
newNode(C) -> next = head(A)
新链表:newNode(C) -> A -> B -> NULL

步骤1:创建新节点
newNode -> +----------+
|  data=0  |
+----------+
|  next=?  |  // 暂时不知道指向哪里
+----------+

步骤2:新节点指向原头节点
newNode -> +----------+
|  data=0  |
+----------+
|  next=head
+----------+
|
v
head -> +----------+    +----------+    +----------+
|  data=1  |    |  data=2  |    |  data=3  |
+----------+    +----------+    +----------+
|  next----+--->|  next----+--->|  next=NULL
+----------+    +----------+    +----------+

步骤3:新节点成为新的 head
newNode 变成新 head -> +----------+
|  data=0  |
+----------+
|  next----+--->+----------+
+----------+    |  data=1  |
+----------+

   |  next----+--->...
+----------+

运行结果:

3.2 尾插法

// 尾插法
Node *add(int data, Node *head) {Node *newNode = createNode(data);if (newNode == NULL) {printf("创建失败,内存分配失败\n");return head;}if (head == NULL) {return newNode;}Node *temp = head;while (temp->next != NULL) {temp = temp->next;}temp->next = newNode;return head;
}
  • 功能:在链表的尾部插入一个新节点
  • 步骤:
    1. 创建新节点并检查是否成功
    2. 特殊情况处理:如果链表为空(head == NULL),直接返回新节点作为头节点
    3. 遍历链表找到最后一个节点:
      • 使用临时指针temp从头部开始移动
      • 循环条件temp->next != NULL表示还没到最后一个节点
    4. 将最后一个节点的next指针指向新节点
    5. 返回原头节点(尾插不改变头节点)
图示逻辑

原链表:head -> A -> B -> NULL
尾插新节点C后:
找到最后一个节点B,令B->next = newNode(C)
新链表:head -> A -> B -> C -> NULL

步骤1:创建新节点
newNode -> +----------+
|  data=4  |
+----------+
|  next=NULL
+----------+

步骤2:找到最后一个节点(3的节点)
temp 从 head 开始移动:
temp -> 1 -> 2 -> 3(此时 temp->next 是 NULL,停止移动)

步骤3:最后一个节点指向新节点
+----------+    +----------+
|  data=3  |    |  data=4  |
+----------+    +----------+
|  next----+--->|  next=NULL
+----------+    +----------+

运行结果:

3.3 指定位置插入

// 插入元素
Node *insert(int index, int data, Node *head) {Node *newNode = createNode(data);if (newNode == NULL) {printf("创建失败,内存分配失败\n");return head;}if (head == NULL) {return newNode;}int count = 0;Node *temp = head;while (temp->next != NULL) {if (count == index) {newNode->next = temp->next;temp->next = newNode;break;}temp = temp->next;count++;}if (count == index) {temp->next = newNode;} else {printf("输入的位置不存在\n");}return head;
}
  • 功能:在链表的指定位置插入新节点
  • 步骤:
    1. 创建新节点并检查是否成功
    2. 特殊情况处理:如果链表为空,直接返回新节点
    3. 遍历链表寻找插入位置:
      • 使用count计数器记录当前位置
      • count等于目标index时,执行插入操作
    4. 插入操作核心:
      • 新节点的next指向当前节点的nextnewNode->next = temp->next
      • 当前节点的next指向新节点(temp->next = newNode
    5. 处理插入位置在链表尾部的情况
    6. 如果位置不存在,打印提示信息
图示逻辑:

现在在索引1处插入数据10

步骤1:创建新节点(10)
newNode -> +-----------+
|  data=10  |
+-----------+
|  next=?   |
+-----------+

步骤2:找到插入位置的前一个节点(索引0的节点,即1的节点)
temp 指向 1 的节点(因为 index-1=0)

步骤3:插入新节点
先让 newNode 指向 temp 的下一个节点(2的节点):
newNode->next = temp->next
+-----------+    +----------+
|  data=10  |    |  data=2  |
+-----------+    +----------+
|  next----+--->|  next----+--->3
+-----------+    +----------+

再让 temp 指向 newNode:
temp->next = newNode
+----------+    +-----------+    +----------+
|  data=1  |    |  data=10  |    |  data=2  |
+----------+    +-----------+    +----------+
|  next----+--->|  next----+--->|  next----+--->3
+----------+    +-----------+    +----------+

运行结果:

4. 删除操作

// 删除元素(按值删除)
struct Node *deleteByE(int data, struct Node *head) {if (head == NULL) {printf("链表为空,无法删除\n");return head;}// 处理头节点是目标节点的情况if (head->data == data) {struct Node *temp = head;head = head->next;free(temp);printf("删除成功\n");return head;}struct Node *prev = head;struct Node *curr = head->next;while (curr != NULL) {if (curr->data == data) {prev->next = curr->next;free(curr);printf("删除成功\n");return head;}prev = curr;curr = curr->next;}printf("元素不存在,无法删除\n");return head;
}
  • 功能:删除链表中第一个值为data的节点
  • 步骤:
    1. 特殊情况处理:如果链表为空,直接返回
    2. 处理头节点就是目标节点的情况:
      • 用临时指针保存头节点
      • 更新头节点为原头节点的下一个节点
      • 释放原头节点的内存
    3. 处理其他位置的节点:
      • 使用两个指针prev(前一个节点)和curr(当前节点)遍历
      • 找到目标节点后,将前一个节点的next指向当前节点的下一个节点
      • 释放当前节点的内存
    4. 如果遍历完链表都没找到目标节点,打印提示信息
图示逻辑
原链表:A -> B -> C -> NULL
删除B节点:
prev(A)->next = curr(B)->next(C)
释放B节点内存
新链表:A -> C -> NULL

现在删除值为10的节点

步骤1:定义 prev 和 curr
prev 指向 1 的节点,curr 指向 10 的节点

步骤2:找到要删除的节点(curr 就是 10 的节点)
+----------+    +-----------+    +----------+
|  data=1  |    |  data=10  |    |  data=2  |
+----------+    +-----------+    +----------+
|  next----+--->|  next----+--->|  next----+--->3
+----------+    +-----------+    +----------+
prev             curr

步骤3:删除节点
prev->next = curr->next(1的节点直接指向2的节点)
+----------+                     +----------+
|  data=1  |                     |  data=2  |
+----------+                     +----------+
|  next----+---------------------+  next----+--->3
+----------+                     +----------+

释放 curr 指向的节点(10的节点被释放)

运行结果:

5. 查找操作

5.1 按值查找位置

// 根据元素查找位置
int getByE(int v, Node *head) {if (head == NULL) {printf("链表为空,不存在任何数据\n");return -1;}int index = 0;int f = -1;Node *temp = head;while (temp->next != NULL) {if (temp->data == v) {f = 1;break;}temp = temp->next;index++;}if (f == 1) {return index;} else {return f;}return index;
}
  • 功能:查找指定值在链表中的位置
  • 步骤:
    1. 特殊情况处理:链表为空时返回 - 1
    2. 初始化索引index为 0,标志位f为 - 1(表示未找到)
    3. 遍历链表:
      • 若找到目标值,将标志位f设为 1 并跳出循环
      • 否则移动到下一个节点,索引加 1
    4. 再根据标志位返回索引或 - 1(未找到)
图示逻辑:

链表:1 -> 2 -> 3 -> 4 -> NULL

查找值3的位置:

index=0,temp指向1:不匹配
index=1,temp指向2:不匹配
index=2,temp指向3:匹配,返回2

运行结果:

5.2 按位置查找值

// 根据位置查找元素
int getByIndex(int index, struct Node *head) {if (head == NULL) {printf("链表为空\n");return -1;}if (index < 0) {printf("位置不能为负数\n");return -1;}struct Node *temp = head;int currIndex = 0;while (temp != NULL) {if (currIndex == index) {return temp->data;}temp = temp->next;currIndex++;}printf("位置超出链表长度\n");return -1;
}
  • 功能:查找链表中指定索引位置的元素值
  • 步骤:
    1. 特殊情况处理:
      • 链表为空时返回 - 1
      • 索引为负数时返回 - 1(位置无效)
    2. 遍历链表:
      • 使用currIndex记录当前位置
      • currIndex等于目标index时,返回当前节点的数据
    3. 如果遍历完链表都没找到对应位置,打印提示信息并返回 - 1
图示逻辑:

链表:1 -> 2 -> 3 -> 4 -> NULL

查找索引2的值:

currIndex=0,temp指向1:不匹配
currIndex=1,temp指向2:不匹配
currIndex=2,temp指向3:匹配,返回3

运行结果:

6.替换操作

// 根据元素替换元素
void replaceByE(int oldVal, int newVal, struct Node *head) {if (head == NULL) {printf("链表为空,无法替换\n");return;}struct Node *temp = head;while (temp != NULL) {if (temp->data == oldVal) {temp->data = newVal;printf("替换成功\n");return;}temp = temp->next;}printf("元素不存在,无法替换\n");
}
  • 功能:将链表中第一个值为oldVal的节点替换为newVal
  • 步骤:
    1. 特殊情况处理:链表为空时直接返回
    2. 遍历链表查找值为oldVal的节点
    3. 找到后更新该节点的datanewVal并返回
    4. 未找到则打印提示信息
运行结果:

7.链表倒置

// 倒置链表
Node *reverseList(struct Node *head) {if (head == NULL || head->next == NULL) {return head;  // 空链表或单节点直接返回}struct Node *prev = NULL;struct Node *curr = head;struct Node *next = NULL;while (curr != NULL) {next = curr->next;  // 保存下一个节点curr->next = prev;  // 反转当前节点指针prev = curr;        // 前指针后移curr = next;        // 当前指针后移}printf("链表倒置完成\n");return prev;  // 新的头节点是原尾节点
}
  • 功能:将链表反转
  • 步骤:
    1. 特殊情况处理:空链表或只有一个节点时直接返回
    2. 使用三个指针遍历并反转链表:
      • prev:指向当前节点的前一个节点
      • curr:指向当前节点
      • next:保存当前节点的下一个节点(防止反转后丢失)
    3. 核心操作:curr->next = prev(反转指针方向)
    4. 循环结束后,prev指向原链表的最后一个节点,成为新的头节点

图示逻辑

原链表:A -> B -> C -> NULL
反转过程:
1. A->next = NULL, prev=A, curr=B
2. B->next = A, prev=B, curr=C
3. C->next = B, prev=C, curr=NULL
结果:C -> B -> A -> NULL

初始状态:
prev=NULL,curr=1,next=?

第1轮循环:
next = curr->next(next=2)
curr->next = prev(1->next=NULL)
prev = curr(prev=1)
curr = next(curr=2)
此时:1 -> NULL,prev=1,curr=2

第2轮循环:
next = curr->next(next=3)
curr->next = prev(2->next=1)
prev = curr(prev=2)
curr = next(curr=3)
此时:2 -> 1 -> NULL,prev=2,curr=3

第3轮循环:
next = curr->next(next=NULL)
curr->next = prev(3->next=2)
prev = curr(prev=3)
curr = next(curr=NULL)
此时:3 -> 2 -> 1 -> NULL,prev=3

循环结束,返回 prev(3 成为新的头节点)

运行结果:

8.打印链表

// 打印链表
void printLinkedList(Node *head) {printf("链表:[");if (head == NULL) {printf("]\n");return;}Node *temp = head;while (temp->next != NULL) {printf("%d->", temp->data);temp = temp->next;}printf("%d]\n", temp->data);
}
  • 功能:打印链表中的所有元素
  • 步骤:
    1. 先打印固定格式 "链表:["
    2. 特殊情况处理:链表为空时直接打印 "]" 并换行
    3. 遍历链表:
      • 对于非最后一个节点,打印 "数据 ->"
      • 对于最后一个节点,直接打印数据和 "]"

9. 清空链表

//清空链表
void freeList(Node *head) {Node *curr = head;while (curr != NULL) {Node *temp = curr;curr = curr->next;free(temp);}
}
  • 功能:释放链表中所有节点的内存,防止内存泄漏
  • 步骤:
    1. 使用curr指针遍历链表
    2. 每次循环中:
      • temp保存当前节点
      • 移动curr到下一个节点
      • 释放temp指向的节点内存
图示逻辑:

链表:1 -> 2 -> 3 -> NULL

步骤1:curr指向1,temp=1,释放1,curr=2
步骤2:curr指向2,temp=2,释放2,curr=3
步骤3:curr指向3,temp=3,释放3,curr=NULL
最终所有节点都被释放,链表为空

运行结果:

10. 主函数与测试

int main() {int cid = 0;// 头节点Node *head = NULL;// 初始链表head = add(1, head);head = add(2, head);head = add(3, head);head = add(4, head);head = add(5, head);printLinkedList(head);while (1) {printf("链表的基本操作\n");printf("== 101:添加元素(尾插)\n");printf("== 102:添加元素(头插)\n");printf("== 201:删除元素\n");printf("== 301:根据位置插入元素\n");printf("== 401:查找元素存在的位置\n");printf("== 402:根据位置查找元素\n");printf("== 501:根据元素替换元素\n");printf("== 601:倒置元素\n");printf("== 701:清空链表\n");printf("== 999:退出程序\n");printf("== 请输入功能编号:\n");scanf("%d", &cid);if (cid == 101) {printf("[101:添加元素(尾插)]>> 请输入一个数字:\n");int data = 0;scanf("%d", &data);head = add(data, head);} else if (cid == 102) {printf("[102:添加元素(头插)]>> 请输入一个数字:\n");int data = 0;scanf("%d", &data);head = addFirst(data, head);} else if (cid == 201) {printf("[201:删除元素]>> 请输入要删除的数字:\n");int data = 0;scanf("%d", &data);head = deleteByE(data, head);} else if (cid == 301) {printf("[301:根据位置插入元素]>> 请输入一个数字 和 一个位置下标: \n");int data = 0;int index;scanf("%d%d", &data, &index);head = insert(index, data, head);} else if (cid == 401) {printf("[401:查找元素存在的位置]>> 请输入一个数字: \n");int data = 0;scanf("%d", &data);int index = getByE(data, head);if (index == -1) {printf("数据不存在\n");} else {printf("%d在链表中的位置:%d\n", data, index);}}  else if (cid == 402) {printf("[402:根据位置查找元素]>> 请输入位置下标: \n");int index;scanf("%d", &index);int val = getByIndex(index, head);if (val != -1) {printf("位置%d的元素是:%d\n", index, val);}}  else if (cid == 501) {printf("[501:根据元素替换元素]>> 请输入旧数字和新数字: \n");int oldVal, newVal;scanf("%d%d", &oldVal, &newVal);replaceByE(oldVal, newVal, head);}  else if (cid == 601) {printf("[601:倒置元素]\n");head = reverseList(head);}  else if (cid == 701) {printf("[701:清空链表]\n");freeList(head);head = NULL;printf("[清空成功 内存释放完成] \n");}  else if (cid == 999) {printf("退出程序,内存释放\n");freeList(head);exit(0);}  else {printf("无效的功能编号\n");}printLinkedList(head);}
}
  • 功能:提供一个交互式界面,让用户可以测试各种链表操作
  • 初始设置:创建了一个包含 1,2,3,4,5 的初始链表,便于测试
  • 主循环:
    1. 打印功能菜单
    2. 读取用户输入的功能编号
    3. 根据编号执行相应的链表操作
    4. 操作完成后打印当前链表状态
    5. 输入 999 时退出程序并释放内存
运行结果:

总结:链表操作核心逻辑

操作核心思路
头插法新节点.next = 原头节点,新节点成为新头
尾插法找到最后一个节点,最后节点.next = 新节点
插入(中间)新节点先连后,再让前节点连新节点(new->next=temp->next; temp->next=new
删除节点前节点跳过待删节点(prev->next = curr->next),释放待删节点内存
链表倒置用 3 个指针反转每个节点的指向(curr->next = prev)

关键知识点总结

  1. 链表的基本概念

    • 链表由节点组成,每个节点包含数据和指向下一个节点的指针
    • 链表不需要连续的内存空间,节点之间通过指针连接
    • 头节点是链表的入口,通过头节点可以访问整个链表
  2. 指针的重要性

    • 链表的各种操作都依赖指针来实现节点之间的连接
    • 理解指针的指向变化是掌握链表操作的关键
    • 操作链表时要注意避免 "悬空指针" 和 "内存泄漏"
  3. 链表操作的核心思想

    • 插入操作:先连接后一个节点,再连接前一个节点
    • 删除操作:找到待删除节点的前一个节点,修改其指针绕过待删除节点
    • 遍历操作:使用临时指针从头部开始,通过next指针依次访问每个节点
  4. 内存管理

    • 使用malloc分配节点内存,使用free释放节点内存
    • 任何时候删除节点都要记得释放其内存
    • 程序结束前要清空链表,避免内存泄漏
  5. 特殊情况处理

    • 空链表(头节点为 NULL)
    • 单节点链表
    • 操作头节点的情况
    • 操作尾节点的情况
http://www.dtcms.com/a/517623.html

相关文章:

  • 单页面竞价网站衡水电商网站建设价格
  • 做海报的网站知乎个人推广app的妙招
  • 如何管理好一个网站wordpress主题百度
  • 上海平台网站建设报价在韩国注册公司需要什么条件
  • 老板说做个网站我要怎么做江宁滨江网站建设
  • 网站建设课程大纲聊城手机网站建设电话
  • Heroku 部署及问题解决
  • 如何做网站热力图yii2 wordpress
  • 锦州市做网站自助建站平台搭建
  • 网站的模块怎么做网站建设方案书写
  • 俄语 网站app store怎么调回中文
  • 贵州专业网站建设公司哪家好纯静态企业网站
  • 水产养殖网站模板源码网站建设与管理实训总结
  • php网站建设找哪家好wordpress 图片路径
  • 【GD32】分散加载文件.sct
  • 桥西做网站什么网站做禽苗好的网站
  • 嵌入式C语言与标准C语言的区别所在
  • 延迟消息、Elasticsearch的安装
  • 搭建网站难吗wordpress 多域名绑定域名
  • 珠宝类企业网站(手机端)科技作品
  • [算法导论] 1≤n≤15,n个整数组成的数组,输出所有不重复且满足条件的排列组合
  • 阿里巴巴怎么建设网站wordpress+百度云图安装
  • siteservercms做的网站在后台进行修改教程服务器打不开网站
  • php网站里放asp极酷wordpress
  • Spring Cloud - Spring Cloud 声明式接口调用(Fiegn 声明式接口调用概述、Fiegn 使用)
  • 宝塔设置加速wordpress站点重庆网站建设 重庆网站制作
  • 游戏是怎么做的视频网站怎么把网站源码
  • 快印店网站建设84wzjs吉林新农村建设网站
  • 给公司做网站需要华多少钱产品设计需要学的软件
  • 如何用纯C++和Linux系统调用从零实现一个自己的Docker