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

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

相关推荐
Byron Loong12 分钟前
【c++】为什么有了dll和.h,还需要包含lib
java·开发语言·c++
Dlrb121125 分钟前
C语言-指针数组与数组指针
c语言·数据结构·算法·指针·数组指针·指针数组·二级指针
坚果派·白晓明30 分钟前
【鸿蒙PC三方库移植适配框架解读系列】第一篇:Lycium C/C++ 三方库适配 — 概述与环境配置
c语言·开发语言·c++·harmonyos·开源鸿蒙·三方库·c/c++三方库
hexu_blog44 分钟前
vue+java实现图片批量压缩
java·前端·vue.js
头歌实践平台1 小时前
Hadoop开发环境搭建
java·大数据·hadoop
Seven971 小时前
一致性Hash算法:如何实现分布式系统中的高效数据分片?
java
摇滚侠1 小时前
IDEA 生成 try catch 快捷键
java·ide·intellij-idea
阿旭超级学得完2 小时前
C++11包装器(function和bind)
java·开发语言·c++·算法·哈希算法·散列表
掉鱼的猫3 小时前
Spring AI 2.0 GA 倒计时:先别急,来看看 Java AI 框架的另一条路
java·openai·agent