数据结构(双向链表)代码详细注释

news2024/9/23 9:37:33

双向链表

1》双向链表的定义

双向链表也叫双链表,与单向链表不同的是,每一个节点有三个区域组成:两个指针域,一个数据域。

前指针域:存储前驱节点的内存地址

后指针域:存储后继节点的内存地址

数据域:存储节点数据

单个双向链表节点:

完整的双向链表

 2》特性

逻辑结构:线性结构

存储结构:链式存储

操作:增删改查

 3》主要功能

创空:

插入:

删除:

按数据删除:

4》代码展示

#include <stdio.h>
#include <stdlib.h>

typedef struct aouble // 重定义节点结构体
{
    int data;             // 数据域
    struct aouble *next;  // 存下一个节点地址的指针域
    struct aouble *prior; // 存上一个节点地址的指针域
} Double_t, *Double_p;

typedef struct doubleLink // 重定义指针结构体
{
    Double_p head; // 指向链表头节点的指针
    Double_p tail; // 指向链表尾节点的指针
    int len;       // 链表的长度
} Doublelink_t, *Doublelink_p;
// 将头尾指针以及长度封装的一个结构体里面用来操作整个双向链表

/*创空双向链表*/
Doublelink_p Create()
{
    Doublelink_p p = (Doublelink_p)malloc(sizeof(Doublelink_t)); // 定义一个指针指向开辟的指针结构体
    if (NULL == p)                                               // 容错判断
    {
        printf("malloc lost\n");
        return NULL;
    }
    p->len = 0;                                             // 初始化双向链表的长度为0
    p->head = p->tail = (Double_p)malloc(sizeof(Double_t)); // 初始化指针结构体的两个头尾指针指向新开辟的节点结构体
    if (NULL == p->head)                                    // 容错判断
    {
        printf("malloc lost\n");
        return NULL;
    }
    p->head->next = NULL;  // 通过指针结构体找到节点结构体,初始化节点结构体的后指针域为NULL
    p->head->prior = NULL; // 通过指针结构体找到节点结构体,初始化节点结构体的后指针域为NULL
    return p;              // 返回指针结构体
}

/*入栈*/
int Push(Doublelink_p p, int post, int data) // post 是要插入的位置,data 是要插入的数据
{
    if (post < 0 || post > p->len) // 容错判断
    {
        printf("post error\n");
        return -1;
    }
    Double_p p_new = (Double_p)malloc(sizeof(Double_t)); // 开辟一个新的节点,定义一个新的节点指针取接受这块空间
    if (NULL == p_new)                                   // 容错判断
    {
        printf("malloc lost\n");
        return -1;
    }
    p_new->data = data;  // 初始化新节点的数据域
    p_new->next = NULL;  // 初始化新节点的后节点指针域
    p_new->prior = NULL; // 初始化新节点的前节点指针域
    // 1)从尾部插入
    if (post == p->len) // 插入的位置等于链表的长度,说明在尾部插入
    {
        p->tail->next = p_new;  // 让尾节点的后节点指针域等于新节点
        p_new->prior = p->tail; // 让新节点的前节点指针域等于尾节点    这两步的作用就是把新节点链接到尾节点上
        p->tail = p_new;        // 让尾节点移动到新节点处,使新节点成为尾节点
    }
    // 2)从中间插入
    else
    {
        Double_p temp = NULL; // 定义一个节点类型指针待用,用来遍历链表
        // 插入位置在前半段
        if (post < p->len / 2) // 判断要插入位置是否在前半段
        {
            temp = p->head;                 // 让temp指向头节点
            for (int i = 0; i <= post; i++) // for循环往后遍历,找到要插入的位置
                temp = temp->next;          // 向后移动
        }
        // 插入位置在后半段
        else
        {
            temp = p->tail;                         // 让temp指向尾节点
            for (int i = p->len - 1; i > post; i--) // for循环往前遍历,找到要插入的位置
                temp = temp->prior;                 // 向前移动
        }
        // 把新节点插入到temp和它前面节点之间    temp现在就是要插入位置的节点
        p_new->prior = temp->prior; // 让新节点的前指针域存temp的前一个节点地址
        temp->prior->next = p_new;  // 让temp的前一个节点的后指针域存新节点的地址      把前面两条线连起来

        p_new->next = temp;  // 新节点的后指针域存temp的地址
        temp->prior = p_new; // temp的前指针域存新节点的地址   把后面两条线连起来
    }
    p->len++; // 链表长度加一
    return 0;
}

/*遍历*/
void show(Doublelink_p p)
{
    Double_p temp = NULL; // 定义一个节点类型指针待用,用来遍历链表
    printf("正向遍历: ");
    temp = p->head->next; // 让temp 指向头节点的下一个节点,即第一个有效节点
    while (temp != NULL)  //当temp 指向不是NULL 时,进入循环继续遍历
    {
        printf("%d ", temp->data);//打印当前temp节点的数据
        temp = temp->next;//temp向后移动
    }
    printf("\n");
    printf("反向遍历: ");
    temp = p->tail;//让temp指向链表的尾节点
    while (temp != p->head) // 相当于遍历无头链表
    {
        printf("%d ", temp->data);//打印当前temp节点的数据
        temp = temp->prior;//temp向前移动
    }
    printf("\n");
}

/*判空*/
int Empty(Doublelink_p p)
{
    return p->len == 0;//p-> len为0 说明链表长度为空
}

/*删除双向链表元素*/
int Delete(Doublelink_p p, int post)//post 要删除节点的位置
{
    if (Empty(p) || post < 0 || post >= p->len)//判空和位置容错判断
    {
        printf("Empty || post error\n");
        return -1;
    }
    Double_p p_del = NULL;//定义一个p_del置空待用,用于指向删除节点
    //1)删除尾节点
    if (post == p->len)//删除位置等于链表长度,说明要删除的时尾节点
    {
        p->tail = p->tail->prior;//让尾节点向前移动
        free(p->tail->next);//释放之前的尾节点
        p->tail->next = NULL;//把新的尾节点的后指针域置空
    }
    //2)删除中间节点
    else
    {
        if (post < p->len / 2)//要删除节点在前半段
        {
            p_del = p->head;//让p_del指向头节点
            for (int i = 0; i <= post; i++)//从头节点开始往后遍历到要删除的节点处
                p_del = p_del->next;//p_del向后移动
        }
        else//要删除的节点再在后半段
        {
            p_del = p->tail;//让p_del 指向尾节点
            for (int i = p->len - 1; i > post; i--)//从尾节点开始向前遍历到要删除的节点处
                p_del = p_del->prior;//p_del向前移动
        }
        //3)开始删除  p_del此时就是要删除的节点
        p_del->prior->next = p_del->next;//让p_del 的前一个节点的后指针域存p_del 的下一个节点
        p_del->next->prior = p_del->prior;//让p_del的后一个节点的前指针域存p_del的上一个节点    就是让他们跨过p_del连接在一起
        free(p_del);//然后把p_del给释放掉
        p_del = NULL;//将p_del置空
    }
    p->len--;//删除一个节点,链表长度减一
    return 0;
}

/*删除指定数据的节点*/
int DeleteSame(Doublelink_p p, int data)//data 指定数据
{
    if (Empty(p))//先判空
    {
        printf("list is Empty\n");
        return -1;
    }
    Double_p p_del = NULL;//定义一个指针置空 待用   用于指向删除节点
    Double_p t = NULL;//定义一个指针置空  待用  用于遍历链表 

    t = p->head->next;//因为头节点的数据域无效,所以要先跳过头节点,让 t 指向头节点下一个节点
    while (t != NULL)//如果 t 所指向节点不为空,则进入循环进行判断,数据是否为指定数据,是否要删除
    {
        if (t->data == data)//如果 t 所指的数据是否是指定的数据
        {
            //1)尾部删除
            if (t == p->tail)//要删除的节点在链表的尾
            {
                //和删除尾部节点的操作相同
                p->tail = p->tail->prior;
                free(p->tail->next);
                p->tail->next = NULL;
            }
            //2)中间节点删除
            else
            {
                //和删除中间的节点的操作相同
                p_del = t;
                p_del->prior->next = p_del->next;
                p_del->next->prior = p_del->prior;
                t = p_del->next;
                free(p_del);
            }
            p->len--;//链表长度减一
        }
        else//如果 t 所指的数据不是指定的数据
        {
            t = t->next;// t 继续向后遍历
        }
    }
}

/*查询*/
int Search(Doublelink_p p, int data)//data  查询的数据   查询数据,返回下标
{
    if (Empty(p))//先判空
    {
        printf("list is Empty\n");
        return -1;
    }
    Double_p q = p->head->next;//定义一个节点类型指针指向头节点下一个节点   因为头节点数据域无效
    int i = 0;//定义一个变量表示下标
    while (q != NULL)//当 q 指向节点不是NULL,进入循环,遍历链表,判断数据是否相等
    {
        if (q->data == data)// q 所指向节点数据和要查询数据相等
            return i;//返回当前下标
        q = q->next;// q 所指向节点数据和要查询数据不相等,则 q 继续向后遍历
        i++;//下标加一
    }
    return -1;
}

/*修改*/
int Modify(Doublelink_p p, int post, int data)//post 修改的位置  data 要修改成的数据
{
    if (Empty(p) || post < 0 || post >= p->len)//判空和位置是否合理 ,容错判断
    {
        return -1;
    }
    Double_p t = NULL;//定义一个指针 t 置空待用  用于遍历链表
 
    if (post < p->len / 2)   //要修改数据在前半段
    {
        t = p->head;// t 指向头节点
        for (int i = 0; i <= post; i++)//循环向后遍历,找到要修改的位置节点
        {
            t = t->next;//向后移动
        }
    }
    else//要修改数据在后半段
    {
        t = p->tail;// t 指向尾节点
        for (int i = p->len - 1; i > post; i--)//循环向前遍历,找到要修改的位置节点
        {
            t = t->prior;//向前移动
        }
    }
    t->data = data;//修改 t 指向节点的数据
    return 0;
}

int main(int argc, char const *argv[])
{
    Doublelink_p H = Create();//创建一个空双向链表
    Push(H, 0, 1);
    Push(H, 1, 2);
    Push(H, 2, 3);
    Push(H, 3, 50);
    Push(H, 3, 100);
    Push(H, 3, 100);
    Push(H, 3, 100);//插入数据
    printf("插入后\n");
    show(H);
    Modify(H, 2, 300);//修改数据
    printf("\n修改 2 位置 为 300\n");
    show(H);
    DeleteSame(H, 100);//删除指定数据节点
    printf("\n删除数据100的节点\n");
    show(H);
    Delete(H, 2);//删除指定位置节点
    printf("\n删除第 2 个数据后\n");
    show(H);
    printf("\n数据100 的下标 %d\n", Search(H, 100));//查询指定数据下标
    printf("\n数据50 的下标 %d\n", Search(H, 50));

    return 0;
}

5》运行结果


今天的分享就到这里结束啦,如果有哪里写的不好的地方,请指正。
如果觉得不错并且对你有帮助的话请给个三连支持一下吧!

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

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

相关文章

Oracle归档日志满了,导致程序打不开,如何解决。

加油&#xff0c;新时代打工人&#xff01; 归档日志错误&#xff0c;登录不上&#xff0c;只能用system 角色登录&#xff0c; 错误提示 oracle 错误257 archiver error connect internal only until freed 解决cmd进入rman RMAN&#xff08;Recovery Manager&#xff09;是一…

喜报 | 麒麟信安“信创云桌面解决方案”在浙江省委党校应用实施,荣膺国家级示范案例

近日&#xff0c;国家工信部网络安全产业发展中心公布了2023年信息技术应用创新解决方案入围获奖名单&#xff0c;麒麟信安“信创云桌面解决方案”在浙江省委党校成功应用实施&#xff0c;获评国家工信部2023年信息技术应用创新解决方案党务政务领域应用示范案例。 据悉&#…

Python、R用RFM模型、机器学习对在线教育用户行为可视化分析|附数据、代码

全文链接&#xff1a;https://tecdat.cn/?p37409 分析师&#xff1a;Chunni Wu 随着互联网的不断发展&#xff0c;各领域公司都在拓展互联网获客渠道&#xff0c;为新型互联网产品吸引新鲜活跃用户&#xff0c;刺激用户提高购买力&#xff0c;从而进一步促进企业提升综合实力和…

Linux--进程管理和性能相关工具

文章目录 进程状态进程的基本状态其他更多态运行(Running或R)可中断睡眠(Interruptible Sleep 或 S)不可中断睡眠(Uninterruptible Sleep 或 D)停止(Stopped 或 T)僵尸(Zombie 或 Z) 状态转换 进程管理相关工具进程树pstreepstree -ppstree -T 进程信息psps输出属性查看进程的父…

C语言-从键盘输入一个字符串,将其中的小写字母全部转换成大写字母,然后输出到一个磁盘文件test中保存,输人的字符串以“!”结束

题目要求&#xff1a; 从键盘输入一个字符串,将其中的小写字母全部转换成大写字母,然后输出到一个磁盘文件test中保存,输人的字符串以"!”结束 1.实现程序&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() {FILE* fp fopen("…

新160个crackme - 038-Eternal Bliss.3

运行分析 需要输入注册码 PE分析 VB程序&#xff0c;32位&#xff0c;无壳 静态分析&动态调试 使用VB Decompiler静态分析&#xff0c;找到关键函数入口点402AC4 ida跳转至402AC4&#xff0c;按F5反汇编&#xff0c;发现有407行代码&#xff0c;配合VB Decompiler得到的代码…

力扣面试经典算法150题:跳跃游戏 II

跳跃游戏 II 今天的题目是力扣面试经典150题中的数组的中等难度题&#xff1a;跳跃游戏II。 题目链接&#xff1a;https://leetcode.cn/problems/jump-game-ii/description/?envTypestudy-plan-v2&envIdtop-interview-150 题目描述 给定一个非负整数数组 nums&#xff0…

springboot框架中filter过滤器的urlPatterns的匹配源码

如下图所示&#xff0c;我使用WebFilter注解的方式定义了一个过滤器&#xff0c;同时定义了过滤器的过滤条件 urlPatterns为/*,可能很多人都知道filter的/*代表所有URL都匹配&#xff0c;但是源码在哪里呢 先打断点看一下调用链 然后跟着调用链慢慢点&#xff0c;看看哪里开始…

redis面试(二十)读写锁WriteLock

写锁WriteLock 和读锁一样&#xff0c;在这个地方执行自己的lua脚本&#xff0c;我们去看一下 和read没有多大的区别 KEYS[1] anyLock ARGV[1] 30000 ARGV[2] UUID_01:threadId_01:write hget anyLock mode&#xff0c;此时肯定是没有的&#xff0c;因为根本没这个锁 …

LangGPT结构化提示词编写实践 #书生大模型实战营#

1.闯关任务&#xff1a; 背景问题&#xff1a;近期相关研究发现&#xff0c;LLM在对比浮点数字时表现不佳&#xff0c;经验证&#xff0c;internlm2-chat-1.8b (internlm2-chat-7b)也存在这一问题&#xff0c;例如认为13.8<13.11。 任务要求&#xff1a;利用LangGPT优化提示…

电脑如何恢复删除的照片?4种实用恢复办法

在日常生活中&#xff0c;我们经常会因为各种原因误删电脑中的照片&#xff0c;而这些照片往往承载着珍贵的回忆。那么&#xff0c;如果不小心删除了照片&#xff0c;我们该如何恢复呢&#xff1f;下面就为大家介绍几种实用的恢复方法。 一、使用回收站恢复 当我们在电脑上删…

【C++】单例模式的解析与应用

C单例模式&#xff1a;深入解析与实战应用 一、单例模式的基本概念二、C中单例模式的实现方式2.1 懒汉式&#xff08;线程不安全&#xff09;2.2 懒汉式&#xff08;线程安全&#xff09;2.3 饿汉式2.4 静态内部类&#xff08;C11及以后&#xff09; 三、单例模式的优缺点四、实…

基于Transformer进行乳腺癌组织病理学图像分类的方法比较

为了提高视觉变压器的精度和泛化能力,近年来出现了基于Poolingbased Vision Transformer (PiT)、卷积视觉变压器(CvT)、CrossFormer、CrossViT、NesT、MaxViT和分离式视觉变压器(SepViT)等新模型。 它们被用于BreakHis和IDC数据集上的图像分类,用于数字乳腺癌组织病理学。在B…

【机器学习】4. 相似性比较(二值化数据)与相关度(correlation)

SMC Simple Matching Coefficient 评估两组二进制数组相似性的参数 SMC (f11 f00) / (f01f10f11f00) 其中&#xff0c;f11表示两组都为1的组合个数&#xff0c;f10表示第一组为1&#xff0c;第二组为0的组合个数。 这样做会有一个缺点&#xff0c;假设是比较稀疏的数据&…

readpaper在读论文时候的默认规定

红色代表主旨思想 蓝色代表专业名词解析

如何为你的SEO策略找到竞争对手的关键词

你有没有想过你的竞争对手是如何总是设法保持领先一步的&#xff1f;或者他们似乎如何扼杀了您所在行业的大部分搜索流量&#xff1f;他们成功的秘诀可能比你想象的要简单——关键词。 在本文中&#xff0c;我们将解释如何使用 SE Ranking、Google Keyword Planner 和 Bing Ke…

Qt坐标系统之三个坐标系和两个变换

前言 Qt坐标系统由QPainter类控制。它和QPaintDevice和QPaintEngine类一起构成Qt绘图系统的基础。QPainter用于执行绘图操作&#xff0c;QPaintDevice是QPainter用来绘制的一个二维空间的抽象&#xff0c;QPaintEngine提供在不同设备绘图的接口。 Qt 的坐标分为逻辑坐标和物理…

深度学习-局部最小值与鞍点【Datawhale X 李宏毅苹果书 AI夏令营】

在网络优化时&#xff0c;有时会出现随时参数不断更新&#xff0c;训练的损失可能不会再下降&#xff0c;但训练结果并不满意。为什么会出现这样的情况呢&#xff1f; 假设排除数据集优劣的问题&#xff0c;可能需要进一步考虑的是为什么网络不再有新的最优值了&#xff0c;在…

【记录】MICCAI BraTs 2020数据集

简介 本文 MICCAI 竞赛中公布的用于脑肿瘤分割 BraTS 2020数据集,根据官方统计,该数据集中共含有660例数据,训练集369、验证集125,测试集166。不过仅有训练集中给出了详细的分割标签,验证集虽然公布了 但是仅为了验证,没有给出具体的Ground Truth。所以如果进行实验主要还…

昇腾 - AscendCL C++应用开发 图像文件的解码时硬件对图像的宽度和高度的处理方式

昇腾 - AscendCL C应用开发 图像文件的解码时硬件对图像的宽度和高度的处理方式 flyfish 假如是这样的 输入图片格式&#xff08;YUV分量比例&#xff09; jpeg(420) 输出图片格式 YUV420SP NV12 8bit 输出图片宽、高对齐要求 宽2对齐 高2对齐 输出图片宽Stride、高St…