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

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

相关推荐
火烧屁屁啦1 分钟前
【JavaEE进阶】初始Spring Web MVC
java·spring·java-ee
w_312345415 分钟前
自定义一个maven骨架 | 最佳实践
java·maven·intellij-idea
岁岁岁平安17 分钟前
spring学习(spring-DI(字符串或对象引用注入、集合注入)(XML配置))
java·学习·spring·依赖注入·集合注入·基本数据类型注入·引用数据类型注入
武昌库里写JAVA20 分钟前
Java成长之路(一)--SpringBoot基础学习--SpringBoot代码测试
java·开发语言·spring boot·学习·课程设计
Q_192849990627 分钟前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
张国荣家的弟弟1 小时前
【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?
java·jar·bi
ZSYP-S1 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos1 小时前
C++----------函数的调用机制
java·c++·算法
是小崔啊1 小时前
开源轮子 - EasyExcel01(核心api)
java·开发语言·开源·excel·阿里巴巴
黄公子学安全2 小时前
Java的基础概念(一)
java·开发语言·python