链表之带头双向循环链表(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;
}

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

相关推荐
空の鱼3 小时前
java开发,IDEA转战VSCODE配置(mac)
java·vscode
P7进阶路4 小时前
Tomcat异常日志中文乱码怎么解决
java·tomcat·firefox
王磊鑫4 小时前
C语言小项目——通讯录
c语言·开发语言
小丁爱养花5 小时前
Spring MVC:HTTP 请求的参数传递2.0
java·后端·spring
CodeClimb5 小时前
【华为OD-E卷 - 第k个排列 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
等一场春雨5 小时前
Java设计模式 九 桥接模式 (Bridge Pattern)
java·设计模式·桥接模式
带刺的坐椅5 小时前
[Java] Solon 框架的三大核心组件之一插件扩展体系
java·ioc·solon·plugin·aop·handler
不惑_6 小时前
深度学习 · 手撕 DeepLearning4J ,用Java实现手写数字识别 (附UI效果展示)
java·深度学习·ui
仟濹6 小时前
【贪心算法】洛谷P1106 - 删数问题
c语言·c++·算法·贪心算法
费曼乐园6 小时前
Kafka中bin目录下面kafka-run-class.sh脚本中的JAVA_HOME
java·kafka