【数据结构】带头双向循环链表

上一篇博客写了链表中最简单的单链表的实现,今天一起来看看链表中最复杂的结构带头双向循环链表的实现吧!

带头双向循环链表:结构最复杂,一般用在单独存储数据,也是实际中使用最多的链表结构。结构图如下:

下面看看它的实现吧!

链表单个节点的结构

带头双向循环链表要实现双向循环,那通过一个节点必然要能找到它的上一个元素,并且也要找到它的下一个元素,还要储存这个节点的数据,所以单个节点的结构体成员有三个:1)存储上一个节点位置的指针;2)存储下一个节点位置的指针;3)存储数据的量

代码:

typedef int LNTypeData;

typedef struct ListNote
{
	struct ListNote* next;//存储下一个节点地址
	struct ListNote* prev;//存储上一个节点地址
	LNTypeData data;//存储数据
}ListNote;

开辟新节点

链表插入数据需要开辟新的节点,因为不知一个函数里要开辟新节点,所以把开辟节点封装为函数。这里将结构体里的指针全都置为空。代码如下:

//开辟节点
ListNote* BuyListNote(LNTypeData x)
{
	ListNote* NewNote = (ListNote*)malloc(sizeof(ListNote));//开辟新节点
	assert(NewNote);//判断是否开辟成功
	NewNote->data = x;
	NewNote->next = NULL;
	NewNote->prev = NULL;
	return NewNote;//返回开辟节点的地址
}

创建返回链表的头结点

开辟一个节点作为链表的头,但是这个头节点不存储实际数据也就是虚设节点;因为是双向循环所以创建之初,指向前一个节点的指针和指向后一个节点的指针都指向它本身,这样做也是为了后面的实现。代码如下:

//创建返回链表的头结点
ListNote* ListNoteInit(LNTypeData x)
{
	ListNote* pHead = BuyListNote(x);
	pHead->prev = pHead;
	pHead->next = pHead;
	return pHead;
}

双链表尾插

在单链表中我们实现这个功能首先要找到为链表的尾,但是在这里不用,我们通过头结点(虚设头节点)就能找到尾,也就是头节点的前一个节点就是尾;只需要记录尾的位置,连接上新的节点,以及作为新的尾与头的连接。

 代码如下:

//尾插
void ListNotePushBack(ListNote* pHead, LNTypeData x)
{
	assert(pHead);

	ListNote* tail = pHead->prev;//保存尾的地址
	ListNote* NewNote = BuyListNote(x);//开辟新节点
	tail->next = NewNote;//将原来的尾与新尾(新节点)相连
	NewNote->prev = tail;
	NewNote->next = pHead;//将新尾与头相连
	pHead->prev = NewNote;
}

双向链表头插 

头插实际上是在头(虚设头节点)的后面的插入,因为我们创建的头实际是虚设的,实际的头是虚设头节点下一个节点;

代码如下:

//头插
void ListNotePushFront(ListNote* pHead, LNTypeData x)
{
	assert(pHead);

	ListNote* next = pHead->next;//存储实际头的地址
	ListNote* NewNote = BuyListNote(x);//开辟节点
//将新建节点插入到链表中并连接
	pHead->next = NewNote;
	NewNote->next = next;
	next->prev = NewNote;
	NewNote->prev = pHead;

}

双向链表尾删

尾删需要记录尾的前一个节点,以防止释放尾后找不到它的前一个节点,那这样也就没办法与头(虚设头结点)连接起来;代码如下:

//尾删
void ListNotePopBack(ListNote* pHead)
{
	assert(pHead);
	assert(pHead->next != pHead);//判断链表中是否有节点

	ListNote* tail = pHead->prev;//记录尾的地址
	ListNote* tailPrev = tail->prev;//记录尾的前一个节点的地址,也就是新尾
//将新尾与头相连形成循环
	tailPrev->next = pHead;
	pHead->prev = tailPrev;
	free(tail);释放要删除的节点
	tail = NULL;
}

双向链表头删

实现和尾删差不多;需要记录实际头的下一个节点的地址;以便与虚设的头相连;代码如下:

//头删
void ListNotePopFront(ListNote* pHead)
{
	assert(pHead);
	assert(pHead->next != pHead);

	ListNote* head = pHead->next;//记录实际头的地址(要是删除的地址)
	ListNote* headNext = head->next;//记录头的下一个节点的地址;
	//将虚设头结点与实际头结点相连
	pHead->next = headNext;
	headNext->prev = pHead;
	free(head);//释放要删除的节点
	head = NULL;
}

双向链表查找

查找需要遍历整个链表;需要注意的是遍历是从实际的头开始;也就虚设头结点的下一个;也要注意遍历的结束条件,因为是循环链表,链表的尾的下一个节点是虚设头结点,虚设头结点也就是遍历结束的条件,代码如下:

//查找
ListNote* ListNoteFind(ListNote* pHead, LNTypeData x)
{
	assert(pHead);
	assert(pHead->next != pHead);

	ListNote* cur = pHead->next;

	while (cur != pHead)//遍历结束条件
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;
}

双向链表在pos前进行插入

这里需要记录pos位置节点的前一个节点,以便插入新节点的相连;代码如下:

//在pos位置前插入
void ListNoteInsert(ListNote* pos, LNTypeData x) 
{
	assert(pos);

	ListNote* posPrev = pos->prev;//记录pos前的节点
	ListNote* NewNote = BuyListNote(x);//创建新节点
    //插入节点,并相连
	posPrev->next = NewNote;
	NewNote->next = pos;
	pos->prev = NewNote;
	NewNote->prev = posPrev;
}

双向链表删除pos位置的节点

这里需要记录pos位置的前一个节点和后一个节点,代码如下:

//删除pos位置的数据
void ListNoteErase(ListNote* pos)
{
	assert(pos);
	ListNote* posPrev = pos->prev;//记录pos前一个节点
	ListNote* posNext = pos->next;//记录pos后一个节点
    //前后节点进行连接
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);//释放pos位置的节点
	pos = NULL;
}

有了在pos前进行插入函数和删除pos位置的节点函数,那我们之前写的头删尾删,头插尾插都可调用这两个函数进行实现,不用再写代码。

双向链表打印

这里和查找一样需要遍历整个链表;那么需要注意的也就一样了,即:遍历是从实际的头开始;也就虚设头结点的下一个;也要注意遍历的结束条件,因为是循环链表,链表的尾的下一个节点是虚设头结点,虚设头结点也就是遍历结束的条件;代码如下:

//打印
void ListNotePrint(ListNote* pHead)
{
	assert(pHead);

	ListNote* cur = pHead->next;
	while (cur != pHead)
	{
		printf("%d ",cur->data);
		cur = cur->next;
	}
	printf("\n");
}

双向链表销毁

链表的销毁需要一个节点一个节点进行释放,注意事项与打印相同;代码如下:

//销毁
void ListNoteDestory(ListNote* pHead)
{
	assert(pHead);

	ListNote* cur = pHead->next;
	while (cur != pHead)
	{
		ListNote* next = cur->next;
		free(cur);
		cur = NULL;
		cur = next;
	}

	free(pHead);
}

带头双向循环链表结构虽然复杂,但只要理清它的链接方式,实现起来我觉得反而比单链表简单。

本篇博客就到这了,因为我也是个初学者难免会出现错误,如果您在阅读中发现错误,请前辈指正,感激不尽!!!

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值