数据结构与算法——带头双向循环链表的实现及原理

目录

一、带头双向循环链表的原理

二、带头双向循环链表的实现

1.带头双向循环链表的定义

2.带头双向循环链表的初始化

3.带头双向循环链表节点的创建

4.打印链表

5.判断链表是否为空

6.双向链表在pos的前面进行插入

7.双向链表在头部/尾部插入数据

8.双向链表删除pos处的节点

9.双向链表在头部/尾部删除数据

10.销毁链表

三、完整代码及演示

总结


一、带头双向循环链表的原理

我们知道链表有带头和不带头,循环与不循环,单向与双向,这几种区别排列组合链表一共有8种,前面我们实现了最简单的同时也是缺陷最多的链表——单链表,那么今天我们来实现带头双向循环链表,这种最完美的链表也是日常生活中常用的链表。

数据结构与算法——带头双向循环链表的实现及原理_第1张图片

我们观察它的细节,一个节点还是由两个部分组成,指针域和数据域,

指针域有next指针,用来指向当前节点的下一个节点的指针

prev指针 ,用来指向当前节点的上一个节点的指针

同时有一个哨兵位的头节点head,方便头插,head的prev指向的就是尾

尾的next指向head,这样就可以快速找到链表的头和尾,这种链表弥补了单向不带头不循环链表的缺点,使用起来十分方便。

话不多说我们就进入实现的阶段

二、带头双向循环链表的实现

1.带头双向循环链表的定义

// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType _data;
	struct ListNode* _next;
	struct ListNode* _prev;
}ListNode;

这种链表的定义想必我们是比较熟悉了,大体结构不变,我们只需要更改链表的指针域就可以了

2.带头双向循环链表的初始化

ListNode* ListCreate()
{
	ListNode* phead = BuyListNode(-1);
	phead->_next = phead;
	phead->_prev = phead;

	return phead;
}

初始化的部分注意一个点,我们让哨兵位的头节点指向它自己,这个在后面会有大用处,同时这个哨兵位的头结点的数据域的数据不用更改,只要是随机值或者-1就可以

3.带头双向循环链表节点的创建

ListNode* BuyListNode(LTDataType x)
{
	ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));

	if (newNode == NULL)
	{
		perror("BuyListNode    malloc");
		exit(-1);
	}

	newNode->_data = x;
	newNode->_next = NULL;
	newNode->_prev = NULL;

	return newNode;
}

这里要注意,每次用完malloc函数一定要判断指针是否为空,有可能有开辟空间失败的情况

4.打印链表

我们这次循环结束的条件就不能是cur指针走向空了,参考带头双向循环链表的结构图,我们不难发现当cur指针走向phead时一次循环就结束了,切记循环结束条件不能让cur走向空,这会出现死循环。

void ListPrint(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));

	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		printf("%d  ", cur->_data);
		cur = cur->_next;
	}

}

5.判断链表是否为空

bool ListEmpty(ListNode* phead)
{
	assert(phead);

	return phead->_next == phead;
}

这个比较简单类似于上面的打印

6.双向链表在pos的前面进行插入

我们在插入数据时一定要保存pos节点的prev,如果不保存,我们将新节点与pos链接后无法找到pos的prev,所以要保存,如果不保存也可以,一定要注意节点的链接顺序,不能出现上述情况。

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	ListNode* prev = pos->_prev;
	ListNode* newNode = BuyListNode(x);

	prev->_next = newNode;
	newNode->_prev = prev;
	newNode->_next = pos;
	pos->_prev = newNode;

}

7.双向链表在头部/尾部插入数据

我们前面已经写过了在pos之前插入数据了

在头部插入数据就是在链表的第一个节点之前插入数据

在尾部插入数据就是在尾部之前插入数据,链表的尾部的prev不就是phead的next吗?

综上所述我们就可以这样来写代码

//尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListInsert(pHead, x);
}

//头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListInsert(pHead->_next, x);
}

还记得前面埋下的伏笔吗?

将哨兵位的头节点的prev和next指向自己,我们如果想在头部或尾部插入数据,我们走读代码

发现它竟然在逻辑上说得通,这就是将哨兵位头节点指向自己的优势,同时在删除头尾节点时它的优势又可以展现

8.双向链表删除pos处的节点

在删除节点时我们必须明确,链表为空就不能删除了

所以一开始我们就要判断链表是否为空,然后保存pos的prev和next可避免节点连接的先后顺序问题。

void ListErase(ListNode* pos)
{
	assert(pos);
	assert(!ListEmpty(pos));

	ListNode* prev = pos->_prev;
	ListNode* next = pos->_next;

	free(pos);

	prev->_next = next;
	next->_prev = prev;

}

9.双向链表在头部/尾部删除数据

这个操作和双向链表在头部/尾部插入数据类似,我们直接复用上面代码

//尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);

	ListErase(pHead->_prev);
}

//头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);

	ListErase(pHead->_next);
}

10.销毁链表

我们整个链表都是malloc出来的而C\C++是没有自动内存回收机制的,所以我们需要手动实现free掉整个链表的操作

void ListDestory(ListNode* pHead)
{
	assert(pHead);

	ListNode* cur = pHead->_next;

	while (cur != pHead)
	{

		ListNode* next = cur->_next;
		free(cur);
		cur = NULL;
		cur = next;
	}
}

三、完整代码及演示

//ListNode.h
#include
#include
#include
#include

// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType _data;
	struct ListNode* _next;
	struct ListNode* _prev;
}ListNode;


//判断链表为空
bool ListEmpty(ListNode* phead);
//创建新节点
ListNode* BuyListNode(LTDataType x);
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
//ListNode.c
#include"ListNode.h"

ListNode* ListCreate()
{
	ListNode* phead = BuyListNode(-1);
	phead->_next = phead;
	phead->_prev = phead;

	return phead;
}

ListNode* BuyListNode(LTDataType x)
{
	ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));

	if (newNode == NULL)
	{
		perror("BuyListNode    malloc");
		exit(-1);
	}

	newNode->_data = x;
	newNode->_next = NULL;
	newNode->_prev = NULL;

	return newNode;
}

bool ListEmpty(ListNode* phead)
{
	assert(phead);

	return phead->_next == phead;
}

void ListPrint(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));

	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		printf("%d  ", cur->_data);
		cur = cur->_next;
	}

}

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	ListNode* prev = pos->_prev;
	ListNode* newNode = BuyListNode(x);

	prev->_next = newNode;
	newNode->_prev = prev;
	newNode->_next = pos;
	pos->_prev = newNode;

}
//尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListInsert(pHead, x);
}

//头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListInsert(pHead->_next, x);
}

void ListErase(ListNode* pos)
{
	assert(pos);
	assert(!ListEmpty(pos));

	ListNode* prev = pos->_prev;
	ListNode* next = pos->_next;

	free(pos);

	prev->_next = next;
	next->_prev = prev;

}
//尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);

	ListErase(pHead->_prev);
}

//头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);

	ListErase(pHead->_next);
}


ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListNode* cur = pHead->_next;

	while (cur!=pHead)
	{
		if (cur->_data == x)
		{
			return cur;
		}

		cur = cur->_next;
	}
	return NULL;
}


void ListDestory(ListNode* pHead)
{
	assert(pHead);

	ListNode* cur = pHead->_next;

	while (cur != pHead)
	{

		ListNode* next = cur->_next;
		free(cur);
		cur = NULL;
		cur = next;
	}
}

//test.c
#include"ListNode.h"

void test1()
{
	ListNode* L = ListCreate();
	ListPushBack(L, 1);
	ListPushBack(L, 2);
	ListPushBack(L, 3);
	ListPushBack(L, 4);
	ListPushBack(L, 5); 

	ListInsert(L, 1);

	ListPushFront(L, 100);


	ListPopBack(L);
	ListPopBack(L);
	ListPopBack(L);

	ListPopFront(L);

	ListPrint(L);

	ListNode*Find=ListFind(L, 100);
	if (Find == NULL)
	{
		printf("无法找到\n");
	}
	else
	{
		printf("%d %p %p\n", Find->_data, Find->_next, Find->_prev);
	}

	ListDestory(L);
}


int main()
{
	test1();
	return 0;
}

总结

完整代码可以从此处获得:data-structure--/Double-ListNode/Double-ListNode at main · 01294268442ww/data-structure-- (github.com)

你可能感兴趣的:(数据结构,链表)