【C实现数据结构】双向带头循环链表的实现(增删查改,判空,销毁)


关于带表头的单链表的实现->带表头的单链表

一、双向(带头)循环链表的作用

典型的面恶心善的数据结构
乍一看,似乎结构在几种链表里最复杂,然而其实际的实现并没有困难到哪里去,反而因为其优秀的双向循环结构可以支持 O(1) 时间复杂度的情况下找到前驱结点,双向链表在多数情况下的插入、删除等操作都要比单链表简单、高效。

单独存储数据相较于其他链表也是更优的选择
222

二、基本的实现

1.基本的函数声明(DList.h)

基本的增删查改,与创建与摧毁链表

  • 增【头插,尾插(可用更普遍的前插,后插来代替)】
  • 删【头删,尾删,根据数据删除】
  • 查(配合插入使用)
  • 改 (修改节点存储数据,本处简写)
  • 创建表头节点 不存储数据
  • 摧毁 释放整条链表(包括表头),防止内存泄漏
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<assert.h>

//本链表为双向带头链表
#pragma once
typedef int LTDataType;//存储的数据类型,方便统一修改
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

// 创建返回链表的头结点.
ListNode* ListCreate();
//创建节点
ListNode* BuyNode(LTDataType x);
// 双向链表销毁
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 ListErase(ListNode* pos);
//看链表除了表头是否为空
bool IsEmpty(ListNode* pHead);
// 双向链表在pos的前面进行插入
void ListInsertFront(ListNode* pos, LTDataType x);
//后插
void ListInsertBack(ListNode* pos, LTDataType x);
//修改
void ListModify(ListNode* pos, LTDataType x);

2.表头/节点的创建

两函数并无本质区别,但表头不用于存储数据,所以加以区别
另外,创建循环链表的节点时切记将新节点的next prev都指向自身,作为最小的循环单元参与程序以避免意想不到的NULL指针

ListNode* ListCreate() {
	ListNode* Head=(ListNode*)malloc(sizeof(ListNode));
	if (Head == NULL)
	{
		perror("malloc error:");
			exit(-1);
	}
	Head->next = Head;//形成循环
	Head->prev = Head;

	return Head;
}

ListNode* BuyNode(LTDataType x) {//创建节点
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		perror("malloc error:");
		exit(-1);
	}
	node->next = node;//形成循环
	node->prev = node;
	node->data = x;
	return node;
}

3.判断链表是否为空

实质在判断链表中是否存在两个及以上节点(包括表头)
无论传址是否为表头都不影响判断

bool IsEmpty(ListNode* pHead) {
	assert(pHead);
	ListNode* cur = pHead->next;
	if (cur != pHead)
		return false;
	else
		return true;
}

更加简便的写法:

bool IsEmpty(ListNode* pHead) {
assert(pHead);
return pHead->next==pHead;
}

4.插入(头插 尾插 前插 后插)

表头的存在为插入的前提判断带来了方便。尾插头插不用考虑链表为空时的特殊情况。
头插,尾插只是插入的特殊情况,往往都可以使用普遍的插入函数来代替实现;写在此处只是为了方便更好的理解

  • 头插
void ListPushFront(ListNode* pHead, LTDataType x) {//头插
	assert(pHead); //若不是有表头的双向链表 则可以不用断言 但此时尾插头插代码里必须加入判断链表是否为空 进行分支执行
	ListNode* newnode = BuyNode(x);
	pHead->next->prev = newnode;
	newnode->next = pHead->next;//具体代码实现
	newnode->prev = pHead;
	pHead->next = newnode;
	
	//ListInsertBack(pHead,x);//利用后插实现
	//ListInsertFront(pHead->next, x);//利用前插实现
}
  • 尾插
void ListPushBack(ListNode* pHead, LTDataType x) {//尾插
	assert(pHead);                          
	ListNode* newnode = BuyNode(x);//注释同头插
	pHead->prev->next = newnode;//直接使用前节点
	newnode->prev = pHead->prev;//具体代码实现
	newnode->next = pHead;
	pHead->prev = newnode;

	//ListInsertFront(pHead,x);//利用前插实现尾插
	//ListInsertBack(pHead->prev, x);//后插实现尾插
}
  • 前插
void ListInsertFront(ListNode* pos, LTDataType x) {//插到前面
	assert(pos);
	ListNode* newnode = BuyNode(x);
	pos->prev->next = newnode;
	newnode->prev = pos->prev;
	newnode->next = pos;
	pos->prev = newnode;
}
  • 尾插
void ListInsertBack(ListNode* pos, LTDataType x) {
	assert(pos);
	ListNode* newnode = BuyNode(x);
	pos->next->prev = newnode;
	newnode->next = pos->next;
	pos->next = newnode;
	newnode->prev = pos;
}

5.删除(头删 尾删 普遍的删除)

  • 头删
void ListPopFront(ListNode* pHead) {
	assert(pHead);
	if (IsEmpty(pHead))
		return;
	else
	{
		ListNode* front = pHead->next->next;
		free(pHead->next);//同尾删
		pHead->next = front;
		front->prev = pHead;
	}
	//ListErase(pHead->next);
}
  • 尾删
void ListPopBack(ListNode* pHead){//尾删
	assert(pHead);
	if (IsEmpty(pHead))//空链表 不再删除
		return;
	else
	{
		ListNode* end = pHead->prev->prev;
		end->next = pHead;
		free(pHead->prev);//释放该指针指向的地址,该指针任然存在
		pHead->prev = end;
	}
	//ListErase(pHead->prev);	
}
  • 通用的删除
void ListErase(ListNode* pos) {
	assert(pos);
	if (IsEmpty(pos))//pos为表头时 不执行;非表头时,不可能为空; 加上是为了可以替换尾删 头删
		return;
	else 
	{
		ListNode* prev0 = pos->prev;
		ListNode* next0 = pos->next;
		prev0->next = next0;
		next0->prev = prev0;
		free(pos);
	}
}

6.查找与修改

  • 查找
ListNode* ListFind(ListNode* pHead, LTDataType x) {//最简单形式,只找到第一个相同数据的节点
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		if (cur->data == x)
			return cur;
		cur = cur -> next;
	}
	printf("未查询到该数据:%d", x);
	return NULL;
}
  • 修改(实际使用往往不仅仅存储一个数据,而是用结构体存储一类数据,此处为简写
//void ListModify(ListNode* pos, LTDataType x) {//修改节点数据
//	assert(pos);
//	pos->data = x;
//}

7.销毁链表(手动释放内存)

在某一结构确定不再使用时,手动释放内存极为重要。 虽然在程序结束后系统会自动释放内存,但在如服务器等长期运行的环境中,程序往往不会或很久才会停止一次,此时未进行手动的释放内存很容易导致严重的内存泄漏

void ListDestory(ListNode* pHead) {
	assert(pHead);
	
		ListNode* cur = pHead->next;
		ListNode* f=NULL ;
		while (cur != pHead)
		{
			f = cur;
			cur = cur->next;
			free(f);
		}

		free(pHead);//不要忘记释放表头 如链表为空,cur一开始就==pHead
}

三、源码汇总

#include"DList.h"

//本链表为双向带头链表

ListNode* ListCreate() {
	ListNode* Head=(ListNode*)malloc(sizeof(ListNode));
	if (Head == NULL)
	{
		perror("malloc error:");
			exit(-1);
	}
	Head->next = Head;//形成循环
	Head->prev = Head;

	return Head;
}

ListNode* BuyNode(LTDataType x) {//创建节点
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		perror("malloc error:");
		exit(-1);
	}
	node->next = node;//形成循环
	node->prev = node;
	node->data = x;
	return node;
}

bool IsEmpty(ListNode* pHead) {
	assert(pHead);
	ListNode* cur = pHead->next;
	if (cur != pHead)
		return false;
	else
		return true;
}

void ListPrint(ListNode* pHead)
{
	assert(pHead);
	printf(" >= Head =>");
	ListNode* cur=pHead->next;
	while (cur != pHead)
	{
		printf("%d =>", cur->data);//如果LTDataType 改变,printf输出时打印的数据类型也要改变
		cur = cur->next;
	}
	printf("\n");

}
void ListPushBack(ListNode* pHead, LTDataType x) {//尾插(默认有表头,或头结点不为NULL)
	assert(pHead);                           //若不是有表头的双向链表 则可以不用断言 但尾插头插代码里必须加入判断链表是否为空 进行分支执行
	ListNode* newnode = BuyNode(x);
	pHead->prev->next = newnode;//直接使用前节点
	newnode->prev = pHead->prev;//具体代码实现
	newnode->next = pHead;
	pHead->prev = newnode;

	//ListInsertFront(pHead,x);//利用前插实现尾插
	//ListInsertBack(pHead->prev, x);//后插实现尾插
}

void ListPopBack(ListNode* pHead){//尾删
	assert(pHead);
	if (IsEmpty(pHead))//空链表 不再删除
		return;
	else
	{
		ListNode* end = pHead->prev->prev;
		end->next = pHead;
		free(pHead->prev);//释放该指针指向的地址,该指针任然存在
		pHead->prev = end;
	}

	//ListErase(pHead->prev);
	
}

void ListPushFront(ListNode* pHead, LTDataType x) {//头插(默认有表头,或头结点不为NULL)
	assert(pHead);//注释同尾插
	ListNode* newnode = BuyNode(x);
	pHead->next->prev = newnode;
	newnode->next = pHead->next;//具体代码实现
	newnode->prev = pHead;
	pHead->next = newnode;
	
	//ListInsertBack(pHead,x);//利用后插实现
	//ListInsertFront(pHead->next, x);//利用前插实现
}

void ListPopFront(ListNode* pHead) {
	assert(pHead);
	if (IsEmpty(pHead))
		return;
	else
	{
		ListNode* front = pHead->next->next;
		free(pHead->next);//同尾删
		pHead->next = front;
		front->prev = pHead;
	}

	//ListErase(pHead->next);
}

ListNode* ListFind(ListNode* pHead, LTDataType x) {//最简单形式,只找到第一个相同数据的节点
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		if (cur->data == x)
			return cur;
		cur = cur -> next;
	}
	printf("未查询到该数据:%d", x);
	return NULL;
}

void ListInsertFront(ListNode* pos, LTDataType x) {//插到前面
	assert(pos);
	ListNode* newnode = BuyNode(x);
	pos->prev->next = newnode;
	newnode->prev = pos->prev;
	newnode->next = pos;
	pos->prev = newnode;
}

void ListInsertBack(ListNode* pos, LTDataType x) {
	assert(pos);
	ListNode* newnode = BuyNode(x);
	pos->next->prev = newnode;
	newnode->next = pos->next;
	pos->next = newnode;
	newnode->prev = pos;
}

void ListErase(ListNode* pos) {
	assert(pos);
	if (IsEmpty(pos))//pos为表头时 不执行;非表头时,不可能为空; 加上是为了可以替换尾删 头删
		return;
	else 
	{
		ListNode* prev0 = pos->prev;
		ListNode* next0 = pos->next;
		prev0->next = next0;
		next0->prev = prev0;
		free(pos);
	}
}

//void ListModify(ListNode* pos, LTDataType x) {//修改节点数据
//	assert(pos);
//	pos->data = x;
//}

void ListDestory(ListNode* pHead) {
	assert(pHead);
	
		ListNode* cur = pHead->next;
		ListNode* f=NULL ;
		while (cur != pHead)
		{
			f = cur;
			cur = cur->next;
			free(f);
		}

		free(pHead);//不要忘记释放表头 如链表为空,cur一开始就==pHead
}

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值