双向带头循环链表

本文详细介绍了如何使用C语言实现双向带头循环链表的初始化、打印、尾插、头插、尾删、头删、查找、在指定位置插入和删除节点以及链表的销毁等操作。每个操作都提供了相应的代码实现,并通过示例展示了操作的效果。

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

目录

一:初始化链表

二:打印链表

三:尾插

 四:头插

 五:尾删

 六:头删

 七:查找

八:在pos的前面进行插入 

 九:删除pos位置的节点

 十:链表的销毁

十一:完整代码



 双向带头循环链表结构:

创建结构体,用于存放数据,上一个节点,和下一个节点。

typedef --- 关键字,类型重命名

typedef int LTDataType;
typedef struct ListNode
{
    LTDataType _data;
    struct ListNode* _next;
    struct ListNode* _prev;
}ListNode;


一:初始化链表


// 创建返回链表的头结点.
ListNode* ListCreate();


初始化时,需要创建哨兵位,即开辟新的节点,由于后续头插,尾插也要开辟新的节点,可以将其分装成为一个函数 --- ListNode* OpenNode(LTDataType x);

//开辟新的节点
ListNode* OpenNode(LTDataType x);

开辟新的节点,需要创建结构体指针去接收,使用 malloc 开辟空间,由于 malloc 又可能失败,需要对新开辟的节点(newnode)进行判空判断,若 newnode 为 NULL --- 空间开辟失败,否则,空间开辟成功。将x放入新节点(newnode)中。

//开辟新的节点
ListNode* OpenNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	//开辟的节点有可能开辟失败
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;//返回类型为 ListNode*
	}
	//开辟成功,使用
	newnode->_data = x;
	newnode->_next = NULL;
	return newnode;
}

 链表初始时,只有一个哨兵位,即判断条件为:pHead->_next = pHead; pHead->_prev = pHead

代码为:

// 创建返回链表的头结点.
ListNode* ListCreate()
{
	ListNode* pHead = OpenNode(-1);
	pHead->_next = pHead;
	pHead->_prev = pHead;			

	return pHead;
}

二:打印链表


 // 双向链表打印
void ListPrint(ListNode* pHead);


打印链表时,可以先将哨兵位打印出来,后续继续打印链表。

打印哨兵位:printf("sentry<==>");

 代码为:

// 双向链表打印
void ListPrint(ListNode* pHead)
{
	//打印哨兵位
	printf("sentry<==>");
	ListNode* cur = pHead->_next;
	//循环终止的条件是 cur != pHead
	while (cur != pHead)
	{
		printf("%d<==>", cur->_data);
		cur = cur->_next;
	}
	printf("\n");//换行
}

三:尾插


 // 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);


尾插时,需要开辟一个新的节点,使用函数 ---  ListNode* OpenNode(LTDataType x);

先找链表的尾,并记录下来(cur), 链表的尾为 pHead->_prev; 然后将链表链接起来。

代码为:

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

	//尾插 --- 插入数据
	assert(pHead);
	ListNode* newnode = OpenNode(x);

	//找尾 , pHead->_prev 即为原来的尾
	ListNode* cur = pHead->_prev;

	cur->_next = newnode;
	newnode->_prev = cur;

	newnode->_next = pHead;
	pHead->_prev = newnode;
}

进行测试,尾插入 1 2 3 4 , 效果为:

 四:头插


 // 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);


头插时,需要插入数据,使用函数 ---  ListNode* OpenNode(LTDataType x);

头插入时,插入的位置为哨兵位后面的位置,需要记录起始位置(ListNode* cur = pHead->_next;)

然后进行连接。

代码为:

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//头插 --- 插入数据
	ListNode* newnode = OpenNode(x);

	//记录原起始位置(pHead 后面的那个节点)
	ListNode* cur = pHead->_next;

	//进行链接
	pHead->_next = newnode;
	newnode->_prev = pHead;
	
	newnode->_next = cur;
	cur->_prev = newnode;
}

头插入 1 2 3 4 ,效果为:

 五:尾删


 // 双向链表尾删
void ListPopBack(ListNode* pHead);


 尾删时,需要判断链表是否只有哨兵位,若链表只有哨兵位的话,则无法进行尾删。

 尾删时,先找链表的尾(ListNode* tail = pHead->_prev;),

及其前一个节点(ListNode* cur = tail->_prev;)

然后进行链接 --- 将 cur 与哨兵位连接起来。

最后释放 原尾节点(tail)。

 代码为:

//判断链表中是否只有哨兵位,真为 true  假为 false
bool LTEmpty(ListNode* pHead)
{
	assert(pHead);

	return pHead->_next == pHead;// 若相等为真,若不相等为假
}

// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	//若链表中只有 哨兵位,无法进行尾删
	assert(!LTEmpty(pHead));//仅有哨兵位时,无法尾删

	ListNode* tail = pHead->_prev;
	ListNode* cur = tail->_prev;

	//进行链接
	pHead->_prev = cur;
	cur->_next = pHead;

	//释放尾节点
	free(tail);
}

尾插入 1 2 3 4 ,打印结果,然后 尾删一次的效果为:

 尾插入 1 2 3 4 ,打印结果,然后 尾删五次(即影响到了哨兵位)的效果为:

 

 观察ListNode.c 文件中的  line 111 行:

 可知,当链表只有哨兵位时,仍在进行尾删,所以报错(链表中只有哨兵位时,不能进行尾删

 六:头删


 // 双向链表头删
void ListPopFront(ListNode* pHead);


 同尾删类似,当链表中只有哨兵位时,无法进行头删,应使用 --- assert(!LTEmpty(pHead)); 进行判断。

头删时,应该找到哨兵位后两个节点(ListNode* prve = pHead->_next;)

(ListNode* cur = prve->_next;) ,并将其保存起来,然后将哨兵位与哨兵位后第二个节点链接起来,释放掉哨兵位后第一个节点。

 代码为:

//判断链表中是否只有哨兵位,真为 true  假为 false
bool LTEmpty(ListNode* pHead)
{
	assert(pHead);

	return pHead->_next == pHead;// 若相等为真,若不相等为假
}

// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	//若链表中只有 哨兵位,无法进行头删
	assert(!LTEmpty(pHead));

	ListNode* prve = pHead->_next;
	ListNode* cur = prve->_next;

	//进行链接
	pHead->_next = cur;
	cur->_prev = pHead;

	//释放头节点
	free(prve);
}

头插入 1 2 3 4 ,打印,然后进行一次头删,效果为:

 头插入 1 2 3 4 ,打印,然后进行5次头删,效果为:

 观察 ListNode.c 的 第 129 行:

  可知,当链表只有哨兵位时,仍在进行头删,所以报错(链表中只有哨兵位时,不能进行头删

 七:查找


// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);//传入起始位置(非链表的),和要查找的内容


 代码为:

// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//找尾 --- cur != pHead , 当 cur == pHead 时,链表开始绕圈圈
	ListNode* cur = pHead->_next;//从 cur = pHead->_next 开始
	while (cur != pHead)
	{
		if (cur->_data == x)
		{
			return cur;
		}
		cur = cur->_next;//进行循环的条件
	}
	return NULL;
}

八:在pos的前面进行插入 


 // 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);


 查找过程返回的节点即为 pos 。

 判断 pos 的值 --- 防止输入错误的 pos 。断言 assert(pos);

 需要开辟一个新的节点,用到 函数 ---  ListNode* OpenNode(LTDataType x);

 链表的长度未知,只能使用 pos 的前后节点,想要在 pos 的前面插入新的节点,需要记录 pos 前一个节点(ListNode* prve = pos->_prev;) ,然后进行链接。

 代码为:

//开辟新的节点
ListNode* OpenNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	//开辟的节点有可能开辟失败
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;//返回类型为 ListNode*
	}
	//开辟成功,使用
	newnode->_data = x;
	newnode->_next = NULL;
	return newnode;
}


// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	//插入数据
	ListNode* newnode = OpenNode(x);

	ListNode* prve = pos->_prev;
	//进行链接
	prve->_next = newnode;
	newnode->_prev = prve;

	newnode->_next = pos;
	pos->_prev = newnode;
}

头插入 1 2 3 4 ,打印结果,进行一次头删,打印结果,查找数据为2的节点,若该节点存在。则在该节点前边插入30,打印结果。

代码如下:

void test2()
{
	ListNode* plist = ListCreate();
	//头插
	ListPushFront(plist, 1);
	ListPushFront(plist, 2);
	ListPushFront(plist, 3);
	ListPushFront(plist, 4);

	//打印
	ListPrint(plist);

	//头删
	ListPopFront(plist);

	//打印
	ListPrint(plist);

	//查找
    ListNode* pos = ListFind(plist, 2);
	if (pos)
	{
		ListInsert(pos, 30);
	}
	//打印
	ListPrint(plist);
}

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

运行效果为:

 九:删除pos位置的节点


 // 双向链表删除pos位置的节点
void ListErase(ListNode* pos);


 查找过程返回的节点即为 pos 。

 判断 pos 的值 --- 防止输入错误的 pos 。断言 assert(pos);

需要记录 pos 的前一个节点(ListNode* cur = pos->_prev;)

和后一个节点(ListNode* tail = pos->_next;)

然后将这两个节点链接在一起,释放掉 pos 节点。

 代码为:

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);

	ListNode* cur = pos->_prev;
	ListNode* tail = pos->_next;

	//链接
	cur->_next = tail;
	tail->_prev = cur;

	free(pos);
}

头插入 1 2 3 4 ,打印结果,进行一次头删,打印结果,查找数据为2的节点,若该节点存在。则在该节点前边插入30,打印结果,最后删除 pos 位置的数值,打印结果。在pos的前面进行插入 的示例上加上一个删除 pos 位置的值。                   

 十:链表的销毁


// 双向链表销毁
void ListDestory(ListNode* pHead);


链表销毁时,应该一个节点一个节点的销毁,先销毁链表中的节点,最后销毁哨兵位。

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

		//一节点一个节点的释放
		free(cur);
		cur = next;
	}
	//释放哨兵位
	free(pHead);

}

十一:完整代码

http://t.csdn.cn/muQw1

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值