数据结构(三)----双向带头循环链表

今天我们来学习第二个常见的链表结构:

双向带头循环链表(配置拉满):

(下称双链表)

1.结构分析

1.带头,有一个哨兵位,这是一个指向头结点但不存值的结点

2.指针域有prev和next两个指针,分别指向上一个和下一个节点

3.链表尾部要指向头结点,构成循环结构

4.函数传参的时候不需要二级指针了,因为哨兵位指向头结点而且它本身的值不会改变

2.实现

首先写List.h中的内容

c 复制代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;
	struct ListNode* next;//双指针域
}LTNode;
//初始化
LTNode* ListInit();
//头/尾 插/删
void ListPushHead(LTNode* phead,LTDataType x);

void ListPopHead(LTNode* phead);

void ListPushBack(LTNode* phead,LTDataType x);

void ListPopBack(LTNode* phead);

//插入、删除、查找(修改)
void ListInsert(LTNode* pos,LTDataType x);

void ListErsae(LTNode* pos);

LTNode* ListFind(LTNode* phead, LTDataType x);

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

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

然后再对其中的接口逐个实现:

初始化函数:

首先要明确,双链表为空时只有一个哨兵位,其prev和next都指向自己

c 复制代码
LTNode* ListInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	phead->prev = phead;
	phead->next = phead;
	return phead;
}
//测试使用:
LTNode* plist = ListInit();//plist里面存储一个哨兵位

尾插函数

c 复制代码
void ListPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)//这个if语句是为了判断我们的动态开辟空间有没有失败
	{
		printf("fail");
		exit(-1);
	}
	newnode->prev = phead->prev;//新尾prev连旧尾
	phead->prev->next = newnode;//旧尾next连新尾
	newnode->next = phead;//新尾next连head
	phead->prev = newnode;//头prev连新尾
	newnode->data = x;//给上数据
}

如果没有反应过来,可以画个图看看

我们发现当我们定义链表结构为双向带头循环链表时,插入数据是很方便的,只需要判断好链接关系就可以了,而我们之前的单链表还需要判断链表为空的情况,这种情况要拿出来特殊处理,但是这个地方当链表为空时也是没有问题的!

尾删函数:

c 复制代码
void ListPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->prev != phead);//哨兵位不能删
	LTNode* tail = phead->prev;//找到尾
	LTNode* prev = tail->prev;//找到尾的上一个
	//删除尾并连上头和尾的上一个(新尾)
	free(tail);
	tail=NULL;
	prev->next = phead;
	phead->prev = prev;
}

头插:

c 复制代码
void ListPushHead(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	newnode->data = x;
	LTNode* next = phead->next;//头插相当于就是插入在哨兵位后面
	phead->next = newnode;//注意链接关系别少写就没问题,逻辑不难
	newnode->prev = phead;
	newnode->next = next;
	next->prev = newnode;
}

头删:

c 复制代码
void ListPopHead(LTNode* phead)
{
	assert(phead);
	assert(phead->next == phead);//同样不能删哨兵位
	LTNode* next = phead->next;
	phead->next = next->next;
	next->next->prev = phead;
	free(next);
	next = NULL;
}

销毁链表:

c 复制代码
void ListDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur!=phead)//释放掉所有的数据空间
	{
		LTNode* next = cur->next;//这里需要定义一个next指针,因为我们把cur给释放掉后就找不到cur->next的了
		free(cur);
		cur = next;
	}
	free(phead);//最后将哨兵位也释放掉,然后置空
	phead = NULL;

}

3.缓存利用率

我们上一章总结顺序表和链表的区别的时候提到:顺序表的缓存利用率高,链表的缓存利用率低,那么到底什么是缓存利用率?这里我给大家大致提一下:我们的计算器存在很多存储方式:

我们在数组中开辟空间和在链表上开辟空间时,这些缓存的命中是不一样的,感兴趣的可以去查看其他有关CPU缓存的知识。

4.总结

关于链表,到这里就结束了,但是在面试中考察比较多,而对有缺陷的单链表考察尤为多,一定要多刷题找思路才行。

相关推荐
passer__jw7677 分钟前
【LeetCode】【算法】283. 移动零
数据结构·算法·leetcode
爱吃生蚝的于勒43 分钟前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
羊小猪~~1 小时前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
脉牛杂德1 小时前
多项式加法——C语言
数据结构·c++·算法
一直学习永不止步2 小时前
LeetCode题练习与总结:赎金信--383
java·数据结构·算法·leetcode·字符串·哈希表·计数
wheeldown10 小时前
【数据结构】选择排序
数据结构·算法·排序算法
躺不平的理查德14 小时前
数据结构-链表【chapter1】【c语言版】
c语言·开发语言·数据结构·链表·visual studio
阿洵Rain14 小时前
【C++】哈希
数据结构·c++·算法·list·哈希算法
Leo.yuan14 小时前
39页PDF | 华为数据架构建设交流材料(限免下载)
数据结构·华为
半夜不咋不困15 小时前
单链表OJ题(3):合并两个有序链表、链表分割、链表的回文结构
数据结构·链表