C语言超详细讲解双向带头循环链表

 更新时间:2023年02月14日 14:04:28   作者:[Pokemon]大猫猫  
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单

在上一篇所讲述的单链表中,存在一些缺陷:

1、在进行尾插和尾删时,需要遍历链表找到尾结点

2、在进行中间插入和删除时,也需要先遍历链表找到前一个结点

对于这些缺陷,可以采用一种结构更为复杂的链表 双向带头循环链表

双向带头循环链表结构虽然复杂,但在链表的操作上带来了很大的优势

一、双向带头循环链表的结构

//存储数据的类型,这里以 int 来举例
typedef int LTDataType;
//结点的类型
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;
	struct ListNode* next;
}LTNode;

二、双向带头循环链表的函数接口

1. 申请结点

在插入等操作时需要申请结点,为了避免麻烦重复的操作,这里将申请结点封装为一个函数

申请结点函数如下:

LTNode* BuyLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		//开辟空间失败,打印错误信息
		perror("malloc");

		//结束程序
		exit(-1);
	}
	newnode->data = x;
	newnode->prev = newnode->next = NULL;
	return newnode;
}

2. 初识化

在双向带头循环链表中,即使没有存储数据也 至少会包含一个哨兵位的头结点

初始化函数如下:

LTNode* InitLT()
{
	//申请头结点,头结点的数据存什么无关紧要
	LTNode* phead = BuyLTNode(-1);
	//改变指针指向,构成循环
	phead->prev = phead->next = phead;
	return phead;
}

3. 打印

为了验证插入、删除等得到的结果是否正确,提供打印函数,这里数据类型以 int 为例,当读者采用的类型不同时,自行更改函数即可

打印函数如下:

void LTPrint(LTNode* phead)
{
	//链表不能为空
	assert(phead);
	LTNode* cur = phead->next;
	printf("head->");
	while (cur != phead)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("head\n");
}

4. 尾插尾删

尾插:在链表的最后一个结点之后插入结点

尾插函数如下:

void LTPushBack(LTNode* phead, LTDataType x)
{
	//链表不能为空
	assert(phead);
	LTNode* newnode = BuyLTNode(x);
	//找到尾结点
	LTNode* tail = phead->prev;
	//改变指针指向
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

尾删:删除链表最后一个结点

尾删函数如下:

void LTPopBack(LTNode* phead)
{
	assert(phead);	//链表不能为空
	assert(phead->next != phead);	//空链表不能删
	//找尾结点及尾结点的前一个结点
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	//改变指针指向
	tailPrev->next = phead;
	phead->prev = tailPrev;
	free(tail);
}

5. 头插头删

头插: 在第一个结点之前插入新结点

头插函数如下:

void LTPushFront(LTNode* phead, LTDataType x)
{
	//链表不能为空
	assert(phead);
	LTNode* newnode = BuyLTNode(x);
	//找到头结点后的第一个结点
	LTNode* first = phead->next;
	//改变指针指向
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;
}

头删:删除链表的第一个结点

头删函数如下:

void LTPopFront(LTNode* phead)
{
	assert(phead);	//链表不能为空
	assert(phead->next != phead);	//空链表不能删
	//找到头结点后的第一个和第二个结点
	LTNode* first = phead->next;
	LTNode* second = first->next;
	//改变指针指向
	phead->next = second;
	second->prev = phead;
	free(first);
}

6. 查找

查找:如果数据存在,返回该数据结点的指针,不存在返回 NULL

查找函数如下:

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	//链表不能为空
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x) return cur;
		cur = cur->next;
	}
	return NULL;
}

7. 中间插入和删除

中间插入:通过查找函数 LTFind 获得指向结点的指针 pos,在 pos 指向的 结点之前 插入结点

在 pos 之前插入结点函数如下:

void LTInsert(LTNode* pos, LTDataType x)
{
	//pos 不能为空
	assert(pos);
	LTNode* newnode = BuyLTNode(x);
	//找到 pos 的前一个结点
	LTNode* posPrev = pos->prev;
	//改变指针指向
	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}

在调用中间插入函数 LTInsert 时

  • 如果在链表头结点之前插入数据,便和尾插函数的功能一样
  • 如果在链表头结点之后插入数据,便和头插函数的功能一样

因此在尾插和头插函数的实现中可以直接调用中间插入函数 LTInsert

尾插和头插函数更改如下:

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	//链表不能为空
	assert(phead);
	LTInsert(phead, x);
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	//链表不能为空
	assert(phead);
	LTInsert(phead->next, x);
}

中间删除:通过查找函数 LTFind 获得指向结点的指针 pos,删除 pos 指向的结点

删除 pos 指向的结点函数如下:

void LTErase(LTNode* pos)
{
	//pos 不能为空
	assert(pos);
	//找到 pos 的前一个和后一个结点
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	//改变指针指向
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

在调用中间删除函数 LTErase 时

  • 如果删除链表头结点的前一个结点,便和尾删函数的功能一样
  • 如果删除链表头结点的后一个结点,便和头删函数的功能一样

因此在尾删和头删函数的实现中可以直接调用中间删除函数 LTErase

尾删和头删函数更改如下:

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);	//链表不能为空
	assert(phead->next != phead);	//空链表不能删
	LTErase(phead->prev);
}
//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);	//链表不能为空
	assert(phead->next != phead);	//空链表不能删
	LTErase(phead->next);
}

8. 判空及求链表长度

判空:判断链表是否为空

判空函数如下:

bool LTEmpty(LTNode* phead)
{
	//链表不能为空
	assert(phead);
	return phead->next == phead;
}

链表长度:链表有效数据个数

链表长度函数如下:

size_t LTSize(LTNode* phead)
{
	//链表不能为空
	assert(phead);
	size_t size = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

9. 销毁单链表

在链表中,存储数据的结点是由自己开辟的,当不使用链表时,应将其销毁

销毁链表函数如下:

void LTDestroy(LTNode* phead)
{
	//链表不能为空
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* curNext = cur->next;
		free(cur);
		cur = curNext;
	}
	free(phead);
}

到此这篇关于C语言超详细讲解双向带头循环链表的文章就介绍到这了,更多相关C语言双向带头循环链表内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • wchar_t,char,string,wstring之间的相互转换

    wchar_t,char,string,wstring之间的相互转换

    以下是对wchar_t,char,string,wstring之间的相互转换进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2013-09-09
  • 浅析内存对齐与ANSI C中struct型数据的内存布局

    浅析内存对齐与ANSI C中struct型数据的内存布局

    当在C中定义了一个结构类型时,它的大小是否等于各字段(field)大小之和?编译器将如何在内存中放置这些字段?ANSI C对结构体的内存布局有什么要求?而我们的程序又能否依赖这种布局
    2013-09-09
  • C++文件输入输出fstream使用方法

    C++文件输入输出fstream使用方法

    C++标准库提供了<fstream>头文件,其中包含了用于文件输入输出的相关类和函数,本文将详细介绍<fstream>头文件的使用方法,包括函数原型、打开文件、读取和写入文件、以及错误处理等注意事项,感兴趣的朋友跟随小编一起看看吧
    2023-10-10
  • C++中vector迭代器失效问题详解

    C++中vector迭代器失效问题详解

    vector是向量类型,它可以容纳许多类型的数据,如若干个整数,所以称其为容器,这篇文章主要给大家介绍了关于C++中vector迭代器失效问题的相关资料,需要的朋友可以参考下
    2021-11-11
  • C++算法之海量数据处理方法的总结分析

    C++算法之海量数据处理方法的总结分析

    本篇文章是对海量数据处理方法进行了详细的总结与分析,需要的朋友参考下
    2013-05-05
  • 详解c++11新特性之模板的改进

    详解c++11新特性之模板的改进

    这篇文章主要介绍了详解c++11新特性之模板的改进,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • c++中std::hash以及万能hash的使用方式

    c++中std::hash以及万能hash的使用方式

    这篇文章主要介绍了c++中std::hash以及万能hash的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • opencv学习笔记C++绘制灰度直方图

    opencv学习笔记C++绘制灰度直方图

    这篇文章主要为大家介绍了opencv学习笔记C++绘制灰度直方图的实现代码,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • Qt重写QTreeView自绘实现酷炫样式

    Qt重写QTreeView自绘实现酷炫样式

    QTreeView,顾名思义,就是一种树形的控件,在我们需要做类似于文件列表的视图时,是一个不错的选择,下面我们就来看看qt如何重写QTreeView实现酷炫样式,感兴趣的可以了解一下
    2023-08-08
  • C++实现LeetCode(35.搜索插入位置)

    C++实现LeetCode(35.搜索插入位置)

    这篇文章主要介绍了C++实现LeetCode(35.搜索插入位置),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07

最新评论