浅谈【数据结构】链表之单链表

news2024/9/20 1:14:57

目录

1、什么是数据?

2、什么是结构

3、什么是数据结构?

4、线性结构(线性表)

4.1线性表的物理结构的实现

5、链表

5.1无头结点的单链表

5.2新内容、老面孔

5.3数组和链表的优缺点

5.4链表的概念

5.5链表的创建步骤

5.5.1创建过程

5.5.2尾插法

5.5.3头插法

5.5.4中间插入法


谢谢帅气美丽且优秀的你看完我的文章还要点赞、收藏加关注

没错,说的就是你,不用再怀疑!!!

希望我的文章内容能对你有帮助,一起努力吧!!!


在IT界有一个公式:程序=数据结构+算法

1、什么是数据?

数据(data)是客观事物的一个符号表示,在计算机科学中式指所有能够输入到计算机中并能够被计算 机程序处理的符号统称。

数据元素(data element)是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。一 个数据元素可以由若干个数据项(data item)组成。数据项是数据不可分割的最小单位。

数据对象(data object)是性质相同的数据元素的集合,是一个数据的子集。

2、什么是结构

结构:数据元素之间的关系的不同性质称之为:结构(structure)

根据数据元素之间的关系的性质不同, 通常有四类基本结构:

  • 集合:结构中的数据元素之间,除了“同属于一个集合的关系”之外,就没有其它关系了。
  • 线性结构:(顺序)
  • 树状结构:(层次)
  • 网状结构:(图状)

3、什么是数据结构?

数据结构的形式定义:数据结构是一个二元组

Data_Sturcture = (D,S)

其中:D是数据元素的有限集,S是D上的关系的有限集(数据元素之间的关系的集合)

结构定义中的“关系”描述的是数据元素之间的逻辑关系(指向关系),因此又称之为:“逻辑结构

逻辑结构”并不一定是数据元素在计算机中的存储(称为:映像)表示,数据元素存储结构称为:数据 的物理结构,又称:“存储结构”。

4、线性结构(线性表)

例子:

26个英文字母(A、B、C、D...Z)

 班上同学的学号

日期

...

线性表的数据元素可以是各种各样、但是同一个线性表中的元素必须要具有相同的特性,即同属于同一 个数据对象,相邻的元素之间存储序偶关系。

若将线性表记为:(a1、a2、a3....an)那么则会存在以下性质:

  • 存在唯一的一个被称为:"第一个"的数据元素
  • 存在唯一的一个被称为:“最后一个”的数据元素
  • 除了第一个元素之外,集合中的每一个数据元素均只有一个前驱结点
  • 除了最后一个元素之外,集合中的每一个数据元素均只有一个后继结点
    • 前驱结点:前面有结点
    • 后继结点:后面有结点

4.1线性表的物理结构的实现

在计算机中是怎么存储线性表的

  • 顺序结构
    • 线性表的顺序表示的是用一组地址连续的存储单元依次存储线性表中数据元素
      • 数组
      • 动态开辟内存空间
  • 链式结构
    • 链式结构存储用的存储单元的地址不一定连续的

5、链表

链表:链式存储的线性表

5.1无头结点的单链表

无头结点的单链表:没有头结点的单向的链式存储的线性表。

问题:我们在定义数组的时候,需要规定数组的长度,定义好了就不能修改了。

int a[10]; // 定义了一个元素为int类型的数组,数组大小为10个int类型

// 修改数组的大小

a = malloc(sizeof(int) * 100); // 不行的,因为a是常量指针,不能改变a指向。

链表就是有一个或者是多个结构体通过指针去进行指向关系构成的。

5.2新内容、老面孔

先来看一下下面这种结构体类型(带指针的结构体)

struct data

{

        int d_data; // 数据

        struct data *p_data; // 指针 存储数据元素的地址。在单链表结构里面,存储下一个数据元素的 内存地址

};

即想要按需分配,又可以进行灵活的删除和增加元素的时候:链表就可以解决这样的问题。

5.3数组和链表的优缺点

数组:

  • 优点:
    • 数组将元素按顺序存放在内存中,而且元素的内存占用都是相同的,因为它的地址连续、所有 可以通过下标快速的对数组元素进行访问。
    • 内存不会有太多的碎片化
  • 缺点:
    • 有可能造成内存资源的浪费
    • 数组元素的插入和删除比较复杂
    • 有可能内存资源不够用,容易造成段错误(内存越界)

链表:

  • 优点:
    • 插入和删除很方便
    • 可以按需分配,不会造成空间的浪费
  • 缺点:
    • 空间不是连续,访问相对数组而言效率要低很多碎片化比数组要高

获取结构体空间方式:

  • 动态内存开辟:堆空间的内存
    • 特性:空间的生存期是随进程持续性,需要手动释放
    • 保存空间地址
  • 通过定义变量:栈空间的内存
    • 特性:作用域结束,空间就会被系统释放
    • 用变量名

5.4链表的概念

链表就是由一个或者是多个包含指针成员变量的结构体,通过其指针变量来指向其他同类型结构体内存 地址来形成一种逻辑上的链式的数据结构。我们把每一个结构体实例(变量/空间)称为该链表的:结点 (node)

  • 首结点
    • 是链表中唯一一个没有前驱结点、只有后继结点的结点。是唯一一个指向其他的结点的结点
    • 首结点同时也是整个链表的首地址。
  • 尾结点
    • 是链表中唯一一个没有后继结点,只有前驱结点的结点。是唯一一个被其他结点指向的结点。

注意:尾结点的指针成员是指向 空 的

  • 可以保证尾结点的指针成员不会成为野指针
  • 可以通过判断指针是否指向空来判断是不是查询到了最后一个元素

***无头结点单链表基础示例***

/*
    无头结点的单链表
*/



#include <iostream>

/*
    结点类型
*/
struct node
{
    // 用来存储数据的空间(成员)称为:数据域
    int data;       

    // 用来保存其他结点的地址(关系)称为:指针域
    struct node *next;
};


/*
    @brief:创建一个新链表
    @return:返回新链表的首结点的地址
*/
struct node *createNewList()
{
    // 新链表的首结点指针
    struct node *newList = nullptr;

    // 循环通过数据不断的去增加新结点到链表中
    while(1)
    {
        int data = -1;
        // 通过键盘获取数据
        std::cin >> data;

        // 判断退出条件
        if(data == -1)
            break;

        // 申请了一个新结点的空间
        struct node *newNode = new struct node;
        newNode->data = data; // 将通过键盘获取到的数据存入结构体的数据域中
        newNode->next = nullptr; // 因为它是一个新结点,暂时是没有后继结点

        // 增加到链表里面:向后增加(尾插法)
        // 做第一次判断:链表中有没有结点
        if(newList == nullptr)
        {
            // 如果newList是nullptr说明该链表里面为空,当前的新节点就是首届点
            newList = newNode;
            continue; // 继续增加新结点
        }

        // 搞一个临时指针,来指向首结点
        struct node *node_ptr = newList;

        // 找尾结点
        while(node_ptr->next)node_ptr = node_ptr->next;
        
        // 到这个位置 node_ptr 此时指向的结点是尾结点
        // 就可以把newNode作为尾结点的后继结点添加到链表里面去了
        node_ptr->next = newNode;
    }
    return newList;
}

// 打印链表(遍历方法)
void printList(struct node *list)
{
    // 判断是不是空链表
    if(list == nullptr)
    {
        std::cout << "链表为空" << std::endl;
        return;
    }

    std::cout << "List( ";
    // 如果不为空打印链表元素
    while(list) // 只要list不为空就一直循环
    {
        // list本来就可以表示首结点
        std::cout << list->data << " ";

        // 让list移动到下一个结点
        list = list->next;
    }
    std::cout << ")" << std::endl;
}

int main()
{
    // 不在栈空间里面申请结点空间
    // 创建一个链表
    struct node *newList = createNewList();

    // 打印链表元素
    printList(newList); // 传入的值是newList存储的地址,并非newList自己的地址
    return 0;
}

5.5链表的创建步骤

  • 每获取一个数据,就一个创建一个结构体空间来存储数据
  • 把数据存放到结构体体空间中
  • 把这个新的结构体加入到链表中

5.5.1创建过程

  • 从无到有的过程:
    • 第一个结点诞生,此时首结点和尾结点都是第一个结点本身
  • 从少到多的过程
    • 在链表中增加结点,可以在链表尾部增加(尾插法)、也可以在链表的中间增加(中间插入 法)、也可以在链表头部增加(头插法)
  • 图示:

5.5.2尾插法

新节点增加在链表原尾结点的后面,即链表原尾结点要指向新结点(成为原尾结点的后继结点),然后 新结点就代替原尾结点成为新的尾结点

  • 特点:先加入的链表的结点在前面,而后加入结点在后面

5.5.3头插法

新结点在原首结点的前面,即新结点指向首结点(成为原首结点的前驱结点),然后新结点代替原首结 点称为新的首结点

  • 特点:先加入链表的结点在后面,后加入链表的结点在前面

5.5.4中间插入法

通过和每个结点数据进行比对,从而找到新结点存放的位置。通过中间插入法创建的链表是有序的。

  • 特点:链表的结点数据是有序的。

***单链表基础操作示例***

/*
    无头结点的单链表
*/

#include <iostream>

/*
    结点类型
*/
struct node
{
    // 用来存储数据的空间(成员)称为:数据域
    int data;       

    // 用来保存其他结点的地址(关系)称为:指针域
    struct node *next;
};


/*
    @brief:头部插入
    @param: list 需要增加新数据的链表指针
    @param: data 是需要存入链表的数据
    @return : 链表首地址
*/
struct node* addNodeHead(struct node *list,int data)
{
    // list 表示就是一个链表(链表首地址/首结点的地址)头插法其实就是在它的首结点前面插入
    struct node *newNode = new struct node;
    newNode->data = data; // 数据存入结点数据域
    newNode->next = list; // 因为新结点作为新的首结点存入链表,那么原有的首结点就变成了新节点后继结点
    return newNode; // 返回newNode因为newNode变成了新的首结点了。更新链表的首地址
}

/*
    @brief: 尾部插入
    @param: list 需要增加新数据的链表指针
    @param: data 是需要存入链表的数据
    @return : 链表首地址
*/
struct node* addNodeTail(struct node *list,int data)
{
    struct node *newNode = new struct node;
    newNode->data = data; // 将通过键盘获取到的数据存入结构体的数据域中
    newNode->next = nullptr; // 因为它是一个新结点,暂时是没有后继结点

    // 搞一个临时指针,来指向首结点
    struct node *node_ptr = list;

    // 找尾结点
    while(node_ptr->next)node_ptr = node_ptr->next;
    
    // 到这个位置 node_ptr 此时指向的结点是尾结点
    // 就可以把newNode作为尾结点的后继结点添加到链表里面去了
    node_ptr->next = newNode;

    return list;
}

/*
    @brief : 中间插入法
    @param : list 需要增加数据的链表首地址
    @param : data 需要增加到链表的数据
    @return : 链表首地址
*/
struct node *addNodeMid(struct node *list,int data)
{
    struct node *newNode = new struct node;
    newNode->data = data; // 将通过键盘获取到的数据存入结构体的数据域中
    newNode->next = nullptr; // 因为它是一个新结点,暂时是没有后继结点

    // 循环比较数据 大到小  小到大
    // struct node *node_previce = nullptr;
    // struct node *node_current = list;

    // 第一种方法两个指针
    // while(node_current)
    // {
    //     if(node_current->data > data)
    //         break; // 找到插入位置了
        
    //     // 把两个指针往后移
    //     node_previce = node_current;
    //     node_current = node_current->next;
    // }

    // // 进行插入
    // if(node_previce == nullptr) // 说明需要作为首结点插入(头插入)
    // {
    //     // 头插代码
    // }
    // else
    // {
    //     // 直接插入到node_previce的后面
    //     newNode->next = node_previce->next; // 要成为node_previce下一个结点的前驱结点
    //     node_previce->next = newNode; // 然后让newNode成为node_previce的后继结点
    // }

    // 第二种方法提前判断
    struct node *node_ptr = list;
    // 如果第一个结点就比data大说明要插入到最前面(头插法)
    if(node_ptr->data > data)
    {
        // 头插法
        list = addNodeHead(list,data);
        return list;
    } 
    while(node_ptr&&node_ptr->next)
    {
        std::cout << node_ptr->data << std::endl;
        
        if(node_ptr->data < data && node_ptr->next->data >= data) // 大于前一个小于后一个
        {
            // 将新结点的next指向当前结点的后继结点node_ptr->next
            newNode->next = node_ptr->next;

            // 让newNode成为node_ptr的后继结点
            node_ptr->next = newNode;
            return list; // 退出增加
        }
        
        // 把指针往后移
        node_ptr = node_ptr->next;
    }

    // 严谨判断一下node_ptr->next是不是为空
    if(node_ptr->next == nullptr) // 尾插法
    {
        node_ptr->next = newNode;
    }

    return list;
}


/*
    @brief:创建一个新链表
    @return:返回新链表的首结点的地址
*/
struct node *createNewList()
{
    // 新链表的首结点指针
    struct node *newList = nullptr;

    // 循环通过数据不断的去增加新结点到链表中
    while(1)
    {
        int data = -1;
        // 通过键盘获取数据
        std::cin >> data;

        // 判断退出条件
        if(data == -1)
            break;

        // 做第一次判断:链表中有没有结点
        if(newList == nullptr)
        {
            struct node *newNode = new struct node;
            newNode->data = data; // 将通过键盘获取到的数据存入结构体的数据域中
            newNode->next = nullptr; // 因为它是一个新结点,暂时是没有后继结点

            // 如果newList是nullptr说明该链表里面为空,当前的新节点就是首届点
            newList = newNode;
            continue;
        }

        // 通过中间插入法增加结点
        newList = addNodeMid(newList,data);

        // 通过尾插法增加结点
        // newList = addNodeTail(newList,data);
    }
    return newList;
}



// 打印链表(遍历方法)
void printList(struct node *list)
{
    // 判断是不是空链表
    if(list == nullptr)
    {
        std::cout << "链表为空" << std::endl;
        return;
    }

    std::cout << "List( ";
    // 如果不为空打印链表元素
    while(list) // 只要list不为空就一直循环
    {
        // list本来就可以表示首结点
        std::cout << list->data << " ";

        // 让list移动到下一个结点
        list = list->next;
    }
    std::cout << ")" << std::endl;
}

int main()
{
    // 不在栈空间里面申请结点空间
    // 创建一个链表
    struct node *newList = createNewList();

    // 打印链表元素
    printList(newList); // 传入的值是newList存储的地址,并非newList自己的地址


    // 删除元素
    int data = 0;
    std::cin >> data ;
    delNodeData(newList,data);

    // 打印链表元素
    printList(newList); // 传入的值是newList存储的地址,并非newList自己的地址
    return 0;
}

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

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

相关文章

【Linux】自动化构建工具makefile

目录 背景 makefile简单编写 .PHONY makefile中常用选项 makefile的自动推导 背景 会不会写makefile&#xff0c;从一个侧面说明了一个人是否具备完成大型工程的能力 ​ ◉ 一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;mak…

MyBatis框架搭建与代码解读

前言: MyBatis是一个灵活的持久层框架&#xff0c;适合与数据库交互&#xff0c;支持自定义SQL和高级映射。搭建MyBatis的基本步骤包括&#xff1a; 环境准备&#xff1a;安装JDK、Maven和IDE。创建项目&#xff1a;建立Maven项目并设置基本信息。添加依赖&#xff1a;在pom.…

「Python数据分析」Pandas进阶,利用concat()函数连接数据(一)

在我们迈向中高级出局数据分析的过程中&#xff0c;数据的合并和连接&#xff0c;是一个非常重要的技能。 现实中&#xff0c;分散在各种数据库&#xff0c;各种数据表格&#xff0c;各种数据存储设备当中的&#xff0c;各式各样的数据&#xff0c;是我们进行数据分析的基础&a…

泛微开发修炼之旅--42Ecology大日志查看软件LogView Pro及教程

我们在项目上经常要看ecology的日志信息&#xff0c;当日志达到几百兆或者时几个G的时候&#xff0c;想要查看日志并且非常方便搜索日志中的信息&#xff0c;并不容易。 今天给大家大日志工具和教程&#xff0c;在附件中&#xff0c;各位自取吧&#xff01; 文章链接&#xff…

老师如何制作分班查询系统?

随着新学期的钟声敲响&#xff0c;老师们又迎来了一年中最忙碌的时期。不仅要处理日常的教学准备工作&#xff0c;还要面对一项重要而繁琐的任务——新生分班。分班完成后&#xff0c;老师们还需要将分班结果及时准确地通知给每一位家长&#xff0c;确保信息的传递无误。这项工…

epoll+线程池模型

&#x1f525;博客主页&#xff1a; 我要成为C领域大神&#x1f3a5;系列专栏&#xff1a;【C核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 ​ 负载均衡技术 …

AI工具 GPT 学术优化 (GPT Academic) 安装实践

GPT 学术优化 (GPT Academic)是一个综合的AI GPT工具包&#xff0c;可以完成各种gpt辅助的工作&#xff0c;比如代码解读、翻译、读论文等功能。官网&#xff1a;GitHub - binary-husky/gpt_academic: 为GPT/GLM等LLM大语言模型提供实用化交互接口&#xff0c;特别优化论文阅读…

Flutter 自动化测试 -appium-flutter-driver

上篇文章有讲述如何通过FlutterDriver实现集成测试Flutter 应用自动化测试-集成测试篇 不熟悉的小伙伴可以先去看看。 什么是Appium Flutter Driver&#xff1f; 作为Flutter开发&#xff0c;FlutterDriver是足够帮助他们进行测试的&#xff0c;而作为自动化测试工程师最大的困…

FFmpeg的入门实践系列五(编程入门之属性查看)

欢迎诸位来阅读在下的博文~ 在这里&#xff0c;在下会不定期发表一些浅薄的知识和经验&#xff0c;望诸位能与在下多多交流&#xff0c;共同努力 文章目录 前期博客参考书籍一、AVFormatContext结构体1. 结构定义2. 字段说明3.示例1&#xff08;打开与关闭音视频文件&#xff0…

机器人学——机械臂轨迹规划-1

引言 理想轨迹 步骤-1 步骤-2 笛卡尔空间下的轨迹规划 步骤-1 步骤-2 三次多项式 矩阵形式求解 det(T): 行列式&#xff0c;非齐次多项式&#xff0c;结果不为零&#xff0c;有唯一解、行列式为零&#xff08;无穷解/无解&#xff0c;还需查看增广矩阵的秩&#xff09; 速度…

Linux网络编程:多路转接--select

1. 初识select 系统提供select函数来实现多路复用输入/输出模型. select系统调用是用来让我们的程序监视多个文件描述符的状态变化的; 程序会停在select这里等待&#xff0c;直到被监视的文件描述符有一个或多个发生了状态改变 select只负责等待&#xff0c;可以等待多个fd&a…

内容创作者福音,4款文章改写神器轻松提升文章质量

在信息爆炸的时代&#xff0c;内容创作成为了连接世界的重要桥梁。作为一名专业创作者&#xff0c;我深知保持内容原创性和高质量的重要性。然而&#xff0c;灵感有时会枯竭&#xff0c;改写文章成为一项耗时且艰巨的任务。幸运的是&#xff0c;市面上有一些文章改写神器&#…

Flask+LayUI开发手记(四):弹出层实现增删改查功能

在上一节用dataTable实现数据列表时&#xff0c;已经加了表头工具栏和表内工具栏&#xff0c;栏内的按钮功能都是用来完成数据的增删改查了&#xff0c;这又分成两类功能&#xff0c;一类是删除或设置&#xff0c;这类功能简单&#xff0c;只需要选定记录&#xff0c;然后提交到…

Flutter 自动化测试 - 集成测试篇

Flutter集成测试 Flutter官方对Flutter应用测试类型做了三个阶段划分&#xff0c;分别为Unit&#xff08;单元&#xff09;测试、Widget&#xff08;组件&#xff09;测试、Integration&#xff08;集成&#xff09;测试。按照维护成本来看的话从左到右依次增高&#xff0c;按照…

预测癌症免疫治疗反应-TIDE数据库学习及知识整理

TIDE&#xff08;Tumor Immune Dysfunction and Exclusion&#xff09; 是一个用于预测癌症患者对免疫检查点抑制剂&#xff08;如PD-1/PD-L1抑制剂&#xff09;反应的算法。研究者通过检测肿瘤建模队列中每个基因的表达与效应性毒性T淋巴细胞(CTL)浸润水平的相互关系及对生存情…

Open3D 近似点体素滤波(36)

Open3D 近似点体素滤波(36) 一、算法介绍二、算法实现1.代码2.效果一、算法介绍 这个算法也是体素滤波, 它保留的点是近似点,也就是新的点,原始点云中对应位置是不存在这些点的。其他的看着类似,下面是代码,滤波抽稀结果 二、算法实现 1.代码 代码如下(示例): …

学习文件IO,让你从操作系统内核的角度去理解输入和输出(Java实践篇)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

【在Linux世界中追寻伟大的One Piece】应用层协议HTTP

目录 1 -> HTTP协议 2 -> 认识URL 2.1 -> urlencode和urldecode 3 -> HTTP协议请求与响应格式 3.1 -> HTTP请求 3.2 -> HTTP响应 4 -> HTTP的方法 4.1 -> HTTP常见方法 5 -> HTTP的状态码 6 -> HTTP常见Header 7 -> 最简单的HTTP服…

Linux系统报错“version ‘GLIBC_2.34‘ not found”解决方法

注意&#xff0c;此文章慎用&#xff0c;glibc不可随意升级&#xff0c;可能导致系统崩溃 一、查看版本 ldd --version 二、添加高版本源 sudo vi /etc/apt/sources.list.d/my.list 进入编辑页面 "i"键进入插入模式 输入源 deb http://th.archive.ubuntu.com/…

【信创】推荐一款超级好用的文件同步备份工具 _ 统信 _ 麒麟 _ 方德

往期好文&#xff1a;【信创】统信UOS打包工具介绍与使用教程 Hello&#xff0c;大家好啊&#xff01;今天给大家推荐一款在Linux系统上超级好用的文件同步和备份工具——FreeFileSync。无论是在日常工作还是数据管理中&#xff0c;文件同步和备份都是至关重要的任务。FreeFile…