您现在的位置是:首页 > 编程 > 

数据结构之双链表(超详解)

2025-07-20 16:24:00
数据结构之双链表(超详解) 链表分类链表总共有八种结构如下。 在众多链表结构中,实际中最常用的有这两种结构:单链表和双向带头循环链表(即双链表)。无头单向非循环链表:结构简单,⼀般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。带头双向循环链表:结构最复杂,⼀般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。结构虽然结构复杂,但是使用代码

数据结构之双链表(超详解)

链表分类

链表总共有八种结构如下。

在众多链表结构中,实际中最常用的有这两种结构:单链表双向带头循环链表(即双链表)。

  1. 无头单向非循环链表:结构简单,⼀般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等
  2. 带头双向循环链表:结构最复杂,⼀般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势。(并且掌握如上两种数据结构的实现,其他种类的链表也能够实现)
双向链表

概念

这里的head充当什么作用呢? 带头链表里的头结点,实际为“哨兵位”,哨兵位结点不存储任何有效元素,只是站在这里"放哨的"。

结构

根据双链表概念,定义结构两个指针分别指向下一个结点和前一个结点的地址,并且每一个结点都能存储数据data

申请结点

创建头节点

根据概念创建一个头结点起到"放哨"的作用,这个结点可随意给值。 为什么用一级指针接收呢? 保证哨兵位节点不能被删除(若被删除则不是双链表),节点的地址也不能发生改变,因此传一级最合适。

尾插、头插

尾插

首先,通过函数创建一个新节点,并将其数据域设置为输入的值x,得到新节点的指newnode

设置新结点的指针以插入到尾部。由于这是一个循环链表,尾节点的next指向头节点,所以将新节点newnode的prev指针指向前一个结点。

更新原链表尾节点的指针,使其next指针指向新节点newnode,这样新节点就被正确地链接到链表的末尾。

最后,调整原头节点phead的prev指针,使其指向新节点newnode,确保链表的循环特性仍然保持,即尾节点的next始终指向头节点。

头插

头插与尾插的方法类似。

尾删、头删

尾删

  1. 保证双链表有效(即至少有一个哨兵位)。
  2. 定义结点del保存尾结点,让尾结点的前一个结点的next指向头结点
  3. 头结点指向del的前一个结点。
  4. 释放尾结点del

头删

头删与尾删的方法类似。

指定位置插入、删除结点

指定位置插入结点

  1. 指向结点是否有效,若无效则断言。
  2. 创建新结点,newnode的next指向未更新链表指定位置的下一个结点;newnode的prev指向指定结点。
  3. 未更新链表指定位置的下一个结点指向newnode结点;指定结点指向newnode结点。

指定位置删除结点

删除指定结点,想要形参的改变影响到实参,那么传二级指针不是更好吗? 事实的确如此,那么我们为什么在这里是传一级指针呢? 目的是为了保证接口一致性。可以发现,我们前面函数的实现大部分都是通过传一级指针实现双链表功能,如果这里突然传二级指针会造成疑惑。 需要注意的是:删除指定结点后,由于形参不能改变实参,因此需要我们手动置指定结点为ULL。

查结点

和单链表一样,我们只需要遍历原链表查结点,若没到则返回ULL。

链表打印

双链表销毁
  1. 定义pcur指向头结点的下一个结点,并遍历双链表
  2. 每次释放pcur结点,并让pcur指向下一个结点,直到pcur与头结点phead相遇为止
  3. 为了保证接口的一致性,这里同样采用传一级指针,需要手动置双链表的头结点为ULL。
双链表实现

List.h

代码语言:javascript代码运行次数:0运行复制
#define _CRT_SECURE_O_WARIGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int LTDataType;

//定义双向链表节点的结构
typedef struct Listode
{
	LTDataType data;
	struct Listode* next;
	struct Listode* prev;
}LTode;


//声明双向链表中提供的方法

//初始化
//void LTInit(LTode** phead);
LTode* LTInit();
//打印双链表
void LTPrint(LTode* phead);

//哨兵位节点不能被删除,节点的地址也不能发生改变,因此传一级最合适
//尾插
void LTPushBack(LTode* phead, LTDataType x);//插入数据之前,,链表必须初始化到只有一个头结点的情况
//头插
void LTPushFront(LTode* phead, LTDataType x);
//尾删
void LTPopBack(LTode* phead);
//头删
void LTPopFront(LTode* phead);
//在pos位置之后插入数据
void LTInsert(LTode* pos, LTDataType x);
//删除pos节点
void LTErase(LTode* pos);
//查节点
LTode* LTFind(LTode* phead, LTDataType x);
//销毁节点
void LTDesTroy(LTode* phead);

代码语言:javascript代码运行次数:0运行复制
#define _CRT_SECURE_O_WARIGS 1
#include"List.h"

//申请节点
LTode* LTBuyode(LTDataType x)
{
	LTode* node = (LTode*)malloc(sizeof(LTode));
	if (node == ULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	//申请成功
	node->data = x;
	node->next = node->prev = node;

	return node;
}

//初始化
LTode* LTInit()
{
	//创建头结点
	//LTode* phead = (LTode*)malloc(sizeof(LTode));
	//phead->data = -1;
	//phead->next = phead->prev = phead;
	//给双向链表创建一个哨兵位
	LTode *phead = LTBuyode(-1);
}
//打印双链表
void LTPrint(LTode* phead)
{
	assert(phead);
	LTode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}
//尾插
void LTPushBack(LTode* phead, LTDataType x)
{
	assert(phead);
	LTode* newnode = LTBuyode(x);
	//phead  newnode  phead->prev
	newnode->prev = phead->prev;
	newnode->next = phead;

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

	phead->next->prev = newnode;
	phead->next = newnode;
}
//尾删
void LTPopBack(LTode* phead)
{
	//链表必须有效且链表不能为空(只有一个哨兵位)
	assert(phead && phead->next != phead);
	//phead  phead->prev->prev  phead->prev
	//phead  del->prev  del  让del=phead->prev
	LTode* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;
	//释放del节点
	free(del);
	del = ULL;
}
//头删
void LTPopFront(LTode* phead)
{
	assert(phead && phead->next != phead);
	//phead phead->next phead->next->next
	//phead del del->next  让del=phead->next
	LTode* del = phead->next;
	phead->next = del->next;
	del->next->prev = phead;
	//释放del节点
	free(del);
	del = ULL;
}
//在pos位置之后插入数据
void LTInsert(LTode* pos, LTDataType x)
{
	assert(pos);
	LTode* newnode = LTBuyode(x);
	//pos  newnode  pos->next
	newnode->next = pos->next;
	newnode->prev = pos;

	pos->next->prev = newnode;
	pos->next = newnode;
}
//删除pos节点
void LTErase(LTode* pos)//二级指针更好
{
	assert(pos);
	//pos->prev  pos pos->next
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;

	free(pos);
	pos = ULL;
}
//查节点
LTode* LTFind(LTode* phead, LTDataType x)
{
	assert(phead);
	LTode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//没有到
	return ULL;
}
//销毁节点
void LTDesTroy(LTode* phead)
{
	assert(phead);
	LTode* pcur = phead->next;
	while (pcur != phead)
	{
		LTode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//此时pcur指向phead,而phead未被销毁
	free(phead);
	phead = ULL;
}

代码语言:javascript代码运行次数:0运行复制
#define _CRT_SECURE_O_WARIGS 1
#include"List.h"

void ListTest()
{
	//LTode* plist = ULL;
	//LTInit(&plist);
	LTode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, );
	LTPushFront(plist, 4);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);
	LTode* find = LTFind(plist, 1);
	//if (find == ULL)
	//{
	//	printf("没有到\n");
	//}
	//else {
	//	printf("到了\n");
	//}
	LTInsert(find, 4);
	LTPrint(plist);
	LTErase(find);//不传二级(保持接口一致性)
	find = ULL;
	LTPrint(plist);
	LTDesTroy(plist);
	plist = ULL;
}

int main()
{
	ListTest();
	return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2025-01-2,如有侵权请联系 cloudcommunity@tencent 删除数据结构链表数据指针存储

#感谢您对电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格的认可,转载请说明来源于"电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格

本文地址:http://www.dnpztj.cn/biancheng/1133845.html

相关标签:无
上传时间: 2025-07-18 23:20:39
留言与评论(共有 19 条评论)
本站网友 美容院眼部护理
15分钟前 发表
1); LTPushBack(plist
本站网友 江铃汽车股票
22分钟前 发表
若无效则断言
本站网友 三亚娱乐项目
6分钟前 发表
查结点和单链表一样
本站网友 怎样去蝴蝶斑
28分钟前 发表
4); LTPrint(plist); LTErase(find);//不传二级(保持接口一致性) find = ULL; LTPrint(plist); LTDesTroy(plist); plist = ULL; } int main() { ListTest(); return 0; }本文参与 腾讯云自媒体同步曝光计划
本站网友 rainmeter皮肤
0秒前 发表
实际中使用的链表数据结构
本站网友 黑枸杞泡水喝的禁忌
3分钟前 发表
由于形参不能改变实参
本站网友 静殊
7分钟前 发表
而phead未被销毁 free(phead); phead = ULL; } 代码语言:javascript代码运行次数:0运行复制#define _CRT_SECURE_O_WARIGS 1 #include"List.h" void ListTest() { //LTode* plist = ULL; //LTInit(&plist); LTode* plist = LTInit(); LTPushBack(plist
本站网友 指甲油怎么洗
18分钟前 发表
LTDataType x) { assert(pos); LTode* newnode = LTBuyode(x); //pos newnode pos->next newnode->next = pos->next; newnode->prev = pos; pos->next->prev = newnode; pos->next = newnode; } //删除pos节点 void LTErase(LTode* pos)//二级指针更好 { assert(pos); //pos->prev pos pos->next pos->next->prev = pos->prev; pos->prev->next = pos->next; free(pos); pos = ULL; } //查节点 LTode* LTFind(LTode* phead
本站网友 中医治疗子宫肌瘤
20分钟前 发表
并让pcur指向下一个结点
本站网友 44214
11分钟前 发表
pcur->data); pcur = pcur->next; } printf("\n"); } //尾插 void LTPushBack(LTode* phead
本站网友 上城国际玫瑰苑
16分钟前 发表
尾节点的next指向头节点
本站网友 痛经吃什么水果
9分钟前 发表
这里同样采用传一级指针
本站网友 石家庄教育信息网
8分钟前 发表
链表打印双链表销毁定义pcur指向头结点的下一个结点
本站网友 沈阳短租
14分钟前 发表
删除结点指定位置插入结点指向结点是否有效
本站网友 世博源
2分钟前 发表
头插尾插首先
本站网友 保利国际花园
1分钟前 发表
因此需要我们手动置指定结点为ULL
本站网友 陈明华
2分钟前 发表
⼀般用在单独存储数据
本站网友 郑州橄榄城房价
1分钟前 发表
实际为“哨兵位”