【数据结构与算法】第四章:链表拓展与线性表总结

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
EMR Serverless StarRocks,5000CU*H 48000GB*H
应用型负载均衡 ALB,每月750个小时 15LCU
简介: 前面介绍了线性表的顺序表和链表,本章讲对链表应用拓展,具体介绍单链表、循环链表、双向链表等,并将顺序表与链表进行比较,更直观的感受两种不同结构的差异所在以及各自的优势短板,最后对线性表进行总结。

 🙊🙊作者主页:🔗求不脱发的博客

📔📔 精选专栏:🔗数据结构与算法

📋📋 精彩摘要:前面介绍了线性表的顺序表和链表,本章讲对链表应用拓展,具体介绍单链表、循环链表、双向链表等,并将顺序表与链表进行比较,更直观的感受两种不同结构的差异所在以及各自的优势短板,最后对线性表进行总结。

💞💞觉得文章还不错的话欢迎大家点赞👍➕收藏⭐️➕评论💬支持博主🤞


📚目录

📖【数据结构与算法】第四章:链表拓展与线性表总结

📝1️⃣单链表

📝2️⃣循环链表

📝3️⃣双向链表

📝4️⃣顺序表和链表的比较

📝5️⃣线性表的应用

📝6️⃣线性表总结


📖【数据结构与算法】第四章:链表拓展与线性表总结


📝1️⃣单链表

✨定义和表示

根据链表结点所含指针个数指针指向指针连接方式,可将链表分为单链表、循环链表、双向链表、二叉链表等。其中单链表、循环链表和双向链表用于实现线性表的链式存储结构,其他用于实现树和图等非线线性结构。

用单链表标示线性表时,数据元素之间的逻辑关系是由结点中的指针指示的。换句话说,指针为数据元素之间的逻辑关系的映像,则逻辑上相邻的两个数据元素其存储的物理位置不要求相邻,由此,这种存储结构为非顺序映像链式映像

根据结点插入位置的不同,链表的创建方法可分为前插法后插法

✨初始化(前插法)

前插法是通过将新结点逐个插入链表的头部(头结点之后)来创建链表,每次申请一个新结点,读入相应的数据元素值,然后将新结点插入到头结点之后image.gif编辑

【算法步骤】:

    1. 创建一个只有头结点的空链表。
    2. 根据待创建链表包括的元素个数n,循环n次执行以下操作:
      1. 生成一个新结点*p;
      2. 输入元素值赋给新结点*p的数据域;
      3. 将新结点*p插入到头结点之后。

        【算法描述】:

        void CreateList_H(LinkList &L,int n)
        {    //逆位序输入n个元素的值,建立带表头结点的单链表L
            L=new LNode;
            L->next=NULL; //先建立一个带头结点的空链表
            for(i=0;i>p->data; //输入元素值赋给新结点p的数据域
                p->next=L->next;
                L->next=p; //将新结点p插入到头结点之后
            }
        }

        image.gif

        ✨初始化(后插法)

        后插法是通过将新结点逐个插入到链表的尾部来创建链表。同前插法一样,每次申请一个新结点,读入相应的数据元素值。不同的是,为了使新结点能够插入到表尾,需要增加一个尾指针r指向链表的尾结点。image.gif编辑

        【算法步骤】:

          1. 创建一个只有头结点的空链表。
          2. 尾指针r初始化,指向头结点。
          3. 根据创建链表包括的元素个数n,循环n次执行以下操作:
            1. 生成一个新结点*p;
            2. 输入元素值赋给新结点*p的数据域;
            3. 将新结点p插入到尾结点r之后;
            4. 尾指针r指向新的尾结点*p。

              【算法描述】:

              void CreateList_R(LinkList &L,int n)
              {
                  //正位序输入n个元素的值,建立带表头结点的单链表L
                  L=new LNode;
                  L->next=NULL; //先建立一个带头结点的空链表
                  r=L; //尾指针r指向头结点
                  for(i=0;i>p->data;//输入元素值赋给新结点p的数据域
                      p->next=NULL; 
                      r->next=p; //将新结点p插入尾结点r之后
                      r=p; //r指向新的尾结点p
                  }
              }

              image.gif

              📝2️⃣循环链表

              image.gif编辑

              ✨定义和表示

              循环链表(Circular Linked List)是另一种形式的链式存储结构。其特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。由此,从表中任一结点出发均可找到表中其他结点,图所示为单链的循环链表。类似地,还可以有多重链的循环链表。

              循环单链表的操作和单链表基本一致,差别仅在于:当链表遍历时,判别当前指针p是否指向表尾结点的终止条件不同。

              在单链表中,判别条件为 p!=NULL p->next!=NULL,而循环单链表的判别条件为p!=Lp->next!=L。

              对于循环链表,有时不给出头指针,而给出尾指针,这样可以更快的找出第一个和最后一个结点。

              ✨循环链表的合并

              将两个线性表合并成一个表时,仅需将第一个表的尾指针指向第二个表的第一个结点,第二个表的尾指针指向第一个表的头结点,然后释放第二个表的头结点。当线性表循环链表作存储结构时,这个操作仅需改变两个指针值即可。

              【算法步骤】:

                1. 将第一个表的尾指针指向第二个表的第一个结点
                2. 第二个表的尾指针指向第一个表的头结点
                3. 释放第二个表的头结点。

                【算法描述】:

                LinkList Connect(LinkList Ta,LinkList Tb)
                {
                    //假设Ta、Tb都是非空的单循环链表
                    p = Ta->next;//p存表头结点
                    Ta->next = Tb->next->next;//Tb表头结点连接Ta表尾
                    delete Tb->next;//释放Tb表头结点
                    Tb->next = p;//修改指针
                    return Tb;
                }

                image.gif


                📝3️⃣双向链表

                image.gif编辑

                ✨定义和表示

                对于单链表及循环链表这类链式存储结构的结点中,只有一个指示直接后继的指针域,由此,从某个结点出发只能顺指针向后寻查其他结点。若要寻查结点的直接前驱,则必须从表头指针出发。换句话说,在单链表中,查找直接后继结点的执行时间为O(1),而查找直接前驱的执行时间为O(n)

                为克服单链表这种单向性的缺点,可利用双向链表(Double Linked List)。

                顾名思义,在双向链表的结点中有两个指针域,一个指向直接后继,另一个指向直接前驱。

                ✨双向链表的存储结构

                和单链的循环表类似,双向链表也可以有循环表,链表中存有两个环,只有一个表头结点的空表。

                typedef struct DuLNode
                {
                    ElemType data; //数据域
                    struct DuLNode *prior; //直接前驱
                    struct DuLNode next; //直接后继
                }DuLNode,DuLinkList;

                image.gif

                ✨双向链表的插入(在第i个位置上插入元素e)image.gif编辑

                【算法步骤】:

                  1. 找到L中第i个元素的位置指针p。
                  2. 生成新结点s,并将结点s数据域置为要插入的元素e。
                  3. 将结点*s插入L中。

                  【算法描述】:

                  Status ListInsert_DuL(DuLinkList &L,int i,ElemType e)
                  {//在带头结点的双向链表L中第i个位置之前插入元素e
                      if(!(p=GetElem_DuL(L,i))) //在L中确定第i个元素的位置指针p
                          return ERROR; //p为NULL时,第i个元素不存在
                      s=new DuLNode; //生成新结点s
                      s->data=e; //将结点s数据域置为e
                      s->prior=p->prior; //将结点*s插入L中
                      p->prior->next=s;//指针转换
                      s->next=p; 
                      p->prior=s;
                      return OK;
                  }

                  image.gif

                  ✨双向链表的删除(删除带头结点的双向链表L中的第i个元素)

                  image.gif编辑

                  【算法步骤】:

                    1. 找到第i个元素的位置指针p。
                    2. 修改被删结点的前驱结点的后继指针。
                    3. 修改被删结点的后继结点的前驱指针。

                    【算法描述】:

                    Status ListDelete_DuL(DuLinkList &L,int i)
                    {    //删除带头结点的双向链表L中的第i个元素
                        if(!(p=GetElem_DuL(L,i))) //在L中确定第i个元素的位置指针p
                            return ERROR; //p为NULL时,第i个元素不存在
                        p->prior->next=p->next; //修改被删结点的前驱结点的后继指针,对应图2.21①
                        p->next->prior=p->prior; //修改被删结点的后继结点的前驱指针,对应图2.21②
                        delete p; //释放被删结点的空间
                        return OK;
                    }

                    image.gif

                    📝4️⃣ 顺序表和链表的比较

                    对于线性表的两种结构:顺序表和链表,在实际应用中,不能笼统地说哪种存储结构更好,由于它们各有优缺点,选用哪种存储结构,则应根据具体问题作具体分析,通常从空间性能和时间性能两个方面作比较分析。

                    比较项目  \  存储结构 顺序表 链表
                    空间 存储空间 5预先分配,会导致空间闲置或溢出现象 动态分配,不会出现存储空间闲置或溢出现象
                    存储密度 不用为表示结点间的逻辑关系而增加额外的存储开销,存储密度等于1 需要借助指针来体现元素间的逻辑关系,存储密度小于1
                    时间 存取元素 随机存取,按位置访问元素的时间复杂度为O(1) 顺序存取,按位置访问元素的时间复杂度为O(n)
                    插入、删除 平均移动约为表中一半元素,时间复杂度为O(n) 不需要移动元素,确定插入、删除位置后,时间复杂度为O(1)
                    适用

                    ① 表长变化不大,且能事先确定变化的范围。

                    ② 很少进行插入或者删除操作,经常按元素位置序号访问数据元素。

                    ① 长度变化大。

                    ② 频繁进行插入或者删除操作。


                    📝5️⃣线性表的应用

                    ✨顺序有序表的合并

                    【算法步骤】:

                      1. 创建一个表长为m+n的空表LC。
                      2. 指针pc初始化,指向LC的第一个元素。
                      3. 指针pa和pb初始化,分别指向LA和LB的第一个元素。
                      4. 当指针pa和pb均未到达相应表尾时,则依次比较pa和pb所指向的元素值,从LA或LB中“摘取”元素值较小的结点插入到LC的最后。
                      5. 如果pb已到达LB的表尾,依次将LA的剩余元素插入LC的最后。
                      6. 如果pa已到达LA的表尾,依次将LB的剩余元素插入LC的最后。

                      【算法描述】:

                      void MergeList_Sq(SqList LA,SqList LB,SqList &LC)
                      {    //已知顺序有序表LA和LB的元素按值非递减排列
                          //归并LA和LB得到新的顺序有序表LC,LC的元素也按值非递减排列
                          LC.length=LA.length+LB.length; //新表长度为待合并两表的长度之和
                          LC.elem=new ElemType[LC.length]; //为合并后的新表分配一个数组空间
                          pc=LC.elem; //指针pc指向新表的第一个元素
                          pa=LA.elem; pb=LB.elem; //指针pa和pb的初值分别指向两个表的第一个元素
                          pa_last=LA.elem+LA.length-1; //指针pa_last指向LA的最后一个元素
                          pb_last=LB.elem+LB.length-1; //指针pb_last指向LB的最后一个元素
                          while((pa<=pa_last)&&(pb<=pb_last)) //LA和LB均未到达表尾
                          {
                              if(pa<=pb) pc++=pa++; //依次“摘取”两表中值较小的结点插入到LC的最后
                              else pc++=pb++;
                          }
                          while(pa<=pa_last) pc++=pa++; //LB已到达表尾,依次将LA的剩余元素插入LC的最后
                          while(pb<=pb_last) pc++=pb++; //LA已到达表尾,依次将LB的剩余元素插入LC的最后
                      }

                      image.gif

                      ✨链式有序表的合并

                      【算法步骤】:

                        1. 指针pa和pb初始化,分别指向LA和LB的第一个结点。
                        2. LC的结点取值为LA的头结点。
                        3. 指针pc初始化,指向LC的头结点。
                        4. 当指针pa和pb均未到达相应表尾时,则依次比较pa和pb所指向的元素值,从LA或LB中“摘取”元素值较小的结点插入到LC的最后。
                        5. 将非空表的剩余段插入到pc所指结点之后。
                        6. 释放LB的头结点。

                        【算法描述】:

                        void MergeList_L(LinkList &LA,LinkList &LB,LinkList &LC)
                        {    //已知单链表LA和LB的元素按值非递减排列
                            //归并LA和LB得到新的单链表LC,LC的元素也按值非递减排列
                            pa=LA->next;pb=LB->next; //pa和pb的初值分别指向两个表的第一个结点
                            LC=LA; //用LA的头结点作为LC的头结点
                            pc=LC; //pc的初值指向LC的头结点
                            while(pa&&pb)
                            {//LA和LB均未到达表尾,依次“摘取”两表中值较小的结点插入到LC的最后
                                if(pa->data<=pb->data) //“摘取”pa所指结点
                                {
                                    pc->next=pa; //将pa所指结点链接到pc所指结点之后
                                    pc=pa; //pc指向pa
                                    pa=pa->next; //pa指向下一结点
                                }
                                else //“摘取”pb所指结点
                                {
                                    pc->next=pb; //将pb所指结点链接到pc所指结点之后
                                    pc=pb; //pc指向pb
                                    pb=pb->next; //pb指向下一结点
                                }
                             } //while
                            pc->next=pa?pa:pb; //将非空表的剩余段插入到pc所指结点之后
                            delete LB; //释放LB的头结点
                        }

                        image.gif


                        📝6️⃣线性表总结

                        (1)线性表的逻辑结构特性是指数据元素之间存在着线性关系,在计算机中表示这种关系的两类不同的存储结构是顺序存储结构(顺序表)和链式存储结构(链表)。

                        (2)对于顺序表,元素存储的相邻位置反映出其逻辑上的线性关系,可借助数组来表示。给定数组的下标,便可以存取相应的元素,可称为随机存取结构。而对于链表,是依靠指针来反映其线性逻辑关系的,链表结点的存取都要从头指针开始,顺链而行,所以不属于随机存取结构,可称之为顺序存取结构。不同的特点使得顺序表和链表有不同的适用情况,表中分别从空间、时间和适用情况3方面对二者进行了比较。

                        (3)对于链表,除了常用的单链表外,在本章还讨论了两种不同形式的链表,即循环链表和双向链表,它们有不同的应用场合。下表对三者的几项有差别的基本操作进行了比较。

                        相关实践学习
                        SLB负载均衡实践
                        本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
                        负载均衡入门与产品使用指南
                        负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
                        相关文章
                        |
                        1月前
                        |
                        算法 索引
                        ❤️算法笔记❤️-(每日一刷-141、环形链表)
                        ❤️算法笔记❤️-(每日一刷-141、环形链表)
                        45 0
                        |
                        17天前
                        |
                        存储 C语言
                        【数据结构】手把手教你单链表(c语言)(附源码)
                        本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
                        44 4
                        |
                        18天前
                        |
                        算法 安全 搜索推荐
                        2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
                        数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
                        |
                        18天前
                        |
                        存储 Web App开发 算法
                        2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
                        数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
                        |
                        1月前
                        |
                        存储 缓存 算法
                        经典算法之链表篇(三)
                        经典算法之链表篇(三)
                        |
                        1月前
                        |
                        算法
                        经典算法之链表篇(二)
                        经典算法之链表篇(二)
                        |
                        1月前
                        |
                        算法 索引
                        经典算法之链表篇
                        经典算法之链表篇
                        |
                        1月前
                        |
                        算法
                        ❤️算法笔记❤️-(每日一刷-160、相交链表)
                        ❤️算法笔记❤️-(每日一刷-160、相交链表)
                        17 1
                        |
                        17天前
                        |
                        C语言
                        【数据结构】双向带头循环链表(c语言)(附源码)
                        本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
                        37 0
                        |
                        18天前
                        |
                        算法 安全 搜索推荐
                        2024重生之回溯数据结构与算法系列学习之王道第2.3章节之线性表精题汇总二(5)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
                        IKU达人之数据结构与算法系列学习×单双链表精题详解、数据结构、C++、排序算法、java 、动态规划 你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!