004 数据结构_带头双向循环链表——“C”

一、前言

上一期博客我们介绍了无头单向非循环链表,这种链表结构虽然简单,但在增删查改上都相较带头双向循环链表麻烦,建议先学习无头单向非循环链表,这种链表在笔试oj题中会出现很多,另外学习这种链表可以训练到我们的思维,这一期我们介绍一种结构虽然复杂,但会有很多的优势,实现起来也会更加简单的带头双向循环链表。

已通过如下测试用例,请放心学习哦!
在这里插入图片描述

Test1()
{
	ListNode* pHead = NULL;
	pHead = ListCreate(-1);
	ListPushFront(pHead, 5); //头插
	ListPushFront(pHead, 4); //头插
	ListPushFront(pHead, 3); //头插
	ListPushFront(pHead, 2); //头插
	ListPushFront(pHead, 1); //头插
	ListPrint(pHead);		 //打印
	ListPushBack(pHead, 6);  //尾插
	ListPushBack(pHead, 7);  //尾插
	ListPushBack(pHead, 8);  //尾插
	ListPushBack(pHead, 9);  //尾插
	ListPushBack(pHead, 10); //尾插
	ListPrint(pHead);	     //打印
	ListDestory(pHead);		 //销毁
}
void Test2()
{
	ListNode* pHead = NULL;
	pHead = ListCreate(-1);
	ListPushFront(pHead, 5); //头插
	ListPushFront(pHead, 4); //头插
	ListPushFront(pHead, 3); //头插
	ListPushFront(pHead, 2); //头插
	ListPushFront(pHead, 1); //头插
	ListPushBack(pHead, 6);  //尾插
	ListPushBack(pHead, 7);  //尾插
	ListPushBack(pHead, 8);  //尾插
	ListPushBack(pHead, 9);  //尾插
	ListPushBack(pHead, 10); //尾插
	ListPopFront(pHead);	 //头删
	ListPopFront(pHead);  	 //头删
	ListPopFront(pHead);	 //头删
	ListPrint(pHead);		 //打印
	ListPopBack(pHead);		 //尾删
	ListPopBack(pHead);	   	 //尾删
	ListPopBack(pHead);		 //尾删
	ListPrint(pHead);		 //打印
	ListDestory(pHead);		 //销毁
}
void Test3()
{
	ListNode* pHead = NULL;
	pHead = ListCreate(-1);
	ListPushBack(pHead, 6);					//尾插
	ListPushBack(pHead, 70);				//尾插
	ListNode* pos = ListFind(pHead, 6);		//查找
	pos->_data = 60;						//修改
	ListPrint(pHead);						//打印
	ListInsert(pHead, 80);					//在哨兵位pHead前插入
	ListInsert(pHead, 90);					//在哨兵位pHead前插入
	ListInsert(pos, 50);					//在pos前插入
	ListPrint(pHead);						//打印
	ListDestory(pHead);					    //销毁
}
void Test4()
{
	ListNode* pHead = NULL;
	pHead = ListCreate(-1);
	ListPushFront(pHead, 1);				  //头插
	ListPushBack(pHead, 2);					  //尾插
	ListPrint(pHead);					   	  //打印
	ListNode* pos = ListFind(pHead, 2);		  //查找
    ListErase(pos);							  //删去任意位置节点
	ListPrint(pHead);						  //打印
	pos = ListFind(pHead, 1);		          //查找
	ListErase(pos);							  //删去任意位置节点
	ListPrint(pHead);						  //打印
	ListDestory(pHead);						  //销毁
}



int main()
{
	Test1();
	Test2();
	Test3();
	Test4();
	return 0;
}

二、带头双向循环链表

优点

带头双向循环链表相较于其他数据结构(如单向链表、数组等)具有以下优点:

1. 可以方便地前后遍历:双向链表支持正向和反向两种遍历方式,因此可以更加方便地进行某些操作,例如将链表反转。

2. 删除节点的效率更高:由于双向链表拥有对前驱节点的引用,因此删除某个节点时不需要从头开始遍历查找其前驱节点。

3. 在某些场景下内存利用率更高:与数组相比,双向链表在插入和删除节点时具有更好的时间复杂度,且不需要像数组一样预先分配大块的连续内存空间,因此可以更加高效地利用内存。

4. 支持背向遍历:特别是在需要反向迭代时,双向链表的性能较单向链表更好。

三、结构特点

当链表中只有一个哨兵位节点时——自己指向自己
在这里插入图片描述

在这里插入图片描述

实现步骤

1、初始化

// 创建返回链表的头结点.
ListNode* ListCreate()
{
	ListNode* pHead = NULL;
	pHead = NewnodeCreate(-1);			//给pHead—>_data赋初始值,这个值随意
	pHead->_prev = pHead;
	pHead->_next = pHead;
	return pHead;
}

2、增

头插


// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* next = pHead->_next;
	ListNode* newnode = NewnodeCreate(x);				//创建新节点
	pHead->_next = newnode;
	newnode->_prev = pHead;
	newnode->_next = next;
	next->_prev = newnode;
	//复用逻辑
	//ListInsert(pHead->_next, x);
}

在这里插入图片描述

尾插

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* tail = pHead->_prev;
	ListNode* newnode = NewnodeCreate(x);				//创建新节点
	tail->_next = newnode;
	newnode->_prev = tail;
	newnode->_next = pHead;
	pHead->_prev = newnode;
	//复用逻辑
	//ListInsert(pHead, x);
}

在这里插入图片描述

在pos任意位置前插入节点

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* newnode = NewnodeCreate(x);
	ListNode* front = pos->_prev;
	front->_next = newnode;
	newnode->_prev = front;
	pos->_prev = newnode;
	newnode->_next = pos;
}

在这里插入图片描述

3、删

头删

// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	if (pHead->_next == pHead)
	{
		return NULL;
	}
	ListNode* first = pHead->_next;
	ListNode* second = first->_next;
	pHead->_next = second;
	second->_prev = pHead;
	free(first);						//释放要删去节点的空间
	//复用逻辑
	//ListErase(pHead->_next);
}

在这里插入图片描述

尾删

// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	if (pHead->_next == pHead)
	{
		return NULL;
	}
	ListNode* tail = pHead->_prev;
	ListNode* front = tail->_prev;
	front->_next = pHead;
	pHead->_prev = front;
	//复用逻辑
	//ListErase(pHead->_prev);     //传入尾节点
}

在这里插入图片描述

删去pos任意位置处的节点

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);						//pos为空
	ListNode* front = pos->_prev;
	ListNode* behind = pos->_next;
	front->_next = behind;
	behind->_prev = front;
	free(pos);
}

在这里插入图片描述

4、查

// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	ListNode* cur = pHead->_next;
	while (cur->_data != x)
	{
		cur = cur->_next;
		if (cur == pHead)
		{
			printf("没有找到,请重新输入\n");
			return NULL;
		}
	}
	return cur;
}

5、改

此处放于测试用例中
	ListNode* pos = ListFind(pHead, 6);		//查找
	pos->_data = 60;						//修改

6、打印

// 双向链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	if (pHead->_next == pHead)			  //仅剩哨兵位节点
	{
		printf("NULL");
	}
	ListNode* cur = pHead->_next;         //将哨兵位下一个节点即头节点赋值给cur
	while (cur != pHead)
	{
		printf("%d", cur->_data);
		if (cur->_next != pHead)
		{
			printf("《==》");
		}
		else
		{
			printf("\n");
		}
		cur = cur->_next;
	}

}

7、销毁

// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		ListNode* next = cur->_next;	//保存当前节点的下一个节点
		free(cur);						//释放当前节点
		cur = next;						//迭代
	}
	free(pHead);
}

小结

今天要分享的内容就到这里啦,如果本文有遗漏疏忽之处,希望你能指出

posted @ 2023-05-13 19:09  Fan_558  阅读(5)  评论(0编辑  收藏  举报  来源