当前位置: 首页 > article >正文

数据结构---详解双向链表

前面我们讲过单向链表,但是链表的结构非常多。

一、链表的分类

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 虽然上面展示了很多链表的结构,但是我们实际上最常用的还是两种结构:单链表和双向链表。
  • 无头单向非循环链表:结构简单,一般不会单独用来存数据。
  • 带头双向循环链表:结构复杂,一般用在单独储存数据。
    v

二、双向链表的实现

  1. 首先创建三个文件
    在这里插入图片描述

头文件List.h:声明函数和定义链表
源文件List.c:用来具体实现函数
测试文件test.c:用来测试工作

1、双向链表的初始化

  • 方法一:直接一个函数生成哨兵位,然后返回哨兵位
LTNode* LTInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	phead->data = -1;  //这里的值随便定,data无效
	phead->next = phead->prev = phead;

	return phead;
}
  • 方法二:传指针,进行加工成哨兵位
void LTInit(LTNode** pphead)
{
	assert(pphead);
	*pphead = (LTNode*)malloc(sizeof(LTNode));
	(*pphead)->data = -1;
	(*pphead)->next = (*pphead)->prev = *pphead;
}

2、双向链表的销毁

void LTDesTroy(LTNode* phead)
{
    //创建一个指向头节点的下一个结点的指针
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
	    //创建一个next指针记录位置,避免后续释放的时候找不到下一个结点
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

3、双向链表的打印

  • 单向链表的打印靠循环循环的结束标志是pcur == NULL,那双向链表又该怎么打印呀?我们指定双向链表是循环的,如果跟单向链表一样的结束条件会出现死循环的情况,我们会发现前面初始化哨兵位的时候,哨兵位无有效数据,同时循环一轮还是回到哨兵位的位置,那我们就可以将pcur != phead作为循环的结束条件
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;

	while (pcur != phead)
	{
		printf("%d-> ", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

4、双向链表的尾插

  • 创建一个新节点,想让新节点的prev指针和next指针分别指向phead->prev和phead,在让尾节点的next指针指向newnode和phead->prev指向newnode。
void LTPushBack(LTNode* phead, LTDatatype x)
{
	assert(phead);
	//创建一个新节点
	LTNode* newnode = LTBuyNode(x);

	newnode->prev = phead->prev; //看图1
	newnode->next = phead;

	phead->prev->next = newnode; //图2
	phead->prev = newnode;
}
  • 图一
    在这里插入图片描述
  • 图二
    在这里插入图片描述

5、双向链表的头插

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;
}
  • 图一
    在这里插入图片描述
  • 图二

6、双向链表判空

  • 链表的头节点的next指针指向自己的时候说明链表里面没有数据
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

7、双向链表尾删

void LTPopBack(LTNode* phead)
{
	assert(!LTEmpty(phead));

	LTNode* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;

	free(del);
	del = NULL;
}
  • 看下图更好的理解
    在这里插入图片描述

8、双向链表头删

void LTPopFront(LTNode* phead)
{
	assert(!LTEmpty(phead));

	LTNode* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;

	free(del);
	del = NULL;
}

在这里插入图片描述

9、在pos之后插入数据

void LTInsert(LTNode* pos, LTDatatype x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);

	newnode->next = pos->next; //看红色的箭头
	newnode->prev = pos;

	pos->next->prev = newnode; //看粉色的箭头
	pos->next = newnode;
}

在这里插入图片描述

10、双向链表查找数据

LTNode* LTFind(LTNode* phead, LTDatatype x)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

11、删除pos位置数据

void LTErase(LTNode* pos) 
{
	assert(pos);

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

	free(pos);
	pos = NULL;
}

在这里插入图片描述

三、双向链表代码总览

1、List.h

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

typedef int LTDatatype;
//定义双线链表
typedef struct ListNode
{
	LTDatatype data;
	struct ListNode* prev;
	struct ListNode* next;
}LTNode;

//双向链表的初始化
LTNode* LTInit();
//void LTInit(LTNode** pphead);
//链表销毁
//传一级指针,保证接口一致性,但需要手动将实参置为NULL
void LTDesTroy(LTNode* phead);
//创建新节点
LTNode* LTBuyNode(LTDatatype x);
//打印
void LTPrint(LTNode* phead);

//尾插
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 LTErase(LTNode* pos);

2、List.c

#define  _CRT_SECURE_NO_WARNINGS 1
#include "List.h"

//双向链表初始化两种方法
LTNode* LTInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	phead->data = -1;
	phead->next = phead->prev = phead;

	return phead;
}

//void LTInit(LTNode** pphead)
//{
//	assert(pphead);
//	*pphead = (LTNode*)malloc(sizeof(LTNode));
//	(*pphead)->data = -1;
//	(*pphead)->next = (*pphead)->prev = *pphead;
//}

//链表的销毁
void LTDesTroy(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

//打印
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;

	while (pcur != phead)
	{
		printf("%d-> ", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}
//创建一个节点
LTNode* LTBuyNode(LTDatatype x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
		return NULL;
	node->data = x;
	node->prev = node->prev = node;

	return node;
}

//尾插
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;
}

//在pos之后插入数据
void LTInsert(LTNode* pos, LTDatatype x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);

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

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

//查找数据
LTNode* LTFind(LTNode* phead, LTDatatype x)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
//删除pos位置数据
void LTErase(LTNode* pos) 
{
	assert(pos);

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

	free(pos);
	pos = NULL;
}

3、test.c

#define  _CRT_SECURE_NO_WARNINGS 1
#include "List.h"

void test()
{
	//第一种初始化方式
	LTNode* plist = LTInit();
	//第二种初始化方式
	//LTNode* plist = NULL;
	//LTInit(&plist);

	//尾插
	LTPushBack(plist, 1);
	LTPrint(plist);
	LTPushBack(plist, 2);
	LTPrint(plist);
	LTPushBack(plist, 3);
	LTPrint(plist);
	LTPushBack(plist, 4);
	LTPrint(plist);

	//链表的销毁
	/*LTDesTroy(plist);
	plist = NULL;*/
	//删除pos位置数据
	/*LTNode* find = LTFind(plist, 2);
	if (find == NULL)
	{
		printf("没有找到!");
	}
	else
	{
		printf("找到了!");
	}

	LTErase(find);
	LTPrint(plist);*/

	//在pos位置之后插入数据
	/*LTNode* find = LTFind(plist, 2);
	if (find == NULL)
	{
		printf("没有找到!");
	}
	else
	{
		printf("找到了!");
	}
	LTInsert(find, 99);
	LTPrint(plist);*/

	//查找
	/*LTNode* find = LTFind(plist, 5);
	if (find == NULL)
	{
		printf("没有找到!");
	}
	else
	{
		printf("找到了!");
	}*/

	//尾删
	/*LTPopBack(plist);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);*/

	//头删
	/*LTPopFront(plist);
	LTPrint(plist);
	LTPopFront(plist);
	LTPrint(plist);
	LTPopFront(plist);
	LTPrint(plist);
	LTPopFront(plist);*/

	//头插
	/*LTPushFront(plist, 1);
	LTPrint(plist);
	LTPushFront(plist, 2);
	LTPrint(plist);
	LTPushFront(plist, 3);
	LTPrint(plist);
	LTPushFront(plist, 4);
	LTPrint(plist);*/
}

int main()
{
	test();

	return 0;
}

http://www.kler.cn/a/395211.html

相关文章:

  • LeetCode105.从前序与中序遍历构造二叉树
  • 3. Sharding-Jdbc核⼼流 程+多种分⽚策略
  • ES6字符串的新增方法
  • 随机数
  • A029-基于Spring Boot的物流管理系统的设计与实现
  • 火车车厢重排问题,C++详解
  • Leecode刷题C语言之统计好节点的数目
  • uniapp luch-request 使用教程+响应对象创建
  • 异步处理之async/await使用技巧分享
  • 【广西-柳州】《柳州市本级信息化建设项目预算支出标准(试行)》(柳财审〔2020〕16号 )-省市费用标准解读系列11
  • Windows搭建流媒体服务并使用ffmpeg推流播放rtsp和rtmp流
  • 【redis】redis
  • c# 在10万条数据中判断是否存在很慢问题
  • 【金猿案例展】科技日报——大数据科技资讯服务平台
  • DB-GPT系列(五):DB-GPT六大基础应用场景part2
  • pyinstaller+upx给python GUI程序添加自定义图标
  • 驾校增加无人机培训项目可行性技术分析
  • 本地搭建你的私有网盘:在Ubuntu上使用Portainer CE安装NextCloud
  • 基于springboot+vue实现的高校电子图书馆的大数据平台 (源码+L文+ppt)4-013
  • Jmeter中的配置原件(四)
  • 机器学习周报(transformer学习1)
  • PG数据库 数据库时间字段 开始时间和结束时间,判断和查询条件的开始和截止时间存在交集,SQL如何编写
  • vue请求数据报错,设置支持跨域请求,以及2种请求方法axios或者async与await
  • golang反射函数注册
  • (十六)JavaWeb后端开发——Spring框架常见注解
  • 【C++】C++基础知识