数据结构与算法初阶4:链表—带头双向循环链表知识讲解

目录

1、带头双向循环链表回顾

2、总体学习框架

3、双向循环链表各子功能介绍

3.1 定义带头双向循环链表 

3.2 初始化链表

3.3 打印结点数据函数

3.4 尾插结点

3.5 尾删结点

3.6 头插结点

3.7 头删结点

3.8 链表是否为空判断函数的创建

3.9 在指定位置pos之前插入结点

3.10 删除指定位置pos对应的数据

3.11 返回任意结点位置的函数

4、结语



1、带头双向循环链表回顾

带头双向循环链表

        在《 数据结构与算法篇初阶3:线性表—链表相关知识点讲解》中,为读者们详细介绍了链表以及链表的多种形式,经过前面一节的学习,我主要从无头单向循环链表为读者们带来详细的原理介绍,并在《数据结构与算法初阶刷题篇1:线性表—单链表OJ面试题训练》为读者们提供了大厂面试常见的单链表经典题型,让大家对单链表的作用及应用背景有了更深的认识。今天这一讲将会介绍我们在今后生活中主要用到的带头双向循环链表。

2、总体学习框架

#pragma once
#include
#include
#include
#include

//定义带头双向循环链表
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode*prev;
	struct ListNode*next;
}LTNode;

//初始化链表
LTNode* ListInit();
//打印链表
void ListPrint(LTNode*phead);
//尾插
void ListPushBack(LTNode*phead, LTDataType x);
//尾删
void ListPopBack(LTNode*phead);
//头插
void ListPushFront(LTNode*phead, LTDataType x);
//头删
void ListPopFront(LTNode*phead);
//判读链表是否为空
bool ListEmpty(LTNode*phead);
//在pos位置之前插入
void ListInsert(LTNode*pos, LTDataType x);
//删除pos位置的数据
void ListErase(LTNode*pos);

//返回某一元素的位置
LTNode* return_pos(LTNode*phead, LTDataType x);

3、双向循环链表各子功能介绍

3.1 定义带头双向循环链表 

      在利用结构体定义带头双向循环链表时,结构体内部包含元素相比单链表略有不同,结点结构体内部定义了三个元素:

1、结点存储的数据类型;

2、创建结构体指针prev,该指针指向前一个结点;

3、创建结构体指针next,该指针指向后一个结点。

//定义带头双向循环链表
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode*prev;
	struct ListNode*next;
}LTNode;

3.2 初始化链表

在初始化链表结点中,分二步走:

1、利用malloc函数动态开辟空间,创建结点,并将结点的前一个指向和后一个指向均置为空,如图所示:

数据结构与算法初阶4:链表—带头双向循环链表知识讲解_第1张图片

 2、当创建好了该结点以后,初始化时将该结点指向的前一个和指向的后一个均指向自己。如下图所示:

数据结构与算法初阶4:链表—带头双向循环链表知识讲解_第2张图片 结点初始化示意图

 程序实现如图所示:

数据结构与算法初阶4:链表—带头双向循环链表知识讲解_第3张图片 结点初始化

      初始化链表总体实现代码如下所示,在这里我提供了定义初始化结点函数的方法,一种返回值是为空,一种返回值是LTNode*,一般而言,我们选择第二种,即带返回值的,这样在后面的程序设计过程中,则不需要使用二级指针。

数据结构与算法初阶4:链表—带头双向循环链表知识讲解_第4张图片

整体初始化链表结点程序代码如下: 

//创建结点函数
LTNode*BuyListNode(LTDataType x)
{
	LTNode*node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	node->data = x;
	node->next = NULL;
	node->prev = NULL;
}

//初始化循环链表
//void ListInit(LTNode** pphead)
//{
//	*pphead = BuyListNode(-1);
//	(*pphead)->next = *pphead;
//	(*pphead)->prev = *pphead;
//}
LTNode* ListInit()
{
	LTNode*phead = BuyListNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

3.3 打印结点数据函数

//打印链表
void ListPrint(LTNode*phead)
{
	assert(phead);
	LTNode*cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

3.4 尾插结点

      尾插结点示意图如下所示:当我们需要尾插时,首先需要找到phead头结点prev指向的前一个结点,在这里即tail,找到了以后,然后再开始链接tail和newnode。大家在学习的时候可以对照着代码和示意图理解。

数据结构与算法初阶4:链表—带头双向循环链表知识讲解_第5张图片 待插入结点初始状态 数据结构与算法初阶4:链表—带头双向循环链表知识讲解_第6张图片 插入结点时的状态
//创建结点函数
LTNode*BuyListNode(LTDataType x)
{
	LTNode*node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	node->data = x;
	node->next = NULL;
	node->prev = NULL;
}

//尾插
void ListPushBack(LTNode*phead, LTDataType x)
{
	assert(phead);
	//创建结点
	LTNode*newnode = BuyListNode(x);
	//尾插
	LTNode*tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;

}

3.5 尾删结点

bool ListEmpty(LTNode*phead)
{
	assert(phead);
	//如果为真,则返回true,否则返回false;
	return phead->next == phead;
}

//尾删
void ListPopBack(LTNode*phead)
{
	assert(phead);
	assert(phead->next != phead);
	//需要考虑链表是否为空
	assert(!ListEmpty(phead));

	//如果不为空,执行下面语句
	LTNode*tail = phead->prev;
	LTNode*tailprev = tail->prev;
	free(tail);
	tailprev->next = phead;
	phead->prev = tailprev;

	//利用ListErase函数
	//assert(phead);
	//assert(phead->next != phead);
	需要考虑链表是否为空
	//assert(!ListEmpty(phead));
    //ListErase(phead->prev);
}

3.6 头插结点

      头插结点示意图如下所示:当我们需要头插时,首先需要找到phead头结点next指向的下一个结点,在这里即next,找到了以后,然后再开始链接next和newnode。大家在学习的时候可以对照着代码和示意图理解。

数据结构与算法初阶4:链表—带头双向循环链表知识讲解_第7张图片 待头插结点初始状态 数据结构与算法初阶4:链表—带头双向循环链表知识讲解_第8张图片 头插结点时的状态示意图
//创建结点函数
LTNode*BuyListNode(LTDataType x)
{
	LTNode*node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	node->data = x;
	node->next = NULL;
	node->prev = NULL;
}

//头插
void ListPushFront(LTNode*phead, LTDataType x)
{
	assert(phead);
	//创建结点
	LTNode*newnode = BuyListNode(x);
	LTNode*next = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = next;
	next->prev = newnode;
}

3.7 头删结点

bool ListEmpty(LTNode*phead)
{
	assert(phead);
	//如果为真,则返回true,否则返回false;
	return phead->next == phead;
}

//头删
void ListPopFront(LTNode*phead)
{
	assert(phead);
	assert(phead->next != phead);
	//需要考虑链表是否为空
	assert(!ListEmpty(phead));

	//如果不为空,执行下面语句
	LTNode*next = phead->next;
	LTNode*next_next = next->next;
	free(next);
	next_next->prev = phead;
	phead->next = next_next;

	//利用ListErase函数
	//assert(phead);
	//assert(phead->next != phead);
	需要考虑链表是否为空
	//assert(!ListEmpty(phead));
	//ListErase(phead->next);
}

3.8 链表是否为空判断函数的创建

bool ListEmpty(LTNode*phead)
{
	assert(phead);
	//如果为真,则返回true,否则返回false;
	return phead->next == phead;
}

3.9 在指定位置pos之前插入结点

//创建结点函数
LTNode*BuyListNode(LTDataType x)
{
	LTNode*node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	node->data = x;
	node->next = NULL;
	node->prev = NULL;
}

//在pos位置之前插入
void ListInsert(LTNode*pos, LTDataType x)
{
	assert(pos);
	LTNode*prev = pos->prev;
	//创建结点
	LTNode*newnode = BuyListNode(x);
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;

}

3.10 删除指定位置pos对应的数据

//删除pos位置的数据
void ListErase(LTNode*pos)
{
	assert(pos);
	LTNode*prev = pos->prev;
	LTNode*next = pos->next;
	prev->next = next;
	next->prev = prev;
	free(pos);
}

3.11 返回任意结点位置的函数

bool ListEmpty(LTNode*phead)
{
	assert(phead);
	//如果为真,则返回true,否则返回false;
	return phead->next == phead;
}
//找到并返回某一元素的位置
LTNode* return_pos(LTNode*phead, LTDataType x)
{
	assert(phead);
	assert(phead->next);
	//需要考虑链表是否为空
	assert(!ListEmpty(phead));
	LTNode*cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

4、结语

      在这一讲中,读者们可以结合我提供的代码思路和关于结点插入的示意图进行理解,头删和尾删结点的思路和插入数据有很多相似的地方,读者们可以自己试着画图理解,在这里我就没有一一绘图示意了。最后,数据结构中关于链表的相关知识就讲解结束了,我们知道链表的类型组成有8种组成类型,但只要我们能够把无头单向非循环链表和带头双向循环链表吃透理解了,其他的就不是事了。欢迎大家点赞、支持、收藏!!!

你可能感兴趣的:(数据结构与算法,数据结构,算法,链表,c语言,c++)