4.双向循环链表的模拟实现

news2025/1/21 13:00:40

1.双向链表的实现

image-20240404225836268

1.1双向链表节点的结构声明

typedef int LTDataType;

typedef struct ListNode
{
    struct ListNode* prev;  // 指向该节点的前一个节点
	struct ListNode* next;  // 指向该节点的后一个节点
	LTDataType data;        // 该节点中存储的数据
}LTNode;					// 将这个结构体重新命名为LTNode

1.2链表的初始化

image-20221221050208838

LTNode* ListInit()
{
    // 创建一个节点,这个节点是哨兵位
	LTNode* guard = (LTNode*)malloc(sizeof(LTNode));
	if (guard == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

    // 这是一个带头双向循环的链表
    // 此时只有一个哨兵位,因此哨兵位的前一个节点和后一个节点都是哨兵位本身
	guard->next = guard;
	guard->prev = guard;

	return guard;
}

1.3 链表的尾插

// 创建一个新节点
LTNode* BuyListNode(LTDataType x)
{
    // 创建一个新节点
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

    // 初始化这个节点的指针,和数据
	node->next = NULL;
	node->prev = NULL;
	node->data = x;
    
	return node;
}

image-20221221045924349

// 尾插
void ListPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

    // 尾插一个数据前,创建一个新节点,将这个数据存储到这个节点中
	LTNode* newnode = BuyListNode(x);
    
    // phead就是头节点,因为是双向循环链表,所以phead->prev就是双向循环链表的尾节点
	LTNode* tail = phead->prev;    

    // 尾插一个新节点,就是将下面三个节点,按照顺序进行连接
    // tail newnode phead 
    // 1.尾节点指向新节点
	tail->next = newnode; 
    // 2.新节点的前指针,指向尾节点
	newnode->prev = tail;
    // 3.新节点的后指针,指向头节点
	newnode->next = phead;
    // 4.头节点的前指针,指向新节点,新节点成为这个双向循环链表的新的尾节点
	phead->prev = newnode;
}

1.4链表的打印

image-20220828163401093

void ListPrint(LTNode* phead)
{
	assert(phead);
	printf("phead<=>");
    
    //不打印哨兵位的头
    // phead就是哨兵位,不存储数据,因此不需要进行打印
	LTNode* cur = phead->next;  
    
    // cur等于phead,说明已经循环了一周,所有的数据都已经被打印了
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

1.5链表的头插

  • 方法一

image-20220828164424170

  • 方法二

image-20220828164452671

// 因为双向循环链表是有哨兵位的,头插的节点是插入到哨兵位之后的
// 也就是将 head newnode d1 这三个节点,按照顺序连接就可以
void ListPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

    // 方法一:
    // 需要关心节点连接的循序(如果先执行第二步,那么phead指向的就是新节点,而不是d1节点)
	// 创建一个要插入的新节点,存储的数据为x
	LTNode* newnode = BuyListNode(x);
    
    // 第一步
    // phead->next就是d1节点
    // 新节点的后指针指向d1节点
	newnode->next = phead->next;
    // phead->next->prev 就是d1节点的前指针;也就是d1节点的前指针指向新节点
	phead->next->prev = newnode;

    // 第二步
    // 哨兵位的后指针指向新节点
	phead->next = newnode;
    // 新节点的前指针指向哨兵位
	newnode->prev = phead;

    
    
    // 方法二:
    /*
	// 不关心顺序(第一步和第二步哪个先执行都可以)
	LTNode* newnode = BuyListNode(x);
	// 1.先保存d1节点的地址
	LTNode* first = phead->next;
	// 第一步:
	// 哨兵位的后指针指向新节点
	phead->next = newnode;
	// 新节点的前指针指向哨兵位
	newnode->prev = phead;
	
	// 第二步:
	// 新节点的后指针指向d1节点
	newnode->next = first;
	// d1节点的前指针指向新节点
	first->prev = newnode;
	*/
    
    // 方法三:
    // 在phead->next位置前插入,也就是头插
    // void ListInsert(LTNode* pos, LTDataType x)  这个函数就是在pos位置前插入一个节点
	// ListInsert(phead->next, x);
}

1.6链表的尾删

image-20240405171132075

// 用来判断是否为空链表,空链表无法头删
bool ListEmpty(LTNode* phead)
{
	assert(phead);

    // 方法一(比较麻烦)
	/*
	if (phead->next == phead)
		return true;
	else
		return false;
	*/

    // 如果相等,那么就是空链表(因为这是一个双向循环链表)
	return phead->next == phead;
}

void ListPopBack(LTNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));

    // 哨兵位的前指针指向的就是双向循环链表的尾节点
	LTNode* tail = phead->prev;
    // 尾节点的前指针指向的前节点将其命名为prev
	LTNode* prev = tail->prev;

    // 要进行尾删,也就是将 phead tail prev 三个节点中的tail删除,再将phead和prev连接
    // 1.连接phead和prev
	prev->next = phead;
	phead->prev = prev;
    
    // 释放要删除的节点空间,将其变为空指针,防止产生野指针
	free(tail);
	tail = NULL;
}

1.7链表的头删

image-20220828170122719

// 对于双向循环链表,头删就是将 phead d1 d2中的d1节点删除,再将phead和d2节点连接
void ListPopFront(LTNode* phead)
{
	assert(phead);
    // 保证这个双向循环链表不是空链表
	assert(!ListEmpty(phead));
	
    // phead->next就是d1节点
    LTNode* first = phead->next;
    // phead->next->next就是d2节点
	LTNode* second = first->next;

    // 连接phead和d2节点
	phead->next = second;
	second->prev = phead;

    // 释放d1节点
	free(first);
	first = NULL;
}

1.8链表节点的数量

size_t ListSize(LTNode* phead)
{
	assert(phead);

	size_t n = 0;
	LTNode* cur = phead->next;
    
    // 当cur != phead,说明链表循环一周结束
	while (cur != phead)
	{
		++n;
		cur = cur->next;
	}

	return n;
}

1.9链表的查找

LTNode* ListFind(LTNode* phead, LTDataType x)
{
	assert(phead);

	size_t n = 0;
    // 哨兵位没有存储数据,不需要进行查找
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}

        // 迭代
		cur = cur->next;
	}

	return NULL;
}

1.10在pos位置之前插入

image-20220828172755297

// 在pos之前插入
// 也就是将prev newnode pos进行连接
void ListInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

    // 将pos前指针指向的节点,命名为prev
	LTNode* prev = pos->prev;
    // 创建一个新节点
	LTNode* newnode = BuyListNode(x);

	// 连接3个节点 prev newnode pos;
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

头插

void ListPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

    //在phead->next之前插入,也就是头插
	ListInsert(phead->next, x);
}

尾插

image-20220828173223249

void ListPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

    //在phead之前插入,也就是尾插
    ListInsert(phead, x);
}

1.11删除pos位置

image-20220828173538770

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

    // prev pos next
	LTNode* prev = pos->prev;
	LTNode* next = pos->next;
    
	// 连接prev和next,释放pos节点
	prev->next = next;
	next->prev = prev;
    
	free(pos);
    //需要调用的人自行置空,因为pos是传值拷贝,在这里将pos置空,主函数中的pos并没有被改变
	//pos = NULL; 
}

尾删

void ListPopBack(LTNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));
    
    //删除phead->prev也就是尾删
	ListErase(phead->prev); 
}

头删

void ListPopFront(LTNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));
	
    // 删除phead->next也就是头删
	ListErase(phead->next);
}

1.12销毁链表


void ListDestory(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
        // 释放当前节点
		free(cur);
        
        // 迭代
		cur = next;
	}

    // 释放哨兵位
	free(phead);
    
    // 可以传二级,内部置空头结点
	// 建议:也可以考虑用一级指针,让调用ListDestory的人置空  (保持接口一致性)
	// phead = NULL;
}

2.双向循环链表的完整实现

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

// 双链表的节点的结构体
typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}LTNode;


// 初始化一个双向循环链表
LTNode* ListInit();
// 销毁这个双向循环链表
void ListDestory(LTNode* phead);

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

// 尾插
void ListPushBack(LTNode* phead, LTDataType x);

// 头插
void ListPushFront(LTNode* phead, LTDataType x);

// 尾删
void ListPopBack(LTNode* phead);

// 头删
void ListPopFront(LTNode* phead);

// 判断链表是否为空
bool ListEmpty(LTNode* phead);

// 链表的节点数量
size_t ListSize(LTNode* phead);

// 查找指定节点
LTNode* ListFind(LTNode* phead, LTDataType x);

// pos位置前插入节点
void ListInsert(LTNode* pos, LTDataType x);
// 删除pos位置的节点
void ListErase(LTNode* pos);
  • List.c
#include "List.h"

// 初始化,并返回哨兵位的地址
LTNode* ListInit()
{
	LTNode* guard = (LTNode*)malloc(sizeof(LTNode));
	if (guard == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

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

	return guard;
}

// 创建一个新节点
LTNode* BuyListNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	node->next = NULL;
	node->prev = NULL;
	node->data = x;
	return node;
}

// 打印链表中的数据
void ListPrint(LTNode* phead)
{
	assert(phead);
	printf("phead<=>");
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

void ListPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

	ListInsert(phead, x);
}

void ListPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	ListInsert(phead->next, x);
}

void ListPopBack(LTNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));

	ListErase(phead->prev);
}

void ListPopFront(LTNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));

	ListErase(phead->next);
}

bool ListEmpty(LTNode* phead)
{
	assert(phead);

	return phead->next == phead;
}

size_t ListSize(LTNode* phead)
{
	assert(phead);

	size_t n = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		++n;
		cur = cur->next;
	}

	return n;
}

LTNode* ListFind(LTNode* phead, LTDataType x)
{
	assert(phead);

	size_t n = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}

		cur = cur->next;
	}

	return NULL;
}

// 在pos之前插入
void ListInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);

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

// 删除pos位置
void ListErase(LTNode* pos)
{
	assert(pos);

	LTNode* prev = pos->prev;
	LTNode* next = pos->next;

	prev->next = next;
	next->prev = prev;
	free(pos);
	//pos = NULL;
}

// 可以传二级,内部置空头结点
// 建议:也可以考虑用一级指针,让调用ListDestory的人置空  (保持接口一致性)
void ListDestory(LTNode* phead)
{
	assert(phead);

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

	free(phead);
}
  • main.c
#define _CRT_SECURE_NO_WARNINGS

#include "List.h"

void TestList1()
{
	// 初始化链表
	LTNode* plist = ListInit();

	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);

	ListPrint(plist);

	ListPushFront(plist, 10);
	ListPushFront(plist, 20);
	ListPushFront(plist, 30);
	ListPushFront(plist, 40);
	ListPrint(plist);

	ListPopBack(plist);
	ListPopBack(plist);
	ListPopBack(plist);
	ListPopBack(plist);
	ListPrint(plist);

	ListPopBack(plist);
	ListPopBack(plist);
	ListPopBack(plist);
	ListPopBack(plist);
	ListPrint(plist);

	// 防止内存泄漏
	ListDestory(plist);
	plist = NULL;
}

void TestList2()
{
	LTNode* plist = ListInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPrint(plist);

	ListPopFront(plist);
	ListPopFront(plist);
	ListPrint(plist);

	ListPopFront(plist);
	ListPopFront(plist);
	ListPrint(plist);

	ListDestory(plist);
	plist = NULL;
}

void TestList3()
{
	LTNode* plist = ListInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPrint(plist);

	// ...
}

int main()
{
	TestList2();

	return 0;
}

3.顺序表和链表的区别

image-20220828180116805

image-20220828180145437
image-20220828180204839

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1571548.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QT-QPainter

QT-QPainter 1.QPainter画图  1.1 概述  1.1 QPainter设置  1.2 QPainter画线  1.3 QPainter画矩形  1.4 QPainter画圆  1.5 QPainter画圆弧  1.6 QPainter画扇形 2.QGradient  2.1 QLinearGradient线性渐变  2.2 QRadialGradient径向渐变  2.3 QConicalGr…

关于怎么在github上查看到历史版本信息

最近在跟着教程实践&#xff0c;会不断往项目里写内容&#xff0c;想保留每次实践的效果&#xff0c;所以每次完成后&#xff0c;会commit并push到github中&#xff0c;下面说明一下怎么查看历史版本二信息。 以我的这篇仓库为例&#xff08;SpringCloudDemo&#xff09; 步骤…

linux使用supervisor部署springboot

supervisor 美&#xff1a;[suːpərvaɪzər ] n.监督人;主管人;指导者; Supervisor是一个进程控制系统工具&#xff0c;用于在Linux系统上管理和监控其他进程。它可以启动、停止、重启和监控应用程序或服务&#xff0c;并在其异常退出时自动重启它们。Supervisor通过提供一个…

Python深度学习车辆特征分析系统(V2.0),附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

LeetCode-79. 单词搜索【数组 字符串 回溯 矩阵】

LeetCode-79. 单词搜索【数组 字符串 回溯 矩阵】 题目描述&#xff1a;解题思路一&#xff1a;回溯 回溯三部曲。这里比较关键的是给board做标记&#xff0c;防止之后搜索时重复访问。解题思路二&#xff1a;回溯算法 dfs,直接看代码,很容易理解。visited哈希&#xff0c;防止…

vscode+anaconda 环境python环境

环境说明&#xff1a; windows 10 vscodeanaconda anaconda 安装&#xff1a; 1、官网下载地址:Free Download | Anaconda 2、安装 接受协议&#xff0c;选择安装位置&#xff0c;一直next&#xff0c;到下面这一步&#xff0c;上面是将Anaconda 添加至环境变量&#xff0…

备战蓝桥杯---线段树应用2

来几个不那么模板的题&#xff1a; 对于删除&#xff0c;我们只要给那个元素附上不可能的值即可&#xff0c;关键问题是怎么处理序号变化的问题。 事实上&#xff0c;当我们知道每一个区间有几个元素&#xff0c;我们就可以确定出它的位置&#xff0c;因此我们可以再维护一下前…

Spark-Scala语言实战(13)

在之前的文章中&#xff0c;我们学习了如何在spark中使用键值对中的keys和values,reduceByKey,groupByKey三种方法。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢…

EdenAI

文章目录 关于 EdenAI功能说明提供的模型Eden AI PlatformIntegrations 和以下平台集成 Python 调用 API异步功能 关于 EdenAI Eden AI 用户界面&#xff08;UI&#xff09;专为处理人工智能项目而设计。 通过 Eden AI Portal&#xff0c;您可以使用市场上最好的引擎 执行无代…

深度学习方法;乳腺癌分类

乳腺癌的类型很多&#xff0c;但大多数常见的是浸润性导管癌、导管原位癌和浸润性小叶癌。浸润性导管癌(IDC)是最常见的乳腺癌类型。这些都是恶性肿瘤的亚型。大约80%的乳腺癌是浸润性导管癌(IDC)&#xff0c;它起源于乳腺的乳管。 浸润性是指癌症已经“侵袭”或扩散到周围的乳…

#SOP#-如何使用AI辅助论文创作

#SOP#-如何使用AI辅助论文创作 ——2024.4.6 “在使用工具的时候&#xff0c;要做工具的主人” 最终交付物&#xff1a; 一份可执行的AI辅助创作论文的指导手册 交付物质量要求&#xff1a; 不为任何AI大模型付费&#xff01;不为任何降重网站付费&#xff01;通过知网检查论…

视频分块上传Vue3+SpringBoot3+Minio

文章目录 一、简化演示分块上传、合并分块断点续传秒传 二、更详细的逻辑和细节问题可能存在的隐患 三、代码示例前端代码后端代码 一、简化演示 分块上传、合并分块 前端将完整的视频文件分割成多份文件块&#xff0c;依次上传到后端&#xff0c;后端将其保存到文件系统。前…

UNIAPP(小程序)每十个文章中间一个广告

三十秒刷新一次广告 ad-intervals"30" <template><view style"margin: 30rpx;"><view class"" v-for"(item,index) in 100"><!-- 广告 --><view style"margin-bottom: 20rpx;" v-if"(inde…

Kafka参数介绍

官网参数介绍:Apache KafkaApache Kafka: A Distributed Streaming Platform.https://kafka.apache.org/documentation/#configuration

深入浅出 -- 系统架构之分布式常见理论概念

随着计算机科学和互联网的发展&#xff0c;分布式场景变得越来越常见&#xff0c;能否处理好分布式场景下的问题&#xff0c;成为衡量一个工程师是否合格的标准。本文我们介绍下分布式系统相关的理论知识&#xff0c;这些理论是我们理解和处理分布式问题的基础。 CAP理论 CAP…

小林coding图解计算机网络|TCP篇06|如何理解TCP面向字节流协议、为什么UDP是面向报文的协议、如何解决TCP的粘包问题?

小林coding网站通道&#xff1a;入口 本篇文章摘抄应付面试的重点内容&#xff0c;详细内容还请移步&#xff1a;小林coding网站通道 文章目录 如何理解UDP 是面向报文的协议如何理解字节流如何解决粘包固定长度的消息 特殊字符作为边界自定义消息结构 如何理解UDP 是面向报文的…

代码随想录算法训练营第三十一天| 理论基础、LeetCode 455.分发饼干、376. 摆动序列、53. 最大子序和

一、理论基础 文章讲解&#xff1a;https://programmercarl.com/%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 1.贪心的定义 贪心的本质是选择每一阶段的局部最优解&#xff0c;从而达到全局最优解。例如&#xff0c;有一堆钞票&#xff0c…

使用 ChatGPT 创建在线课程:一步一步指南与提示模板

原文&#xff1a;Creating Online Courses with ChatGPT 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 谢谢 作为对你支持的感谢&#xff0c;随意定制本书中列出的任何提示&#xff0c;并将其作为你自己的重新销售。是的&#xff0c;对你免费。 它们都结构良好且用…

移动开发技术历史演化简介h5,跨平台,原生的各种技术实现方案的简单介绍

移动端的开发技术是指针对移动设备如智能手机和平板电脑等便携终端进行应用程序和服务创建的过程。本文将主要介绍一下移动端的开发技术的历史进化历程。讲述h5&#xff0c;跨平台&#xff0c;原生的各种技术实现方案和他们各自的优势与不足。 移动开发&#xff0c;不仅是编程技…

自动化测试框架Robot Framework入门

什么是RF RF是一个基于 Python 的、可扩展的关键字驱动的自动化 验收测试框架、验收测试驱动开发 &#xff08;ATDD&#xff09;、 行为驱动开发 &#xff08;BDD&#xff09; 和机器人流程自动化 &#xff08;RPA&#xff09;。它 可用于分布式、异构环境&#xff0c;其中自动…