带头双向链表

本文详细介绍了带头双向循环链表的实现,包括定义链表结构、初始化、插入、删除、查找等操作。同时对比了顺序表与链表在存取方式、逻辑结构、空间分配等方面的区别,强调了链表在任意位置插入删除的优势以及顺序表在随机访问和CPU缓存命中率上的优点。
摘要由CSDN通过智能技术生成

1.带头双向循环链表

实际中要实现的链表结构非常多样,以下情况结合起来就有8中链表结构:

1.链接方向:单向、双向
2.带不带头节点(哨兵卫)
3..循环、非循环

1.无头单向非循环链表:结构最简单,一般不会单独用来存放数据。实际中跟多的是作为其他数据结构的子结构,比如哈希桶、图
2.带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

2.带头双向循环链表的实现

2.1定义双向链表

//对数据类型进行重命名,这样可以对多种数据类型进行存储,有利于控制链表
typedef int LTDataType;
//结点(结构体)
typedef struct LTNode
{
	DateType val;
	struct LTNode* next;//结点的后继
	struct LTNode* prev;//结点的前驱
}LTNode;

2.2函数接口(双向链表的操作)

//初始化
LTNode* ListInit();
//打印
void ListPrint(LTNode* phead);
//尾插
void ListPushBack(LTNode* phead, LTDataType x);
//头插
void ListPushFront(LTNode* phead, LTDataType x);
//尾删
void ListPopBack(LTNode* phead);
//头删
void ListPopFront(LTNode* phead);
//删除链表结点时,用于判断链表是否为空,为空返回真,不为空返回假
bool ListEmpty(LTNode* phead);
//计算链表大小
size_t ListSize(LTNode* phead);
//链表查找
LTNode* ListFind(LTNode* phead, LTDataType x);
//链表插入
void ListInsert(LTNode* pos, LTDataType x);
//链表删除
void ListErase(LTNode* pos);
//链表销毁
void ListDestroy(LTNode* phead);

//NOTE:不适用二级指针解决方案
//1.c++的引用
//2.带返回值类型
//3.建立头结点(哨兵卫)

2.2.1初始化双向链表

//初始化 首先Buy一个 然后头节点的前驱后继都指向自己
LTNode* LTInit()
{
	//创建一个哨兵位
	LTNode* phead = BuyLTNode(-1);
	//带头双向循环链表的初始化
	//就是先建立一个头结点
	//头结点的两个指针域都指向头结点,形成自环
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

2.2.2创建新结点

LTNode* BuyLTNode(DateType x)
{
	//以下过程就是创建哨兵卫的操作
	//动态开辟一个新空间
	LTNode* newnode = (LTNode*)malloc(sizeof(struct LTNode));
	//开辟失败操作
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	//前驱后继都指向空
	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->val = x;
	return newnode;
}

2.2.3打印双向链表操作

void LTPrint(LTNode* pHead)
{
	//断言判断pHead是否为空
	assert(pHead);
	//当cur!=pHead时遍历整个链表
	LTNode* cur = pHead->next;
	while (cur != pHead)
	{
		printf("%d", cur->val);
		printf("<-->");
		cur = cur->next;
		if (cur == pHead)
		{
			printf("phead");
		}
	}
	printf("\n");
}

2.2.4头插法

void LTPushFront(LTNode* pHead,DateType x)
{
	//创建一个节点
	//LTNode* newnode = BuyLTNode(x);
	//LTNode* first = pHead->next;
	//pHead->next = newnode;
	//newnode->prev = pHead;

	//newnode->next = first;
	//first->prev = newnode;
	
	//复用
	LTInsert(pHead->next, x);
}

2.2.5头删法

//头删法 free掉frist 然后phead->next 指向second second->prev指向phead
void LTPopFront(LTNode* pHead)
{
	//assert(pHead);
	//assert(pHead->next != pHead);
	//LTNode* cur = pHead->next;
	//LTNode* next = cur->next;
	//pHead->next = next;
	//next->prev = pHead;
	//free(cur);
	//cur = NULL;

	LTErase(pHead->next);

}

2.2.6尾插法

void LTPushBack(LTNode* pHead,DateType x)
{
	//assert(pHead);
	//LTNode* newnode = BuyLTNode(x);
	记录尾节点地址
	//LTNode* LTtail = pHead->prev;
	//LTtail->next = newnode;
	//newnode->prev = LTtail;

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

	//复用
	LTInsert(pHead,x);
}

2.2.7尾删法

void LTPopBack(LTNode* pHead)
{
	//assert(pHead);
	//assert(pHead->next!= pHead);
	//LTNode* pTail = pHead->prev;
	//LTNode* prev = pTail->prev;
	//pHead->prev = prev;
	//prev->next = pHead;
	//free(pTail);

	//pTail = NULL;
	LTErase(pHead->prev);
}

2.2.8查找

LTNode* LTFind(LTNode* pHead, DateType x)
{
	assert(pHead);
	//遍历寻找
	LTNode* cur = pHead->next;
	while (cur != pHead)
	{
		if (cur->val == x)
		{
			return cur;
		}
		//迭代
		cur = cur->next;
	}
	return NULL;
}

2.2.9判断链表是否为空

//判断双向链表是否为空 return phead->next==phead
bool IsLTEmpty(LTNode* pHead)
{
	assert(pHead);
	//if (pHead->next == pHead)
	//{
	//	return true;
	//}
	//return false;
	return pHead->next == pHead;
}

2.2.10链表大小

//LTSize的大小  
size_t LTSize(LTNode* pHead)
{
	assert(pHead);
	assert(!IsLTEmpty(pHead));
	LTNode* cur = pHead->next;
	int count = 0;
	while (cur != pHead)
	{
		count++;
		cur = cur->next;
	}
	return count;
}

2.2.11销毁链表

//销毁链表
void LTDestory(LTNode* pHead)
{
	assert(pHead);
	LTNode* cur = pHead->next;
	
	while (cur != pHead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	//释放哨兵卫
	free(pHead);
	pHead = NULL;
}

3.顺序表与链表的区别

1、存取方式
顺序表支持随机存取,而链表不支持,它只能从表头按顺序存取元素。
2、逻辑结构与存储结构
在顺序表中,逻辑上相邻的元素,其对应的物理位置也相邻,而链表中,逻辑上相邻的元素,其物理位置不一定相邻。
3、空间分配
在顺序表中,对于静态存储,一旦存储空间装满就不能扩充,再加入新元素将导致内存溢出;对于动态存储,存储空间装满时可以扩充,但需要移动大量元素导致效率降低,而且当内存中没有更大块的连续存储空间时导致内存分配失败。在链表中,对于静态存储,与顺序表的静态存储类似;对于动态存储,结点的空间只在需要的时候申请分配,只要内存有空间就可以分配成功。
4、存储密度
顺序表中,无论静态存储还是动态存储,其存储密度均为1,因为数组空间只用来存数据元素。而在链表中,对于静态存储和动态存储,每个结点除了存储数据元素自身外,还会至少存储直接后继的存储位置信息。相对于顺序表,链表的存储密度要低得多。

顺序表的优点:
1.尾插尾删效率很高
2.随机访问(利用下标访问)
3.相比链表结构,顺序表CPU高速缓存命中率更高
顺序表的缺点:
1.头部和中部插入删除效率低。(O(N))
2.扩容:性能消耗,空间浪费

链表的优点:
1.任意位置插入删除效率很高。O(1)
2.按需申请释放
链表的缺点:
1.不支持随机访问

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值