【C语言】带头双向循环链表

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

链表中有多种结构,诸如:单链表,双向链表,带头单链表……
本次介绍《关于名字听起来很牛逼但是实现起来简单的一批这个结构》——带头双向循环链表


一、什么是带头双向循环链表?

顾名思义

  • 带头:该链表带有一个傀儡结点,也称哨兵结点,傀儡结点不算作有效数据,只作为处理数据时便捷的一种手段结构。
  • 双向:在单链表中,一个单链表结点存储有一个数据域和一个指针域,指针域保存的是下一个结点的地址,而双向则表示一个结点中不仅保存有下一个结点(next)的地址,也保存有上一个结点(prev)的地址。
  • 循环:不循环的链表走到底一定会走到空(NULL),带循环就走不到空(NULL)。

图例:
在这里插入图片描述


二、具体实现

1.结点的定义

代码如下:

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType val;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;
  • val:数据域
  • next:存储下一个结点的地址
  • prev:存储上一个结点的地址

2.获取一个值为x的新结点

代码如下:

ListNode* BuyListNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (!newnode)
	{
		perror("malloc()");
		exit(-1);
	}
	newnode->val = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

用malloc给新结点开辟一块空间,如果malloc开辟空间失败,用perror打印失败信息,然后exit(-1)直接结束程序,如果malloc开辟空间成功,则正常赋值,这里newnode的next和prev不初始化也无所谓,反正后面插入也要修改指向,最后return新开辟的结点。


3.创建返回链表的头结点

代码如下:

ListNode* ListCreate()
{
	ListNode* pHead = (ListNode*)malloc(sizeof(ListNode));
	if (!pHead)
	{
		perror("malloc()");
		exit(-1);
	}
	pHead->next = pHead;
	pHead->prev = pHead;
	return pHead;
}

开辟一个结点,用于当作傀儡结点,傀儡结点不存储有效的数据,因此数据域不需要赋值,因为是循环,所以一开始next和prev都是指向的自己。


4.链表的销毁

代码如下:

void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while(cur != pHead)
	{
		ListNode* curNext = cur->next;
		free(cur);
		cur = curNext;
	}
	free(pHead);
}

assert断言,按道理来说pHead不可能为空,但当调用者错误的定义,比如:

ListNode* pHead = NULL;

此时如果不加以断言就进行访问的话,势必会造成不必要的麻烦,因此加上断言也大大的提升了程序的安全性,销毁的过程也很简单,但是需要注意,如果直接free掉cur结点是不行的,因为这样你就找不到下一个结点的地址了,因此先把cur->next存放在curNext中,这样再对cur进行free就没问题了,最后再把傀儡结点也free之后,链表的销毁就完成了。


5.链表的打印

代码如下:

void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		printf("%d ", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

打印非常简单,打印当前cur的val值,cur再等于cur->next即可,因为傀儡节点不算作有效数据,而且该链表是循环的,所以最后势必会回到傀儡节点,所以循环继续的条件就是cur != pHead(傀儡结点),当cur等于傀儡结点时,说明链表中的内容已经全部打印完毕了。


6.链表的尾插

代码如下:

void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = BuyListNode(x);
	pHead->prev->next = newnode;
	newnode->prev = pHead->prev;
	pHead->prev = newnode;
	newnode->next = pHead;
}

简单的改变指向即可,利用该结构的特点,pHead的prev即是尾,所以很便利。


7.链表的尾删

代码如下:

void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->next != pHead);
	ListNode* tail = pHead->prev;
	pHead->prev = pHead->prev->prev;
	pHead->prev->next = pHead;
	free(tail);
}

8.链表的头插

代码如下:

void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = BuyListNode(x);
	newnode->next = pHead->next;
	pHead->next->prev = newnode;
	pHead->next = newnode;
	newnode->prev = pHead;
}

9.链表的头删

代码如下:

void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->next != pHead);
	ListNode* prev = pHead->next;
	pHead->next = pHead->next->next;
	pHead->next->prev = pHead;
}

10.链表的按内容查找(链表中的第一个val等于x的结点)

代码如下:

ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

找到并返回链表中的第一个val为x的结点,找不到返回空(NULL)。


11.链表的按下标查找(下标从0开始,傀儡结点不算做有效数据)

代码如下:

ListNode* IndexFind(ListNode* pHead, int index)
{
	assert(pHead);
	assert(pHead->next != pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead && index > 0)
	{
		cur = cur->next;
		index--;
	}
	if (cur == pHead)
	{
		return NULL;
	}
	return cur;
}

和按内容查找不同,这是按下标查找,需要注意的是傀儡结点并不算作有效数据,因此下标0从傀儡节点的next开始,也需要注意新插入结点时各结点下标的改变。


12.链表的在pos的前面进行插入

代码如下:

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* newnode = BuyListNode(x);
	pos->prev->next = newnode;
	newnode->prev = pos->prev;
	newnode->next = pos;
	pos->prev = newnode;
}

需要结合Find使用,也就是前面的9、10两点,Find找到的匹配的结点作为参数进行链式访问,在此结点前插入一个新的结点,也是简单的修改指向即可。


13.链表的删除pos位置的节点

代码如下:

void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* cur = pos;
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(cur);
}

需要结合Find使用,也就是前面的9、10两点,Find找到的匹配的结点作为参数进行链式访问,删除pos结点,也是简单的修改指向即可。


三、完整代码

1.Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"DoubleLinkList.h"
void Test1()
{
	ListNode* pHead = ListCreate();
	//ListPushBack(pHead, 1);
	//ListPushBack(pHead, 2);
	//ListPushBack(pHead, 3);
	//ListPushBack(pHead, 4);
	//ListPushBack(pHead, 5);
	ListPrint(pHead);
	ListPopBack(pHead);
	ListPrint(pHead);
	ListPopBack(pHead);
	ListPrint(pHead);
	ListPopBack(pHead);
	ListPrint(pHead);
	ListPopBack(pHead);
	ListPrint(pHead);
	ListPopBack(pHead);
	ListPrint(pHead);
}

void Test2()
{
	ListNode* pHead = ListCreate();
	//ListPushFront(pHead, 5);
	//ListPushFront(pHead, 4);
	//ListPushFront(pHead, 3);
	//ListPushFront(pHead, 2);
	//ListPushFront(pHead, 1);
	ListPrint(pHead);
	ListPopFront(pHead);
	ListPrint(pHead);
	ListPopFront(pHead);
	ListPrint(pHead);
	ListPopFront(pHead);
	ListPrint(pHead);
	ListPopFront(pHead);
	ListPrint(pHead);
	//ListPopFront(pHead);
	//ListPrint(pHead);
}

void Test3()
{
	ListNode* pHead = ListCreate();
	ListPushBack(pHead, 1);
	ListPushBack(pHead, 2);
	ListPushBack(pHead, 3);
	ListPushBack(pHead, 4);
	ListPushBack(pHead, 5);
	ListPrint(pHead);
	ListInsert(IndexFind(pHead, 0), 0);
	ListPrint(pHead);
	ListInsert(ListFind(pHead, 0), -1);
	ListPrint(pHead);
	//ListErase(IndexFind(pHead, 0));
	//ListPrint(pHead);
	//ListErase(IndexFind(pHead, 0));
	//ListPrint(pHead);
	//ListErase(IndexFind(pHead, 0));
	//ListPrint(pHead);
	ListErase(ListFind(pHead, -1));
	ListPrint(pHead);
	ListErase(ListFind(pHead, 0));
	ListPrint(pHead);
	ListErase(ListFind(pHead, 1));
	ListPrint(pHead);
}

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

2.DoubleLinkList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"DoubleLinkList.h"

//获取一个值为x的新结点
ListNode* BuyListNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (!newnode)
	{
		perror("malloc()");
		exit(-1);
	}
	newnode->val = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

// 创建返回链表的头结点.
ListNode* ListCreate()
{
	ListNode* pHead = (ListNode*)malloc(sizeof(ListNode));
	if (!pHead)
	{
		perror("malloc()");
		exit(-1);
	}
	pHead->next = pHead;
	pHead->prev = pHead;
	return pHead;
}

// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while(cur != pHead)
	{
		ListNode* curNext = cur->next;
		free(cur);
		cur = curNext;
	}
	free(pHead);
}

// 双向链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		printf("%d ", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = BuyListNode(x);
	pHead->prev->next = newnode;
	newnode->prev = pHead->prev;
	pHead->prev = newnode;
	newnode->next = pHead;
}

// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->next != pHead);
	ListNode* tail = pHead->prev;
	pHead->prev = pHead->prev->prev;
	pHead->prev->next = pHead;
	free(tail);
}

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = BuyListNode(x);
	newnode->next = pHead->next;
	pHead->next->prev = newnode;
	pHead->next = newnode;
	newnode->prev = pHead;
}

// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->next != pHead);
	ListNode* prev = pHead->next;
	pHead->next = pHead->next->next;
	pHead->next->prev = pHead;
}

// 双向链表按内容查找(链表中的第一个val等于x的结点)
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

双向链表按下标查找(下标从0开始,傀儡结点不算做有效数据)
ListNode* IndexFind(ListNode* pHead, int index)
{
	assert(pHead);
	assert(pHead->next != pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead && index > 0)
	{
		cur = cur->next;
		index--;
	}
	if (cur == pHead)
	{
		return NULL;
	}
	return cur;
}

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

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* cur = pos;
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(cur);
}

3.DoubleLinkList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

// 带头+双向+循环链表增删查改实现

//便于后续链表存储数据的类型的修改
typedef int LTDataType;

//结点
typedef struct ListNode
{
	LTDataType val;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

// 创建返回链表的头结点.
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);
// 双向链表按内容查找(链表中的第一个val等于x的结点)
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
//双向链表按下标查找(下标从0开始,傀儡结点不算做有效数据)
ListNode* IndexFind(ListNode* pHead, LTDataType x);

总结

带头双向循环链表是一个很厉害的结构,实现起来也很简单。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值