【数据结构初阶】千字文章带你征服 “ 双向链表 ”(附源码)

news2024/11/15 21:51:28

hi,bro!又见面啦

目录

前言:

一、链表的分类

二、双向链表

1、  概念与结构

2、  双向链表的实现

2.1  定义双向链表的结构

2.2  初始化

2.3  尾插

2.4  头插

2.5  打印

2.6  尾删

2.7  头删

2.8  查找

2.9  在pos结点之后插入结点

2.10  删除指定位置结点

2.11  销毁

2.12  销毁2

2.13  初始化2

3、源码

List.h

List.c 

test.c 

三、顺序表和链表的比较

Bye Bye Bye ————————


前言:

前面我们学习了单链表,单链表是链表的一种,今天我们即将要学习的双向链表也是其中之一。我们前面没有具体介绍链表的分类,所以在学习双向链表之前,先了解下链表的分类。

一、链表的分类

链表的结构多样,有下面8种链表结构(2×2×2): 

链表说明:

何为循环:尾结点的next指针不为NULL

链表结构虽多,我们常用的就两种结构,单链表双向带头循环链表

  • 无头单向非循环链表(单链表)结构简单,一般不用来单独存储数据。实际上更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  • 带头双向循环链表(双向链表)结构最复杂一般用在单独存储数据,实际中使用的链表数据结构,都是带头双向循环链表。虽然这个结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。
  • 在带头链表里面,除了头结点(哨兵位),其他结点都存储有效的数据。

二、双向链表

1、  概念与结构

带头双向循环链表

双向链表的结点结构:数据 + 指向后一个结点的指针 + 指向前一个节点的指针 

struct ListNode
{
    int data;
    struct ListNode* next;
    struct ListNode* prev;
}

头结点的prev指针指向尾结点,尾结点的next指针指向头结点,就这样实现了循环。 

【注意】 带头链表的头结点实际为 哨兵位 ,哨兵位结点不存储任何有效数据,只是在这里放哨占位子的。而前面单链表里面的头结点并不表示真的表示有头结点,而是为了表示方便,这种表述是不规范的。单链表里面的第一个结点并非真的头结点。

2、  双向链表的实现

2.1  定义双向链表的结构

//定义双向链表的结构
typedef int LTDataType ;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;

2.2  初始化

//创建新结点
LTNode* LTBuyNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc file!");
		exit(1);
	}
	newnode->data = x;
	//等于自身,实现循环
	newnode->next = newnode->prev = newnode;

	return newnode;

}

//初始化
void LTInit(LTNode** pphead)
{
	//创建新结点
	*pphead = LTBuyNode(-1);
}

双向链表为空:表示只有一个哨兵位。

2.3  尾插

第一个结点:第一个有效的结点,里面存储有效的数据。

哨兵位:头结点。

//插入
//第一个参数传一级还是二级,要看phead指向的结点会不会发生改变
//如果发生改变,那么phead的改变要影响实参,传二级
//如果不发生改变,那么phead不会影响实参,传一级
//phead指向的结点是哨兵位,不会发生改变,故传一级

//尾插
void LTPushBack(LTNode* phead,LTDataType x)
{
	assert(phead);

	LTNode* newnode = LTBuyNode(x);
	//phead newnode phead->prev
	newnode->next = phead;
	newnode->prev = phead->prev;

	phead->prev->next = newnode;
	phead->prev = newnode;

}

2.4  头插

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	//phead newnode phead->next
	newnode->next = phead->next;
	newnode->prev = phead;

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

2.5  打印

//打印
void LTPrint(LTNode* phead)
{
	assert(phead);

	//从第一个有效的结点开始打印
	LTNode* pcur = phead->next;
	while (pcur!=phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

2.6  尾删

//判空
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}
//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	//在尾删之前要判空
	assert(!LTEmpty(phead));

	LTNode* del = phead->prev;
	LTNode* prev = del->prev;
    
    //phead del(phead->prev) prev(del->prev)
	phead->prev = prev;
	prev->next = phead;

	free(del);
	del = NULL;
}

2.7  头删

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	//在头删之前要判空
	assert(!LTEmpty(phead));

	LTNode* del = phead->next;
	phead->next = del->next;
	del->next->prev = phead;

	free(del);
	del = NULL;
}

2.8  查找

//查找
LTNode* LTFind(LTNode* phead,LTDataType x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

2.9  在pos结点之后插入结点

//在pos结点之后插入数据
void LTInsert(LTNode* phead, LTNode* pos,LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(100);

	//pos newnode pos->next
	newnode->next = pos->next;
	newnode->prev = pos;

	pos->next->prev = newnode;
	pos->next = newnode;

}

2.10  删除指定位置结点

//删除指定位置结点
void LTErase(LTNode* phead, LTNode* pos)
{
	assert(phead);

	//pos->prev pos pos->next
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;

	free(pos);
	pos = NULL;

}

为了保持接口的一致性,优化接口都为一级指针,见下:

2.11  销毁

//销毁
void LTDesTroy(LTNode** pphead)
{
	assert(pphead && *pphead);

	LTNode* pcur = (*pphead)->next;
	while (pcur != *pphead)
	{
		LTNode* Next = pcur->next;
		free(pcur);
		pcur = Next;
	}

	//销毁哨兵位结点
	free(*pphead);
	*pphead = NULL;
	pcur = NULL;
}

2.12  销毁2

//销毁2
void LTDesTroy2(LTNode* phead)  //传一级指针,需要手动将plist置为空
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur!=phead)
	{		
		LTNode* Next = pcur->next;
		free(pcur);
		pcur = Next;
	}
	free(phead);
	phead = NULL;
	pcur = NULL;

}

2.13  初始化2

用返回值的方式实现

//初始化2
LTNode* LTInit2()
{
	LTNode* phead = LTBuyNode(-1);
	return phead;
}

3、源码

List.h

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

//定义双向链表的结构
typedef int LTDataType ;
typedef struct ListNode
{
	int data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;

//初始化
void LTInit(LTNode** pphead);

//尾插
void LTPushBack(LTNode* phead,LTDataType x);

//头插
void LTPushFront(LTNode* phead,LTDataType x);

//打印
void LTPrint(LTNode* phead);

//尾删
void LTPopBack(LTNode* phead);

//头删
void LTPopFront(LTNode* phead);

//查找
LTNode* LTFind(LTNode* phead,LTDataType x);

//在pos结点之后插入数据
void LTInsert(LTNode* phead,LTNode* pos,LTDataType x);

//删除指定位置结点
void LTErase(LTNode* phead, LTNode* pos);

//销毁
void LTDesTroy(LTNode** pphead);

//销毁
void LTDesTroy2(LTNode* phead);

//初始化2
LTNode* LTInit2();

List.c 

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"

//创建新结点
LTNode* LTBuyNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc file!");
		exit(1);
	}
	newnode->data = x;
	//等于自身,实现循环
	newnode->next = newnode->prev = newnode;

	return newnode;

}

//初始化
void LTInit(LTNode** pphead)
{
	//创建新结点
	*pphead = LTBuyNode(-1);
}

//插入
//第一个参数传一级还是二级,要看phead指向的结点会不会发生改变
//如果发生改变,那么phead的改变要影响实参,传二级
//如果不发生改变,那么phead不会影响实参,传一级
//phead指向的结点是哨兵位,不会发生改变,故传一级

//尾插
void LTPushBack(LTNode* phead,LTDataType x)
{
	assert(phead);

	LTNode* newnode = LTBuyNode(x);
	//phead newnode phead->prev
	newnode->next = phead;
	newnode->prev = phead->prev;

	phead->prev->next = newnode;
	phead->prev = newnode;

}

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	//phead newnode phead->next
	newnode->next = phead->next;
	newnode->prev = phead;

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

//打印
void LTPrint(LTNode* phead)
{
	assert(phead);

	//从第一个有效的结点开始打印
	LTNode* pcur = phead->next;
	while (pcur!=phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

//判空
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}
//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	//在尾删之前要判空
	assert(!LTEmpty(phead));

	LTNode* del = phead->prev;
	LTNode* prev = del->prev;

	phead->prev = prev;
	prev->next = phead;

	free(del);
	del = NULL;
}

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	//在头删之前要判空
	assert(!LTEmpty(phead));

	LTNode* del = phead->next;
	phead->next = del->next;
	del->next->prev = phead;

	free(del);
	del = NULL;
}

//查找
LTNode* LTFind(LTNode* phead,LTDataType x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

//在pos结点之后插入数据
void LTInsert(LTNode* phead, LTNode* pos,LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(100);

	//pos newnode pos->next
	newnode->next = pos->next;
	newnode->prev = pos;

	pos->next->prev = newnode;
	pos->next = newnode;

}

//删除指定位置结点
void LTErase(LTNode* phead, LTNode* pos)
{
	assert(phead);

	//pos->prev pos pos->next
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;

	free(pos);
	pos = NULL;

}

//销毁
void LTDesTroy(LTNode** pphead)
{
	assert(pphead && *pphead);

	LTNode* pcur = (*pphead)->next;
	while (pcur != *pphead)
	{
		LTNode* Next = pcur->next;
		free(pcur);
		pcur = Next;
	}

	//销毁哨兵位结点
	free(*pphead);
	*pphead = NULL;
	pcur = NULL;
}

//销毁
void LTDesTroy2(LTNode* phead)  //传一级指针,需要手动将plist置为空
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur!=phead)
	{		
		LTNode* Next = pcur->next;
		free(pcur);
		pcur = Next;
	}
	free(phead);
	phead = NULL;
	pcur = NULL;

}

//初始化2
LTNode* LTInit2()
{
	LTNode* phead = LTBuyNode(-1);
	return phead;
}

test.c 

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"

void ListTest01()
{
	LTNode* plist = NULL;
	//双向链表头结点不能为空,要初始化
	LTInit(&plist);

	LTNode* plist = LTInit2();

	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPrint(plist);

	LTPushFront(plist, 6);
	LTPrint(plist);
	
	LTPopBack(plist);
	LTPrint(plist);

	LTPopFront(plist);
	LTPrint(plist);

	LTNode* pos = LTFind(plist, 3);
	if (pos == NULL)
	{
		printf("没有找到\n");
	}
	else
	{
		printf("找到了\n");
	}

	LTInsert(plist, pos, 100);
	LTPrint(plist);

	LTErase(plist,pos);
	LTPrint(plist);

	LTDesTroy(&plist);

	//此时plist为野指针,虽然保存的有地址,但其中的地址已被释放
	LTDesTroy2(plist);
	plist = NULL;
}


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

三、顺序表和链表的比较

    不同点顺序表链表(单链表)
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问O(1)O(N)
任意位置插入或者删除数据可能需要搬移元素,效率低只需改变指针指向
插入动态顺序表,空间不够时需要扩容,可能会发生空间浪费没有容量的概念,按需申请释放,不存在空间浪费
应用场景元素高效存储+频繁访问任意位置高效插入和删除

今天双向链表的学习就结束了,休息一下吧。


完——

Bye Bye Bye ————————

Bye Bye Bye_*NSYNC_高音质在线试听_Bye Bye Bye歌词|歌曲下载_酷狗音乐酷狗音乐为您提供由*NSYNC演唱的高清音质无损Bye Bye Byemp3在线听,听Bye Bye Bye,只来酷狗音乐!icon-default.png?t=N7T8https://t4.kugou.com/song.html?id=dzeLrbfCPV2

至此,结束——

我是云边有个稻草人

期待与你的下一次相遇 。。。。。。

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

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

相关文章

实验室责任人员管理保障实训系统安全

在智慧校园的实训管理生态中&#xff0c;实验室责任人员的角色犹如精密机器中的关键齿轮&#xff0c;他们不仅是实验室安全与高效运转的守护者&#xff0c;更是实训教学质量的直接塑造者。这一角色的重要性&#xff0c;在智慧校园的数字化转型中得到了前所未有的凸显&#xff0…

过期知识:thinkphp5 使用migrate给现有的数据表新增表字段

个人开发网站记录, 这个文章主要是个以后健忘的我看的. 我在搞我的画笔审核 , 发现数据表的画笔数据在审核驳回的时候还是软删除好一些, 免得用户找不到之前上传的画笔数据, 后期也可以考虑重新显示给用户,让用户可以修改画笔信息重新提交审核. 这个时候想起了…

ViewModel相关

郭霖公众号 原作者原文 前言 ViewModel不仅是Activity和Fragment的数据集中管理和通讯&#xff0c;也是促进了MVVM和MVI架构规范&#xff0c;此文为深入理解ViewModel 概念 ViewModel是复制准备和管理Activity和Fragment数据的类&#xff0c;他还处理Activity或Fragment与应…

探索天穹数仓自治能力的新实践

探索天穹数仓自治能力的新实践 随着业务和技术的发展&#xff0c;传统数仓模式向数智数仓模式演进&#xff0c;数据治理面临诸多挑战。自治平台采用双引擎策略&#xff0c;注重感知能力、观测能力、诊断能力和优化能力的建设&#xff0c;实现了对数据的精细化管理。例如&#x…

鸿蒙应用框架开发【基于原生能力的无障碍模式】

基于原生能力的无障碍模式 介绍 本示例基于系统提供的无障碍阅读能力&#xff0c;实现了无障碍扩展服务集成、原生组件屏幕朗读以及多个控件组合标注。 效果图预览 原生组件屏幕朗读&#xff1a; 创建说明&#xff1a; 在已创建工程的ets文件夹下创建accessibility文件夹&…

SCIEI双检CCF期刊,硕博毕业生的福音,投稿欲从速!

SCI&EI双检CCF期刊&#xff0c;目前已稳定检索46年&#xff0c;CCF-C类&#xff0c;且发文量稳定&#xff0c;国人友好&#xff0c;发过的人都说审稿极速。 期刊详情 【期刊简介】IF&#xff1a;4.0-5.0 JCR1区中科院3区 【出版社】Elsevier出版社 【检索情况】SCI&a…

【文件fd】深入理解和实现Linux底下一切皆文件 | 系统和语言文件操作二者关系_封装 | 系统调用为什么怎样封装成库函数

目录 1.系统调用的打开/读/写文件操作 2.如何理解Linux底下一切皆文件 2.1设备属性 2.2设备的操作方法 3.如何实现Linus底下一切皆文件 4.源码查看 5.系统和语言文件操作二者关系 5.1 flags选项和C语言的"w""a"方式 二者的关系 5.2 系统的文件描…

llama-3.1下载部署

llama-3.1 下载 下载 huggingface 详情页填写申请后等待审核 点击 头像->setting->access token 创建token 配置环境变量 下载模型 pip install -U huggingface_hubhuggingface-cli download --resume-download meta-llama/Meta-Llama-3.1-8B-Instruct --local-di…

Linux ——互斥量

1.进程线程间的互斥相关背景概念 临界资源&#xff1a;多线程执行流共享的资源就叫做临界资源临界区&#xff1a;每个线程内部&#xff0c;访问临界资源的代码&#xff0c;就叫做临界区互斥&#xff1a;任何时刻&#xff0c;互斥保证有且只有一个执行流进入临界区&#xff0c;…

使用 cPanel WHM 重置 MySQL 根密码

MySQL 是托管在 cPanel 管理服务器上的网站的主要数据库软件&#xff0c;广泛用于 WordPress 和电子商务应用程序&#xff0c;例如 Magento。由于 MySQL 需要管理多个不同网站和数据库的读写权限&#xff0c;因此它是一个多用户系统。 每个用户账户都有一组权限限制其访问。而M…

【报错解决】Sql server 2022连接数据库时显示证书链是由不受信任的颁发机构颁发的

SSMS 20在连接Sql server 2022数据库时有如下报错&#xff1a; A connection was successfully established with the server, but then an error occurred during the login process. (provider: SSL Provider, error: 0 - 证书链是由不受信任的颁发机构颁发的。 原因是尝试使…

C++进阶 二叉搜索树

目录 二叉搜索树概念 二叉搜索树的模拟实现 二叉搜索树的查找 二叉搜索树的插入 二叉搜索树的删除 二叉搜索树的性能分析 二叉搜索树的应用 K模型 KV模型 二叉搜索树概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树…

nginx的反向代理及负载均衡

nginx的反向代理 安装包链接https://nginx.org/download/nginx-1.26.1.tar.gz yum -y install gcc gcc-c pcre-devel openssl-devel [rootstaticserver ~]# tar -xzvf nginx-1.26.1.tar.gz [rootstaticserver nginx-1.26.1]#./configure --prefix/usr/local/nginx --userngi…

怎么提高视频的声音?提高视频的声音的多种方法

在制作和编辑视频的浩瀚旅途中&#xff0c;声音质量不仅是引导观众情感波动的舵手&#xff0c;更是构建故事氛围、深化主题表达不可或缺的基石。它如同画面背后的灵魂&#xff0c;悄无声息地牵引着每一位观众的思绪&#xff0c;穿梭于现实与想象的边界。从温馨的旁白讲述到激昂…

单天下载1W+?木途美APP对比体验

在当下的民宿市场&#xff0c;木鸟、途家、美团三家民宿预订平台遥遥领先。木鸟民宿最新发布的报告中提到&#xff0c;7月以来民宿订单环比上涨88%&#xff0c;尽管酒店业进入量涨价跌时代&#xff0c;但民宿平台们似乎活得更好了。 特色房源为王永不过时 房源量大意味着覆盖…

Linux系统之DHCP服务配置

1、准备阶段 Windows&#xff08;客户端&#xff09;开启Vmnet8网卡Linux6&#xff08;服务端&#xff09;网络连接选择NAT模式&#xff0c;并配置IP地址为192.168.11.1/24Linux5&#xff08;客户端&#xff09;网络连接选择NAT模式将NAT的DHCP功能取消 2、DHCP服务器相关软件…

(vue)el-cascader级联选择器按勾选的顺序传值,摆脱层级约束

(vue)el-cascader级联选择器按勾选的顺序传值,摆脱层级约束 需求&#xff1a;按勾选的顺序给后端传值 难点&#xff1a;在 Element UI 的 el-cascader 组件中&#xff0c;默认的行为是根据数据的层级结构来显示选项&#xff0c;用户的选择也会基于这种层级结构&#xff0c;el-…

SQL必知必会

SQL必知必会 一些SQL知识&#xff0c;出自极客时间陈旸老师《SQL必知必会》 https://time.geekbang.org/column/intro/100029501 基础 视图 视图作为一张虚拟表&#xff0c;帮我们封装了底层与数据表的接口。它相当于是一张表或多张表的数据结果集。视图的这一特点&#x…

【C/C++】C语言到C++的入门知识点(主要适用于C语言精通到Qt的C++开发入门)

【C/C】C语言到C的入门知识点&#xff08;主要适用于C语言精通到Qt的C开发入门&#xff09; 文章目录 C语言与C的不同C中写C语言代码C语言到C的知识点Qt开发中需要了解的C基础知识namespace输入输出字符串类型class类构造函数和析构函数&#xff08;解析函数&#xff09;类的继…

20240801 每日AI必读资讯

&#x1f50a;OpenAI向ChatGPT Plus用户推出高级语音模式 - 只给一小部分Plus用户推送&#xff0c;全部Plus用户要等到秋季 - 被选中的Alpha 测试的用户将收到一封包含说明的电子邮件&#xff0c;并在其移动应用中收到一条消息。 - 同时视频和屏幕共享功能继续推出&#xff…