快速上手双向循环链表【C语言】

1. 双向循环链表的结构

首先,双向链表区别去单链表,它拥有两个节点
在这里插入图片描述
一个在前,一个在后,它的节点结构如下

//优化前
struct DListNode
{
	struct Node* prev;
	int data;
	struct Node* next;
};

//为了更加方便我们后面使用该结构体,我们可以使用typedef关键字来定义自己习惯的数据类型名

//优化后
typedef int DLDataType;
typedef struct DListNode
{
	struct Node* prev;
	DLDataType data;
	struct Node* next;
}DLNode;

知道结构后,我们只需要多创造几个节点然后利用其节点的指针,使其链接起来,就是我们的双向链表了。
在这里插入图片描述
双向循环链表就是双向链表上将首尾串起来
分别是 1节点的prev指向5节点,5节点的next指向1节点。
在这里插入图片描述

2.双向循环链表的创建

(1)创造节点

首先我们先创建一个节点

DLNode* CreateNode(DLDataType x)
{
	//首先我们创造一个节点
	DLNode* Node = (DLNode*)malloc(sizeof(DLNode)); 
	//当malloc失败后结束
	if(Node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	//将新开的节点指针初始化为空,防止野指针
	Node->data = x;
	Node->prev = NULL;
	Node->next = NULL;
	return Node;
}

双链表需要注意的是,每当我们创建一个新节点,我们需要让其与前驱节点2次链接
一次是新节点的prev指向前驱节点
一次是前驱节点的next指向新节点

(2)初始化哨兵位

代码实现:

//构造一个空的双向循环链表
//初始化哨兵位
DLNode* CreateList()
{
	DLNode* phead = CreateNode(0);//调用上面的CreateNode()函数创建数据域为0的头指针指向头节点。
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

3.双向循环链表的尾插尾删

(1)尾插:

思路:双向循环链表首尾相连

void DLPushBack(DLNode* phead,DLDataType x)
{
	assert(phead); 
	DLNode* newnode = CreateNode(x);
	DLNode* tail = phead->prev;//tail指向链表的尾
	
	tail->next = newnode; 	//让尾指向新节点
	newnode->prev = tail;	//新节点的prev指向(old)尾
	
	newnode->next = phead;	//新节点的next指向phead
	phead->prev = newnode;	//phead的prev指向newnode
}
尾插的思维图

在插入newnode前
在这里插入图片描述

插入让其首尾相连后
在这里插入图片描述

(2)尾删:

思路:先找到链表的尾的前一个

void DLPopBack(DLNode* phead)
{
	assert(phead);
	assert(phead->next != phead);//判断链表是否为空
	DLNode* tail = phead->prev; //找到链表的尾
	DLNode* tailPrev = tail->prev;//找到链表尾的前一个
	
	tailPrev->next = phead;	//(new)尾的next指向phead
	phead->prev = tailPrev;//phead的prev指向(new)尾
	free(tail);
}
尾删的思维图

原链表
在这里插入图片描述
删除后:
在这里插入图片描述

4.双向循环链表的头插头删

(1)头插:

画图思维实现:
在这里插入图片描述

第一种:
代码实现:

void DLPushFront(DLNode* phead,DLDataType x)
{
	assert(phead);
	DLNode* newnode = CreateNode(x);
	//使第一个节点与新节点链接
	newnode->next = phead->next;
	phead->next->prev=newnode;
	//使头节点与新节点链接
	phead->next = newnode;
	newnode->prev = phead;
}

第二种:
无关顺序
代码实现:

void DLPushFront(DLNode* phead,DLDataType x)
{
	assert(phead);
	DLNode* newnode = CreateNode(x);
	//让first拿到第一个节点
	DLNode* first = phead->next;
	//phead newnode first 三指针纯粹的链接关系
	//顺序无关
	//哨兵位phead与新节点的链接
	phead->next = newnode;
	newnode->prev = phead;
	//新节点与第一个节点的链接
	newnode->next = first;
	first->prev = newnode;
}

(2)头删:

思维画图实现:
在这里插入图片描述

代码实现:

void DLPopFront(DLNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	DLNode* first = phead->next;
	DLNode* second = first->next;
	free(first);
	phead->next = second;
	second->prev = phead;
}

5.双向循环链表的查找和Print

(1)查找

查找代码实现:

DLNode* DLFind(DLNode* phead,DLDataType x)
{
	assert(phead);
	DLNode* cur = phead->next;
	while(cur != phead)
	{
		if(cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

(2)Print

Print代码实现:

void DLPrint(DLNode* phead)
{
	assert(phead);
	DLNode* cur = phead->next;
	while(cur != phead)
	{
		printf("%d ",cur->data);
		cur = cur->next //向后进
	}
	printf("\n");
}

6.双向循环链表在任意位置之前插入删除任意位置的元素

(1)插入:

思维画图实现:
在这里插入图片描述

代码实现:

void DLInsert(DLNode* pos,DLDataType x)
{
	assert(pos);
	DLNode* prev = pos->prev;
	DLNode* newnode = CreateNode(x);
	// prev newnode pos 三指针链接
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

由上的代码 我们可以优化尾插与头插
直接复用:

//尾插
void DLPushBack(DLNode* phead,DLDataType x)
{
	assert(phead); 
	DLInsert(phead,x);//此时Insert的pos与phead在同一位置。
}
//头插
void DLPushFront(DLNode* phead,DLDataType x)
{
	assert(phead);
	DLInsert(phead->next,x);
}

(2)删除:

思维画图实现:
在这里插入图片描述

代码实现:

void DLErase(DLNode* pos)
{
	assert(pos);
	DLNode* prev = pos->prev;
	DLNode* next = pos->next;
	free(pos);	//删除pos位置
	prev->next = next;
	next->prev = prev;
}

由上的代码 我们可以优化尾删与头删
直接复用:

//尾删
void DLPopBack(DLNode* phead)
{
	assert(phead);
	assert(phead->next != phead);//判断链表是否为空
	DLErase(phead-prev);
}
//头删
void DLPopFront(DLNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	DLErase(phead->next);
}

7.双向循环链表的判空,长度和销毁

(1)判空

判空代码实现:

bool LTEmpty(DLNode* phead)
{
	assert(phead);
	//当哨兵位的next为自己,开始循环时,则链表为空
	return phead->next == phead;
}

(2)长度(size)

长度代码实现:

size_t DLSize(DLNode* phead)
{
	assert(phead);
	DLNode* cur = phead->next;
	while(cur != phead)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

(3)链表的销毁

销毁链表的代码实现:

void DLDestroy(DLNode* phead)
{
	assert(phead);
	DLNode* cur = phead->next;//拿到第一个节点位置
	while(cur != phead)
	{
		DLNode* next = cur->next;//记录其下一个位置
		free(cur);
		cur = next;
	}
	free(phead);
}

8.总结

双向循环链表相较于单链表更为复杂,但是上手却更为简单。
文章若有遗漏和错误,请多多指出,Thanks!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值