链表之带头双向循环链表(C语言版)

我们之前已经介绍过链表的知识了,这里我们直接开始实现带头双向循环链表

数据结构之单链表(不带头单向非循环链表)-CSDN博客

第一步:定义结构体

cpp 复制代码
//定义结构体
typedef int SLTDateType;
typedef struct Listnode
{
	SLTDateType date;
	struct Listnode* prev;
	struct Listnode* next;
}SL;

注意:我们这里要两个指针,一个指向链表前一个,一个指向链表下一个。

第二步:实现开辟空间函数

cs 复制代码
//开辟空间函数定义
SL* BuyListNode(SLTDateType x)
{
	SL* nownode = (SL*)malloc(sizeof(SL));//动态开辟一个结构体大小的空间
	nownode->date = x;//将开辟的空间中结构体成员date赋值为x
	nownode->next = NULL;//将该结构体成员尾指针置为空
	nownode->prev = NULL;//将该结构体成员头指针置为空
	return nownode;//返回该结构体地址
}

第三步:实现初始化函数

cpp 复制代码
//初始化函数定义
SL* ListInit()
{
	//该函数是创造一个head结构体放在链表的开头,满足带头链表
	SL* phead = BuyListNode(0);//将该结构开辟空间,并且将成员date赋值0
	phead->next = phead;//将phead的尾指针指向自己
	phead->prev = phead;//将phead的头指针指向自己
	return phead;//返回该结构体地址
}

第四步:实现双向链表打印

cpp 复制代码
// 双向链表打印定义
void ListPrint(SL* phead)
{
	assert(phead);
	SL* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->date);
		cur = cur->next;
	}
	printf("\n");
}

第五步:实现四大接口(头删 尾删 头插 尾插)

cpp 复制代码
//双向链表尾插定义
void ListPushBack(SL* phead, SLTDateType x)
{
	/*尾插的实现可以分成四步:
	1.将链表的最后一个元素尾指针从指向链表第一个元素变成指向开辟的结构体
	2.将开辟的结构体头指针指向链表最后一个元素
	3.将链表的第一个元素头指针从指向链表最后一个元素变成指向开辟的结构体
	4.将开辟的结构体尾指针指向链表第一个元素
	*/
	assert(phead);
	SL* tail = phead->prev;//定义一个结构体指针指向头位置的头指针指向的位置,即为链表的最后一个元素
	SL* nownode = BuyListNode(x);//开辟一个结构体空间
	tail->next = nownode;//将tail结构体尾指针指向开辟的结构体的位置
	nownode->prev = tail;//将开辟的结构体的头指针指向tail的位置,即为链表的最后一个元素
	nownode->next = phead;//将开辟的结构体的尾指针指向链表的开头,即为phead
	phead->prev = nownode;//将phead的头指针指向开辟的结构体nownode
}
cpp 复制代码
// 双向链表头插定义
void ListPushFront(SL* phead, SLTDateType x)
{
	assert(phead);//检查
	assert(phead->next!=NULL);//检查
	SL* nownode = BuyListNode(x);
	SL* next = phead->next;
	phead->next = nownode;
	nownode->prev = phead;
	nownode->next = next;
	next->prev = nownode;
}
cpp 复制代码
// 双向链表尾删定义
void ListPopBack(SL* phead)
{
	assert(phead);//检查
	assert(phead->next != NULL);//检查
	SL* first = phead->prev;
	SL* second =first->prev;
	phead->prev = second;
	second->next = phead;
	free(first);//释放first
	first = NULL;//指针置为空
}
cpp 复制代码
// 双向链表头删定义
void ListPopFront(SL* phead)
{
	assert(phead);
	SL* first = phead->next;
	SL* second = first->next;
	phead->next =second ;
	second->prev =phead ;
	free(first);
	first = NULL;
}

大家可以自己画图理解下,我的第一个注释写的详细

下面我们检验代码:

cpp 复制代码
#include "SList.h"
void test1()
{
	//我们实现尾插1 2 3,再头插3 2 1,然后打印观察
	// 再头删去 3 2 ,尾删 3 2,然后打印观察
	//调用函数测试
	SL* plist = ListInit();
	//双向链表尾插调用
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	// 双向链表头插调用
	ListPushFront(plist,3);
	ListPushFront(plist,2);
	ListPushFront(plist,1);
	//双向链表打印调用
	ListPrint(plist);
	// 双向链表头删调用
	ListPopFront(plist);
	ListPopFront(plist);
	// 双向链表尾删调用
	ListPopBack(plist);
	ListPopBack(plist);
	// 双向链表打印调用
	ListPrint(plist);
}
int main()
{
	test1();
	return 0;
}

结果:

噢耶,对了,现在我们可以继续下一步了!

第六步:实现特殊接口

cpp 复制代码
// 双向链表查找定义
SL* ListFind(SL* phead, SLTDateType x)
{
	assert(phead);
	SL* cur = phead->next;
	while (cur != phead)
	{
		if (cur->date ==x )
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
cpp 复制代码
// 双向链表在pos的前面进行插入定义
void ListInsert(SL* pos, SLTDateType x)
{
	assert(pos);
	SL* first = pos->prev;
	SL* newnode = BuyListNode(x);
	first->next= newnode;
	newnode->prev=first;
	newnode->next=pos;
	pos->prev=newnode;
}
cpp 复制代码
// 双向链表删除pos位置的结点定义
void ListErase(SL* pos)
{
	assert(pos);
	SL* first = pos->prev;
	SL* second = pos->next;
	first->next = second;
	second->prev = first;
}
cpp 复制代码
// 双向链表销毁定义
void ListDestory(SL* phead)
{
	assert(phead);
	SL* cur = phead->next;
	while (cur != phead)
	{
		SL* cur2 = cur->next;
		free(cur);
		cur = cur2;
	}
	free(phead);
	phead = NULL;
}

再次进行检查:

cpp 复制代码
void test2()
{
	SL* plist = ListInit();
	//双向链表尾插调用
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	// 双向链表打印调用
	ListPrint(plist);
	SL* pos = ListFind(plist, 2);
	if(pos != NULL)
	{
		// 双向链表在pos的前面进行插入
		ListInsert(pos, 30);
		// 双向链表打印调用
		ListPrint(plist);
		// 双向链表删除pos位置的结点
		ListErase(pos);
		// 双向链表打印调用
		ListPrint(plist);
	}
	free(pos);
	pos = NULL;
	// 双向链表销毁
	ListDestory(plist);
}
int main()
{
	//test1();
	test2();
	return 0;
}

结果:

综上:我们成功实现了带头双向循环链表!

全部代码如下:

这个是SList.c文件

cpp 复制代码
#include "SList.h"

//开辟空间函数定义
SL* BuyListNode(SLTDateType x)
{
	SL* nownode = (SL*)malloc(sizeof(SL));//动态开辟一个结构体大小的空间
	if (nownode == NULL)
	{
		perror(nownode);
		exit(-1);
	}
	nownode->date = x;//将开辟的空间中结构体成员date赋值为x
	nownode->next = NULL;//将该结构体成员尾指针置为空
	nownode->prev = NULL;//将该结构体成员头指针置为空
	return nownode;//返回该结构体地址
}
//初始化函数定义
SL* ListInit()
{
	//该函数是创造一个head结构体放在链表的开头,满足带头链表
	SL* phead = BuyListNode(0);//将该结构开辟空间,并且将成员date赋值0
	phead->next = phead;//将phead的尾指针指向自己
	phead->prev = phead;//将phead的头指针指向自己
	return phead;//返回该结构体地址
}
// 双向链表打印定义
void ListPrint(SL* phead)
{
	assert(phead);
	SL* cur = phead->next;	
	while (cur != phead)
	{
		printf("%d ", cur->date);
		cur = cur->next;
	}
	printf("\n");
}
//双向链表尾插定义
void ListPushBack(SL* phead, SLTDateType x)
{
	/*尾插的实现可以分成四步:
	1.将链表的最后一个元素尾指针从指向链表第一个元素变成指向开辟的结构体
	2.将开辟的结构体头指针指向链表最后一个元素
	3.将链表的第一个元素头指针从指向链表最后一个元素变成指向开辟的结构体
	4.将开辟的结构体尾指针指向链表第一个元素
	*/
	assert(phead);
	SL* tail = phead->prev;//定义一个结构体指针指向头位置的头指针指向的位置,即为链表的最后一个元素
	SL* nownode = BuyListNode(x);//开辟一个结构体空间
	tail->next = nownode;//将tail结构体尾指针指向开辟的结构体的位置
	nownode->prev = tail;//将开辟的结构体的头指针指向tail的位置,即为链表的最后一个元素
	nownode->next = phead;//将开辟的结构体的尾指针指向链表的开头,即为phead
	phead->prev = nownode;//将phead的头指针指向开辟的结构体nownode
}
// 双向链表头插定义
void ListPushFront(SL* phead, SLTDateType x)
{
	assert(phead);//检查
	assert(phead->next!=NULL);//检查
	SL* nownode = BuyListNode(x);
	SL* next = phead->next;
	phead->next = nownode;
	nownode->prev = phead;
	nownode->next = next;
	next->prev = nownode;
}
// 双向链表尾删定义
void ListPopBack(SL* phead)
{
	assert(phead);//检查
	assert(phead->next != NULL);//检查
	SL* first = phead->prev;
	SL* second =first->prev;
	phead->prev = second;
	second->next = phead;
	free(first);//释放first
	first = NULL;//指针置为空
}
// 双向链表头删定义
void ListPopFront(SL* phead)
{
	assert(phead);
	SL* first = phead->next;
	SL* second = first->next;
	phead->next =second ;
	second->prev =phead ;
	free(first);
	first = NULL;
}
// 双向链表查找定义
SL* ListFind(SL* phead, SLTDateType x)
{
	assert(phead);
	SL* cur = phead->next;
	while (cur != phead)
	{
		if (cur->date ==x )
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
// 双向链表在pos的前面进行插入定义
void ListInsert(SL* pos, SLTDateType x)
{
	assert(pos);
	SL* first = pos->prev;
	SL* newnode = BuyListNode(x);
	first->next= newnode;
	newnode->prev=first;
	newnode->next=pos;
	pos->prev=newnode;
}
// 双向链表删除pos位置的结点定义
void ListErase(SL* pos)
{
	assert(pos);
	SL* first = pos->prev;
	SL* second = pos->next;
	first->next = second;
	second->prev = first;
}
// 双向链表销毁定义
void ListDestory(SL* phead)
{
	assert(phead);
	SL* cur = phead->next;
	while (cur != phead)
	{
		SL* cur2 = cur->next;
		free(cur);
		cur = cur2;
	}
	free(phead);
	phead = NULL;
}

头文件:SList.h文件

cpp 复制代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//定义结构体
typedef int SLTDateType;
typedef struct Listnode
{
	SLTDateType date;
	struct Listnode* prev;
	struct Listnode* next;
}SL;
//双向链开辟空间函数声明
SL* BuyListNode(SLTDateType x);
//双向链初始化函数声明
SL* ListInit();
// 双向链表打印声明
void ListPrint(SL* phead);
//双向链表尾插声明
void ListPushBack(SL* phead, SLTDateType x);
// 双向链表头插声明
void ListPushFront(SL* plist, SLTDateType x);
// 双向链表尾删声明
void ListPopBack(SL* phead);
// 双向链表头删声明
void ListPopFront(SL* phead);
// 双向链表查找声明
SL* ListFind(SL* phead, SLTDateType x);
// 双向链表在pos的前面进行插入声明
void ListInsert(SL* pos, SLTDateType x);
// 双向链表删除pos位置的结点声明
void ListErase(SL* pos);
// 双向链表销毁声明
void ListDestory(SL* phead);

下面是我们的调试文件:test.c

cpp 复制代码
#include "SList.h"
void test1()
{
	//我们实现尾插1 2 3,再头插3 2 1,然后打印观察
	// 再头删去 3 2 ,尾删 3 2,然后打印观察
	//调用函数测试
	SL* plist = ListInit();
	//双向链表尾插调用
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	// 双向链表头插调用
	ListPushFront(plist,3);
	ListPushFront(plist,2);
	ListPushFront(plist,1);
	//双向链表打印调用
	ListPrint(plist);
	// 双向链表头删调用
	ListPopFront(plist);
	ListPopFront(plist);
	// 双向链表尾删调用
	ListPopBack(plist);
	ListPopBack(plist);
	// 双向链表打印调用
	ListPrint(plist);
}
void test2()
{
	SL* plist = ListInit();
	//双向链表尾插调用
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	// 双向链表打印调用
	ListPrint(plist);
	SL* pos = ListFind(plist, 2);
	if(pos != NULL)
	{
		// 双向链表在pos的前面进行插入
		ListInsert(pos, 30);
		// 双向链表打印调用
		ListPrint(plist);
		// 双向链表删除pos位置的结点
		ListErase(pos);
		// 双向链表打印调用
		ListPrint(plist);
	}
	free(pos);
	pos = NULL;
	// 双向链表销毁
	ListDestory(plist);
}
int main()
{
	//test1();
	test2();
	return 0;
}

希望大家坚持学下去,在链表这里你可以发现无穷的乐趣,当然建议大家链表这里还是多刷题,这样才能帮助我们更好理解它。

相关推荐
南宫生12 分钟前
力扣每日一题【算法学习day.132】
java·学习·算法·leetcode
计算机毕设定制辅导-无忧学长35 分钟前
Maven 基础环境搭建与配置(一)
java·maven
风与沙的较量丶2 小时前
Java中的局部变量和成员变量在内存中的位置
java·开发语言
m0_748251722 小时前
SpringBoot3 升级介绍
java
极客先躯3 小时前
说说高级java每日一道面试题-2025年2月13日-数据库篇-请说说 MySQL 数据库的锁 ?
java·数据库·mysql·数据库的锁·模式分·粒度分·属性分
程序员侠客行3 小时前
Spring事务原理 二
java·后端·spring
小猫猫猫◍˃ᵕ˂◍3 小时前
备忘录模式:快速恢复原始数据
android·java·备忘录模式
liuyuzhongcc3 小时前
List 接口中的 sort 和 forEach 方法
java·数据结构·python·list
五月茶3 小时前
Spring MVC
java·spring·mvc
sjsjsbbsbsn4 小时前
Spring Boot定时任务原理
java·spring boot·后端