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

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

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

链表之带头双向循环链表(C语言版)_第1张图片

第一步:定义结构体

//定义结构体
typedef int SLTDateType;
typedef struct Listnode
{
	SLTDateType date;
	struct Listnode* prev;
	struct Listnode* next;
}SL;

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

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

//开辟空间函数定义
SL* BuyListNode(SLTDateType x)
{
	SL* nownode = (SL*)malloc(sizeof(SL));//动态开辟一个结构体大小的空间
	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;
}

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

下面我们检验代码:

#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;
}

结果:
链表之带头双向循环链表(C语言版)_第2张图片

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

第六步:实现特殊接口

// 双向链表查找定义
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;
}

再次进行检查:

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;
}

结果:

链表之带头双向循环链表(C语言版)_第3张图片

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

全部代码如下:

这个是SList.c文件

#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文件

#pragma once
#include 
#include 
#include 
//定义结构体
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

#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;
}

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

你可能感兴趣的:(链表,c语言,java)