C语言 数据结构【双向链表】动态模拟实现

引言

用动态的方式实现双向链表的注意接口,以代码注释为主。

一、什么是双向链表

单向链表是可以向后依次遍历每个元素,双向链表是既可以依次向后遍历每个元素,也可以依次向前,遍历每个元素。

有了单链表的实现,双向链表的实现是很类似的。
注意:这里是实现的是带"哨兵位"的双向链表,即带头双向循环链表**。也有不带头的双向循环链表,可以自己尝试尝试。**

**、动态双向链表模拟实现**

分3个文件:

cpp 复制代码
List.c  //双链表的主要代码(核心)
List.h  //双链表的函数的声明
test.c  //测试代码

1、双链表的结构

定义在List.h文件中

cpp 复制代码
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
//双链表的结构:
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;      //存储的数据
	struct ListNode* next;//指向前一个地址的指针
	struct ListNode* prev;//指向下一个地址的指针
}LTNode;

2、申请新结点,初始化哨兵位

cpp 复制代码
LTNode* LTBuyNode(LTDataType x)  //申请新结点
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
  	newnode->next = newnode->prev = newnode; //结点自己指向自己
	return newnode;
}

LTNode* LTInit() //初始化哨兵位
{
	LTNode* phead = LTBuyNode(-1); //将哨兵位的值设置成无效的值
	return phead;
}

3、尾插,头插

小提示:先修改新结点的指针,再修改原链表的指针

以理解为主,记代码没意思

cpp 复制代码
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* newnode = LTBuyNode(x);
	newnode->prev = phead->prev;  //新结点的前驱指向最后一个结点
	newnode->next = phead;        //新结点的后驱指向哨兵位

	phead->prev->next = newnode;  //先找到原链表的最后一个结点,将其后驱改成新结点的地址
	phead->prev = newnode;        //最后将哨兵位的前驱指向新结点(即最后一行结点)
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->next = phead->next;
	newnode->prev = phead;

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

4、打印,判断链表是否为空

cpp 复制代码
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;  //指向第一个结点
	while (pcur != phead)    //回到哨兵位时,结束打印
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;    //依次向后找结点,打印结点内容
	}
	printf("\n");
}

//判断有无元素
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;  //当哨兵位自己指向自己说明没有元素,链表为空,否则,链表里面就是有元素的
}

5、尾删,头删

cpp 复制代码
//尾删
void LTPopBack(LTNode* phead)
{
	assert(!LTEmpty(phead));
	LTNode* del = phead->prev; //拿到尾结点

	del->prev->next = phead;   //修改尾结点前一个结点的后驱
	phead->prev = del->prev;   //修改头结点的前驱

	free(del);
	del = NULL;
}
//头删
void LTPopFront(LTNode* phead)
{
	assert(!LTEmpty(phead));
	LTNode* del = phead->next;

	del->next->prev = phead;  //修改头结点的下一个结点的前驱
	phead->next = del->next;  //修改头结点的后驱

	free(del);
	del = NULL;
}

6、查找

cpp 复制代码
//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)  //依次向后遍历链表
	{
		if (pcur->data == x)
		{
			return pcur;   //找到返回结点的地址
		}
		pcur = pcur->next;
	}
	return NULL; //没有就返回NULL
}

7、在pos位置前或后插入数据

cpp 复制代码
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);

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

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

//在pos位置前插入数据
void LTInsertAfter(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);

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

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

8、删除pos位置的结点

cpp 复制代码
//删除pos位置的结点
void LTErase(LTNode* pos)
{
	assert(pos);
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;

	free(pos);
	pos = NULL;
}

9、销毁双向链表

cpp 复制代码
销毁双向链表(法一:因为要对头节点做修改,传二级指针)
void LTDesTroy(LTNode** pphead)
{
	assert(pphead);
	LTNode* pcur = (*pphead)->next;
	while (pcur != *(pphead))
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(*pphead);
	*pphead = NULL;
}
cpp 复制代码
//销毁双向链表(法二:为了保持代码的统一性,这里也传一级指针,但是调用完函数后需要手动将头结点置为NULL;)
void LTDesTroy(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
}

三、所以代码

说白了:双向链表的实现和修改就是对结点地址的修改,只要想明白了,是很简单的

1、List.h中的代码:

cpp 复制代码
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
//双链表的结构:
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;      //存储的数据
	struct ListNode* next;//指向前一个地址的指针
	struct ListNode* prev;//指向下一个地址的指针
}LTNode;

void LTPrint(LTNode* phead);

LTNode* LTInit();
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);

//判断有无元素
bool LTEmpty(LTNode* phead);

//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);

//查找
LTNode* LTFind(LTNode* phead, LTDataType x);

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

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

//销毁双向链表(法一:因为要对头节点做修改,传二级指针)
//void LTDesTroy(LTNode** pphead);

//销毁双向链表(法二:为了保持代码的统一性,这里也传一级指针,但是调用完函数后需要手动将头结点置为NULL;)
void LTDesTroy(LTNode* phead);

2、List.c中的代码

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
LTNode* LTBuyNode(LTDataType x)  //申请新结点
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
  	newnode->next = newnode->prev = newnode; //结点自己指向自己
	return newnode;
}

LTNode* LTInit() //初始化哨兵位
{
	LTNode* phead = LTBuyNode(-1); //将哨兵位的值设置成无效的值
	return phead;
}

void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;  //指向第一个结点
	while (pcur != phead)    //回到哨兵位时,结束打印
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;    //依次向后找结点,打印结点内容
	}
	printf("\n");
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* newnode = LTBuyNode(x);
	newnode->prev = phead->prev;  //新结点的前驱指向最后一个结点
	newnode->next = phead;        //新结点的后驱指向哨兵位

	phead->prev->next = newnode;  //先找到原链表的最后一个结点,将其后驱改成新结点的地址
	phead->prev = newnode;        //最后将哨兵位的前驱指向新结点(即最后一行结点)
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->next = phead->next;
	newnode->prev = phead;

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

//判断有无元素
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;  //当哨兵位自己指向自己说明没有元素,链表为空,否则,链表里面就是有元素的
}

//尾删
void LTPopBack(LTNode* phead)
{
	assert(!LTEmpty(phead));
	LTNode* del = phead->prev; //拿到尾结点

	del->prev->next = phead;   //修改尾结点前一个结点的后驱
	phead->prev = del->prev;   //修改头结点的前驱

	free(del);
	del = NULL;
}
//头删
void LTPopFront(LTNode* phead)
{
	assert(!LTEmpty(phead));
	LTNode* del = phead->next;

	del->next->prev = phead;  //修改头结点的下一个结点的前驱
	phead->next = del->next;  //修改头结点的后驱

	free(del);
	del = NULL;
}
//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)  //依次向后遍历链表
	{
		if (pcur->data == x)
		{
			return pcur;   //找到返回结点的地址
		}
		pcur = pcur->next;
	}
	return NULL; //没有就返回NULL
}
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);

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

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

//在pos位置前插入数据
void LTInsertAfter(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);

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

	pos->prev->next = newnode;
	pos->prev = newnode;
}
//删除pos位置的结点
void LTErase(LTNode* pos)
{
	assert(pos);
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;

	free(pos);
	pos = NULL;
}

//销毁双向链表(法一:因为要对头节点做修改,传二级指针)
//void LTDesTroy(LTNode** pphead)
//{
//	assert(pphead);
//	LTNode* pcur = (*pphead)->next;
//	while (pcur != *(pphead))
//	{
//		LTNode* next = pcur->next;
//		free(pcur);
//		pcur = next;
//	}
//	free(*pphead);
//	*pphead = NULL;
//}

void LTDesTroy(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
}

3、测试时的代码(test.c中):

cpp 复制代码
#include"List.h"

int main()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	
	/*LTPushFront(plist, 3);
	if (LTFind(plist, 2))
	{
		printf("找到了\n");
	}
	else
	{
		printf("无\n");
	}*/

	//LTInsert(LTFind(plist, 3), 4);
	//LTInsertAfter(LTFind(plist, 3), 4);
	//LTErase(LTFind(plist, 2));
	LTPrint(plist);
	LTDesTroy(plist);
	plist = NULL;

	return 0;
}
相关推荐
雾月555 小时前
LeetCode 941 有效的山脉数组
java·开发语言·数据结构·算法·leetcode·职场和发展
java1234_小锋8 小时前
一周学会Pandas2 Python数据处理与分析-Pandas2二维数据结构-DataFrame
数据结构·python·pandas
ChoSeitaku8 小时前
NO.79十六届蓝桥杯备战|数据结构-扩展域并查集-带权并查集|团伙|食物链|银河英雄传说(C++)
数据结构·c++·蓝桥杯
WG_179 小时前
图论:多源最短路
数据结构·c++·算法
Tanecious.10 小时前
初阶数据结构--树
数据结构
魂兮-龙游10 小时前
C语言:字符串处理函数strstr分析
c语言·开发语言·数据处理·字符串处理
CS创新实验室10 小时前
数据结构:用生活中的例子解释 AOE 网中活动的最早和最迟开始时间的含义和计算方法
数据结构·计算机考研·408计算机
爱coding的橙子10 小时前
蓝桥杯备赛 Day 20 树基础
数据结构·c++·算法·蓝桥杯·深度优先
乌旭12 小时前
英伟达Blackwell架构深度拆解:新一代GPU如何突破算力瓶颈?
数据结构·人工智能·深度学习·机器学习·ai·架构·ai编程
这一wa是晚安15 小时前
5.数据结构-图
数据结构