C语言带头双向循环链表的示例代码

 更新时间:2022年11月08日 08:40:12   作者:南猿北者  
这篇文章主要介绍了如何利用C语言实现带头双向循环链表,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

前言

对于链表来说,不只有单链表这一个品种;

链表有很多种形态

按方向分:单向、双向

按带不带头:带头、不带头

按循环:循环、不循环

1、单向或则双向:

2、带头或者不带头:

3、循环或者不循环:

组合排列一下的话,链表一共有8种形态!!!

今天我们就来学习一下结构最复杂的带头双向循环链表!!!;

虽然名字听上去比较复杂,但是实现起来比单链表(全名:不带头、不循环、单向链表)更加简单,也不需要过多考虑特殊情况;
 

两种链表的比较:(上面是单链表,下面是带头双向循环链表)

结构分析

首先链表的头节点是不存储有效数据的(该节点被称为哨兵位),其次我们只需要知道改头节点的指针就能找到整个链表,并且便于对整个链表进行维护;

当然既然是双向的嘛,那节点一定有个指针域指向前一个节点,另一个指针域指向后一个节点;

那么我们的单个节点的数据结构就是:

现在我们定义了一个plist指针用来维护整个链表,根据上面说的plist就应该用来存储哨兵位的头节点的指针,那么如何表示链表为NULL的情况?

链表为空:

就是:

head->next=head;

head->prev=head;

链表的基本操作实现

创建节点

ListNode* ListCreate(LTDataType x)
{
	ListNode* NewNode = (ListNode*)malloc(sizeof(ListNode));
	if (!NewNode)
		exit(-1);
	NewNode->val = x;
	NewNode->prev = NULL;
	NewNode->next = NULL;
	return NewNode;
}

我们在创建节点的时候就一起将数据域初始化,方标后续操作;

初始化链表

void InitDummyHead(ListNode* pHead)
{
	assert(pHead);
	pHead->prev = pHead;
	pHead->next = pHead;
}

链表销毁

// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	ListNode* next = NULL;
	while (cur!=pHead)
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
	free(cur);
}

实现思路:

打印链表

除了哨兵位的节点存到是无效数据不打印外,其他节点的数据都要打印:

// 双向链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		ListNode* next = cur->next;
		printf("%d->",cur->val);
		cur = next;
	}
	printf("NULL\n");
}

链表尾插

该链表的尾插,比单链表的尾插简单太多了,不用遍历找尾:

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
    ListNode* NewNode = ListCreate(x);
	ListNode* tail = pHead->prev;
	tail->next = NewNode;
	NewNode->prev = tail;
	pHead->prev = NewNode;
	NewNode->next = pHead;
}

链表尾删

由于是循环的,哨兵位的前一个节点就是尾节点,同时尾节点的前一个节点我们也不用遍历,可以很轻松的拿到:

// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(!is_Empty(pHead));//判空
	ListNode* tail = pHead->prev;
	ListNode* prev = tail->prev;
	ListNode* next = pHead;
	free(tail);
	prev->next = next;
	next->prev = prev;
}

链表头插

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* prev = pHead;
	ListNode* cur = pHead->next;
	ListNode* NewNode = ListCreate(x);
	prev->next = NewNode;
	NewNode->prev = prev;
	NewNode->next = cur;
	cur->prev = NewNode;
}

链表头删

// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(!is_Empty(pHead));//判空
	ListNode* prev = pHead;
	ListNode* cur = pHead->next;
	ListNode* next = cur->next;
	free(cur);
	prev->next = next;
	next->prev = prev;
}

链表查找

// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	assert(!is_Empty(pHead));//表都为NULL了,就没办法找了
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		if (cur->val == x)
			return cur;
		else
			cur = cur->next;
	}
	return NULL;
}

链表pos位置前面去插入

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);//pos不能为NULL,由于参数限制我们无法对pos判断是否为哨兵位头节点,因此我们假设pos传的都是合法指针和NULL
	ListNode* NewNode = ListCreate(x);
	ListNode* prev = pos->prev;
	NewNode->next = pos;
	pos->prev = NewNode;
	prev->next = NewNode;
	NewNode->prev = prev;
}

删除pos位置

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);//由于参数限制,我们无法判断表是否为NULL;
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	free(pos);
	prev->next = next;
	next->prev = prev;
}

链表判空

//判断链表是否为NULL
bool is_Empty(ListNode* pHead)
{
	assert(pHead);
	return pHead == pHead->prev;
}

代码复用

我们上面既然实现了在pos位置之前插入和删除pos位置的数据;

那么:

ListInsert(plist,x);//相当于尾插
ListInsert(plist->next, x);//相当于头插
ListErase(plist->next);//相当于头删
ListErase(plist->prev);//相当于尾删;

那么实际上我们只要实现ListInsertListErase这两个接口就能快速实现出带头双向循环链表了;

总代码及头文件

头文件的包含:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType val;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

// 创建返回链表的头结点.
ListNode* ListCreate(LTDataType x);
//初始化哨兵位的头节点;
void InitDummyHead(ListNode* pHead);
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
//判断链表是否为NULL
bool is_Empty(ListNode* pHead);

功能代码实现:

#include"DList.h"
ListNode* ListCreate(LTDataType x)
{
	ListNode* NewNode = (ListNode*)malloc(sizeof(ListNode));
	if (!NewNode)
		exit(-1);
	NewNode->val = x;
	NewNode->prev = NULL;
	NewNode->next = NULL;
	return NewNode;
}
void InitDummyHead(ListNode* pHead)
{
	assert(pHead);
	pHead->prev = pHead;
	pHead->next = pHead;
}
// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	ListNode* next = NULL;
	while (cur!=pHead)
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
	free(cur);
}
// 双向链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		ListNode* next = cur->next;
		printf("%d->",cur->val);
		cur = next;
	}
	printf("NULL\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	/*ListNode* NewNode = ListCreate(x);
	ListNode* tail = pHead->prev;
	tail->next = NewNode;
	NewNode->prev = tail;
	pHead->prev = NewNode;
	NewNode->next = pHead;*/
	ListInsert(pHead,x);//函数复用
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(!is_Empty(pHead));//判空
	/*ListNode* tail = pHead->prev;
	ListNode* prev = tail->prev;
	ListNode* next = pHead;
	free(tail);
	prev->next = next;
	next->prev = prev;*/
	ListErase(pHead->prev);//函数复用
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	/*ListNode* prev = pHead;
	ListNode* cur = pHead->next;
	ListNode* NewNode = ListCreate(x);
	prev->next = NewNode;
	NewNode->prev = prev;
	NewNode->next = cur;
	cur->prev = NewNode;*/
	ListInsert(pHead->next,x);//函数复用
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(!is_Empty(pHead));//判空
	/*ListNode* prev = pHead;
	ListNode* cur = pHead->next;
	ListNode* next = cur->next;
	free(cur);
	prev->next = next;
	next->prev = prev;*/
	ListErase(pHead->next);//函数复用
}
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	assert(!is_Empty(pHead));//表都为NULL了,就没办法找了
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		if (cur->val == x)
			return cur;
		else
			cur = cur->next;
	}
	return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);//pos不能为NULL,由于参数限制我们无法对pos判断是否为哨兵位头节点,因此我们假设pos传的都是合法指针和NULL
	ListNode* NewNode = ListCreate(x);
	ListNode* prev = pos->prev;
	NewNode->next = pos;
	pos->prev = NewNode;
	prev->next = NewNode;
	NewNode->prev = prev;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);//由于参数限制,我们无法判断表是否为NULL;
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	free(pos);
	prev->next = next;
	next->prev = prev;
}
//判断链表是否为NULL
bool is_Empty(ListNode* pHead)
{
	assert(pHead);
	return pHead == pHead->prev;
}

以上就是C语言带头双向循环链表的示例代码的详细内容,更多关于C语言带头双向循环链表的资料请关注脚本之家其它相关文章!

相关文章

  • C++隐式转换问题分析及解决办法

    C++隐式转换问题分析及解决办法

    在本篇文章里小编给大家整理了关于C++隐式转换问题分析及解决办法,有需要的朋友们可以学习下。
    2020-02-02
  • QString使用正则操作的接口实现

    QString使用正则操作的接口实现

    这篇文章主要介绍了QString使用正则操作的接口实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • C语言实现销售管理系统课程设计

    C语言实现销售管理系统课程设计

    这篇文章主要为大家详细介绍了C语言实现销售管理系统课程设计,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • 深入理解C++的动态绑定与静态绑定的应用详解

    深入理解C++的动态绑定与静态绑定的应用详解

    本篇文章是对C++中的动态绑定与静态绑定进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • 详解Matlab如何绘制小提琴图

    详解Matlab如何绘制小提琴图

    小提琴图 (Violin Plot)是用来展示多组数据的分布状态以及概率密度。这种图表结合了箱形图和密度图的特征,主要用来显示数据的分布形状。本文将介绍如何利用Matlab绘制小提琴图,需要的可以参考一下
    2022-02-02
  • C语言模拟实现扫雷游戏

    C语言模拟实现扫雷游戏

    这篇文章主要为大家详细介绍了C语言模拟实现扫雷游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-01-01
  • C语言深入浅出讲解直接插入排序算法的实现

    C语言深入浅出讲解直接插入排序算法的实现

    插入排序也是最简单的一类排序方法,我今天介绍的也是插入排序里最直观且浅显易懂的直接插入排序。对这个很简单的排序,记得当时也是花了近两个晚上才搞懂它的原理的,接下来就来介绍一下
    2022-05-05
  • C语言数据结构之单链表存储详解

    C语言数据结构之单链表存储详解

    链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。本文将和大家一起聊聊C语言中单链表的存储,感兴趣的可以学习一下
    2022-07-07
  • C语言的动态内存分配及动态内存分配函数详解

    C语言的动态内存分配及动态内存分配函数详解

    这篇文章主要为大家详细介绍了C语言的动态内存分配及动态内存分配函数,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • C语言开发实现井字棋及电脑落子优化示例详解

    C语言开发实现井字棋及电脑落子优化示例详解

    以前上课经常和同桌玩起井字棋,那么我们就当我们回忆童年,现在也用C语言来实现井字棋,本次代码相对于初阶的井字棋,在电脑下棋代码部分做了优化,使得电脑更加具有威胁
    2021-11-11

最新评论