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

数据结构——单链表

目录

1、链表的定义

2、链表的分类

1、不带头结点的单向不循环链表

2、带头结点的单向不循环链表

3、不带头结点单向循环链表

4、带头结点的单向循环链表

5、不带头结点的双向不循环链表

6、带头结点的双向不循环链表

7、不带头结点的双向循环链表

8、带头节点的双向循环链表

链表的结构

1、结构

2、创造一个节点

3、初始化链表

4、求链表长度

5、打印链表

6、查找元素

根据数值查找

根据下标查找

7、插入

根据下标插入

头插

尾插

8、删除

根据下标删除

头删

尾删

9、销毁


前言:

上次介绍了一种数据的存储结构——顺序表,但是他会有缺陷:当我们要插入元素和申请空间的时候一般情况下后扩容原来的2倍,但是不一定每一个空间都装数据,这样会造成空间的浪费,而且进行插入和删除的时候,对应实现的接口的时间复杂的位O(N),效率第,因此一些大佬有发明了一个链表。下面是单链表的介绍。

1、链表的定义

链表(Linked List)是一种线性表,由一系列节点(Node)组成,每个节点包含数据域指针域,节点之间通过指针链接在一起。

2、链表的分类

链表的分类有很多种,如下:

主要看他三个方面:是否单向,是否带头结点,是否循环,这样就有了八种链表。

注意:节点(现代)和结点(偏传统)这两种叫法都可以。

下面是链表的具体情况,在介绍链表的具体情况之前先区分一下头结点和头指针

头结点(也称为哨兵位):他和普通的节点在结构上是一样的,也分为数值域和指针域,只不过他的数值域不存储有效数据,然后他的指针域指向的是第一个有效节点的地址。

头指针:他仅仅是一个结构体类型的指针用于存储节点的地址,他是链表最开始的部分。

1、不带头结点的单向不循环链表

2、带头结点的单向不循环链表

3、不带头结点单向循环链表

4、带头结点的单向循环链表

5、不带头结点的双向不循环链表

6、带头结点的双向不循环链表

7、不带头结点的双向循环链表

8、带头节点的双向循环链表

链表的结构

下面是对有头结点的不循环单链表的介绍

1、结构

//List.h #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <assert.h> typedef int LDataType; //定义数值域存放的是int的类型的变量,利用LDataType代替,方便对数值域的类型进行修改 typedef struct ListNode { LDataType data; // 存储数据 struct ListNode* next; // 存放后继结点地址, }LNode, *LinkList;

这里对LDataType进行重命名,以至于对链表数值的存储方便修改,更具有普适性。

定义结构体变量,需要注意这里的struct ListNode* next不能写成LNode*或者是*LinkList,因为这里next是在重命名之前的。

还要知道这里有一个typedef和结构体的知识,

struct ListNode==LNode; struct ListNode*==*LinkList

2、创造一个节点

List.h //创造一个新的节点,节点的数值域是data LNode* BuyListNode(int data); //List.c //创造一个新的节点,节点的数值域是data LNode* BuyListNode(int data) { LNode* newNode = (LNode*)malloc(sizeof(LNode*)); if (newNode == NULL) { printf("malloc创建空间失败"); exit(-1); } newNode->data = data; newNode->next = NULL; return newNode; }

这里创造完节点之后要判空,而且要把节点的next置为空,防止野指针,还要返回节点的地址是为了能让链表之间的节点连起来。

3、初始化链表

//List.h LNode* ListInit(); //List.c LNode* ListInit() { LNode* node = BuyListNode(-1);//头结点,初始化完了之后只有他一个 return node; }

这里初始化的是有头结点的链表,随便给他一个-1的值

4、求链表长度

int ListSize(LNode* L) { assert(L);//L是头结点 int size = 0; LNode* cur = L->next;//第一个有效节点 while (cur)//从第一个有效节点开始,这样也是遍历有头结点单链表的代码 { size++; cur = cur->next; } return size; }

这里涉及到一个遍历链表的循环,cur是第一个有效节点0开始。

5、打印链表

//List.h void ListPrint(LNode* L); //List.c //打印链表 void ListPrint(LNode* L) { assert(L); LNode* cur = L->next;//第一个有效节点,这里的L是哨兵位头结点,数值域不放东西 while (cur) { printf("%d->",cur->data); cur = cur->next; } printf("NULL\n"); }

6、查找元素

根据数值查找

//获取第一个节点为x的链表,并返回其地址 LNode* ListLocateElem(LNode* L, LDataType x) { assert(L); LNode* cur = L->next; while (cur)//遍历,一个一个比较 { if (cur->data == x) { return cur; } cur = cur->next; } return NULL; }

通过一个一个的比对查找

根据下标查找

//获取下标是i的节点,并且返回其地址 LNode* ListGetElem(LNode* L, int i) { assert(L); assert(i >= 0); int j = 0; LNode* iNode = L->next; while (iNode&&j<i) { j++; iNode = iNode->next; } return iNode; }

7、插入

根据下标插入

//在下标为i的节点处插入一个节点 LNode* ListInsert(LNode* L, int i) { assert(L); assert(i >= 0); int j = -1;//从头结点开始,不然头插不了 LNode* i_1Node = L; while (i_1Node && j < i-1) { i_1Node = i_1Node->next; j++; } assert(i_1Node);//先判断是否符合是空的 LNode* newNode = BuyListNode(30);//再来创造新的节点,如果顺序打乱可能创造了节点没有连接,形成野指针 newNode->next= i_1Node->next; i_1Node->next = newNode; return newNode; }

链表的插入很容易错,因为进行头插的时候要从头结点开始遍历而不是从第一个有效节点开始,不然覆盖不了所有节点。

还有当插入链表的时候,一定要先修改新节点的next,也就是从后面开始修改,再将i_1Node的next和newNode进行连接,防止找不到后面的节点。

头插

//头插,数值是x void ListPushFront(LNode* L, LDataType x) { assert(L); LNode* newNode = BuyListNode(x); newNode->next = L->next; L->next=newNode; }

尾插

//尾插,数值是x void ListPushBack(LNode* L, LDataType x) { assert(L); LNode* newNode = BuyListNode(x); newNode->next = NULL; // 情况1:空链表 if (L->next == NULL) { L->next = newNode; return; } // 情况2:非空链表 LNode* cur = L->next; while (cur->next != NULL) { cur = cur->next; } cur->next = newNode; }

这里要判断一下是否是空表

8、删除

根据下标删除

//在下标为i的节点处删除一个节点,返回该节点处的值 LDataType ListDelete(LNode* L, int i) { assert(L); assert(i>=0); int j = -1; LNode* i_1Node = L; while (i_1Node && j < i-1 ) { i_1Node = i_1Node->next; j++; } assert(i_1Node&& i_1Node->next); LNode* iNode = i_1Node->next; LDataType x = iNode->data; i_1Node->next = iNode->next; free(iNode); return x; }

这里同理的要注意一下头指针,在进行删除第一个有效节点的时候,同样从头结点开始

头删

//头删,并且返回删除的值 LDataType ListPopFront(LNode* L) { assert(L); assert(L->next); LNode* first = L->next; LDataType x = first->data; L->next = first->next; free(first); first = NULL; return x; }

尾删

//尾删,返回删除的值 LDataType ListPopBack(LNode* L) { assert(L); assert(L->next); LNode* prev = L; LNode* cur = L->next; while (cur->next) { prev = cur; cur = cur->next; } LDataType x = cur->data; free(cur); prev->next = NULL; return x; }

9、销毁

//销毁链表 void ListDestroy(LNode* L) { assert(L); LNode* cur = L; while (cur) { LNode* next = cur->next; free(cur); cur = next; } }

这里不用置空是因为是局部变量,会销毁的。

http://www.jsqmd.com/news/1047868/

相关文章:

  • 朝阳市黄金回收多少钱一克?本地实体门店回收价格对比整理 - 马刺总冠军
  • 大兴安岭地区黄金回收去哪儿好?整理了5家靠谱实体店地址电话 - 三大殿
  • 黄景行电脑软件发布新软件:PC Keeper
  • 吉林市2026年黄金回收报价,内行人整理实体门店回收清单 - 三大殿
  • 遂宁市2026年黄金回收报价,内行人整理实体门店回收清单 - 开始就结束
  • 2026年|导师说论文像AI写的?用这2个高效方法降低AIGC率!
  • WebLogic弱密码漏洞复现与防御:从原理到实战攻防
  • 2026 郑州黄金回收附近门店地址避坑指南:靠谱回收渠道甄选标准详解 - 奢侈品回收
  • 亨得利官方售后服务中心核验报告:最新线下门店地址及联系方式汇总 - 亨得利中国服务中心
  • 承德市今日黄金回收价格多少?本地5家口碑门店报价参考 - 马刺总冠军
  • 贺州市黄金回收实体店怎么选?这份清单帮你货比三家 - 开始就结束
  • 郑州百达翡丽腕表回收 2026 核心白皮书:专业门店地址与靠谱渠道全解析 - 奢侈品回收
  • 2026 正规备案收金店,称重透明结算无隐藏扣费 - 讯息早知道
  • 聊城市今日黄金回收价格多少?本地5家口碑门店报价参考 - 结束就开始
  • 金华市黄金回收猫腻多怎么办?整理了5家诚信回收店供参考 - 三大殿
  • MAA明日方舟助手:如何用智能图像识别技术实现全自动游戏辅助
  • 临汾市闲置黄金变现多少钱?本地5家回收门店最新报价参考 - 结束就开始
  • 威海黄金回收实测:六家门店避坑测评 - 余生黄金回收
  • 武汉营业性演出许可证一条龙代办公司哪家好 - 速递信息
  • 深入解析MC9S12KG128的S12MSCANV2:从CAN总线原理到寄存器级驱动实战
  • Android Framework核心解密:Binder跨进程通信机制深度剖析
  • 文心流程图怎么导出?AI 导出鸭一键解决格式错乱与兼容难题
  • 2026安徽省宣城市中考一两百分怎么办?口碑优选宠物护理专业最新发布 - cc江江
  • 赤峰市黄金回收去哪儿好?整理了5家靠谱实体店地址电话 - 马刺总冠军
  • 郑州劳力士腕表回收 2026 推荐指南:专业门店地址与高价回收渠道深度测评 - 奢侈品回收
  • Gin vs Actix-Web:Go 与 Rust 两大顶尖 Web 框架全维度深度对比
  • 深圳亨得利劳力士维修价格查询全攻略:2026年罗湖华润大厦官方售后深度实测,水鬼迪通拿日志保养报价与欧米茄卡地亚帝舵浪琴百达翡丽宝珀积家爱彼维修费用对比 - 亨得利腕表维修中心
  • 赤峰市黄金回收实体店怎么选?这份清单帮你货比三家 - 马刺总冠军
  • 红河哈尼族彝族自治州黄金回收去哪儿好?整理了5家靠谱实体店地址电话 - 开始就结束
  • 大连市今日黄金回收价格多少?本地5家口碑门店报价参考 - 嵩山路大王