数据结构-带头双向循环链表(增删查改详解)

在上一篇博客中,详细介绍了单链表的增删查改,虽然单链表的结构简单,但是用起来却不是那么顺手。因此根据单链表的种种缺点,这篇博客所介绍的带头双向循环链表将会带来极大的优化。

上图就是带头双向循环链表的主要结构了,很多同学看到这个结构就想知难而退了,但是只要耐心深究,就会发现除了结构比单链表复杂以外,它的实现反而简单了!!!

目录

1 链表的结点结构

2 链表初始化

3 链表打印

4  链表的增删查改

4.1 链表尾插

4.2 链表尾删

4.3 链表头插

4.4 链表头删

4.4 查找链表中的值

4.5  在对应位置前面插入

4.6 删除对应位置的结点

5 链表销毁

6 完整代码及运行结果


1 链表的结点结构

typedef struct DListNode
{
    struct DListNode* next;
    struct DListNode* prev;
    DLTDataType val;
}DLTNode;

细心的同学肯定会发现,这次链表中的结构相比于单链表多了一个prev的结构体指针,有了它的  存在就能实现双向的功能了!

2 链表初始化

在初始化链表前我们需要创建一个哨兵卫的头结点,要创建结点,那依旧少不了结点创建函数。

DLTNode* BuyListNode(DLTDataType x)
{
	DLTNode* newnode = (DLTNode*)malloc(sizeof(DLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->val = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

有了结点创建函数,我们就可以开始对链表进行初始化了。

DLTNode* DLTInit()
{
	DLTNode* phead = BuyListNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

这里要注意,因为我们的链表是循环的,所以链表最后是不指向空的,而是要循环回哨兵卫的头结点! 我们通过调试,也可以很清楚的看到他们的地址都是相同的。

3 链表打印

void DLTPrint(DLTNode* phead)
{
	assert(phead);

	DLTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

打印链表非常简单,唯一需要注意的是,遍历结束的标志就是遍历的指针回到了哨兵卫的头结点!

4  链表的增删查改

4.1 链表尾插

void DLTPushBack(DLTNode* phead, DLTDataType x)
{
	//因为带哨兵卫的头结点,链表不可能为空,若为空就出错了
	assert(phead);

	DLTNode* newnode = BuyListNode(x);
	//找到尾结点
	DLTNode* next = phead->prev;
	
	newnode->next = phead;
	phead->prev = newnode;
	next->next = newnode;
	newnode->prev = next;
}

在开始对链表进行增删查改的时候,双向带头循环链表的优势就开始体现出来了,就像这次的尾插,我们不需要一个结点一个结点的遍历过去,通过哨兵卫的上一个结点就能马上找到尾结点,然后在尾结点和哨兵卫之间直接插入就完事了!

4.2 链表尾删

void DLTPopBack(DLTNode* phead)
{
	assert(phead);
	//哨兵卫头结点不能删除
	assert(phead->next != phead);

	//找到尾结点和尾结点的前一个结点
	DLTNode* next = phead->prev;
	DLTNode* nextPrev = next->prev;

	phead->prev = nextPrev;
	nextPrev->next = phead;
	free(next);
}

尾删也变得更加方便,依旧通过哨兵卫的上一个结点就能快速找到尾结点。

4.3 链表头插

void DLTPushFront(DLTNode* phead, DLTDataType x)
{
	assert(phead);

	DLTNode* newnode = BuyListNode(x);
	DLTNode* cur = phead->next;

	newnode->next = cur;
	cur->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
}

双向带头循环链表的头插相比于单链表的头插也更为方便,由于哨兵卫的存在,我们只是改变的是结构体的内部,但是在单链表中却是要改变链表的头,就需要传二级指针或者需要改变返回值。

4.4 链表头删

void DLTPopFront(DLTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	DLTNode* first = phead->next;
	DLTNode* second = first->next;
	phead->next = second;
	second->prev = phead;
	free(first);
}

4.4 查找链表中的值

DLTNode* DLTFind(DLTNode* phead, DLTDataType x)
{
	assert(phead);
	DLTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

4.5  在对应位置前面插入

void DLTInsert(DLTNode* pos, DLTDataType x)
{
	assert(pos);

	DLTNode* newnode = BuyListNode(x);
	DLTNode* posPrev = pos->prev;
	newnode->next = pos;
	pos->prev = newnode;
	posPrev->next = newnode;
	newnode->prev = posPrev;
}

在单链表中,我们要在pos位置前插入结点,那必须遍历找到pos位置的前一个结点,而且还要重点考虑会不会是头插这种情况。在这里,我们就完全不需要考虑这么多,通过prev指针,可以很轻松的实现这个功能。 

4.6 删除对应位置的结点

void DLTErase(DLTNode* pos)
{
	assert(pos);

	DLTNode* posPrev = pos->prev;
	DLTNode* posNext = pos->next;
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

5 链表销毁

void DLTDestroy(DLTNode* phead)
{
	assert(phead);
	DLTNode* cur = phead->next;
	while (cur != phead)
	{
		DLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

对于链表的销毁,我们依旧是要一步步来,要注意链表和顺序表不同,是不能直接free的! 

6 完整代码及运行结果

//DList.h


#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int DLTDataType;

typedef struct DListNode
{
	struct DListNode* next;
	struct DListNode* prev;
	DLTDataType val;
}DLTNode;

DLTNode* BuyListNode(DLTDataType x);
//初始化
DLTNode* DLTInit();

//打印
void DLTPrint(DLTNode* phead);

//尾插
void DLTPushBack(DLTNode* phead, DLTDataType x);
//尾删
void DLTPopBack(DLTNode* phead);

//头插
void DLTPushFront(DLTNode* phead, DLTDataType x);
//头删
void DLTPopFront(DLTNode* phead);

//查找
DLTNode* DLTFind(DLTNode* phead, DLTDataType x);

//在pos位置前插入
void DLTInsert(DLTNode* pos, DLTDataType x);

//删除pos位置的结点
void DLTErase(DLTNode* pos);

//链表销毁
void DLTDestroy(DLTNode* phead);
//DList.c


#define _CRT_SECURE_NO_WARNINGS 1
#include"DList.h"

DLTNode* BuyListNode(DLTDataType x)
{
	DLTNode* newnode = (DLTNode*)malloc(sizeof(DLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->val = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}


//初始化
DLTNode* DLTInit()
{
	DLTNode* phead = BuyListNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

//打印
void DLTPrint(DLTNode* phead)
{
	assert(phead);

	DLTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

//尾插
void DLTPushBack(DLTNode* phead, DLTDataType x)
{
	//因为带哨兵卫的头结点,链表不可能为空,若为空就出错了
	assert(phead);

	DLTNode* newnode = BuyListNode(x);
	//找到尾结点
	DLTNode* next = phead->prev;
	
	newnode->next = phead;
	phead->prev = newnode;
	next->next = newnode;
	newnode->prev = next;
}

//尾删
void DLTPopBack(DLTNode* phead)
{
	assert(phead);
	//哨兵卫头结点不能删除
	assert(phead->next != phead);

	//找到尾结点和尾结点的前一个结点
	DLTNode* next = phead->prev;
	DLTNode* nextPrev = next->prev;

	phead->prev = nextPrev;
	nextPrev->next = phead;
	free(next);
}

//头插
void DLTPushFront(DLTNode* phead, DLTDataType x)
{
	assert(phead);

	DLTNode* newnode = BuyListNode(x);
	DLTNode* cur = phead->next;

	newnode->next = cur;
	cur->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
	//DLTInsert(phead->next, x); //复用
}
//头删
void DLTPopFront(DLTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	DLTNode* first = phead->next;
	DLTNode* second = first->next;
	phead->next = second;
	second->prev = phead;
	free(first);
}

//查找
DLTNode* DLTFind(DLTNode* phead, DLTDataType x)
{
	assert(phead);
	DLTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

//在pos位置前插入-- 注意pos位置不能是哨兵卫的头结点 C++中会改善这一点
void DLTInsert(DLTNode* pos, DLTDataType x)
{
	assert(pos);

	DLTNode* newnode = BuyListNode(x);
	DLTNode* posPrev = pos->prev;
	newnode->next = pos;
	pos->prev = newnode;
	posPrev->next = newnode;
	newnode->prev = posPrev;
}

//删除pos位置的结点-- 注意pos位置不能是哨兵卫的头结点 C++中会改善这一点
void DLTErase(DLTNode* pos)
{
	assert(pos);

	DLTNode* posPrev = pos->prev;
	DLTNode* posNext = pos->next;
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

//链表销毁
void DLTDestroy(DLTNode* phead)
{
	assert(phead);
	DLTNode* cur = phead->next;
	while (cur != phead)
	{
		DLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值