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

数据结构——链表的基本操作

链表

链表的存储结构

typedef struct Node{int data; // 数据域struct Node *next; // 指针域(就是和当前节点同类型的另一个节点)}Node;

链表初始化

  • 带头结点
// 一般都采用动态的方式去创建
// 初始化带头结点的链表
Node* InitNode(){Node* head = (Node*)malloc(sizeof(Node));head->next = NULL;return head;
}
  • 不带头结点
Node* InitNode(){return NULL;
}

链表的插入

注:下面的几个基本操作都是带头结点的

  • 按位置插入
// 表头插入
void InsertNode(Node* head, int e)
{// 检查头结点是否有效if(head == NULL){return;}Node* s = (Node*)malloc(sizeof(Node)); // 这里还可以加一步看是否初始化成功s->data = e; s->next = head->next;head->next = s;
}// 指定位置插入
// 思路:需要找到目标位置的前驱结点
void InsertNode(Node* head, int i, int e)
{// 检查头结点是否有效if(head == NULL){return;}Node* s = (Node*)malloc(sizeof(Node)); Node* p = head;int count = 0;while(p != NULL && count < i-1){p = p->next;count++;}s->data = e; s->next = p->next;p->next = s;	
}

链表的删除

// 按值删除
void DeleteNode(Node* head, int e)
{Node* p = head;while(p != NULL && p->next->data != e){p = p->next; // 待删除结点的前驱结点}if(p->next == NULL){printf("Not find.");return;}Node* temp = p->next;p->next = temp->next;free(temp);temp = NULL; // 避免野指针
}

链表的查找

  • 按值查找,返回位置
int FindNode(Node* head, int e)
{Node* p = head;int count = 1;while(p != NULL){if(p->data == e){return count}p = p->next; count++;}return -1 // 返回值是int类型
}

链表的修改

  • 按位置修改
void modifyNode(Node* head, int i, int e)
{Node* p = head;int count = 1;while(p != NULL && count < i){p = p->next;count++;}if(p == NULL){printf("Invalid location.");return;}p->data = e;
}

带头结点链表基本操作的完整代码

  • 在使用带头结点链表的过程中,一定要正确区分头节点和数据节点,请看下面错误示例
// 错误示例
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>typedef struct Node {int data; // 数据域struct Node* next; // 指针域
}Node;Node* InitNode() {Node* head = (Node*)malloc(sizeof(Node));head->next = NULL;return head;
}void InsertNode(Node** head, int i, int e)
{// 检查头结点是否有效if (head == NULL){return;}Node* s = (Node*)malloc(sizeof(Node));Node* p = *head;int count = 0;// 找到指定位置的前驱结点while (p != NULL && count < i - 1){p = p->next;count++;}s->data = e;s->next = p->next;p->next = s;
}//按值删除
void DeleteNode(Node** head, int e)
{Node* p = *head;while (p != NULL && p->next->data != e){p = p->next; // 待删除结点的前驱结点}if (p->next == NULL){printf("Not find.");return;}Node* temp = p->next;p->next = temp->next;free(temp);temp = NULL; // 避免野指针
}// 按位修改
void modifyNode(Node** head, int i, int e)
{Node* p = *head;int count = 1;while (p != NULL && count < i){p = p->next;count++;}if (p == NULL){printf("Invalid location.");return;}p->data = e;
}// 按值查找,返回位置
int FindNode(Node* head, int e)
{Node* p = head;int count = 1;while (p != NULL){if (p->data == e){printf("Find it at position %d\n", count);return count;}p = p->next;count++;}return -1; // 返回值是int类型
}void PrintNode(Node* head) {if (head == NULL) {printf("链表为空\n");return;}Node* p = head;printf("链表元素: ");while (p != NULL) {printf("%d ", p->data);p = p->next;}printf("\n");
}int main() {Node* head = InitNode(); // 初始化顺序表// 测试插入操作InsertNode(&head, 1, 10);InsertNode(&head, 2, 20);InsertNode(&head, 3, 30);PrintNode(head);DeleteNode(&head, 30);PrintNode(head);           // 应该输出: 10 20 modifyNode(&head, 2, 25);PrintNode(head);           // 应该输出: 10 25FindNode(head, 25); // 应该返回: 2return 0;
}

输出:
在这里插入图片描述
原因:

  1. 头节点的 data 未初始化,导致打印出随机垃圾值
  2. 插入、查找、修改等操作都错误地包含了头节点,导致位置计算偏移

正确代码:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>typedef struct Node {int data;          // 数据域struct Node* next; // 指针域
} Node;// 初始化带头节点的链表(头节点不存储实际数据)
Node* InitNode() {Node* head = (Node*)malloc(sizeof(Node));if (head == NULL) {printf("内存分配失败\n");return NULL;}head->next = NULL;  // 头节点的next初始化为NULL// 头节点数据域可以不用初始化(因为不使用)return head;
}// 在第i个位置插入元素e(i从1开始,从第一个数据节点计数)
void InsertNode(Node** head, int i, int e) {// 检查参数合法性if (head == NULL || *head == NULL || i < 1) {printf("插入失败:参数不合法或链表未初始化\n");return;}Node* s = (Node*)malloc(sizeof(Node));if (s == NULL) {printf("内存分配失败\n");return;}s->data = e;Node* p = *head;  // p指向头节点(前驱节点的起点)int count = 0;    // 头节点对应count=0,数据节点从count=1开始// 找到第i-1个数据节点的前驱(头节点或某个数据节点)while (p != NULL && count < i - 1) {p = p->next;count++;}if (p == NULL) {printf("插入位置超出链表长度\n");free(s);return;}// 插入新节点s->next = p->next;p->next = s;
}// 按值删除节点(删除第一个匹配的节点)
void DeleteNode(Node** head, int e) {if (head == NULL || *head == NULL || (*head)->next == NULL) {printf("删除失败:链表为空或未初始化\n");return;}Node* p = *head;  // p指向头节点(从这里开始查找前驱)// 查找待删除节点的前驱(停在待删除节点的前一个节点)while (p->next != NULL && p->next->data != e) {p = p->next;}if (p->next == NULL) {printf("未找到值为%d的节点\n", e);return;}// 删除节点Node* temp = p->next;p->next = temp->next;free(temp);temp = NULL;
}// 按位置修改节点值(i从1开始,从第一个数据节点计数)
void modifyNode(Node** head, int i, int e) {if (head == NULL || *head == NULL || (*head)->next == NULL || i < 1) {printf("修改失败:链表为空或位置不合法\n");return;}Node* p = (*head)->next;  // 直接指向第一个数据节点int count = 1;            // 数据节点从1开始计数// 找到第i个数据节点while (p != NULL && count < i) {p = p->next;count++;}if (p == NULL) {printf("修改位置超出链表长度\n");return;}p->data = e;  
}// 按值查找,返回位置(从1开始,从第一个数据节点计数)
int FindNode(Node* head, int e) {if (head == NULL || head->next == NULL) {printf("查找失败:链表为空\n");return -1;}Node* p = head->next;  // 跳过头节点,从第一个数据节点开始int count = 1;         // 数据节点从1开始计数while (p != NULL) {if (p->data == e) {printf("Find it at position %d\n", count);return count;}p = p->next;count++;}printf("未找到值为%d的节点\n", e);return -1;
}// 打印所有数据节点(跳过头节点)
void PrintNode(Node* head) {if (head == NULL || head->next == NULL) {printf("链表为空\n");return;}Node* p = head->next;  // 跳过头节点,从第一个数据节点开始打印printf("链表元素: ");while (p != NULL) {printf("%d ", p->data);p = p->next;}printf("\n");
}int main() {Node* head = InitNode();  // 初始化带头节点的链表// 测试插入操作InsertNode(&head, 1, 10);  // 位置1插入10InsertNode(&head, 2, 20);  // 位置2插入20InsertNode(&head, 3, 30);  // 位置3插入30PrintNode(head);           // 输出:链表元素: 10 20 30// 测试删除操作DeleteNode(&head, 30);PrintNode(head);           // 输出:链表元素: 10 20// 测试修改操作modifyNode(&head, 2, 25);PrintNode(head);           // 输出:链表元素: 10 25// 测试查找操作FindNode(head, 25);        // 输出:Find it at position 2return 0;
}

注:这里再说明一个问题:

head == NULL || *head == NULL 

这个检查是很有必要的,是对两种错误场景进行检查

  1. head == NULL:表示传递给函数的二级指针本身就是无效的(比如调用者错误地传入了 NULL 作为参数,如 InsertNode(NULL, 1, 10))。这种情况下,连 head 都不能访问(会直接触发空指针解引用错误),必须优先检查。
  2. head == NULL:表示二级指针 head 本身有效(非空),但它指向的头节点指针是无效的(比如链表未初始化)。这种情况下,head 是合法指针,但 head 是空指针,需要单独处理。

无头结点链表的增删改查

  • 注:这里使用了二级指针,不同于之前的顺序表,其操作的是结构体,当我们传递SqList* L(一级指针)时,已经可以通过指针访问并修改结构体内部的所有成员(数组和长度)
  • 而链表为什么使用二级指针的原因是:C语言的函数参数传递是值传递—函数接收的是参数的副本,而非参数,所以如果使用一级指针(Node*)作为参数,函数内部修改的只是头指针的副本,无法影响外部实际的头指针。只有通过二级指针(Node**),才能真正修改外部头指针的指向
// 错误示例:使用一级指针
void InsertAtHead(Node* head, int value) {Node* newNode = createNode(value);newNode->next = head;head = newNode;  // 这里修改的只是函数内部的副本,外部头指针不变
}// 正确示例:使用二级指针
void InsertAtHead(Node** head, int value) {Node* newNode = createNode(value);newNode->next = *head;  // *head表示外部的头指针*head = newNode;        // 直接修改外部头指针的指向
}
  • 如果操作会改变头指针的指向,就必须用二级指针;如果只是修改节点内部的数据或 next 指针,一级指针就足够了

完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>typedef struct Node {int data; // 数据域struct Node* next; // 指针域
}Node;Node* InitNode() {return NULL;
}// 使用二级指针才能修改头指针
void InsertNode(Node** head, int i, int e)
{// 判断位置是否合法while (i < 1){return;}Node* s = (Node*)malloc(sizeof(Node));s->data = e;// 表头插入if (i == 1){s->next = *head;*head = s;return;}Node* p = *head;int count = 1;while (p != NULL && count < i - 1){p = p->next;count++;}if (p == NULL) {printf("插入位置超出链表长度\n");free(s);  // 释放已分配的内存return;}s->next = p->next;p->next = s;return;}//按值删除
void DeleteNode(Node** head, int e)
{// 处理空链表if(*head == NULL){printf("Empty list.");return;}Node* p = *head;Node* prev = NULL;// 查找要删除的结点及其前驱结点while (p != NULL && p->data != e){prev = p;p = p->next;}// 未找到if(p == NULL){printf("Not find.");return;}// 处理头结点的删除if(prev == NULL){*head = p->next;}else {prev->next = p->next;}free(p);p = NULL; // 避免野指针
}// 按位修改
bool UpdateNodeByIndex(Node** head, int i, int e)
{if(*head == NULL || i < 1){printf("Empty list or invalid position.");return false;}Node* p = *head;int count = 1;while(p != NULL && count < i){p = p->next;count++;}p->data = e;return true;
}void PrintNode(Node* head) {if (head == NULL) {printf("链表为空\n");return;}Node* p = head;printf("链表元素: ");while (p != NULL) {printf("%d ", p->data);p = p->next;}printf("\n");
}int main() {Node* head = InitNode(); // 初始化顺序表// 测试插入操作InsertNode(&head, 1, 10);InsertNode(&head, 2, 20);InsertNode(&head, 3, 30);PrintNode(head);DeleteNode(&head, 30);PrintNode(head);           // 应该输出: 10 20 UpdateNodeByIndex(&head, 2, 25);PrintNode(head);           // 应该输出: 10 25return 0;
}

文章转载自:

http://UcIbb9H7.nqgds.cn
http://1UPthKqq.nqgds.cn
http://J3p7oCNb.nqgds.cn
http://lGW3jtOJ.nqgds.cn
http://wPwC0drz.nqgds.cn
http://BfaPGDTa.nqgds.cn
http://HFr8NtYI.nqgds.cn
http://9BmroV9t.nqgds.cn
http://jWq4ee8R.nqgds.cn
http://VmMw7g9v.nqgds.cn
http://fNMWtQFe.nqgds.cn
http://pI9pSPlD.nqgds.cn
http://a8DDVUgF.nqgds.cn
http://fpLNELjQ.nqgds.cn
http://Yh6gWZBV.nqgds.cn
http://nI9w5r8P.nqgds.cn
http://qyNXpvTG.nqgds.cn
http://ANKTkP9q.nqgds.cn
http://PM3quuF2.nqgds.cn
http://DwMGiUVb.nqgds.cn
http://0t40FD3P.nqgds.cn
http://erfMl3V5.nqgds.cn
http://vUPW3VT9.nqgds.cn
http://5Dd6Q5N6.nqgds.cn
http://Cb5vpP1J.nqgds.cn
http://H6R1mSw0.nqgds.cn
http://m6NYskPq.nqgds.cn
http://4xfPXcZu.nqgds.cn
http://tFB81qdg.nqgds.cn
http://Rp9hLh0J.nqgds.cn
http://www.dtcms.com/a/377319.html

相关文章:

  • 华为基本命令
  • [rStar] 搜索代理(MCTS/束搜索)
  • 聊一聊 .NET 某跨境物流系统 内存暴涨分析
  • langchain+通义千问,实现简单地RAG应用
  • 【Spring】原理解析:Spring Boot 自动配置
  • 象形柱状图(Vue3)
  • RESTful API:@RequestParam与@PathVariable实战对比
  • 【ESP系列】ESP32S3
  • kafka集群部署与使用
  • Linux-Shell编程之sed和awk
  • 无人设备遥控器之状态反馈技术篇
  • 4.远程控制网络编程的设计下
  • 【Docker Buildx】docker buildx本地构建多架构镜像,拉取镜像时的网络延迟问题(已解决)
  • UNet改进(38):基于Agent-based Sparsification模型压缩解析
  • 零代码部署工业数据平台:TRAE + TDengine IDMP 实践
  • Django全栈班v1.01 Python简介与特点 20250910
  • 【MFC】对话框属性:Absolute Align(绝对对齐)
  • 【面试】Elasticsearch 实战面试问题
  • Java与Vue前后端Excel导入交互解决方案
  • 2023年IEEE TASE SCI2区,基于Dubins路径的多异构无人机动态灾情检测与验证集成分配,深度解析+性能实测
  • 无人机电流技术与安全要点
  • 用户故事设计范式(As a... I want to... So that...)
  • 技嘉B760+i5 12400F+ 华硕tuf rtx5060装机配置方案|仅供参考2025.09.10
  • PSO-BP粒子群优化BP神经网络回归预测+SHAP分析+PDP部分依赖图,可解释机器学习,Matlab代码
  • HarmonyOS编写教师节贺卡
  • 点晴免费OA系统为企业提供高效办公的解决方案
  • Python:Scapy 网络交互与安全的工具库
  • web中的循环遍历
  • 行业学习【电商】:腾讯视频、携程算“电商”吗?
  • 使用 `matchMedia()` 方法检测 JavaScript 中的媒体状态