【每天学习亿点点系列】——重温双向带头循环链表

本文详细介绍了如何实现双向带头循环链表,包括初始化、创建节点、插入、删除、查找、打印等基本操作。特别强调了使用哨兵节点解决插入问题,以及在操作链表时注意的细节,如赋值顺序、删除节点时的内存释放等。此外,还提供了完整的头文件和测试文件,便于读者理解和实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

头文件以及测试文件的实现

List.h

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>

typedef int ListDataType;
typedef struct List
{
	struct List* pre;
	struct List* next;
	ListDataType x;
}List;

void Listinit(List** pphead);//为了让双向链表自己指向自己
void ListBackPush(List** pphead, ListDataType x);
void ListFrontPush(List** pphead, ListDataType x);
void ListPrint(List* pphead);
void ListBackPop(List** pphead);
void ListFrontPop(List** pphead);
List* ListFind(List* pphead,ListDataType x);
void ListPushAfter(List** pphead, ListDataType x, List* pos);
void ListPushBefore(List** pphead, ListDataType x, List* pos);
void ListPopAfter(List** pphead, List* pos);
void ListPopBefore(List** pphead, List* pos);
void ListPop(List** pphead, List* pos);
int ListSize(List* pphead);
bool ListEmpty(List* pphead);
void ListDestory(List** pphead);

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
void test()
{
	List* phead;
	Listinit(&phead);
	ListBackPush(&phead, 1);
	ListBackPush(&phead, 2);
	ListFrontPush(&phead, 3);
	ListBackPop(&phead);
	ListFrontPop(&phead);
	List* pos=ListFind(phead,1);
	ListPushAfter(&phead, 2, pos); //复用,相当于尾插
	ListPushBefore(&phead, 3, pos);//复用,相当于头插
	ListPopAfter(&phead, pos);//复用,相当于尾删
	//ListPopBefore(&phead, pos);//复用,相当于头删
	/*ListBackPop(&phead);*/
	/*printf("%d\n", ListEmpty(phead));*/
	/*ListPopBefore(&phead, pos);*/ //检验156行的断言
	ListPop(&phead, pos);
	ListPrint(phead);
}
int main()
{
	test();
	return 0;
}

各个接口的实现

1.初始化

void Listinit(List** pphead)
{
	assert(pphead);
	assert(*pphead);
	List* head = (List*)malloc(sizeof(List));
	if (head == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	else
	{
		*pphead = head;
	}
	(*pphead)->next = *pphead;
	(*pphead)->pre = *pphead;
}

2.创建节点

//创建节点
List* BuyNode(ListDataType x)
{
	List* newnode = (List*)malloc(sizeof(List));
	newnode->x = x;
	return newnode;
}

3.尾插

//尾插
void ListBackPush(List** pphead, ListDataType x)    //这边不能用二级指针,因为你else里面那种情况会改变头节点,而你不希望它改变,但是你if里面的情况希望改变,故用这种写法写不出来
{
	assert(pphead); 
	//由于初始化的原因,此时双向链表里面已经有一个节点了, 迫于没有办法,用带哨兵位的写法,用哨兵位可以解决上面的问题,因为这时我不需要分情况来说
	List* newnode = BuyNode(x);   
	newnode->pre = (*pphead)->pre;
	(*pphead)->pre->next = newnode;
	newnode->next = (*pphead);
	(*pphead)->pre = newnode;
}

4.头插

//头插
void ListFrontPush(List** pphead, ListDataType x)
{
	assert(pphead);
	List* newnode = BuyNode(x);
	newnode->next = (*pphead)->next;
	(*pphead)->next->pre = newnode;
	(*pphead)->next = newnode;
	newnode->pre = (*pphead);
}

5.尾删

//尾删
void ListBackPop(List** pphead)
{
	assert(pphead);
	assert(*pphead);
	(*pphead)->pre->pre->next = (*pphead);  //这儿要注意写的时候的先后顺序
	(*pphead)->pre = (*pphead)->pre->pre;
}

6.头删

//头删
void ListFrontPop(List** pphead)
{
	assert(pphead);
	assert(*pphead);
	(*pphead)->next->next->pre = (*pphead);   //同样也要注意顺序
	(*pphead)->next = (*pphead)->next->next;
}

7.打印

//打印
void ListPrint(List* pphead)
{
	assert(pphead);
	List* cur = pphead->next;
	while (cur != pphead)
	{
		printf("%d ", cur->x);
		cur = cur->next;
	}
}

8.查找

List* ListFind(List* pphead,ListDataType x)
{
	assert(pphead);
	List* cur = pphead->next;
	while (cur != pphead)
	{
		if (cur->x == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
}

9在任意位置之后插入

//在任意位置之后插入
void ListPushAfter(List** pphead, ListDataType x, List* pos)
{
	assert(pphead);
	assert(*pphead);
	List* newnode = BuyNode(x);
	newnode->next = pos->next;
	pos->next->pre = newnode;
	pos->next = newnode;
	newnode->pre = pos;
}

10.在任意位置之前插入

//在任意位置之前插入
void ListPushBefore(List** pphead, ListDataType x, List* pos)
{
	assert(pphead);
	assert(*pphead);
	List* newnode = BuyNode(x);
	pos->pre->next = newnode;
	newnode->pre = pos->pre;
	newnode->next = pos;
	pos->pre = newnode;
}

11.记录节点个数


//记录节点个数
int ListSize(List* pphead)
{
	List* cur = pphead->next;
	int n = 0;
	while (cur != pphead)
	{
		n++;
		cur = cur->next;
	}
	return n;
}

12.在任意位置之后删除

//在任意位置之后删除
void ListPopAfter(List** pphead, List* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(ListSize(*pphead) != 1);
	pos->next->next->pre = pos;         //同上注意顺序
	/*pos->next = pos->next->next;*/
	List*temp = pos->next->next;      //注意删除时这儿的写法,要先创建一个临时变量保存一下,因为你free掉了pos->next,如果你下面再将pos->next->next赋值给pos->next的话
	                                  //肯定时会出问题的。
	free(pos->next);
	pos->next = temp;
}

13.在任意位置之前删除

//在任意位置之前删除
void ListPopBefore(List** pphead, List* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(ListSize(*pphead) != 1);
	pos->pre->pre->next = pos;          //同上注意顺序
	/*pos->pre = pos->pre->pre;*/
	List*temp = pos->pre->pre;          //同上面注意点一样
	free(pos->pre);
	pos->pre = temp;
}

14.删除当前位置

//删除当前位置
void ListPop(List** pphead, List* pos)
{
	assert(pphead);
	assert(*pphead);
	pos->pre->next = pos->next;
	pos->next->pre = pos->pre;
	free(pos);
	pos = NULL;
}

15.判空

//判空
bool ListEmpty(List* pphead)
{
	/*return pphead == NULL;*/  //因为是有哨兵位的,所以不能这么判断
	return pphead->next == pphead;
}

16.销毁

//销毁
void ListDestory(List** pphead)
{
	assert(pphead);
	//free(*pphead);  //链表不想顺序表那样,在物理上是连续的,所以不能这样释放,那是顺序表的释放方法
	//*pphead = NULL;
	List* cur = (*pphead)->next;
	while (cur != *pphead)
	{
		List* next = cur->next;
		free(cur);
		cur = next;
	}
	free(*pphead);
}

注意点

1.双向链表要初始化这个接口的,不同与单链表

初始化为了让双向链表自己指向自己,形成环形

2.带个哨兵位,解决插入的问题

//尾插
void ListBackPush(List** pphead, ListDataType x)    //这边不能用二级指针,因为你else里面那种情况会改变头节点,而你不希望它改变,但是你if里面的情况希望改变,故用这种写法写不出来
{
	assert(pphead); 
	//由于初始化的原因,此时双向链表里面已经有一个节点了, 迫于没有办法,用带哨兵位的写法,用哨兵位可以解决上面的问题,因为这时我不需要分情况来说
	List* newnode = BuyNode(x);   
	newnode->pre = (*pphead)->pre;
	(*pphead)->pre->next = newnode;
	newnode->next = (*pphead);
	(*pphead)->pre = newnode;
}

在这里,你刚刚初始话的那个节点就当做哨兵位,之后删除什么的都不需要包括它,它只是为了方便我们插入而已,同时也解决了注释中所说的问题。

3.注意赋值时的先后顺序

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

像这些因为交换顺序会影响赋值的,都要十分注意。

4.删除时要把节点free掉,同时要注意写法

在这里插入图片描述

这儿要free掉你要删除的节点,如果不删除,只改变链接的关系,那只是表面删除,并没有彻底的把那个节点弄掉,某种意义上说也时可以的,但是不好,而且在free掉后给指针赋值时也要注意,如果有你free掉的成分,那么就要提前将它保存到一个变量中,就像上图一样,下面两个接口也是要注意同样的情况。
在这里插入图片描述

5.判空与以往不同,因为有哨兵位

在这里插入图片描述

6.不要像释放顺序表一样释放,因为空间物理上不连续

在这里插入图片描述

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个数学不怎么好的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值