数据结构——线性链表/单链表设计
在设计单链表时,我们首先要引入单链表的定义,你首先得知道它是什么?
结点:用一组任意的存储单元存储线性表的数据元素(存储单元可以是连续的,也可以是不连续的),对其中一个数据元素来说,不仅要存储其本身的信息之外没还需存储一个指示其直接后继的信息(即直接后继的存储位置),这两部分信息组成这个数据元素的存储映像,称为结点。
线性链表/单链表:结点包括两个域,存储数据元素信息的被称为数据域,存储其直接后继存储位置的域称为指针域,指针域中存储的信息称作指针/链。多个结点链接成一个链表,即为线性表的链式存储结构,又由于此链表的每个结点中只包含一个指针域,故又称为线性链表或单链表。
基础设计:整个链表的存取必须从头指针开始进行头指针指示链表中第一结点(即第一个数据元素的存储映像)的存储位置,同时由于最后一个数据元素没有直接后继,则其指针为空。我们一般会在单链表的第一个结点之前附设一个结点,称之为头结点。头结点的数据域可以不存储任何信息,也可以存储如线性表的长度等类的附加信息,头结点的指针域存储直线法第一个结点的指针(即第一个元素结点的存储位置)
而我们下来要设计的也就是带头结点的单链表。单链表头结点值保存第一个有效节点的地址,也就是只需要使用其指针域即可。
深入理解和设计:
结构体设计
我们先对其进行结构体设计,单链表里面的结点只有数据域和指针域,因此结构体里面的成员我们只需要设计两个就好了。
typedef struct Node {ElemType data; //数据域struct Node *next; //指针域
}Node,*PNode;
初始化
//初始化
void Init_List(PNode pn) {assert(pn != NULL);if (pn == NULL) return;pn->next = NULL;
}
我们对其进行初始化的时候,也就是先对其头结点进行初始化,由于我们对其数据域不进行使用,因此是需要对其指针域进行初始化—— next -> NULL 就行了。
至于这里的 assert 和 if 两处的代码我在之前的不定长顺序表中有说过~大家可以过去看看,顺便对比一下顺序表和链表的区别。
附上链接~
数据结构——不定长顺序表_WLin.的博客-CSDN博客https://blog.csdn.net/m0_70184760/article/details/124641621?spm=1001.2014.3001.5501在下面的设计叙述中,大家一定要注意结点自身地址和此结点的指针域是不一样的,结点的指针域是其下一个要指向结点的自身地址,不要搞混淆了。
插入——头插
先申请一个新的结点,并对其进行初始化,然后让其指针域指向这个链表头结点的指针域,之后再让头结点的指针域指向我们申请的这个新结点的自身地址。
举个简单的例子叭:假设头结点为A,头结点后面的结点为B,我们申请的新结点为C。插入就是先让 C —> B,再让 A —> C
ps:不能先让头结点指向新结点的自身地址,即也就是 A —> C,再让 C —> B,这样是一定不可以的!再A—>C的时候,就会断开A和B的联系,造成B后面的数据缺失,就找不到B了,C也不可能指向B
//头插
bool Insert_head(PNode pn, ElemType val) {assert(pn != NULL);if (pn == NULL) return false;struct Node* pnewnode = (struct Node*)malloc(1 * sizeof(struct Node));assert(pnewnode != NULL);if (pnewnode == NULL) return false;pnewnode->data = val;pnewnode->next = NULL;pnewnode->next = pn->next; //让新结点的指针域指向头结点指向其后继结点的地址pn->next = pnewnode; //让头结点指向新结点的地址return true;
}
插入—— 尾插
那既然是尾插,那我们肯定要先找到链表的尾部,这个时候就需要定义一个指针来替我们找到链表的尾部,并且链表的尾部结点的指针指向NULL,那么我们就需要修改尾结点的指针域,让其指向我们申请新结点的地址,并让新结点的指针域指向NULL。
//尾插
bool Insert_tail(PNode pn, ElemType val) {assert(pn != NULL);if (pn == NULL) return false;struct Node* pnewnode = (struct Node*)malloc(1 * sizeof(struct Node));assert(pnewnode != NULL);if (pnewnode == NULL) return false;pnewnode->data = val;pnewnode->next = NULL;struct Node* p = pn; //申请一个指针for (; p->next != NULL; p = p->next); //走到链表的尾部pnewnode->next = p->next; //新结点的指针域指向尾结点的指向p->next = pnewnode; //尾结点的指针域指向新结点的地址return true;}
插入 —— 按位置插
也是先要申请一个指针来替我们走到我们想要的位置的上一个结点,然后申请新结点,新结点指向pos的地址,然后再将上一个结点的指针域指向我们申请的新结点的地址。
//插入——按位置
bool Insert_pos(PNode pn, int pos, ElemType val) {assert(pn != NULL);if (pn == NULL) return false;struct Node* pnewnode = (struct Node*)malloc(1 * sizeof(struct Node));assert(pnewnode != NULL);if (pnewnode == NULL) return false;pnewnode->data = val;pnewnode->next = NULL;struct Node* p = pn;for (int i = 0; i < pos; i++) { //走到想要位置的上一个结点p = p->next;}pnewnode->next = p->next;p->next = pnewnode;return true;}
删除都是要对链表进行判空操作(写在了删除后面)
在设计删除时,有的删除会申请两个指针,是因为一个需要指向/存储待删结点本身的地址,方便删除之后进行释放,不至于造成数据丢失或者内存泄漏,另一个指针则是需要指向待删结点的上一个结点,因为需要该结点来指向待删结点的指针域(可以理解为跳过了待删结点)。
删除——头删
这里不仅要对指针判空,也要对链表进行判空!
先让一个结构体指针保存我们待删除的结点,然后更改头结点的指针域指向,之后再释放我们申请出的结构体指针就好了
//头删
bool Del_head(PNode pn) {assert(pn != NULL);if (pn == NULL) return false;if (IsEmpty(pn)) return false;struct Node* p = pn->next;pn->next = p->next;free(p);p = NULL;return true;
}
删除 —— 尾删
在这里我们先申请一个指针,让其走到倒数第二个结点,然后在申请一个结点保存倒数第二个结点的指针域的指向(也就是尾结点),然后再让倒数第二个结点的指向为NULL,之后释放我们第二次申请得到指针。
//尾删
bool Del_tail(PNode pn) {assert(pn != NULL);if (pn == NULL) return false;if (IsEmpty(pn)) return false;struct Node *p = pn;while (p->next->next != NULL) {p = p->next;}struct Node* q = p->next;p->next = NULL;free(q);q = NULL;return true;
}
删除 —— 按位置删
这里我们依然需要两个指针,一个走到待删位置的上一个结点(因为这个结点的指针域指向的是我们待删位置结点的地址),一个用来存储待删结点的地址,以便后面释放。
//按位置删
bool Del_pos(PNode pn, int pos) {assert(pn != NULL);if (pn == NULL) return false;if (IsEmpty(pn)) return false;struct Node* p = pn; for (int i = 0; i < pos; i++) { //让指针走到待删位置结点的上一个结点p = p->next;}struct Node* q = p->next; //让指针指待删位置的结点p->next = q->next;free(q);q = NULL;return false;
}
删除——按值删
调用了查找函数,后面会写到,查找函数返回的是查找值的结点地址。
之后再申请一个指针指向该结点的上一个结点。
//按值删
bool Del_val(PNode pn, ElemType val) {assert(pn != NULL);if (pn == NULL) return false;if (IsEmpty(pn)) return false;struct Node* p = Search(pn, val); //这里调用了查找函数if (p == NULL) return false; //返回NULL,说明没找到//执行下面的就说明找到了待删值所在结点的地址struct Node* q = pn; for (; q->next != p; q = q->next); //指针指向待删值结点的上一个结点q->next = p->next;free(p);p = NULL;return true;
}
判空
直接看头结点的指针域是否为NULL 就行
//判空
bool IsEmpty(PNode pn) {return pn->next == NULL;
}
查找
通过传值,对链表进行遍历查找,找到了就返还回去,否则就返还NULL
//查找
struct Node* Search(PNode pn, ElemType val) { assert(pn != NULL);if (pn == NULL) return NULL;struct Node* p = pn->next; for (; p != NULL; p = p->next) { //遍历查找if (p->data == val) {return p; //找到,返回结点}}return NULL;
}
获取有效个数
//获取有效个数
int Get_length(PNode pn) {int count = 0;struct Node* p = pn->next; for (; p != NULL; p = p->next) {count++;}return count;
}
清空
调用销毁函数就好
//清空
void Clear(PNode pn) {Destroy1(pn); //调用销毁函数就好
}
销毁1——使用头结点
//销毁1 无限头删
void Destroy1(PNode pn) {while (pn->next != NULL) {struct Node* p = pn->next;pn->next = p->next;free(p);p = NULL;}}
销毁2 —— 不使用头结点
使用两个指针来进行销毁,只要p不等于NULL,那么就让q指向p->next,并把p释放了,在让p=q。
//销毁2
void Destroy2(PNode pn) {assert(pn != NULL);if (pn == NULL) return;struct Node* p = pn->next;struct Node* q;pn->next = NULL;while (p != NULL){q = p->next;free(p);p = q;}}
打印
//打印
void Show(PNode pn) {for (struct Node* p = pn->next; p != NULL; p = p->next){printf("%d ", p->data);}printf("\n");}
测试用例:
int main() {struct Node head;Init_List(&head);for(int i=0; i<20; i++){Insert_pos(&head, i, i+1);}Show(&head);Insert_head(&head, 100);Insert_tail(&head, 200);Show(&head);printf("------------------->\n");Del_head(&head);Del_tail(&head);Show(&head);Del_pos(&head, 4);Show(&head);Del_val(&head, 14);Show(&head);printf("length = %d\n", Get_length(&head));Destroy1(&head);//Destroy2(&head);Show(&head);return 0;
}
测试结果:
数据结构——线性链表/单链表设计相关推荐
- 数据结构_Java_基于 线性表-单链表的初始化、逆序、去重、非递减序列的合并(开辟新链表先整体插入一个链表全部元素,再遍历另外一个链表寻找合适位置插入 、开辟新链表实现舍弃原链表)等操作实现
写在前面 不久前学习了数据结构线性表-数组-链表的相关知识,用C/C++语言实现了 单链表的系列相关操作 .见往期博客: 数据结构实验2_C语言_基于顺序表的非递减有序表的合并.线性表元素的增.删.改 ...
- 数据结构基础(8) --单链表的设计与实现(1)之基本操作
链表简介 数组的缺点: 1.元素插入:除了在数组的末尾插入元素之外,在数组的其他任何位置插入元素都需要进行数组元素的频繁移动(插入位置之后的元素都需往后移动), 时间复杂度约为O(N); 2.数组的删 ...
- python的线性链表_Python线性表——单链表-阿里云开发者社区
Python线性表--单链表 线性表简介 线性表是一种线性结构,它是由零个或多个数据元素构成的有限序列.线性表的特征是在一个序列中,除了头尾元素,每个元素都有且只有一个直接前驱,有且只有一个直接后继, ...
- python数据结构基础(单链表,多链表,二叉树)
python数据结构基础(单链表,多链表,二叉树) 数据结构指数据对象中数据元素之间的关系 Python 给我们提供了很多现成的数据结构类型,这些系统自己定义好的,不需要我们自己去定义的数据结构叫做 ...
- 数据结构精讲——单链表
新手必会数据结构精讲--单链表 链表的介绍 概念:链表是一种物理存储结构上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 . 实际中链表的结构非常多样,以下情况组合起来就 ...
- 数据结构与算法--单链表相关面试题
此文章仅作为自己学习过程中的记录和总结,同时会有意地去用英文来做笔记,一些术语的英译不太准确,内容如有错漏也请多指教,谢谢! 一.概述 获取单链表的有效元素个数[新浪面试题1] 获取单链表倒数第k个结 ...
- 数据结构之——《单链表》
数据结构之--<单链表> 1.链表概念 2.链表分类 3.接口函数实现 1.链表概念 链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现. ...
- 数据结构上机-尾、头插法建立单链表-单链表遍历C语言完整代码实现
点击此处跳转视频链接:数据结构上机-尾.头插法建立单链表-单链表遍历C语言完整代码实现
- 建立单链表 单链表的插入_单链列表插入
建立单链表 单链表的插入 All possible cases: 所有可能的情况: Inserting at beginning 开始插入 Inserting at the ending 在末尾插入 ...
- 数据结构--线性表链式存储(链表)--单链表
定义: 单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素. 链表中的数据是以节点来表示的,每个结点的构成:元素( 数据元素的映象) + 指针(指示后继元素存储位置),元素 ...
最新文章
- 雷军写代码水平如何?
- Ubuntu网络配置方法
- Python (2) 除法
- java 与 .net socket_java.net.Socket/java.net.ServerSocket-TCP Socket编程
- SAP Fiori应用中事务锁的实现 - Transaction Lock实现机制
- PHP 500 -Invalid command RewriteEngine的解决
- 基于JAVA+SpringMVC+Mybatis+MYSQL的英语在线学习系统
- 戴尔电脑安装win 7
- node mysql json_Node.js JSON模块用法实例分析
- eMMC的MMC模式与SPI模式
- 如何避免量化交易策略模型过度拟合
- WPS中插入“公式”后行距不正常的解决办法
- 【NLP】华为发布「国产Copilot内核」PanGu-Coder,而且真的能用中文哦!
- 近期看到的很有意思的文章
- WIFI系列协议--802.11ax--wifi6--高效率无线标准简称HE--11Gbit
- Python Pandas 列数据筛选方法汇总
- Mybatis-----实验小结
- 一般处理程序可以类似路由的_网络核心动作路由处理程序和方法
- APS系统的实施步骤,外行人都能看懂
- 智慧农业整体解决方案
热门文章
- echart水滴_echart 水滴图
- esp8266 蓝牙耳机_基于Qualcomm QCC3001/TWS迷你型入耳式蓝牙耳机方案
- 新手淘客你必须知道的秘密
- 公网远程Everything快速搜索私有云资料【内网穿透】
- 【日语】日语动词的敬体与简体
- mina框架CumulativeProtocolDecoder.doDecode方法浅析
- Ollydbg逆向分析并修改helloworld程序
- PX4和ardupilot(APM)的对比
- stm32 c语言 位带,我对STM32所用位带操作宏的超详细剖析、优势分析及应用推广探索研究(持续更新,欢迎讨论交流)...
- Lenovo笔记本BIOS详解