数据结构之手撕链表(讲解➕源代码)

0.引言

我们在学习过顺序表之后,会发现两点不是很优秀的操作:
1.顺序表的头插和中间的插入:
        非常麻烦,需要不断的覆盖数据。
2.动态开辟空间:
        a.一般动态开辟的空间都是以2倍的形式开辟,当我们已经开辟了100个空间,并且存满了,此时我们还需要存放5个数据,那么就又需要开辟200个空间了,我们存放5个数据之后,还剩余了195个空间没有放数据,这也就导致了空间的浪费
        b. 而我们开辟新空间,拷贝数据,释放旧空间还会有一定的消耗

注意⚠️⚠️⚠️
        我们在申请空间的时候必须要主动给它释放掉,因为申请空间的时候,是在堆上申请的,这段空间不会随着程序的结束而自然释放掉,所以要在我们程序结束之前,主动释放掉这段空间。

那有没有一种结构会很简便呢?答案肯定是有的,就是我们本次要讲解的链表。


1.链表的概念

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的 。也就是说链表的物理结构不连续,但逻辑结构连续

我们通常定义一个结构体来表示链表的节点,结构体内部要包括节点的值和指向下一个节点的指针。

链表开始的节点称作:头节点,通常用head表示;

链表末尾的节点称作:尾结点,通常用tail表示;

其余节点称作:中间节点。

2.链表的逻辑模型

如图:在逻辑模型,可以看出它们是一个接着一个的,在逻辑上连续。

3.链表的物理模型

如图:在物理模型中,我们看到每个节点的地址都不是连续的,但是通过指针链接起来了。

 4.链表的分类

4.1 单向链表

        

4.2 双向链表

4.3 带头单向链表(带哨兵位)

4.4带头双向链表(带哨兵位)

4.5循环单链表

4.6循环双向链表

4.7带头循环单链表

4.8带头循环双向链表

5. 单链表的实现(接口实现代码)

5.1单链表的定义

typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;

5.3单链表的销毁

void SLT_Destroy(SLTNode*phead)//销毁
{while (phead != NULL){SLTNode *over = phead->next;free(phead);phead = over;}
}

5.4单链表的动态申请一个节点空间

SLTNode* BuySListNode(SLTDataType x) //创造新节点
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->data = x;newnode->next = NULL;return newnode;
}

5.5单链表的头插头删

void SLTPushFront(SLTNode** pphead, SLTDataType x) //头插
{SLTNode* newnode = BuySListNode(x);newnode->next = *pphead;*pphead = newnode;
}void SLTPopFront(SLTNode** pphead) //头删
{// 空assert(*pphead);// 非空SLTNode* newhead = (*pphead)->next;free(*pphead);*pphead = newhead;
}

5.6单链表的尾插尾删

void SLTPushBack(SLTNode** pphead, SLTDataType x) //尾插
{SLTNode* newnode = BuySListNode(x);if (*pphead == NULL){// 改变的结构体的指针,所以要用二级指针*pphead = newnode;}else{SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}// 改变的结构体,用结构体的指针即可tail->next = newnode;}
}void SLTPopBack(SLTNode** pphead) //尾删
{// 1、空assert(*pphead);// 2、一个节点// 3、一个以上节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* tail = *pphead;while (tail->next->next){tail = tail->next;}free(tail->next);tail->next = NULL;}
}

5.7单链表的在pos位置之前的插入

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)//pos之前插入
{assert(pphead);assert(pos);if (pos == *pphead){SLTPushFront(pphead, x);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuySListNode(x);prev->next = newnode;newnode->next = pos;}
}

5.8单链表的在pos位置之后的插入

void SLTInsertAfter(SLTNode* pos, SLTDataType x)//在pos之后插入
{assert(pos);SLTNode* newnode = BuySListNode(x);pos->next = newnode;newnode->next = pos->next;
}

5.9单链表的删除pos位置的节点

void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);if (pos == *pphead){SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);//pos = NULL;}
}

5.10单链表的删除pos位置之后的节点

void SLTEraseAfter(SLTNode* pos)
{assert(pos);// 检查pos是否是尾节点assert(pos->next);SLTNode* posNext = pos->next;pos->next = posNext->next;free(posNext);posNext = NULL;
}

5.11单链表的查找

SLTNode* SLTFind(SLTNode* phead, SLTDataType x) //查找
{SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}

6.带头双向循环链表的实现(接口实现代码)

6.1带头双向循环链表的定义

typedef int LTDataType;
typedef struct ListNode
{struct ListNode* prev; //前驱LTDataType data;struct ListNode* next; //后继
}ListNode;

6.2带头双向循环链表的初始化

ListNode* ListInit() //初始化链表
{ListNode *head = (ListNode*) malloc(sizeof (ListNode));if(head ==  NULL){perror("初始化开辟空间失败");exit(-1);}head->data = -1;head->prev = head;head->next = head;return head;
}

6.3带头双向循环链表的销毁

void ListDestory(ListNode* pHead) //销毁
{int len = ListSize(pHead) + 1;while (len--)ListPopFront(pHead);
}

6.4带头双向循环链表的打印

void ListPrint(ListNode* phead) //打印
{assert(phead);ListNode *phead_next = phead->next;printf("phead <-> ");while(phead_next != phead){printf("%d <-> ",phead_next->data);phead_next = phead_next->next;}printf("phead\n");
}

6.5带头双向链表的增加节点

ListNode* BuyListNode(LTDataType x) //增加节点
{ListNode *newnode = (ListNode*) malloc(sizeof (ListNode));if(newnode == NULL){perror("增加节点开辟空间失败");exit(-1);}newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}

6.6带头双向循环链表的在pos之前插入

void ListInsert(ListNode* pos,LTDataType x)//在pos位置之前插入
{assert(pos);ListNode *newnode = BuyListNode(x);ListNode *pos_prev = pos->prev;pos_prev->next = newnode;newnode->prev = pos_prev;newnode->next = pos;pos->prev = newnode;
}

6.7带头双向循环链表删除pos位置

void ListErase(ListNode* pos)//删除pos位置的节点
{assert(pos);ListNode *pos_next = pos->next;ListNode *pos_prev = pos->prev;pos_prev->next = pos_next;pos_next->prev = pos_prev;free(pos);
}

6.8带头双向循环链表的尾插尾删

void ListPushBack(ListNode* phead,LTDataType x)//尾插
{assert(phead);ListInsert(phead,x);
}void ListPopBack(ListNode* phead)//尾删
{assert(phead);ListErase(phead->prev);
}

6.9带头双向循环链表的头插头删

void ListPushFront(ListNode* phead,LTDataType x)//头插
{assert(phead);ListInsert(phead->next,x);
}void ListPopFront(ListNode* phead)//头删
{assert(phead);ListErase(phead->next);
}

6.10带头双向循环链表的长度求解

int ListSize(ListNode* phead)//求链表的长度
{assert(phead);int len = 0;ListNode *phead_next = phead->next;while(phead != phead_next){len++;phead_next = phead_next->next;}return len;
}

6.11带头双向循环链表的寻找某一节点

ListNode*ListFind(ListNode* phead, LTDataType num)//寻找某一个节点
{assert(phead);ListNode *find = phead->next;while(find != phead){if(find->data == num){return find;}find = find->next;}return NULL;
}

7.顺序表和链表的区别

不同点顺序表链表

存储空间上 

物理上一定连续逻辑一定连续,物理不一定

任意位置插入或者删除

元素

需要覆盖数据通过指针就能找到

插入 

动态顺序表需要扩容没有容量概念,需要一个给一个

缓存利用率

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/138809.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【LVGL】SquareLine Studio入门基础操作

目录 1.SquareLine Studio基础2.SquareLine Studio创建小部件3.SquareLine Studio创建样式4.SquareLine Studio创建事件和响应动作5.SquareLine Studio创建动画6.SquareLine Studio导出项目7.SquareLine Studio导出到现有工程中8.SquareLine Studio添加自定义开发板9.SquareLin…

ICMP协议(二)

一 ping工作原理 ① 为什么ping不通 "ping不通分为两类" 1) 请求没有到目标服务器细节&#xff1a; 要注意是ip不通还是域名不能解析导致ping不通2) 请求到了目标服务器,但是没有回包 "常见原因" 1、对方关机/ip不存在备注&#xff1a; ping同网段不…

Linux传统跨进程通信原理

文章目录 前言一、进程隔离二、进程空间划分&#xff1a;用户空间(User Space)/内核空间(Kernel Space)三、系统调用&#xff1a;用户态与内核态四、Linux下传统IPC跨进程通信原理1、发送进程通过系统调用&#xff0c;将需要发送的数据拷贝到Linux进程的内核空间中的缓存区(数据…

Hadoop问题:start-all.sh显示未找到命令

在sbin文件夹下是start-all.sh可以运行的&#xff0c;但是到了别的文件夹下就不行了&#xff0c;于是想到了是文件路径问题&#xff0c;因为hadoop环境是和java环境一起配置的导致sbin写成了bin 解决办法&#xff1a; 打开.bashrc配置hadoop的环境变量 sudo vim ~/.bashrc …

WAF绕过-信息收集之反爬虫延时代理池 46

老师用的阿里云的服务器&#xff0c;装了宝塔和安全狗&#xff0c; 演示案例 Safedog-默认拦截机制分析绕过-未开CC 没有打开防止流量攻击的安全狗&#xff0c; 而这里&#xff0c;get请求可以直接看到返回结果&#xff0c;而head就不行。 我们就给工具换成get请求 在没有c…

求解八皇后问题

一、实验目的 利用回溯法搜索或爬山法找到八皇后问题的一个可行解。 二、实验内容 有一个 8 8 的棋盘&#xff0c;现在要将8个皇后放到棋盘上&#xff0c;满足&#xff1a;对于每一个皇后&#xff0c;在 自己所在的行、列、两个对角线都没有其他皇后。求所有满足的摆放方式…

苹果10月24日推送iOS 17.1:修复iPhone 12辐射超标问题 信号会更差

前段时间在iPhone 15系列发布的当天&#xff0c;法国突然宣布iPhone 12不能在该国销售&#xff0c;理由是iPhone 12超过了当地无线电频率暴露的法定范围。 根据法国监管机构ANFR(国家频率管理局)发布的最新消息&#xff0c;苹果将会在10月24日推送iOS 17.1正式版&#xff0c;届…

lenovo联想笔记本ThinkPad系列T15p或P15v Gen3(21DA,21DB,21D8,21D9)原厂Win11系统镜像

下载链接&#xff1a;https://pan.baidu.com/s/1V4UXFhYZUNy2ZQ8u4x1AFg?pwdqz0s 系统自带指纹驱动、人脸识别驱动、显卡、声卡等所有驱动、出厂主题壁纸、Office办公软件、Lenovo联想电脑管家等预装程序 所需要工具&#xff1a;32G或以上的U盘 文件格式&#xff1a;ISO …

使用UniApp实现视频数组自动下载与播放功能:一步步指导

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

Python+高光谱数据预处理-机器学习-深度学习-图像分类-参数回归

涵盖高光谱遥感数据处理的基础、python开发基础、机器学习和应用实践。重点解释高光谱数据处理所涉及的基本概念和理论&#xff0c;旨在帮助学员深入理解科学原理。结合Python编程工具&#xff0c;专注于解决高光谱数据读取、数据预处理、高光谱数据机器学习等技术难题&#xf…

PC电脑 VMware安装的linux CentOs7如何扩容磁盘?

一、VM中进行扩容设置 必须要关闭当前CentOS&#xff0c;不然扩展按钮是灰色的。 输入值必须大于当前磁盘容量。然后点击扩展&#xff0c;等待扩展完成会提示一个弹框&#xff0c;点击确定&#xff0c;继续确定。 二、操作CentOS扩容——磁盘分区 第一步设置完成。那就启动 …

java springboot VUE粮食经销系统开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 springboot VUE 粮食经销系统是一套完善的完整信息管理类型系统&#xff0c;结合springboot框架和VUE完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架&#xff08;MVC模式开发&#xff09; &#xff0c;系统具有完整的源代码和数…