【数据结构与算法】之双向链表及其实现!

个人主页:秋风起,再归来~

数据结构与算法

个人格言:悟已往之不谏,知来者犹可追

克心守己,律己则安!

目录

1、双向链表的结构及概念

2、双向链表的实现

[2.1 要实现的接口(List.h)](#2.1 要实现的接口(List.h))

[2.2 链表的初始化](#2.2 链表的初始化)

[2.3 链表的销毁](#2.3 链表的销毁)

[2.4 链表的打印](#2.4 链表的打印)

[2.5 链表的尾插](#2.5 链表的尾插)

[2.6 链表的尾删](#2.6 链表的尾删)

[2.7 链表的头插](#2.7 链表的头插)

[2.8 链表的头删](#2.8 链表的头删)

[2.8 链表的查找](#2.8 链表的查找)

[2.9 pos位置插入数据](#2.9 pos位置插入数据)

[2.10 pos位置删除数据](#2.10 pos位置删除数据)

3、完整代码

List.h

List.c

Test.c(本人在实现双向链表时的测试代码)

[4、 完结散花](#4、 完结散花)


1、双向链表的结构及概念

我们这里要实现的数据结构是带头双向循环的链表(简称双向链表)

下面就是该链表的物理模型啦~

2、双向链表的实现

2.1 要实现的接口(List.h)

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

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* prev;//前驱指针
	LTDataType data;
	struct ListNode* next;//后驱指针
}LTNode;

//链表的初始化
//void LTInit(LTNode** pphead);带参数的初始化
LTNode* LTInit();

//链表销毁
void LTDestroy(LTNode* phead);

//链表的打印
void LTPrint(LTNode* phead);

//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x);

//链表的尾删
void LTPopBack(LTNode* phead);

//链表的头插
void LTPushFront(LTNode* phead, LTDataType x);

//链表的头删
void LTPopFront(LTNode* phead);

//在双向链表中查找数据
LTNode* LTFind(LTNode* phead, LTDataType x);

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);

//删除pos位置节点
void LTErase(LTNode* pos);

2.2 链表的初始化

注意:在初始化的时候一定要让头结点的prev指针和next指针都指向自己!

//链表的初始化
//void LTInit(LTNode** pphead);带参数的初始化
LTNode* LTInit()
{
	//初始化时创建一个带哨兵卫的头结点
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL)
	{
		perror("malloc fail!\n");
		return NULL;
	}
	phead->next = phead->prev = phead;
	phead->data = -1;
	return phead;
}

2.3 链表的销毁

注意:我们一定是从链表的头结点(头结点中并没有有效数据的存储)的下一个位置开始销毁链表的!

//链表销毁
void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* pcur=phead->next;
	LTNode* next = NULL;
	//结束条件是当pcur不等于篇phead
	while (pcur!=phead)
	{
		next = pcur->next;
		free(pcur);
		pcur = next;
	}
}

并且我们在调用链表的销毁函数后依然要手动释放动态内存开辟的phead头结点 !

为尽量确保接口传递参数的一致性我们并没有传递头结点的地址,所以我们并不能在链表的销毁函数中free我们的头结点!

LTDestroy(plist);
//动态开辟的头结点需要手动释放
free(plist);
plist = NULL;

2.4 链表的打印

遍历链表打印头结点,循环结束的条件是pcur=phead,继续的条件是pcur!=phead

//链表的打印
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	LTNode* next = NULL;
	//结束条件是当pcur不等于篇phead
	while (pcur != phead)
	{
		next = pcur->next;
		printf("%d->", pcur->data);
		pcur = next;
	}
	printf("\n");
}

2.5 链表的尾插

新节点的创建(单独封装成为一个函数)

//新节点的创建
LTNode* ListCreatNode(LTDataType x)
{
	LTNode* NewNode = (LTNode*)malloc(sizeof(LTNode));//开辟空间
	if (NewNode == NULL)//判断空间是否开辟成功
	{
		perror("malloc fail");
		return NULL;
	}
	NewNode->data = x;//赋值
	NewNode->next = NULL;//置空
	NewNode->prev = NULL;
	return NewNode;
}

链表的尾插 (在为尾插接口中直接调用创建节点的函数)

//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newNode = ListCreatNode(x);//先创建一个新节点
	LTNode* tail = phead->prev;
	newNode->prev = tail;
	newNode->next = phead;
	tail->next = newNode;
	phead->prev = newNode;
}

2.6 链表的尾删

注意各个节点的指向!

//链表的尾删
void LTPopBack(LTNode* phead)
{
	//尾删的前提是双向链表不为空
	assert(phead && phead->next != phead);
	LTNode* tail = phead->prev;
	phead->prev = tail->prev;
	tail->prev->next=phead;
	free(tail);
	tail = NULL;
}

2.7 链表的头插

//链表的头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newNode = ListCreatNode(x);//先创建一个新节点
	newNode->next = phead->next;
	newNode->prev = phead;
	phead->next->prev = newNode;
	phead->next = newNode;
}

2.8 链表的头删

//链表的头删
void LTPopFront(LTNode* phead)
{
	//头删的前提是双向链表不为空
	assert(phead && phead->next != phead);
	LTNode* start = phead->next;
	phead->next = start->next;
	start->next->prev = phead;
	free(start);
	start= NULL;
}

2.8 链表的查找

返回值是该指向该数据节点的结构体指针,如没有找到,直接返回空!

//在双向链表中查找数据
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	LTNode* next = NULL;
	//结束条件是当pcur不等于篇phead
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		next = pcur->next;
		pcur = next;
	}
	return NULL;
}

2.9 pos位置插入数据

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newNode = ListCreatNode(x);//先创建一个新节点
	newNode->next = pos->next;
	newNode->prev = pos;
	pos->next->prev = newNode;
	pos->next = newNode;
}

2.10 pos位置删除数据

//删除pos位置节点
void LTErase(LTNode* pos)
{
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

3、完整代码

List.h

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

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* prev;//前驱指针
	LTDataType data;
	struct ListNode* next;//后驱指针
}LTNode;

//链表的初始化
//void LTInit(LTNode** pphead);带参数的初始化
LTNode* LTInit();

//链表销毁
void LTDestroy(LTNode* phead);

//链表的打印
void LTPrint(LTNode* phead);

//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x);

//链表的尾删
void LTPopBack(LTNode* phead);

//链表的头插
void LTPushFront(LTNode* phead, LTDataType x);

//链表的头删
void LTPopFront(LTNode* phead);

//在双向链表中查找数据
LTNode* LTFind(LTNode* phead, LTDataType x);

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);

//删除pos位置节点
void LTErase(LTNode* pos);

List.c

#include"List.h"

//链表的初始化
//void LTInit(LTNode** pphead);带参数的初始化
LTNode* LTInit()
{
	//初始化时创建一个带哨兵卫的头结点
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL)
	{
		perror("malloc fail!\n");
		return NULL;
	}
	phead->next = phead->prev = phead;
	phead->data = -1;
	return phead;
}

//链表销毁
void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* pcur=phead->next;
	LTNode* next = NULL;
	//结束条件是当pcur不等于篇phead
	while (pcur!=phead)
	{
		next = pcur->next;
		free(pcur);
		pcur = next;
	}
}

//链表的打印
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	LTNode* next = NULL;
	//结束条件是当pcur不等于篇phead
	while (pcur != phead)
	{
		next = pcur->next;
		printf("%d->", pcur->data);
		pcur = next;
	}
	printf("\n");
}

//新节点的创建
LTNode* ListCreatNode(LTDataType x)
{
	LTNode* NewNode = (LTNode*)malloc(sizeof(LTNode));//开辟空间
	if (NewNode == NULL)//判断空间是否开辟成功
	{
		perror("malloc fail");
		return NULL;
	}
	NewNode->data = x;//赋值
	NewNode->next = NULL;//置空
	NewNode->prev = NULL;
	return NewNode;
}

//链表的尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newNode = ListCreatNode(x);//先创建一个新节点
	LTNode* tail = phead->prev;
	newNode->prev = tail;
	newNode->next = phead;
	tail->next = newNode;
	phead->prev = newNode;
}

//链表的尾删
void LTPopBack(LTNode* phead)
{
	//尾删的前提是双向链表不为空
	assert(phead && phead->next != phead);
	LTNode* tail = phead->prev;
	phead->prev = tail->prev;
	tail->prev->next=phead;
	free(tail);
	tail = NULL;
}

//链表的头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newNode = ListCreatNode(x);//先创建一个新节点
	newNode->next = phead->next;
	newNode->prev = phead;
	phead->next->prev = newNode;
	phead->next = newNode;
}

//链表的头删
void LTPopFront(LTNode* phead)
{
	//头删的前提是双向链表不为空
	assert(phead && phead->next != phead);
	LTNode* start = phead->next;
	phead->next = start->next;
	start->next->prev = phead;
	free(start);
	start= NULL;
}

//在双向链表中查找数据
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	LTNode* next = NULL;
	//结束条件是当pcur不等于篇phead
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		next = pcur->next;
		pcur = next;
	}
	return NULL;
}

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newNode = ListCreatNode(x);//先创建一个新节点
	newNode->next = pos->next;
	newNode->prev = pos;
	pos->next->prev = newNode;
	pos->next = newNode;
}

//删除pos位置节点
void LTErase(LTNode* pos)
{
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

Test.c(本人在实现双向链表时的测试代码)

#define _CRT_SECURE_NO_WARNINGS

#include"LIst.h"

void TestList1()
{
	LTNode* plist;
	plist = LTInit();//初始化链表
	LTPushBack(plist,1);
	LTPushBack(plist,2);
	LTPushBack(plist,3);
	LTPushFront(plist, 4);
	LTPushFront(plist, 4);
	/*LTPopFront(plist);
	LTPopFront(plist);*/
	LTNode* pos=LTFind(plist, 2);
	printf("删除pos位置之前\n");
	LTPrint(plist);
	LTErase(pos);
	printf("删除pos位置之后\n");
	LTPrint(plist);
	//LTInsert(pos, 5);
	//LTPopBack(plist);
	//LTPopBack(plist);
	//LTPopBack(plist);
	LTDestroy(plist);
	//动态开辟的头结点需要手动释放
	free(plist);
	plist = NULL;
}

int main()
{
	TestList1();

	return 0;
}

4、 完结散花

好了,这期的分享到 这里就结束了~

如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~

如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~

我们下期不见不散~~

相关推荐
ChoSeitaku19 分钟前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表
workflower1 小时前
数据结构练习题和答案
数据结构·算法·链表·线性回归
一个不喜欢and不会代码的码农1 小时前
力扣105:从先序和中序序列构造二叉树
数据结构·算法·leetcode
No0d1es3 小时前
2024年9月青少年软件编程(C语言/C++)等级考试试卷(九级)
c语言·数据结构·c++·算法·青少年编程·电子学会
bingw01143 小时前
华为机试HJ42 学英语
数据结构·算法·华为
Yanna_1234565 小时前
数据结构小项目
数据结构
木辛木辛子6 小时前
L2-2 十二进制字符串转换成十进制整数
c语言·开发语言·数据结构·c++·算法
誓约酱6 小时前
(动画版)排序算法 -希尔排序
数据结构·c++·算法·排序算法
誓约酱6 小时前
(动画版)排序算法 -选择排序
数据结构·算法·排序算法
可别是个可爱鬼7 小时前
代码随想录 -- 动态规划 -- 完全平方数
数据结构·python·算法·leetcode·动态规划