[数据结构]实现双向链表

news2024/11/28 10:41:38

作者华丞臧.
专栏【数据结构】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎在评论区指出。
在这里插入图片描述


文章目录

  • 一、带头双向循环链表
  • 二、带头双向循环链表接口实现
    • 2.1 双向链表的初始化、打印和销毁
    • 2.2 动态申请结点
    • 2.3 双向链表尾插和头插
    • 2.4 双向链表的尾删和头删
    • 2.5 双向链表的查找
    • 2.6 双向链表指定位置之前插入和指定位置删除
    • 2.7 链表判空和链表长度
    • 2.8 测试


一、带头双向循环链表

在前面单向链表的学习中,不难发现单向链表有不少缺点,如改变头结点时需要传二级指针,尾删和指定位置操作时间复杂为O(N)效率较低
在这里插入图片描述

带头双向循环链表结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。
在这里插入图片描述

二、带头双向循环链表接口实现

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int LTDataType;

typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;


//初始化
ListNode* ListInit();

// 双向链表打印
void ListPrint(ListNode* phead);

// 动态申请一个结点
ListNode* BuySListNode(LTDataType x);

// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x);

// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x);

// 双向链表尾删
void ListPopBack(ListNode* phead);

// 双向链表头删
void ListPopFront(ListNode* phead);

// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x);

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);

// 双向链表删除pos位置的结点
void ListErase(ListNode* pos);

//判空
bool ListEmpty(ListNode* phead);

//链表长度
size_t ListSize(ListNode* phead);

// 双向链表销毁
void ListDestory(ListNode* plist);


2.1 双向链表的初始化、打印和销毁

初始化函数当中需要在堆上申请创建一个头结点,然后把头结点地址返回;需要注意头结点pheadnextprev指针要指向phead自己。
在这里插入图片描述

销毁双向链表时,注意主函数当中用来保存头结点的结构体指针plist并没有在销毁函数当中被改变,需要在主函数当中置为NULL指针,否则plist就是野指针

//初始化
ListNode* ListInit()
{
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	if (phead == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	phead->next = phead->prev = phead;
	return phead;
}

// 双向链表打印
void ListPrint(ListNode* phead)
{
	assert(phead);

	ListNode* cur = phead->next;

	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");

}

// 双向链表销毁
void ListDestory(ListNode* phead)
{
	ListNode* cur = phead->next;

	while (cur != phead)
	{
		ListNode* tmp = cur;
		cur = cur->next;
		free(tmp);
	}
	free(phead);
}

2.2 动态申请结点

在堆上申请一块空间用来存放新插入的结点,返回申请的内存地址。

// 动态申请一个结点
ListNode* BuySListNode(LTDataType x)
{
	ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
	if (newNode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newNode->data = x;
	newNode->next = newNode->prev = NULL;
	return newNode;
}

2.3 双向链表尾插和头插

双向链表中有一个指向前一个结点的指针prev,有一个指向下一个结点的指针next;并且头指向尾、尾指向头形成循环;这样在进行尾插时就不需要遍历链表,只需要通过头结点就可以找到尾结点,其时间复杂度为O(1)
在这里插入图片描述

带头结点链表的头插不会改变链表的头结点也就不需要二级指针,其时间复杂度也是O(1)
注意当链表为空时,不需要进行删除操作,所以断言一下看链表是否为空。
在这里插入图片描述

// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
    assert(phead);//断言phead不为空

	ListNode* cur = phead->prev;
	ListNode* newNode = BuySListNode(x);

	cur->next = newNode;
	phead->prev = newNode;

	newNode->next = phead;
	newNode->prev = cur;
}



// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);
	//申请节点
	ListNode* newNode = BuySListNode(x);

	newNode->next = phead->next;
	phead->next = newNode;
	newNode->prev = phead;
	newNode->next->prev = newNode;
}

2.4 双向链表的尾删和头删

单向链表的尾删需要遍历找到尾结点的前一个结点;而双向循环链表的尾删可以通过头结点找到尾结点尾结点的前一个结点,不需要遍历链表就可以进行尾删操作,其时间复杂度时O(1)
在这里插入图片描述

带头结点链表的头删不会改变链表的头结点也就不需要二级指针,其时间复杂度也是O(1)
在这里插入图片描述

// 双向链表尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));
	
	ListNode* tail = phead->prev->prev;

	free(tail->next);

	phead->prev = tail;
	tail->next = phead;
}

// 双向链表头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));//断言链表不为空
	
	ListNode* next = phead->next->next;

	free(phead->next);
	phead->next = next;
	next->prev = phead;
}

2.5 双向链表的查找

遍历链表,把对应结点的地址返回。

// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
	assert(phead);

	ListNode* cur = phead->next;

	//遍历
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

2.6 双向链表指定位置之前插入和指定位置删除

指定位置的操作需要和查找配合使用,在pos之前插入比之单向链表无疑简单很多,不需要遍历链表就可以找到前一个结点,通过pos结点的prev指针找到前一个结点,实现指定位置之前插入,其时间复杂度为O(1)
在这里插入图片描述
同样的,指定位置删除也不需要遍历链表,可以通过pos结点的prevnext指针找到前后结点,就可以实现指定位置删除,其时间复杂度为O(1)
在这里插入图片描述

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	ListNode* newNode = BuyListNode(x);

	newNode->next = pos;
	pos->prev->next = newNode;
	newNode->prev = pos->prev;
	pos->prev = newNode;
}

// 双向链表删除pos位置的结点
void ListErase(ListNode* pos)
{
	assert(pos);

	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

2.7 链表判空和链表长度

带头双向循环链表为空时,通过初始化函数可以得知头结点phead指向phead形成循环,也就是说链表为空时头结点下一个结点还是头结点即(phead->next == phead)。
求双向链表的长度可以在结果体中加一个size表示链表的长度,可以遍历链表计算链表的长度把长度返回也可以得到链表的长度大小。

//判空
bool ListEmpty(ListNode* phead)
{
	assert(phead); 

	return phead->next == phead;
}

//链表长度
size_t ListSize(ListNode* phead)
{
	assert(phead);

	size_t size = 0;
	ListNode* cur = phead->next;
	//遍历
	while (cur != phead)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

2.8 测试

测试带头双向循环链表接口实现的正确性。

#include "List.h"

void Test1()
{
	ListNode* plist = ListInit();

	//尾插
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPrint(plist);

	//头插
	ListPushFront(plist, 10);
	ListPushFront(plist, 20);
	ListPushFront(plist, 30);
	ListPushFront(plist, 40);
	ListPrint(plist);

	//尾删
	ListPopBack(plist);
	ListPopBack(plist);
	ListPrint(plist);

	//头删
	ListPopFront(plist);
	ListPopFront(plist);
	ListPrint(plist);

	//在pos位置之前插入
	ListInsert( ListFind(plist,10), 50);
	ListPrint(plist);

	//删除pos位置
	ListErase(ListFind(plist, 1));
	ListPrint(plist);

	ListDestory(plist);
	plist = NULL;
}

int main()
{
	Test1();
	return 0;
}

程序运行结果:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

动态规划算法的题到底应该怎么做?思路教给你自己写

本文是我通过做题和借鉴一些前人总结的套路而得出的思路和方法&#xff0c;刚好这次CSDN第八周的周赛上出了三道动态规划的题目&#xff0c;我会结合题目对我的思路进行一个输出&#xff0c;会从最简单的一维dp开始讲解到二维dp&#xff0c;希望对你有帮助&#xff0c;有错误希…

MySQL数据库基础知识

今天是更新数据库的第一篇&#xff0c;关于数据库环境搭建问题博主先不在这里介绍了&#xff0c;博主今天是直接讲知识了&#xff0c;等以后的博客&#xff0c;博主再更新数据库搭建问题。在这里我们使用命令行式客户端&#xff0c;先不使用windows下的图形化界面&#xff0c;使…

ReadingTime-十一月

CV文章浅读_not_everday0x1105.CAViT for video object re-id 2022_中科院这个月主要是要学习pytorch和一些CV baseline的复现&#xff0c;搞搞毕设雏形&#x1f199; 以后还是把笔记写纸上要么写博客&#xff0c;不放本地了&#x1f628; 网页版小绿鲸zen好用 &#x1f603; …

峰会实录 | 基于StarRocks和腾讯云EMR构建云上Lakehouse

作者&#xff1a;腾讯云EMR业务负责人陈龙&#xff08;本文为作者在 StarRocks Summit Asia 2022 上的分享&#xff09; 我目前负责腾讯云EMR 的研发工作&#xff0c;此前先后在百度、支付宝做后端研发。2011年加入腾讯&#xff0c;先后参与了腾讯云Redis、腾讯云云数据库、Ap…

小米 Civi 2 (ziyi) 机型解锁bl 获取root权限教程 +其他机型参数对比+救砖

*********机型优点与其他机型参数对比***************** 小米 Civi 2 (ziyi) 国行版机型前置由3200万主摄3200万超广角组成的双摄是它最大的亮点&#xff0c;配有4颗柔光灯。自拍相当不错。他的后置主摄采用5000万像素相机&#xff0c;IMX766传感器&#xff0c;1/1.56英寸感光…

【MybatisPlus】CRUD操作,映射匹配兼容性,ID生成策略,逻辑删除,乐观锁

文章目录MybatisPlus简介一、数据层基本的开发1. 引入jar包2. 配置数据源3. 编写实体类4. 创建Dao接口5. 测试二、CRUD使用1. 查询2. 添加3. 删除4. 修改5. 分页查询三、条件查询1. 条件查询的方式2. 多条件查询四、映射匹配兼容性1. 表字段与编码属性设计不同步2. 编码中添加了…

矩阵理论复习(二)

内积空间的定义 模与内积 向量x和y的夹角 正交向量、正交组和正交矩阵 度量矩阵 基向量内积、度量矩阵、任意向量内积之间的关系 欧式空间的两个基对应的度量矩阵彼此合同 度量矩阵的行列式的几何问题 正交补子空间 内积空间子空间U与U的正交补子空间的直和 …

LeetCode 138. 复制带随机指针的链表

难度 中等 题目链接 示例&#xff1a; 解题思路&#xff1a; 首先&#xff0c;大家肯定会这样想&#xff1a;定义一个cur循环遍历&#xff0c;每次遍历一个&#xff0c;就malloc一个。 当遍历后面的时候&#xff0c;就开始尾插。 但现在有一个问题是&#xff1a;这个random指…

通信直放站基础知识

直放站的定义 直放站&#xff08;中继器&#xff09;属于同频放大设备&#xff0c;是指在无线通信传输过程中起到信号增强的一种无线电发射中转设备。直放站的基本功能就是一个射频信号功率增强器。直放站在下行链路中&#xff0c;由施主天线从现有的覆盖区域中拾取信号&#x…

《嵌入式 - 嵌入式大杂烩》CoreMark性能测试

1 CoreMark简介 CoreMark是由EEMBC(Embedded Microprocessor Benchmark Consortium)的Shay Gla-On于2009年提出的一项基准测试程序&#xff0c;CoreMark的主要目标是简化操作&#xff0c;并提供一套测试单核处理器核心的方法。测试标准是在配置参数的组合下单位时间内运行的Co…

【树莓派不吃灰系列】快速导航

目录1. 通用篇2. Python篇3. 编程IO篇❤️ 博客主页 单片机菜鸟哥&#xff0c;一个野生非专业硬件IOT爱好者 ❤️❤️ 本篇创建记录 2022-11-06 ❤️❤️ 本篇更新记录 2022-11-06 ❤️&#x1f389; 欢迎关注 &#x1f50e;点赞 &#x1f44d;收藏 ⭐️留言 &#x1f4dd;&…

Slave_IO_Running: No 的解决

原因&#xff1a; 两台主从数据库的uuid相同了&#xff08;没猜错的话&#xff0c;各位的第二台虚拟机都是克隆了第一台&#xff0c;然后就改了个ip对吧&#xff09;。 解决办法&#xff1a; 必须跟着步骤来&#xff0c;一步也不能多&#xff0c;更不能少&#xff0c;请仔细…

arcgis 生成切片并发布服务

需要准备&#xff1a; 1、需要进行切割的tif文件&#xff1b; 2、切片方案文件 需要确认&#xff1a; 1、tif文件的数据源坐标系&#xff1b; 2、现有切片方案能否满足需求&#xff1b; 3、部署的地图引擎是经纬度投影还是墨卡托投影&#xff1b; 具体操作流程&#xff1a; 1…

TCP/IP协议数据链路层

目录 一、数据链路层和网络层的关系 二、以太网 1、以太网格式 2、认识MTU 三、ARP协议 1、ARP协议的作用 2、ARP协议的工作流程 3、ARP数据报的格式 4、思考&#xff1a;浏览器中输入url后, 发生的事情&#xff08;经典面试题&#xff09; 一、数据链路层和网络层的…

通信行业的必备网站

今天突然看到一首诗感触满满&#xff1a; 官策作者陈京 生如蝼蚁&#xff0c;当有鸿鹄之志&#xff01; 命如纸薄&#xff0c;应有不屈之心&#xff01; 大丈夫生于天地间&#xff0c;岂能郁郁久居人下。 当以梦为马&#xff0c;不负韶华。 乾坤未定&#xff0c;你我皆是黑马&…

西安交大讲座-实际场景(3个)出发,用户检测方式,数据驱动的网络用户行为分析建模与异常检测

讲座搬运&#xff0c;侵删&#xff01; 目录 研究背景 研究问题 问题与挑战 网络舆情分析 政策法规知识图谱 构建利益方群体识别模型 系统 ​编辑 第二个工作-异常群体映射与定位 多源社交网络用户身份管理 不用场景的用户身份映射 多个平台关联 黑产检测 基于图模型…

基于FreeCAD的dxf转机械手代码的一种实现方法

dxf文件在2D图形中使用广泛&#xff0c;将图形文件自动转换为机械手可识别的轨迹代码是机械手全自动化中一个软件衔接节点。理想的轨迹自动化转换程序可以在电脑里面直接虚拟仿真生成机械手轨迹&#xff0c;简化现场人员机械手示教流程&#xff0c;在效率和远程支持上有着实际的…

2021 神经网络压缩 (李宏毅

首先&#xff0c;为什么需要对神经网络模型进行压缩呢&#xff1f;我们在之前的课程中介绍过很多大型的深度学习模型&#xff0c;但当我们想要将这些大模型放在算力比较小的边缘设备或者其他IoT设备里面&#xff0c;就需要对大模型进行压缩。 Lower latency&#xff1a;低时延 …

刷题笔记之八(字符串通配符+参数解析+计算日期到天数)

目录 1. dateadd(datepart&#xff0c;number&#xff0c;date)函数是在日期中添加或减去指定的时间间隔 2. DML数据库操作语言负责数据的增删查改 3. 修改表结构的关键字都是alter table 表名&#xff0c;再加修改的语句 4. between and条件查询范围前闭后闭 5. 使用索引…

目标检测(1)—— 基础知识和常用数据集

一、什么是目标检测 一张图片&#xff0c;经过网络后得到输出&#xff0c;检测出感兴趣目标的一个位置&#xff0c;比如下图的车在什么地方&#xff0c;狗在什么地方&#xff1b;还要输出相应位置的目标是什么类别的。 目标检测&#xff1a;位置&#xff0b;类别 矩形框&…