双向链表的初步练习

𝙉𝙞𝙘𝙚!!👏🏻‧✧̣̥̇‧✦👏🏻‧✧̣̥̇‧✦ 👏🏻‧✧̣̥̇:  Solitary-walk

      ⸝⋆   ━━━┓
     - 个性标签 - :来于“云”的“羽球人”。 Talk is cheap. Show me the code
┗━━━━━━━  ➴ ⷯ

本人座右铭 :   欲达高峰,必忍其痛;欲戴王冠,必承其重。

👑💎💎👑💎💎👑 
💎💎💎自💎💎💎
💎💎💎信💎💎💎
👑💎💎 💎💎👑    希望在看完我的此篇博客后可以对你有帮助哟

👑👑💎💎💎👑👑   此外,希望各位大佬们在看完后,可以互赞互关一下,看到必回
👑👑👑💎👑👑👑   

目录

 


1. 双向链表的初始化

2. 双向链表的销毁

3. 双向链表的尾插

4. 双向链表的头插

5. 双向链表的尾删

6.双向链表的头删

7. 双向链表的指定位置之后的插入

8. 双向链表的指定位置的删除
9. 双向链表的按值查找

10.链表打印


首先在我们进行各项具体任务之前,咱还是需要把前期工作准备好的 

1.先进行自定义链表的声明

2.一些函数的声明

3.必要头文件的引入

首先我们要知道什么是双向链表,顾名思义,对于一个结点而言既有指向自己的指针,也有指向其他结点的指针域

如图所示:

链表结构体类型的定义代码如下:

typedef struct ListNode 
{DataType data;//数据域struct ListNode* pre;//前驱域struct ListNode*next;//后继域
}ListNode;

 1.初始化

 注意这里的带头双向链表不同于前面单链表的头节点

为了好区分,这里我们称之为"哨兵位"

哨兵位只是占一个位置,并不存储任何有效的数据

 当我们只有哨兵位这个结点,思考一下,如何变成一个双向循环链表

你想对了吗??

	phead->pre = phead->next = phead;//让他变成双向循环链表

初始化代码之不采用 传参的方法

ListNode* Init()//初始化,采用不传参的形式,但是需把哨兵位这个结点返回
{//自己需要创建一个哨兵位ListNode* phead = (ListNode*)malloc(sizeof(ListNode));assert(phead);//可能开辟失败phead->data = -1;phead->pre = phead->next = phead;//记住这里要让他变成双向循环链表return phead;
}

初始化代码之采用 传参的方法

注意这里我们形参是用二级指针来接收的

因为实参我们需要传链表的地址,否则我们是无法完成初始化的

因为函数形参只是实参的一份临时拷贝,对形参的临时修改并不会影响我们的实参

void Init(ListNode** pphead)//初始化,采用传参的形式
{//注意一下操作都是基于 phead是哨兵位来进行的 他只占一个位置,并不存储任何有效数据assert(pphead);*pphead = (ListNode*)malloc(sizeof(ListNode));if (*pphead == NULL){perror("malloc fail\n");return 1;//既然开辟失败,没必要执行下面代码}(*pphead)->data = -1;//注意优先级顺序(*pphead)->next = (*pphead)->pre = NULL;//构建一个双向循环链表
}
2.销毁

说白了,其实就是一个结点一个结点进行删除

这自然就需要遍历了

我们在删除之前需要先找到删除结点后一个结点

关键是我们实参到底是指针还是指针地址

 让要删除的结点为 del = phead->next;

那么要删除的结点下一个结点为 del->next

我们不妨试一下~~~

实参为指针

void Destroy(ListNode* phead)//链表销毁;想一下,传一级指针还是二级?  在这里我们传入一级指针,为了保持接口一致性
{//销毁我们是一个一个进行删除,自然就需要遍历assert(phead);ListNode* del = phead->next;while (del != phead){ListNode* next = del->next;free(del);/*del = NULL;*/    //  ?del = next;}//来到这说明,此时只有一个哨兵位free(phead)phead = NULL;}

 注意当我们只是简单调用销毁函数的时候,代码并不是我们所设想的一样

正常来说,我们的plist的值应该为NULL

 

为了解决这个问题我们就需要在传一级指针的时候,手动把plist 置为空 

 实参为指针地址

 代码如下,但是问题又来了

void LTDestroy(ListNode** pphead) 
{assert(pphead && *pphead);ListNode* cur = (*pphead)->next;while ( cur!=*pphead ){ListNode* next = cur->next;free(cur);cur = next;}free(*pphead);*pphead = NULL;}

 对于这个问题容本up主先买个关子,欲知后事如何 且听下回分解

为了保持接口一致性,我们这里就使用一级指针来接收

3.尾插

顾名思义,是在尾结点后面进行插入,注意这里在哨兵位的前面进行插入也是可以滴~~~

接下来就是改变指针的指向

1)首先既然是尾插就需要为要插进来的数据开辟结点

这里涉及到创建结点的函数

2)其次是处理指针指向

3)先处理node的pre ,next

4)处理 原来尾结点,哨兵位

 尾插草图如下:

 结点创建函数的代码如下:

ListNode* ListBuyNode(x)//创建结点
{ListNode* node = (ListNode*)malloc(sizeof(ListNode));if (node == NULL){perror("malloc fail\n");return;}//来到这,说明空间开辟成功node->data = x;node->next = node->pre = NULL;return node;
}

 

 

 尾插完整代码:

void PushBack(ListNode* phead,DataType x)//尾插
{assert(phead);//为插入的数据开辟空间ListNode* node = ListBuyNode(x);//先处理node 的前驱,后继node->pre = phead->pre; //  phead->pre->next原来尾结点的后继,node->pre = phead->pre->next这里出现了野指针的问题 phead->pre->next没有具体指向node->next = phead;//接下来在处理 原来尾结点,哨兵位/*,这样写是错的,以下2句话不可以颠倒已经把node这个结点给覆盖掉了,在执行65行代码时,node 是未知的,因为这个结点已经被覆盖了,phead->pre->next此时他的指向也就是未知的,调试时不会报错,但是遍历读取他就会报错了,就像你在打印函数里进行打印时,出现野指针phead->pre = node;phead->pre->next = node;*/phead->pre->next = node;phead->pre = node;//别忘了,每尾插进来的结点最终都是一个新的尾结点
}

4.头插

首先是在哨兵位是后面进行的,每插入进来应该结点,此节点就会成为一个新的头节点

同上,需要先创建一个结点

其次找到原来头节点 phead->next 

 头插草图如下:

头插完整代码如下:

void PushFront(ListNode* phead,DataType x)//头插
{//头插是在哨兵位的后面进行插入assert(phead);//为插入的数据开辟空间ListNode* node = ListBuyNode(x);//处理node 的后继,前驱node->pre = phead;node->next = phead->next;//处理phead ,phead->next /*这样写也是对的phead->next->pre = node;*/phead->next = node;//头节点要进行更新phead->next->pre = node;}

5.尾删

注意我们不能上来就行删除

需要先找到要删除结点 (del)前一个也就是 del->pre

这里我们一定要注意结点的先后顺序

完整代码如下:

void PopBack(ListNode* phead)//尾删
{//注意不能直接删除,需要先找到尾结点的前一个结点assert(phead);//先判断以下是否为空,有2种写法/*if (phead->pre == phead){return 9;}*/assert(phead->next != phead);//是否为空ListNode* del = phead->pre;//把要删除的结点先保存起来//删除之前,先要构成一个新的双向循环链表del->pre->next = phead;phead->pre = del->pre;/*错误的,顺序不能颠倒,因为此时 del->pre已经被覆盖了phead->pre = del->pre;//新的尾结点del->pre->next = phead;*/free(del);del = NULL;}

 

7. 指定位置之后的插入
草图如下:

 

1)要为插入的数据创建结点

2)找到指定位置(pos)之后的结点 (pos->next)和之前的结点(pos->pre

3)  改变指针指向

指定位置之后插入对应完整代码:

void InsertAfter(ListNode* pos, DataType x)//指定位置之后插入
{assert(pos);//为插入的数据开辟空间ListNode* node = ListBuyNode(x);//处理 node 的前驱,后继node->next = pos->next;node->pre = pos;//处理 pos 和 pos->next的prepos->next = node;pos->next->pre = node;}
8. 双向链表的指定位置的删除

对应草图如下:

 直接进行删除是不行滴~~~

先要找到指定位置(pos)之后的结点 (pos->next)和之前的结点(pos->pre

其次改变指针走向

void Earse(ListNode* pos)//指定位置删除
{assert(pos);//需要找到pos之前的一个结点和之后的一个结点pos->next->pre = pos->pre;pos->pre->next = pos->next;free(pos);pos = NULL;
}
9. 双向链表的按值查找

 这里自然就需要一个一个进行遍历了

按值查找对应完整代码:

ListNode* Find(ListNode* phead, DataType x)//在链表中按值查找,若是找到则返回对应的结点
{assert(phead);ListNode* pcur = phead->next;//定义一个用来循环遍历的指针while (pcur != phead){if (pcur->data == x){return pcur;//直接返回对应结点}pcur = pcur->next;}printf("没有找到\n");
}

 10链表打印

方法同上,对链表进行遍历

void Print(ListNode* phead)//链表打印
{assert(phead);ListNode* pcur = phead->next;//定义一个用来 遍历的指针,注意他初始值是 phead->nextwhile (pcur != phead){printf("%d-> ", pcur->data);pcur = pcur->next;//记得更新}printf("\n");
}

 


List.c对应完整代码

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"//void Init(ListNode** pphead)//初始化,采用传参的形式
//{
//	//注意一下操作都是基于 phead是哨兵位来进行的 他只占一个位置,并不存储任何有效数据
//	assert(pphead);
//	*pphead = (ListNode*)malloc(sizeof(ListNode));
//	if (*pphead == NULL)
//	{
//		perror("malloc fail\n");
//		return 1;//既然开辟失败,没必要执行下面代码
//	}
//
//	(*pphead)->data = -1;//注意优先级顺序
//	(*pphead)->next = (*pphead)->pre = NULL;//构建一个双向循环链表
//}ListNode* Init()//初始化,采用不传参的形式,但是需把哨兵位这个结点返回
{//自己需要创建一个哨兵位ListNode* phead = (ListNode*)malloc(sizeof(ListNode));assert(phead);//可能开辟失败phead->data = -1;phead->pre = phead->next = phead;//记住这里要让他变成双向循环链表return phead;
}
ListNode* ListBuyNode(x)//创建结点
{ListNode* node = (ListNode*)malloc(sizeof(ListNode));if (node == NULL){perror("malloc fail\n");return;}//来到这,说明空间开辟成功node->data = x;node->next = node->pre = NULL;return node;
}
void Print(ListNode* phead)//链表打印
{assert(phead);ListNode* pcur = phead->next;//定义一个用来 遍历的指针,注意他初始值是 phead->nextwhile (pcur != phead){printf("%d-> ", pcur->data);pcur = pcur->next;//记得更新}printf("\n");
}void PushBack(ListNode* phead,DataType x)//尾插
{assert(phead);//为插入的数据开辟空间ListNode* node = ListBuyNode(x);//先处理node 的前驱,后继node->pre = phead->pre; //  phead->pre->next原来尾结点的后继,node->pre = phead->pre->next这里出现了野指针的问题 phead->pre->next没有具体指向node->next = phead;//接下来在处理 原来尾结点,哨兵位/*,这样写是错的,以下2句话不可以颠倒已经把node这个结点给覆盖掉了,在执行65行代码时,node 是未知的,因为这个结点已经被覆盖了,phead->pre->next此时他的指向也就是未知的,调试时不会报错,但是遍历读取他就会报错了,就像你在打印函数里进行打印时,出现野指针phead->pre = node;phead->pre->next = node;*/phead->pre->next = node;phead->pre = node;//别忘了,每尾插进来的结点最终都是一个新的尾结点
}
void PushFront(ListNode* phead,DataType x)//头插
{//头插是在哨兵位的后面进行插入assert(phead);//为插入的数据开辟空间ListNode* node = ListBuyNode(x);//处理node 的后继,前驱node->pre = phead;node->next = phead->next;//处理phead ,phead->next /*这样写也是对的phead->next->pre = node;*/phead->next = node;//头节点要进行更新phead->next->pre = node;}
void PopBack(ListNode* phead)//尾删
{//注意不能直接删除,需要先找到尾结点的前一个结点assert(phead);//先判断以下是否为空,有2种写法/*if (phead->pre == phead){return 9;}*/assert(phead->next != phead);//是否为空ListNode* del = phead->pre;//把要删除的结点先保存起来//删除之前,先要构成一个新的双向循环链表del->pre->next = phead;phead->pre = del->pre;/*错误的,顺序不能颠倒,因为此时 del->pre已经被覆盖了phead->pre = del->pre;//新的尾结点del->pre->next = phead;*/free(del);del = NULL;}
void PopFront(ListNode* phead)//头删
{assert(phead);assert(phead->next != phead);//确保不为空ListNode* del = phead->next;//以下2句没有先后顺序之分del->next->pre = phead;phead->next = del->next;free(del);del = NULL;}
void InsertAfter(ListNode* pos, DataType x)//指定位置之后插入
{assert(pos);//为插入的数据开辟空间ListNode* node = ListBuyNode(x);//处理 node 的前驱,后继node->next = pos->next;node->pre = pos;//处理 pos 和 pos->next的prepos->next = node;pos->next->pre = node;}
void Earse(ListNode* pos)//指定位置删除
{assert(pos);//需要找到pos之前的一个结点和之后的一个结点pos->next->pre = pos->pre;pos->pre->next = pos->next;free(pos);pos = NULL;
}
ListNode* Find(ListNode* phead, DataType x)//在链表中按值查找,若是找到则返回对应的结点
{assert(phead);ListNode* pcur = phead->next;//定义一个用来循环遍历的指针while (pcur != phead){if (pcur->data == x){return pcur;//直接返回对应结点}pcur = pcur->next;}printf("没有找到\n");
}
void Destroy(ListNode* phead)//链表销毁;想一下,传一级指针还是二级?  在这里我们传入一级指针,为了保持接口一致性
{//销毁我们是一个一个进行删除,自然就需要遍历assert(phead);ListNode* del = phead->next;while (del != phead){ListNode* next = del->next;free(del);/*del = NULL;*/    //  ?del = next;}//来到这说明,此时只有一个哨兵位free(phead);phead = NULL;}
void LTDestroy(ListNode** pphead) 
{assert(pphead && *pphead);ListNode* cur = (*pphead)->next;while ( cur!=*pphead ){ListNode* next = cur->next;free(cur);cur = next;}free(*pphead);*pphead = NULL;}

 List.h对应完整代码

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>#include<stdlib.h>
//双向链表的实现
typedef int DataType;
typedef struct ListNode {DataType data;//数据域struct ListNode* pre;//前驱域struct ListNode*next;//后继域
}ListNode;//接口函数的声明
//void Init(ListNode** pphead);//初始化,采用传参的形式
ListNode* Init();//初始化,采用不传参的形式,但是需把哨兵位这个结点返回
void Print(ListNode* phead);//链表打印
void PushBack(ListNode* phead,DataType x);//尾插,这里传入一级指针即可,因为返回的头节点我们不需要进行更改
void PushFront(ListNode* phead, DataType x);//头插
void PopBack(ListNode* phead);//尾删
void PopFront(ListNode* phead);//头删
void InsertAfter(ListNode* pos,DataType x);//指定位置之后插入
void Earse(ListNode* pos);//指定位置删除
ListNode* Find(ListNode*phead,DataType x);//按值查找,若是找到则返回对应的结点
void Destroy(ListNode* phead);//链表销毁;想一下,传一级指针还是二级?为了保持接口一致性我们传入一级指针

ok,以上就是我要为大家进行share的一些基本内容,都来到这里了,要是感觉我写的还不错的话,各位大佬烦劳点个赞,互关以下呗~~~

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

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

相关文章

函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)

函数栈帧的创建和销毁[以C语言代码为例,汇编代码的角度分析] 一.前言1.几个问题2.几个说明 二.相关寄存器和汇编命令的简要说明三.从汇编代码调试的角度逐步分析函数栈帧的创建于销毁1.函数栈区的知识:2.逐步调试分析1.保存__tmainCRTStartup这个函数栈帧的栈底地址2.正式进入m…

合肥中科深谷嵌入式项目实战——人工智能与机械臂(三)

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000 python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 作者&#xff1a;爱吃饼干的小白鼠。Python领域优质创作者&#xff0c;2022年度博客新星top100入围&#xff0c;荣获多家平台专家称号。…

kafka3.X集群安装(不使用zookeeper)

一、kafka集群实例角色规划 在本专栏的之前的一篇文章《kafka3种zk的替代方案》已经为大家介绍过在kafka3.0种已经可以将zookeeper去掉。 上图中黑色代表broker&#xff08;消息代理服务&#xff09;&#xff0c;褐色/蓝色代表Controller(集群控制器服务) 左图&#xff08;kafk…

[BUUCTF NewStarCTF 2023 公开赛道] week4 crypto/pwn

再补完这个就基本上完了. crypto RSA Variation II Schmidt-Samoa密码系统看上去很像RSA,其中Npqq, 给的eN给了d from secret import flag from Crypto.Util.number import *p getPrime(1024) q getPrime(1024)N p*p*qd inverse(N, (p-1)*(q-1)//GCD(p-1, q-1))m bytes…

计算机视觉的相机选型

#你一般什么时候会用到GPT?# 目前市面上的工业相机大多是基于CCD&#xff08;ChargeCoupled Device&#xff09;或CMOS&#xff08;Complementary Metal Oxide Semiconductor&#xff09;芯片的相机。一般CCD制造工艺更加复杂&#xff0c;也会更贵一点&#xff01; 1、CCD工…

40基于MATLAB,使用模板匹配法实现车牌的识别。

基于MATLAB&#xff0c;使用模板匹配法实现车牌的识别。具体包括将原图灰度化&#xff0c;边缘检测&#xff0c;腐蚀操作&#xff0c;车牌区域定位&#xff0c;车牌区域矫正&#xff0c;二值化&#xff0c;均值滤波&#xff0c;切割&#xff0c;字符匹配&#xff0c;最终显示车…

mycat2.X读写分离

一、数据库中间件介绍 二、下载安装包 2.1下载地址: 下载两个一个是mycat程序,一个是mycat的驱动 http://dl.mycat.org.cn/2.0/install-template/mycat2-install-template-1.20.zip http://dl.mycat.org.cn/2.0/1.21-release/mycat2-1.21-release-jar-with-dependencies-2…

git本地搭建服务器[Vmware虚拟机访问window的git服务器]

先按照https://zhuanlan.zhihu.com/p/494988089说明下载好Gitblit然后复制到tomcat的webapps目录下,如下: 双击"startup.bat"启动tomcat: 然后访问"http://127.0.0.1:8080/gitblit/"即可看到git的界面: 说明git服务器已经能够成功运行了! Vmware虚拟机…

vue源码分析(七)—— createComponent

文章目录 前言一、createComponent 参数说明二、createComponent 源码详解1.baseCtor的实际指向2.extend 方法3.判断Ctor是否是函数的判断4.installComponentHooks方法5.返回一个带标识的组件 vnode 前言 createComponent文件的路径&#xff1a; src\core\vdom\create-componen…

windows下使用FFmpeg开源库进行视频编解码完整步聚

最终解码效果: 1.UI设计 2.在控件属性窗口中输入默认值 3.复制已编译FFmpeg库到工程同级目录下 4.在工程引用FFmpeg库及头文件 5.链接指定FFmpeg库 6.使用FFmpeg库 引用头文件 extern "C" { #include "libswscale/swscale.h" #include "libavdevic…

Failed to prepare the device for development

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

IDEA 使用技巧

文章目录 语言支持简化编写 有问题&#xff0c;可暂时跳过 个人常用快捷键插件主题插件功能插件 碰到过的问题 除了一些在Linux上用vim开发的大佬&#xff0c;idea算是很友好的集成开发工具了&#xff0c;功能全面&#xff0c;使用也很广泛。 记录一下我的 IDEA 使用技巧&#…

【数据分析】上市公司半年报数据分析

前言 前文介绍过使用网络技术获取上市公司半年报数据的方法&#xff0c;本文将对获取到的数据进行简要的数据分析。 获取数据的代码介绍在下面的两篇文章中 【java爬虫】使用selenium获取某交易所公司半年报数据-CSDN博客 【java爬虫】公司半年报数据展示-CSDN博客 全量数…

Java工具库——commons-lang3的50个常用方法

未来的你&#xff0c;我亲爱的女孩&#xff0c;愿此刻无忧无虑&#xff0c;开心&#xff0c;快乐… 工具库介绍 Apache Commons Lang 3&#xff08;通常简称为Commons Lang 3&#xff09;是Apache Commons项目中的一个Java工具库&#xff0c;它提供了一系列实用的工具类和方法…

骨传导耳机到底好用吗,2023年骨传导耳机该怎么选

骨传导耳机到底好用吗&#xff0c;骨传导耳机是一种完全颠覆你听音体验的黑科技&#xff01;不仅能够让你享受音乐的同时保护你的听力&#xff0c;还能让你感受到一种前所未有的新鲜感。很显然&#xff0c;骨传导耳机是真的好用&#xff0c;现在市面上有许多品牌的骨传导耳机&a…

24 行为型模式-访问者模式

1 访问者模式介绍 访问者模式在实际开发中使用的非常少,因为它比较难以实现并且应用该模式肯能会导致代码的可读性变差,可维护性变差,在没有特别必要的情况下,不建议使用访问者模式。 2 访问者模式原理 3 访问者模式实现 我们以超市购物为例,假设超市中的三类商品: 水果,糖…

.NET、VUE利用RSA加密完成登录并且发放JWT令牌设置权限访问

后端生成公钥私钥 使用RSA.ToXmlString(Boolean) 方法生成公钥以及私钥。 RSACryptoServiceProvider rSA new(); string pubKey rSA.ToXmlString(false);//公钥 string priKey rSA.ToXmlString(true);//私钥 后端将生成的公钥发送给前端 创建一个get请求&#xff0c;将…

【Linux】centOS7安装配置及Linux的常用命令---超详细

一&#xff0c;centOS 1.1 centOS的概念 CentOS&#xff08;Community Enterprise Operating System&#xff09;是一个由社区支持的企业级操作系统&#xff0c;它是以Red Hat Enterprise Linux&#xff08;RHEL&#xff09;源代码为基础构建的。CentOS提供了一个稳定、可靠且…

macOS M1安装wxPython报错‘tiff.h‘ file not found的解决方法

macOS12.6.6 M1安装wxPython失败&#xff1a; 报错如下&#xff1a; imagtiff.cpp:37:14: fatal error: tiff.h file not found解决办法&#xff1a; 下载源文件重新编译&#xff08;很快&#xff0c;5分钟全部搞定&#xff09;&#xff0c;分三步走&#xff1a; 第一步&…

完整的电商平台后端API开发总结

对于开发一个Web项目来说&#xff0c;无论是电商还是其他品类的项目&#xff0c;注册与登录模块都是必不可少的&#xff1b;注册登录功能也是我们在日常生活中最长接触的&#xff0c;对于这个业务场景的需求与逻辑大概是没有什么需要详细介绍的&#xff0c;市面上常见的邮箱注册…