[C语言实现]带你手撕带头循环双链表

news2024/11/19 0:36:11


目录

什么是双链表?

带头结点的优势:

双链表的实现:


什么是循环双链表?

众所周知,顺序表的插入和删除有时候需要大量移动数据,并且每次开辟空间都可能会浪费大量内存和CPU资源,于是我们有了链表,我们之前已经实现了单链表,我们可以发现单链表的一个劣势——每次查找都必须从头结点开始,并且这是一个不可逆的过程,你不可以通过下一个结点找到前一个,只能通过前一个结点找到下一个。

我们将链表的结点空间不再一分为二,而是一分为三,分别是:存储数据的空间,存储下一个结点地址的空间,存储上一个结点地址的空间。这样我们就可以通过一个结点找到它的上一个结点和下一个结点了。

最后我们再将首结点的prev指向尾结点,尾结点的next指向首结点,就变成了循环双链表。

我们在此基础上,再为这个结构加入头结点。

带头结点的优势:

头结点就像是一个哨兵,一直站在第一个位置,它的data域不存储任何值,next域指向第一个有效的链表节点,prev域指向链表的尾结点。

我们为什么需要头结点?

1. 头结点可以让插入操作变得更加方便,不需要分有无结点的情况讨论,因为永远存在一个头结点

2. 头结点可以让我们函数传参的时候不需要传二级指针,因为此时不需要改变head本身。

此外还有其他优点,在之后的代码实现中我们会慢慢体会到头结点的优势。


双链表的代码实现+详细注释:

头文件:

//List.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;
	struct ListNode* next;
}LTNode;

LTNode* ListNodeInit(LTNode* phead);//初始化
void LTPushBack(LTNode* phead, LTDataType x);//尾插
void LTPushFront(LTNode* phead, LTDataType x);//头插
void LTPopFront(LTNode* phead);//头删
void LTPopBack(LTNode* phead);//尾删
void LTPrint(LTNode* phead);//打印 
void LTInsert(LTNode* pos, LTDataType x);//pos前一个位置的插入
void LTErase(LTNode* pos);//pos当前位置的删除
LTNode* LTFind(LTNode* phead, LTDataType x);//查找
LTNode* LTDestroy(LTNode* phead);//销毁

函数实现:

//List.c
#include "List.h"
LTNode* ListNodeInit(LTNode* phead)
{
    //创建头结点
    LTNode* head = (LTNode*)malloc(sizeof(LTNode));
    //防止内存开辟失败
    assert(head);
    //初始化头结点
    head->prev = head->next = head;
    //返回头结点
    return head;
}
void LTPushBack(LTNode* phead, LTDataType x)
{
    //断言 防止用户传入空指针
    assert(phead);
    //尾插
    LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
    assert(newnode);
    newnode->data = x;
    //找到尾结点
    LTNode* tail = phead->prev;
    //开始插入
    newnode->next = phead;
    phead->prev = newnode;
    tail->next = newnode;
    newnode->prev = tail;
}
void LTPushFront(LTNode* phead, LTDataType x)
{
    //断言
    assert(phead);
    //头插
    LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
    assert(newnode);
    newnode->data = x;
    //找到第一个有效节点
    LTNode* Next = phead->next;
    //开始插入
    Next->prev = newnode;
    newnode->next = Next;
    phead->next = newonde;
    newnode->prev = phead;
}
void LTPopBack(LTNode* phead)
{
    assert(phead);
    //找尾
    LTNode* tail = phead->prev;
    //判空
    if(tail == phead);
        return;
    //找到尾结点的前一个结点(倒数第二个)
    LTNode* prevTail = tail->prev;
    //删除尾结点
    prevTail->next = phead;
    phead->prev = prevTail;
    free(tail);     
}
void LTPopFront(LTNode* phead)
{
    //同上
    assert(phead);
    LTNode* del = phead->next;
    if(del == phead)
        return;
    LTNode* next = del->next;
    next->prev = phead;
    phead->next = next;
    free(del);
}
//以下两个接口需要配合查找使用 所以删除接口不需要判断是否为头结点
void LTInsert(LTNode* pos, LTDataType x)
{
    assert(pos);
    LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
    assert(newnode);
    newnode->data = x;
    LTNode* prev = pos->prev;
    //插入
    pos->prev = newnode;
    newnode->next = pos;
    newnode->prev = prev;
    prev->next = newnode;
}
void LTErase(LTNode* pos)
{  
    assert(pos);
    LTNode* prev = pos->prev;
    LTNode* next = pos->next;
    prev->next = next;
    next->prev = prev;
    free(pos);
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* cur = phead;
    while(cur != phead)
    {
        if(cur->data == x)
            return cur;
        cur = cur->next;
    }
    return NULL;
}
void LTPrint(LTNode* phead)
{
    assert(phead);
    LTNode* cur = phead->next;
    printf("head");
    while(cur != phead)
    {
        printf("<-%d->", cur->data);
        cur = cur->next;
    }
    printf("head\n");
}
void ListDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = 	phead->next;
	while(cur != phead)
	{
		LTNode* next = cur->next;
		ListErase(cur);
		cur = next;
	}
	free(phead);
}

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

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

相关文章

【实用篇】SpringCloud01

SpringCloud01 1.认识微服务 随着互联网行业的发展&#xff0c;对服务的要求也越来越高&#xff0c;服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢&#xff1f; 1.0.学习目标 了解微服务架构的优缺点 1.1.单体架构 单体架构&#xff…

基于复旦微 JFM7K325T 全国产FPGA的高速数据采集、图像处理方案

板卡概述 PCIE-XM711 是一款基于 PCIE 总线架构的高性能数据预处理 FMC载板&#xff0c;板卡采用复旦微的 JFM7K325T FPGA 作为实时处理器&#xff0c;实现 各个接口之间的互联。该板卡可以实现 100%国产化。 板卡具有 1 个 FMC&#xff08;HPC&#xff09;接口&#xff0c;1 路…

这10道测试用例面试题,面试官肯定会问到

前言 软件测试面试中&#xff0c;测试用例是非常容被问到的一个点&#xff0c;今天小编就给大家把最常见的测试用例方面的问题给大家整理出来&#xff0c;希望对大家的面试提供帮助。 1、 什么是测试用例‍ 答&#xff1a;测试用例的设计就是如何覆盖所有软件表现出来的状态…

硬盘有多少图片取决我的网速, Python获取硬盘少女图集

目录 前言开发环境:案例实现的步骤:代码展示尾语 &#x1f49d; 前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 图片数据在网络中随处可见, 如果需要一些图片素材 一睹为快把! ! ! 开发环境: 解释器: python 3.8 编辑器: pycharm 2022.3 专业版 内置模块使用 os &g…

CH341的I2C接口编程说明

CH341的I2C接口特性&#xff1a; 1、支持I2C速度20K/100K/400K/750K&#xff1b; 2、默认不支持设备的ACK应答监测&#xff0c;即忽略ACK状态&#xff1b;强制支持需修改软件&#xff1b; 引脚序号功能说明24SCL23SDA Windows系统SPI通讯接口函数 HANDLE WINAPI CH341OpenD…

Piccolo傻瓜式环境配置

首先下载Piccolo的代码 Piccolo下载网址点击Code然后点击Download ZIP。可能有点慢&#xff0c;总共要下139M。 下载CMAKE CMAKE下载网址下载完后安装CMAKE 构建环境 将下来的Piccolo-main压缩包解压为Piccolo-main文件夹。打开CMAKE&#xff0c;如下图进行目录选择&…

Linux - 第19节 - 网络基础(传输层二)

1.TCP相关实验 1.1.理解listen的第二个参数 在编写TCP套接字的服务器代码时&#xff0c;在进行了套接字的创建和绑定之后&#xff0c;需要调用listen函数将创建的套接字设置为监听状态&#xff0c;此后服务器就可以调用accept函数获取建立好的连接了。其中listen函数的第一个参…

LeetCode - 1049 最后一块石头的重量 II (0-1背包)

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://blog.csdn.net/caroline_wendy/article/details/130935119 LeetCode:1049. 最后一块石头的重量 II 题目:有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合,从中选…

人脸识别3:C/C++ InsightFace实现人脸识别Face Recognition(含源码)

人脸识别3&#xff1a;C/C InsightFace实现人脸识别Face Recognition(含源码) 目录 1. 前言 2. 项目安装 &#xff08;1&#xff09;项目结构 &#xff08;2&#xff09;配置开发环境(OpenCVOpenCLbase-utilsTNN) &#xff08;3&#xff09;部署TNN模型 &#xff08;4&a…

【C++】手把手教你模拟实现string类

模拟实现string 前言类的成员变量构造函数析构函数size和length[ ] 重载迭代器赋值运算符重载和拷贝构造函数拷贝构造函数赋值运算符重载现代式写法 reserve 和 resizereserveresize 字符串追加push_backappend insertpos位置插字符pos位置插字符串 erase>> 和 <<&…

Linux---用户权限(权限位、chowd、chown)

1. 权限位 序号1&#xff0c;表示文件、文件夹的权限控制信息 序号2&#xff0c;表示文件、文件夹所属用户 序号3&#xff0c;表示文件、文件夹所属用户组 权限细节总共分为10个槽位&#xff1a; 举例&#xff1a;drwxr-xr-x&#xff0c;表示&#xff1a; 这是一个文件夹&…

如何使用Metasploit进行后渗透攻击?

后渗透攻击&#xff08;PostExploitation&#xff09;是整个渗透测试过程中最能够体现渗透测试团队创造力与技术能力的环节。前面的环节可以说都是在按部就班地完成非常普遍的目标&#xff0c;而在这个环节中&#xff0c;需要渗透测试团队根据目标组织的业务经营模式、保护资产…

关于PyQt5的环境搭建

目录 一、需要的环境 二、安装python 1、python安装链接 三、安装PyQt5 1、使用豆瓣的镜像 2、配置环境变量 四、安装pycharm 1、pycharm官网链接 五、配置环境 1、找到设置 2、添加designer 3、配置ui 4、配置rc 六、注意问题 一、需要的环境 1、安装好python安装…

【Linux CAN应用编程(1)】初识CAN总线(附全文代码)

接下来我们学习 CAN 应用编程&#xff0c;CAN 是目前应用非常广泛的现场总线之一&#xff0c;主要应用于汽车电子和工业领域&#xff0c;尤其是汽车领域&#xff0c;汽车上大量的传感器与模块都是通过 CAN 总线连接起来的。CAN 总线目前是自动化领域发展的热点技术之一&#xf…

1、Vue简介与环境搭建

目录 一、Vue简介二、Vue开发环境1 - 环境安装2 - 新建Vue项目3 - VS Code4 - Vue项目的目录结构 一、Vue简介 官方文档&#xff1a;https://cn.vuejs.orgVue的api风格&#xff1a;选项式 API&#xff08;Vue 2&#xff09; 和组合式 API&#xff08;Vue 3&#xff09;**选项式…

怎么把pdf转成word?转换途径一览

在日常生活和工作中&#xff0c;我们常常需要处理各种文档格式。其中&#xff0c;PDF 作为一种流行的跨平台文件格式&#xff0c;广泛应用于技术文档、报告、合同和电子书等领域。但是&#xff0c;当我们需要修改 PDF 文件内容时&#xff0c;却往往会遇到困难。这时&#xff0c…

JavaScript引擎,V8引擎的原理

为什么需要JavaScript引擎&#xff1f; 高级的编程语言都是需要转成最终的机器指令来执行 我们所编写的js无论h是交给浏览器或者node执行&#xff0c;最后都是需要被CPU执行的 CPU只认识自己的指令集&#xff08;机器语言&#xff09;才能被CPU所执行 所以我们需要js引擎帮助我…

掌握了它,软件测试拿下25K轻轻松松!

了解软件测试这行的人都清楚&#xff0c;功能测试的天花板可能也就15k左右&#xff0c;而自动化的起点就在15k左右&#xff0c;当然两个岗位需要掌握的技能肯定是不一样的。 如果刚入门学习完软件测试&#xff0c;那么基本薪资会在7-8k左右&#xff0c;这个薪资不太高主要是因…