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

链表(C语言)—学习笔记

一、简介

链表的基本概念

        链表由一系列节点组成,每个节点包含数据域和指针域。数据域存储具体的数据,指针域存储指向下一个节点的地址(在单链表中),通过指针将各个节点连接起来,形成一个链式的结构,数据就可以按照这个链条依次访问。

链表的类型

  • 单链表:每个节点只有一个指向下一个节点的指针,节点依次相连形成一个单向的链条,只能从链表头开始沿着指针方向依次访问后续节点。比如火车车厢,每节车厢(节点)只连接着后面一节车厢,只能朝着一个方向前进。
  • 双向链表:每个节点有两个指针,分别指向前一个节点和后一个节点,这样可以在链表中双向移动,既可以从前往后访问,也可以从后往前访问。就像双向车道,车辆可以双向行驶。
  • 循环链表:分为单循环链表和双循环链表。单循环链表的尾节点的指针指向头节点,形成一个环形;双循环链表不仅尾节点的指针指向头节点,头节点的前向指针也指向尾节点。例如操场上的跑道,运动员可以一直循环跑下去。

链表的操作

初始化链表、插入操作、删除操作、查找操作、遍历操作、链表长度计算等

链表的优缺点

  • 优点
    • 动态分配内存:链表不需要预先分配固定大小的内存空间,在需要添加新节点时可以动态分配内存,适合数据数量不确定的场景。比如一个在线游戏的玩家列表,玩家数量随时可能变化,使用链表可以灵活处理。
    • 插入和删除效率高:在链表中插入或删除一个节点,只需要修改相关节点的指针,时间复杂度为O(1)(前提是已经知道要操作的位置)。例如在一个人员名单链表中添加或移除一个人,只需要调整相关人员之间的连接关系。
  • 缺点
    • 随机访问效率低:要访问链表中的某个节点,需要从链表头开始依次遍历,直到找到目标节点,时间复杂度为O(n)。就像在一个长长的队伍中找某个人,只能从队伍开头一个一个地找。
    • 额外的指针开销:每个节点都需要额外的指针来存储下一个节点(或前后节点)的地址,会增加内存的使用。

链表的应用场景

实现栈、队列等数据结构。 内存管理、文件系统等领域。

二、链表操作

typedef struct ListNode {    //定义链表的结构体int val;struct ListNode *next;
}ListNode;
//创建一个新的结点
ListNode *CreateNode(const int val) {ListNode *NewNode = (ListNode *)malloc(sizeof(ListNode));if (NewNode == NULL) {printf("内存分配失败");return NULL;}NewNode->val = val;NewNode->next = NULL;return NewNode;
}

1. 初始化链表

初始化链表是创建链表的起始步骤。对于单链表,通常会设置一个头指针,若链表为空,头指针指向 NULL。在有头节点的链表中,会先创建一个头节点,该节点的数据域可空置不用,其指针域初始化为 NULL,头指针指向这个头节点。初始化操作是后续所有链表操作的基础,为链表后续的节点添加等操作做好准备。

void InitList(ListNode *head) {head->next = NULL;
}

2. 插入操作

  • 头插入:创建新节点后,直接将新节点的指针指向当前链表的头节点(若有头节点则指向头节点之后的第一个数据节点),然后更新头指针使其指向新节点。这种插入方式时间复杂度为 O(1),因为只涉及到指针的重新赋值,不依赖链表的长度。例如在一个记录学生信息的链表中,若新入学一名学生,使用头插入可以快速将其信息添加到链表前端。
void InsertHead(ListNode *head,const int val) {ListNode *NewNode = CreateNode(val);if (NewNode == NULL) {return;}NewNode->next = head->next;head->next = NewNode;
}
  • 尾插入:首先要遍历整个链表,找到链表的尾节点(尾节点的指针域为 NULL)。创建新节点后,将尾节点的指针指向新节点,新节点的指针域置为 NULL。由于需要遍历链表,其时间复杂度为 O(n),其中 n 是链表的长度。比如在一个任务列表链表中,将新任务添加到列表末尾,就可以使用尾插入。
void InsertEnd(ListNode *head, const int val) {ListNode *NewNode = CreateNode(val);if (NewNode == NULL) {return;}if (head == NULL) {head = NewNode;}else {ListNode *temp = head;while (temp->next != NULL) {temp = temp->next;}temp->next = NewNode;}
}
  • 指定位置插入:先遍历链表找到要插入位置的前一个节点。创建新节点后,让新节点的指针指向原位置的节点,再将前一个节点的指针指向新节点。此操作同样需要遍历链表来定位插入位置,时间复杂度为 O(n)。例如在一个按成绩排序的学生链表中,要将一个新学生插入到合适的位置,就需要先找到插入点。

3. 删除操作

  • 删除头节点:将头指针直接指向原头节点的下一个节点(若有头节点则跳过原头节点后的第一个数据节点),然后释放原头节点的内存。该操作时间复杂度为 O(1),因为只涉及指针的修改和一次内存释放。比如在一个新闻列表链表中,若第一条新闻过时需要删除,就可以使用这种方式。
void DeleteHead(ListNode *head) {if (head == NULL) {printf("链表为空,无法删除头结点。\n");return;}ListNode *temp = head;head->next = head->next->next;//free(temp->next);    //这个free有点问题,有没有大佬帮忙解决一下printf("头结点已删除。\n");
}
  • 删除尾节点:需要遍历链表找到倒数第二个节点,将其指针域置为 NULL,然后释放尾节点的内存。由于要遍历链表,时间复杂度为 O(n)。例如在一个历史记录链表中,删除最后一条记录时就需要进行尾节点删除操作。
void DeleteEnd(ListNode *node) {//如果链表为空(即传入的 node 为 NULL),则直接返回,不进行任何操作。if (node == NULL) {   return;}//如果链表只有一个节点(即 node->next 为 NULL),则释放该节点,并将指针置为 NULLif (node->next == NULL) {free(node);}else {while (node->next->next != NULL) {node = node->next;}free(node->next);node->next = NULL;}
}
  • 删除指定节点:先遍历链表找到要删除节点的前一个节点,然后将前一个节点的指针指向要删除节点的下一个节点,最后释放要删除节点的内存。此操作的时间复杂度也是 O(n),因为需要遍历链表来定位节点。例如在一个联系人链表中,删除某个特定联系人的信息时就会用到。

4. 查找操作

从链表的头节点开始,依次比较每个节点的数据域与要查找的值。若找到匹配的节点,则返回该节点的指针;若遍历完整个链表都未找到,则返回 NULL。查找操作的时间复杂度为 O(n),因为在最坏情况下需要遍历整个链表。比如在一个图书信息链表中查找某本特定图书的信息,就需要使用查找操作。

ListNode* findElement(ListNode* head, int target) {ListNode* current = head;while (current != NULL) {if (current->val == target) {return current;}current = current->next;}return NULL;
}

5. 遍历操作

从链表的头节点开始,通过节点的指针域依次访问每个节点,对每个节点的数据域进行相应的处理,如打印数据等。遍历操作会依次访问链表中的每一个节点,时间复杂度为 O(n)。例如在一个员工信息链表中,要打印出所有员工的信息,就需要进行遍历操作。

void printList(ListNode* head) {ListNode* current = head->next;while (current != NULL) {printf("%d ", current->val);current = current->next;}printf("\n");
}

6. 链表长度计算

从链表的头节点开始,设置一个计数器,每访问一个节点计数器加 1,直到遍历完整个链表,计数器的值就是链表的长度。此操作需要遍历链表,时间复杂度为 O(n)。比如在一个商品列表链表中,计算商品的数量时就会用到长度计算操作。

int LengthNode(const ListNode* head) {if (head == NULL) {return 0;}int len=0;while (head->next != NULL) {head = head->next;len++;}return len;
}

http://www.xdnf.cn/news/1051795.html

相关文章:

  • 淘宝/天猫API系列-商品列表页采集接口教程
  • win10 乌班图系统安装(推荐)
  • 安装前端vite框架,后端安装fastapi框架
  • 第二十章 Centos8的使用
  • 苏州SAP代理商:哲讯科技助力企业数字化转型
  • CSS 第四天 复合选择器、CSS特性、背景属性、显示模式
  • 前端api中使用data传参源码解释
  • 第8章:Neo4j性能优化
  • SCADA|KingSCADA4.0中历史趋势控件与之前版本的差异
  • Codeception中如何使用Fixtures优化测试
  • 说说聚合路由器
  • 【编译原理】第十章 优化
  • 影视剧学经典系列-梁祝-陶渊明《感士不遇赋并序》
  • Google DeepMind研究:大语言模型(LLMs) 思维链解码(CoT-decoding)方法解析
  • MCP案例 - 数据可视化工具服务器
  • 《从入门到精通:解锁Android Studio的无限可能》
  • 第六章:连接查询优化 - 多表联查不再慢
  • Ubuntu中ESP32使用记录
  • 模拟设计的软件工程项目
  • 软件工程瀑布模型学习指南
  • Vue 3 路由跳转全面指南(Composition API + <script setup>)
  • SpringBoot电脑商城项目--用户注册功能
  • 使用 socat 和 xinetd 将程序绑定到端口运行
  • 电磁场与电磁波篇---梯度散度旋度
  • C#最佳实践:为何应减少方法参数
  • pandas
  • golang-linux环境配置
  • 【工具教程】如何批量识别大量图片的文字并重命名图片,图片文件批量文件识别改名的详细操作步骤和注意事项
  • SpringBoot电脑商城项目--项目分析及搭建
  • 玫瑰动态爱心代码