带头双向循环链表

1. 带头双向循环链表

链表的分类

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

怎么算出8种情况:每次两种情况,三次,所以是2*2*2=8

1. 单向或者双向

2. 带头或者不带头

3. 循环或者非循环

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:

1. 无头单向非循环链表:结构简单,一般不会单独用来单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希表,图的邻接表等等。另外这种解构在笔试面试中出现很多。

2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,是带头双向循环链表。另外这个结构虽然复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

2. 带头双向循环链表概念

带头双向循环链表是一种链表数据结构,它具有以下特点:

1. 头结点:带头双向循环链表包含一个头结点,它位于链表的起始位置,并且不存储实际数据。头结点的前驱指针指向尾节点,头节点的后继指针指向第一个实际数据节点。

2. 循环连接:尾节点的后继指针指向头节点,而头结点的前驱指针指向尾节点,将链表形成一个循环连接的闭环。这样可以使链表在遍历时可以无限循环,方便实现循环操作。

3. 双向连接:每个节点都有一个前驱指针和一个后继指针,使得节点可以向前和向后遍历。前驱指针指向前一个节点,后继指针指向后一个节点。

总结:带头双向循环链表可以支持在链表的任意位置进行插入和删除操作,并且可以实现正向和反向的循环遍历。通过循环连接的特性,链表可以在连续的循环中遍历所有节点,使得链表的操作更加灵活和高效。

3. 带头双向链表的实现

1. 带头双向循环链表的结构

typedef int LTDataType;//代码中将int定义为LTDataType是为了提供代码的可读性和可维护性,并增加代码的灵活性。
 
typedef struct ListNode
{
    LTDataType data;
	struct ListNode* next;//存储下一个节点的地址
	struct ListNode* prev;//存储上一个节点的地址	
}LTNode;//重新命名结构体类型

通过将int定义为LTDataType,可以在代码中使用LTDataType作为数据类型,而不是直接使用int。这样做的好处有以下几点:

可读性:使用LTDataType作为数据类型可以使代码更具可读性。LTDataType作为一个自定义的数据类型名称,可以更好地表达代码中数据的含义和用途,提高代码的可理解性。
可维护性:将int定义为LTDataType可以方便地在代码中统一修改数据类型。如果将来需要将数据类型更改为其他类型,只需修改typedef语句中的定义,而不需要在整个代码中逐个修改具体的数据类型,减少了修改的工作量和出错的可能性。
灵活性:通过使用LTDataType,可以在代码中轻松更改数据类型,而不会对代码的其他部分产生影响。这种抽象化的方式可以使代码更具通用性,便于在不同的场景中重用。                       
原文链接:https://blog.youkuaiyun.com/weixin_65186652/article/details/132290503

2. 动态申请节点函数

LTNode* BuyLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
    
    //刚申请下的堆区空间有可能开辟失败,所以要进行检查
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
 
    //开辟好后就赋值
	newnode->data = x;
	newnode->prev = NULL;
	newnode->next = NULL;
 
	return newnode;
}

3. 链表的初始化

//初始化--因为要改动指向结构体的指针,所以要么就取地址,用二级指针接收。
//要么就像下面这样,用返回值接收。
LTNode* LTInit()// 由于形参phead是实参plist的拷贝
{
	LTNode* guard = BuyLTNode(-1);
	guard->next = guard;
	guard->prev = guard;
	return guard;
}
int main()
{
    LTNode* plist = LTInit();
}

4. 链表打印

void LTPrint(LTNode* phead)
{
	assert(phead);
	printf("guard<==>");
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d<==>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

5. 链表尾部插入节点

与单链表有两个不一样的点:

情况一:

1. 单链表尾插节点需要遍历全链表,当指针走到链表最后一个节点的时候,判断判断tail->next是否为NULL,若为NULL,则跳出遍历的循环,尾插新结点。然而带头双向循环链表不需要遍历链表,只需要对哨兵位的头节点的prev域解引用,直接找到带头双向循环链表的尾节点,尾插新节点。

情况二:

2. 头指针的区别:带头双向循环链表不需要判断头指针是否指向NILL,是因为哨兵位的头节点也是有它的地址的,添加新节点时只需要直接在尾节点尾插。然而单链表却需要判断头指针是否指向NIULL,而且需要用到二级指针,比较棘手。

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	
	LTNode* tail = phead->prev;//通过哨兵位的头节点的prev找到链表最后一个结点,并用tail指向
	LTNode* newnode = BuyLTNode(x);
	
	tail->next= newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

6. 链表头部插入节点

//方法一,不需要创建变量
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyLTNode(x);
 
	newnode->next = phead->next;//把头结点后面的d1的地址赋值给新结点的next
	phead->next->prev = newnode;//d1指向新节点
	
	phead->next = newnode;//改变头节点的next,让它指向新结点
	newnode->prev = phead;//新结点的prev指向phead头插完毕.
}

7. 链表尾删节点

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next == phead);
	LTNode* tail= phead->prev;
	LTNode* Prev= tail->prev ;
	
	Prev->next = phead;
	phead->prev = Prev;
    free(tail);	//先删除再链接
    tail = NULL;
}

8. 链表头删节点

void LTPopFront(LTNode* phead)
{
	assert(phead);
	LTNode* first = phead->next;
	LTNode* second = first->next;
 
	phead->next = second;
	second->prev = phead;  
	free(first);
    first=NULL;
}

9. 链表查找/修改某个值

 
LTNode* STFind(LTNode* phead, LTDataType x)
{
	//assert(phead);
	LTNode* cur = phead->next;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
		if (cur->data == phead->data)//若重新回到哨兵位,则说明链表遍历完毕,找不到x值,返回NULL
		{
			break;
		}
	}
	return NULL;
}

10. 在链表pos位置之前插入值

void LTInsert(LTNode* pos,LTDataType x)//输入要删除的数的位置即可
{
	assert(pos);
 
	LTNode* newnode = BuyLTNode(x);
	LTNode* prev = pos->prev;
 
	prev->next = newnode;
	newnode->next = pos;
	newnode->prev = prev;
	pos->prev = newnode;
}

11. 在链表pos位置处删除此节点

void LTErase(LTNode* pos)
{
	assert(pos);
	
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	 
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

12. 释放链表动态申请的空间

void LTDestroy(LTNode* phead)
{
	assert(phead);
 
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
    phead=NULL;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值