IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 【数据结构】3.2双链表(C语言) -> 正文阅读

[数据结构与算法]【数据结构】3.2双链表(C语言)

【数据结构】——3.2双链表

一、双链表的概念及结构

1. 双链表的概念

? 双向带头循环链表:结构最复杂,但是操作最方便,使用两个指针分别存储前后结点的位置,并且尾结点的下一位又回到了头结点。是实际开发中所使用的链表

双链表结构

2. 双链表结构

typedef int LTDataType;

typedef struct ListNode
{
	LTDataType data;		//数据域
	struct ListNode* next;	//指向下一个结点
	struct ListNode* prev;	//指向上一个结点
} ListNode;

二、双链表的实现

1. 初始化

  1. 初始化时不传参数,直接将链表头指针返回更简便
  2. 初始化时要创建头结点,并将链表头结点的prevnext指针指向自己,构成循环
ListNode* ListInit(void)
{
	ListNode* guard = (ListNode*)malloc(sizeof(ListNode));
	if (guard == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}
	guard->next = guard;
	guard->prev = guard;
	return guard;
}

2. 动态申请结点

? 动态申请结点时,要将前后指针初始化为NULL,值由参数传递

ListNode* BuyListNode(LTDataType x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}
	node->next = NULL;
	node->prev = NULL;
	node->data = x;
	return node;
}

3. 遍历打印双链表

? 判断链表遍历结束的条件不再是尾结点的next为NULL了,而是尾结点的next是头指针

void ListPrint(ListNode* plist)
{
	assert(plist != NULL);

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

4. 判断双链表是否为空

? 判断链表没有元素的条件是头结点的next指向自己

_Bool ListEmpty(ListNode* plist)
{
	assert(plist != NULL);
	return plist->next == plist;
}

5. 获取双链表结点个数

  1. 使用无符号整型存储链表结点个数
  2. 遍历双链表,记录结点个数
size_t ListSize(ListNode* plist)
{
	assert(plist != NULL);

	size_t count = 0;
	ListNode* cur = plist->next;
	while (cur != plist)
	{
		count++;
		cur = cur->next;
	}
	return count;
}

6.插入数据

6.1 尾插

  1. 链表尾插要先改变新结点的两个指针,再改变前结点的next指针和后结点的prev指针
  2. 可以声明指针变量记录尾结点位置,再进行插入(这里没有这样做)
void ListPushBack(ListNode* plist, LTDataType x)
{
	assert(plist != NULL);

	ListNode* newnode = BuyListNode(x);

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

6.2 头插

? 头插和尾插差不多,只是在头结点的后边插入结点

void ListPushFront(ListNode* plist, LTDataType x)
{
	assert(plist != NULL);

	ListNode* newnode = BuyListNode(x);

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

6.3 插入

  1. 插入函数默认插入到pos结点之前,插入到pos后的函数大同小异,不做实现
  2. 相对于单链表来说不用遍历查找pos之前的结点,非常方便且高效
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos != NULL);
	
	ListNode* newnode = BuyListNode(x);

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

7. 删除数据

7.1 尾删

  1. 双向链表尾删时不用找尾,直接删除头结点前的结点就可以
  2. 删除之前要先对链表判空,若为空可以进行断言处理(这里不做处理)
void ListPopBack(ListNode* plist)
{
	assert(plist != NULL);

	if (ListEmpty(plist))
	{
		return;
	}

	ListNode* node = plist->prev;
	node->prev->next = plist;
	plist->prev = node->prev;
	free(node);
}

7.2 头删

? 跟尾删差不多,只是删除结点为第一个结点

void ListPopFront(ListNode* plist)
{
	assert(plist != NULL);

	if (ListEmpty(plist))
	{
		return;
	}

	ListNode* node = plist->next;
	plist->next = node->next;
	node->next->prev = plist;
	free(node);
}

7.3 删除

? 删除指针指向的结点,调整前后指针指向,然后直接删除即可

void ListErase(ListNode* pos)
{
	assert(pos != NULL);

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

8. 查找数据

  1. 查找就是直接遍历,比较值,并返回结点的地址
  2. 查找有修改功能,调用者可以根据结点的指针修改内容
ListNode* ListFind(ListNode* plist, LTDataType x)
{
	assert(plist != NULL);

	ListNode* cur = plist->next;
	while (cur != plist)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

9. 销毁双链表

  1. 顺序遍历链表,依次释放结点,最后释放头结点
  2. 销毁时只销毁内存,不将链表头指针置空,要由调用者完成。(带置空的销毁函数参数要传二级指针)
void ListDetroy(ListNode* plist)
{
	assert(plist != NULL);

	ListNode* cur = plist->next;
	ListNode* next = NULL;
	while (cur != plist)
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
	free(cur);
}

四、双链表的相关问题

1. 实现双链表的相关问题

1.1 初始化问题

带头结点的双向循环链表需要初始化,申请头结点空间,并将前后指针指向自己形成循环

1.2 二级指针问题

  1. 带头结点的链表无需使用二级指针传参,头指针一定指向头结点
  2. 头指针一定要断言,头结点一定存在

1.3 头结点问题

  1. 头结点的数据域是不使用的,不能用来存储结点个数,数据域的大小有限,结点个数多时就会发生溢出现象
  2. 头结点的出现方便插入和删除时不用单独判断第一个结点操作时而改变头指针

五、双链表的特点

1. 双链表的优点

  1. 任意位置插入删除效率高
  2. 按需申请释放,减少空间浪费

2. 双链表的缺点

  1. 不支持随机访问

3. 顺序表与双链表的比较

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,物理上不一定连续
随机访问支持 O ( 1 ) O(1) O(1)不支持 O ( n ) O(n) O(n)
任意位置插入删除需要移动元素 O ( n ) O(n) O(n)只用修改指针方向 O ( 1 ) O(1) O(1)
插入空间不够时需要扩容没有容量概念
应用场景元素高效存储和频繁访问任意位置插入和删除
空间利用率

六、完整代码

代码保存在gitee中:点击完整代码参考

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-08-19 19:31:09  更:2022-08-19 19:32:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 21:17:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码