【数据结构与算法】双向带头循环链表(附源码)

本文介绍了双向带头循环链表的结构,包括头节点、前驱后继指针以及循环特性。文章详细阐述了链表的初始化、插入(头插、尾插、任意位置插入)、删除(头删、尾删、任意位置删除)等操作的接口实现,并提供了源码实现。双链表简化了插入和删除操作,无需寻找前驱或后继节点。
摘要由CSDN通过智能技术生成

 

目录

一.前言

二.双向带头循环链表的结构

三.接口实现

A.初始化 ListNodeinit 和销毁 Listdestroy

1.ListNodeinit

2.Listdestroy

B.插入

1.头插  ListNodepushfront

2.尾插 ListNodepushback

3.插入 ListNodeinsert

C.删除

1.头删 ListNodepopfront

2.尾删 ListNodepopback

3.删除 ListNodeerase

D.打印  ListNodeprint

四.源码

List.h

List.c

test.c


一.前言

在前面的博客中,我们学习了顺序表和结构最简单的链表——单链表,但是单链表存在在着一些不足,比如单链表的插入和删除的操作,总是要找到指定节点的前驱或是后继,这样就会比较麻烦。

那么本篇文章所讲述的双向带头循环链表(以后简称双链表),就可以很好解决这个问题。

二.双向带头循环链表的结构

1.该链表有一个哨兵位节点,即头节点

2.每个节点都包含一个prev 指针和 next 指针,分别指向当前节点的前驱和后继;

3.头节点的 prev 指向的是尾节点,尾节点的next 指向的是头节点,这样就实现了循环

请看图示:

 别看结构这么复杂,但其实它是一个很厉害的结构,代码实现会很简单。

三.接口实现

A.初始化 ListNodeinit 和销毁 Listdestroy

1.ListNodeinit

这个接口用来创建哨兵位头节点,并使该节点的 prev 和 next 都指向自己以实现循环的目的。

LNode* Buynewnode(DLdatatype x)   //定义一个 malloc 新节点的函数,以便后续需要
{
	LNode* newnode = (LNode*)malloc(sizeof(LNode));
	assert(newnode);
	newnode->prev = NULL;
	newnode->next = NULL;
	newnode->data = x;
	return newnode;
}
LNode* ListNodeinit()
{
	LNode* phead = (LNode*)malloc(sizeof(LNode));
	assert(phead);
	phead->prev = phead;  //使其都指向自己
	phead->next = phead;
	phead->data = -1;
	return phead;
}

2.Listdestroy

我们需要遍历链表,并定义一个next 来保存 cur 的下一个节点,在链表都free 完后,再销毁头节点;

注意:应该是从头节点的 next 开始遍历

void ListNodedestroy(LNode* phead)
{
	assert(phead);

	LNode* cur = phead->next;
	while (cur != phead)
	{
		LNode* next = cur->next;
		free(cur);
		cur = next;
	}

	free(phead);
	phead = NULL;
}

B.插入

1.头插  ListNodepushfront

1.这里要头插就太简单了,但是我们最好保存一下头节点的 next ,这样就不应考虑链接时的顺序问题;

2.使头节点的 next 指向新节点,然后新节点的 prev 指向头节点;

3.新节点的 next 指向保存的节点,保存的节点的 prev 指向新节点;

这里并不需要考虑链表为空的情况,这就是双链表的强大之处。

void ListNodepushfront(LNode* phead, DLdatatype x)
{
	assert(phead);

	LNode* newnode = Buynewnode(x);

	LNode* first = phead->next;  //保存头节点的下一个

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

2.尾插 ListNodepushback

1.尾插就需要找尾;

2.这里的找尾很简单,就是头节点的 prev

3.将尾节点与新节点链接起来。

void ListNodepushback(LNode* phead, DLdatatype x)
{
	assert(phead);

	LNode* newnode = Buynewnode(x);
	LNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;

}

3.插入 ListNodeinsert

1.再插入前需要写一个 find 函数,用来寻找指定的位置 pos

2.找到 pos 的前驱 prev ;

3.将前驱,pos,新节点链接起来。

LNode* ListNodefind(LNode* phead,DLdatatype x)
{
	assert(phead);

	LNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}

		cur = cur->next;

	}

	return NULL;
}

void ListNodeinsert(LNode* pos, LNode* phead, DLdatatype x)
{
	assert(phead);
	assert(pos);

	LNode* newnode = Buynewnode(x);
	LNode* prev = pos->prev;

	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

总结:其实可以用一个插入接口就可以实现头插和尾插这两个接口。

C.删除

1.删除时要注意的点是不能把头节点也给删了,如果删了就破坏了双链表的结构;

2.如果是空链表也不能删除。

1.头删 ListNodepopfront

1.删除时保存其下一个节点;

2.链接头节点 phead 和 保存的下一个节点;

3.删除 phead 的next。

void ListNodepopfront(LNode* phead)
{
	assert(phead);
	assert(ListNodeempty(phead));
	if (phead->next == phead)
	{
		perror("ListNodepopfront");
		return;
	}

	LNode* first = phead->next;
	LNode* second = first->next;
	phead->next = second;
	second->prev = phead;

	free(first);
	first = NULL;

	
}

2.尾删 ListNodepopback

1.找尾,即:phead的prev;

2.找尾节点的前驱 prev ,使其next指向phead,phead的prev指向该前驱

void ListNodepopback(LNode* phead)
{
	assert(phead);
	assert(ListNodeempty(phead));

	if (phead->next == phead)
	{
		perror("ListNodepopback");
		exit(-1);
	}

	LNode* tail = phead->prev;
	LNode* prev = tail->prev;

	prev->next = phead;
	phead->prev = prev;
	free(tail);
	tail = NULL;
}

3.删除 ListNodeerase

1.调用 find 函数找到要删除的节点pos;

2.找到 pos 的前驱和后继

3.链接其前驱,后继

4.删除pos。

void ListNodeerase(LNode* pos, LNode* phead)
{
	assert(phead);
	assert(pos);

	if (phead->next == phead)
	{
		perror("ListNodeerase");
		exit(-1);
	}
	LNode* prev = pos->prev;
	LNode* next = pos->next;

	prev->next = next;
	next->prev = prev;

	free(pos);
	pos = NULL;
}

总结:这里可以复用删除函数去实现头删和尾删。

D.打印  ListNodeprint

注意是从头节点的下一个节点开始遍历,且循环终止条件是看是否等于 phead,因为双链表没有 NULL。

void ListNodeprint(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d <=> ", cur->data);
		cur = cur->next;
	}

	printf("\n");

	
}

四.源码

List.h

#pragma once


#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int DLdatatype;

typedef struct ListNode
{
	struct ListNode* prev;
	struct ListNode* next;
	DLdatatype data;
}LNode;

LNode* ListNodeinit();

void ListNodeprint(LNode* phead);

void ListNodepushback(LNode*phead,DLdatatype x);

void ListNodepushfront(LNode* phead, DLdatatype x);

void ListNodepopback(LNode* phead);

void ListNodepopfront(LNode* phead);

LNode* ListNodefind(LNode* phead,DLdatatype x);

void ListNodeinsert(LNode* pos, LNode* phead, DLdatatype x);

void ListNodeerase(LNode* pos, LNode* phead);

void ListNodedestroy(LNode* phead);

List.c

#define _CRT_SECURE_NO_WARNINGS

#include "List.h"

LNode* Buynewnode(DLdatatype x)
{
	LNode* newnode = (LNode*)malloc(sizeof(LNode));
	assert(newnode);
	newnode->prev = NULL;
	newnode->next = NULL;
	newnode->data = x;
	return newnode;
}

LNode* ListNodeinit()
{
	LNode* phead = (LNode*)malloc(sizeof(LNode));
	assert(phead);
	phead->prev = phead;
	phead->next = phead;
	phead->data = -1;
	return phead;
}

void ListNodeprint(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d <=> ", cur->data);
		cur = cur->next;
	}

	printf("\n");

	
}

void ListNodepushback(LNode* phead, DLdatatype x)
{
	assert(phead);

	/*LNode* newnode = Buynewnode(x);
	LNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;*/

	ListNodeinsert(phead, phead, x);

	
}

void ListNodepushfront(LNode* phead, DLdatatype x)
{
	assert(phead);

	/*LNode* newnode = Buynewnode(x);

	LNode* first = phead->next;

	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;*/

	ListNodeinsert(phead->next, phead, x);
}

bool ListNodeempty(LNode* phead)
{
	assert(phead);

	return phead->next == phead;
}

void ListNodepopback(LNode* phead)
{
	assert(phead);
	//assert(ListNodeempty(phead));

	if (phead->next == phead)
	{
		perror("ListNodepopback");
		exit(-1);
	}

	/*LNode* tail = phead->prev;
	LNode* prev = tail->prev;

	prev->next = phead;
	phead->prev = prev;
	free(tail);
	tail = NULL;*/

	ListNodeerase(phead->prev, phead);
}

void ListNodepopfront(LNode* phead)
{
	assert(phead);
	//assert(ListNodeempty(phead));
	if (phead->next == phead)
	{
		perror("ListNodepopfront");
		return;
	}

	/*LNode* first = phead->next;
	LNode* second = first->next;
	phead->next = second;
	second->prev = phead;

	free(first);
	first = NULL;*/

	ListNodeerase(phead->next, phead);
}

LNode* ListNodefind(LNode* phead,DLdatatype x)
{
	assert(phead);

	LNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}

		cur = cur->next;

	}

	return NULL;
}

void ListNodeinsert(LNode* pos, LNode* phead, DLdatatype x)
{
	assert(phead);
	assert(pos);

	LNode* newnode = Buynewnode(x);
	LNode* prev = pos->prev;

	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

void ListNodeerase(LNode* pos, LNode* phead)
{
	assert(phead);
	assert(pos);

	if (phead->next == phead)
	{
		perror("ListNodeerase");
		exit(-1);
	}
	LNode* prev = pos->prev;
	LNode* next = pos->next;

	prev->next = next;
	next->prev = prev;

	free(pos);
	pos = NULL;
}

void ListNodedestroy(LNode* phead)
{
	assert(phead);

	LNode* cur = phead->next;
	while (cur != phead)
	{
		LNode* next = cur->next;
		free(cur);
		cur = next;
	}

	free(phead);
	phead = NULL;

}

test.c

至于test.c 就不贴出来了,这里面主要是一些测试接口的代码,没必要贴出来。


😽本篇文章到此就结束了,若有错误或是建议,欢迎小伙伴们指出;😻

😍请多多支持博主哦~🥰

🤩谢谢你的阅读~😃

 

评论 44
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值