【数据结构】— 线性表之『双向链表』的实现

 ꧁   各位大佬们好!很荣幸能够得到您的访问,让我们一起在编程道路上任重道远!꧂

☙ 博客专栏:【数据结构初阶】

⛅ 本篇内容简介:数据结构初阶中的双向循环带头链表的实现!

⭐ 了解作者:励志成为一名编程大牛的学子,目前正在升大二的编程小白。

励志术语:编程道路的乏味,让我们一起学习变得有趣!


文章目录

✀ 前言

✂ 双向链表接口的实现

✀ 双向链表的结构

✀ 双向链表的初始化

✀ 双向链表的创建一个新节点

✀ 双向链表的打印

✀ 双向链表的尾插

✀ 双向链表的尾删

✀ 双向链表的头插

✀ 双向链表的头删

 ✀ 双向链表的判空

✀ 求双向链表的长度

✀ 求双向链表的查找

✀ 双向链表在pos之前插入

✀ 双向链表删除pos位置

 ✀ 双向链表的销毁

✂ 项目源代码

✀ List.h文件

 ✀ List.c文件

 ✀ test.c文件

✂ 结束语


✀ 前言

在前面中,简单介绍了带头双向循环链表的结构,在这里我们来一起实现一下这个双向链表,我们先来看一下这个的结构:

 其中head是作为哨兵位的头节点,不存储数据(包括链表的长度)。因为链表存储的数据可能不仅仅是int类型。

✂ 双向链表接口的实现

✀ 双向链表的结构

从上面的结构中,我们可以看到有:

1. 数据的存储

2. 一个前指针

3. 一个后指针

代码实现:

typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* prev;
	LTDataType Data;
	struct ListNode* next;
}ListNode;

✀ 双向链表的初始化

//双向链表的初始化  不用二级指针,利用返回值
ListNode* ListInit();

带头节点链表的初始化就是,创建一个哨兵位的头结点,头节点的prev指向自己,next也指向自己。在这里我们可以利用一级指针,也可以用二级指针,如果利用一级指针,我们就需要初始化函数的返回值。

图解结构:

 代码实现:

//双向链表的初始化  不用二级指针,利用返回值
ListNode* ListInit()
{
	//创造哨兵位的头节点
	ListNode* guard = (ListNode*)malloc(sizeof(ListNode));
	if (guard == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	guard->next = guard;
	guard->prev = guard;

	return guard;

}

✀ 双向链表的创建一个新节点

//节点的创建
ListNode* BuyListNode(LTDataType x);
//节点的创建
ListNode* BuyListNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	//判空
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->Data = x;
	newnode->prev = NULL;
	newnode->next = NULL;

	return newnode;
}

✀ 双向链表的打印

//双向链表的打印
void ListPrint(ListNode* phead);

思路:打印函数的实现,就是直接遍历,定义一个phead后一个的指针,当cur != phead时就打印Data值。

图解:

 代码实现:

//双向链表的打印
void ListPrint(ListNode* phead)
{
	assert(phead);

	ListNode* cur = phead->next;
	printf("guard<=>");
	while (cur != phead)
	{
		printf("%d<=>", cur->Data);
		cur = cur->next;
	}
	printf("\n");
}

✀ 双向链表的尾插

//双向链表的尾插
void ListPushBack(ListNode* phead, LTDataType x);

思路:双向链表的尾插非常简单,因为头节点的prev就尾,先定义一个尾节点tail,然后直接将新节点链接到tail节点上即可。

图解:

 代码实现:

//双向链表的尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);

	//创造节点
	ListNode* newnode = BuyListNode(x);
	ListNode* tail = phead->prev;
	// 记录尾的前一个
	phead->prev = newnode;
	newnode->next = phead;
	newnode->prev = tail;
	tail->next = newnode;
}

测试尾插(将1,2,3,4数据尾插进入链表):


✀ 双向链表的尾删

//双向链表的尾删
void ListPopBack(ListNode* phead);

思路:我们需要记录尾节点的前一个,将phead的prev链接到尾节点的前一个。注:(这里需要对链表进行判空处理)

图解:

代码实现:

//双向链表的尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	//判空
	assert(!ListEmpty(phead));
	ListNode* tail = phead->prev;
	//记录前一个
	ListNode* prev = tail->prev;
	phead->prev = prev;
	prev->next = phead;
	free(tail);
	tail = NULL;
}

测试尾删(成功尾删):


✀ 双向链表的头插

//双向链表的头插
void ListPushFront(ListNode* phead, LTDataType x);

思路:我们需要记录phead的下一个(next),再将newnode链接到phead与next之间。

图解:

 代码实现:

//双向链表的头插
void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);

	ListNode* newnode = BuyListNode(x);
	//记录phead的后一个
	ListNode* next = phead->next;
	phead->next = newnode;
	newnode->next = next;
	next->prev = newnode;
	newnode->prev = phead;
}

测试头插(尾插10,20,30,40):


✀ 双向链表的头删

//双向链表的头删
void ListPopFront(ListNode* phead);

思路:我们需要记录phead的下一个(first),再记录first的下一个(second),将phead与second链接,最后释放first节点。

图解:

代码实现:

//双向链表的头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	//判空
	assert(!ListEmpty(phead));

	//记录第一个与第二个
	ListNode* first = phead->next;
	ListNode* second = first->next;

	phead->next = second;
	second->prev = phead;

	free(first);
	first = NULL;
}

 测试头删(成功头删):


 ✀ 双向链表的判空

//双向链表的判空
bool ListEmpty(ListNode* phead);

思路:判断phead->next等不等于phead,如果不等不为空,等于为空。

代码实现:

//双向链表的判空
bool ListEmpty(ListNode* phead)
{
	assert(phead);

	return phead->next == phead;
}

✀ 求双向链表的长度

//双向链表的长度
size_t ListSize(ListNode* phead);

思路:采用计数器遍历的方法,cur为phead的下一个,!= phead,count++。

代码实现:

//双向链表的长度
size_t ListSize(ListNode* phead)
{
	assert(phead);
	size_t count = 0;

	ListNode* cur = phead->next;
	while (cur != phead)
	{
		count++;
		cur = cur->next;
	}
	return count;
}

✀ 求双向链表的查找

//双向链表的查找
ListNode* ListFind(ListNode* phead, LTDataType x);

思路:也是采用遍历的方法,Data值==x,返回cur的地址,迭代往后走。

代码实现:

//双向链表的查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
	assert(phead);

	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->Data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	//没有找到
	return NULL;
}

✀ 双向链表在pos之前插入

//双向链表在pos之前插入
void ListInsert(ListNode* pos, LTDataType x);

思路:记录pos位置的前一个(prev),在prev节点与pos之间链接newnode节点。

图解:

 代码实现:

//双向链表在pos之前插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	//记录pos的前一个
	ListNode* prev = pos->prev;
	ListNode* newnode = BuyListNode(x);

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

测试在pos位置之前插入(和ListFind函数配合使用):


✀ 双向链表删除pos位置

//双向链表删除pos位置
void ListErase(ListNode* pos);

思路:记录pos位置的前一个(prev),和pos位置的后一个(next),将prev节点与next节点链接,最后释放pos节点。

图解:

测试删除pos位置节点(例:pos为10位置的节点):


 ✀ 双向链表的销毁

//双向链表的销毁
void ListDestroy(ListNode* phead);

思路:定义cur指针为phead的next,next指针为cur的next,释放cur,cur再等于next,迭代往后走。最后guard哨兵位的节点,我们手动制空。

图解:

 代码实现:

//双向链表的销毁
void ListDestroy(ListNode* phead)
{
	assert(phead);
	//遍历销毁
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

✂ 项目源代码

✀ List.h文件

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once

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

typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* prev;
	LTDataType Data;
	struct ListNode* next;
}ListNode;

//双向链表的初始化  不用二级指针,利用返回值
ListNode* ListInit();

//节点的创建
ListNode* BuyListNode(LTDataType x);

//双向链表的打印
void ListPrint(ListNode* phead);

//双向链表的尾插
void ListPushBack(ListNode* phead, LTDataType x);

//双向链表的尾删
void ListPopBack(ListNode* phead);

//双向链表的头插
void ListPushFront(ListNode* phead, LTDataType x);

//双向链表的头删
void ListPopFront(ListNode* phead);

//双向链表的判空
bool ListEmpty(ListNode* phead);

//双向链表的长度
size_t ListSize(ListNode* phead);

//双向链表的查找
ListNode* ListFind(ListNode* phead, LTDataType x);

//双向链表在pos之前插入
void ListInsert(ListNode* pos, LTDataType x);

//双向链表删除pos位置
void ListErase(ListNode* pos);

//双向链表的销毁
void ListDestroy(ListNode* phead);

 ✀ List.c文件

#include"List.h"

//双向链表的初始化  不用二级指针,利用返回值
ListNode* ListInit()
{
	//创造哨兵位的头节点
	ListNode* guard = (ListNode*)malloc(sizeof(ListNode));
	if (guard == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	guard->next = guard;
	guard->prev = guard;

	return guard;

}

//节点的创建
ListNode* BuyListNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	//判空
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->Data = x;
	newnode->prev = NULL;
	newnode->next = NULL;

	return newnode;
}

//双向链表的打印
void ListPrint(ListNode* phead)
{
	assert(phead);

	ListNode* cur = phead->next;
	printf("guard<=>");
	while (cur != phead)
	{
		printf("%d<=>", cur->Data);
		cur = cur->next;
	}
	printf("\n");
}


//双向链表的尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);

	创造节点
	//ListNode* newnode = BuyListNode(x);
	//ListNode* tail = phead->prev;
	 记录尾的前一个
	//phead->prev = newnode;
	//newnode->next = phead;
	//newnode->prev = tail;
	//tail->next = newnode;

	ListInsert(phead, x);
}

//双向链表的尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	//判空
	assert(!ListEmpty(phead));
	//ListNode* tail = phead->prev;
	记录前一个
	//ListNode* prev = tail->prev;
	//phead->prev = prev;
	//prev->next = phead;
	//free(tail);
	//tail = NULL;

	ListErase(phead->prev);
}


//双向链表的判空
bool ListEmpty(ListNode* phead)
{
	assert(phead);

	return phead->next == phead;
}

//双向链表的长度
size_t ListSize(ListNode* phead)
{
	assert(phead);
	size_t count = 0;

	ListNode* cur = phead->next;
	while (cur != phead)
	{
		count++;
		cur = cur->next;
	}
	return count;
}

//双向链表的头插
void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);

	//ListNode* newnode = BuyListNode(x);
	记录phead的后一个
	//ListNode* next = phead->next;
	//phead->next = newnode;
	//newnode->next = next;
	//next->prev = newnode;
	//newnode->prev = phead;

	ListInsert(phead->next,x);
}

//双向链表的头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	//判空
	assert(!ListEmpty(phead));

	记录第一个与第二个
	//ListNode* first = phead->next;
	//ListNode* second = first->next;

	//phead->next = second;
	//second->prev = phead;

	//free(first);
	//first = NULL;

	ListErase(phead->next);
}

//双向链表的销毁
void ListDestroy(ListNode* phead)
{
	assert(phead);
	//遍历销毁
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}


//双向链表的查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
	assert(phead);

	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->Data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	//没有找到
	return NULL;
}

//双向链表在pos之前插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	//记录pos的前一个
	ListNode* prev = pos->prev;
	ListNode* newnode = BuyListNode(x);

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

//双向链表删除pos位置
void ListErase(ListNode* pos)
{
	assert(pos);
	//记录前一个与后一个
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;

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

	free(pos);
	pos = NULL;
}

 ✀ test.c文件

#include"List.h"

void test1()
{
	ListNode* plist = ListInit();

	//测试尾插
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);

	//打印
	ListPrint(plist);

	//测试尾删
	ListPopBack(plist);
	ListPopBack(plist);
	ListPopBack(plist);

	ListPrint(plist);

	//测试头插
	ListPushFront(plist, 10);
	ListPushFront(plist, 20);
	ListPushFront(plist, 30);
	ListPushFront(plist, 40);

	ListPrint(plist);

	//测试头删
	ListPopFront(plist);

	ListPrint(plist);

	//测试在pos之前插入
	ListInsert(ListFind(plist, 20), 50);

	ListPrint(plist);

	//删除pos位置
	ListErase(ListFind(plist, 10));

	ListPrint(plist);

	ListDestroy(plist);
	plist = NULL;//手动制空


}

int main()
{
	test1();
	return 0;
}

✂ 结束语

相信各位大佬们都已经开学了,那我们就放下假期玩耍的心,一起开心写代码叭!开学第一遍博客— 双向带头循环链表的实现送给大家,祝各位大佬在学业上节节攀升!

【写在最后·想告诉你】

在互联网这个行业里

任何时候都要学好技术

永远都是 技术为王

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

甘宸しぐれ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值