【开卷数据结构 】 顺序表与链表(二)

简介: 【开卷数据结构 】 顺序表与链表

🌺链表


🌷链表的定义


线性表的链式存储又称为单链表,其特点是用一组任意的存储单元来存储线性表的数据元素。


为了表示每个数据元素ai与其后续数据元素ai+1之间的逻辑关系,a1除了存储自身的信息之外,还需要存储一个指向其后续内容的信息(一般为后续内容的地址)。这两部分信息组成数据元素ai的存储映像,称为结点。

7f325dec46ba8245c4efae62482334bb_e367d391ed824147bcf0ed36296caccd.png


单链表中结点类型的描述如下:


typedef struct LNode  //定义单链表结点类型 
{
  ElemType data;    //数据元素 
  struct LNode *next;  //指向下一地址 
}LNode, *LinkList;


🌷链表的存储方式


整个链表的存取必须从头指针开始进行,头指针指向链表的第一个结点的存储位置。同时由于最后一个元素没有直接后继,则单链表的最后一个结点的指针为空(NULL)


我们来想一想,如果用链表来存储【Ceylan】,它的逻辑状态是什么样的呢?


首先应该需要一个头指针L,它指向第一个元素 'C',通过 'C' 所在结点的信息,可以访问下一节点 'e' 所在位置,以此类推,就可以完整找到整个【Ceylan】

2eca93a83e64ea423c00e47c7fcb1046_watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQ2V5bGFuXw==,size_20,color_FFFFFF,t_70,g_se,x_16.png


🌷单链表的初始化


单链表的初始化就是建立一个新的空的单链表。


🔺实现原理


1️⃣生成新结点作为头结点,用头指针L指向头结点


2️⃣头结点指向空


💬 代码演示


Status InilList(LinkList &L)
{//构建一个空的链表L 
  L=new LNode;  //生成新结点作为头结点,用头指针L指向头结点 
  L->next=NULL;  //头结点指向空 
  return OK; 
}


🌷头插法建立单链表


🔺实现原理


1️⃣建立一个空表,生成新节点


2️⃣将数据存放到新节点的数据域中


3️⃣将新节点插入到当前链表的表头,也就是头结点之后

c4961ca2e2161aed7eed125c8b80edd1_watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQ2V5bGFuXw==,size_20,color_FFFFFF,t_70,g_se,x_16.png


💬 代码演示


void CreateList_H(LinkList &L,int n)
{
  L=new LNode;  
  L->next=NULL;           //先建立一个带头结点的空链表 
  for(int i=0;i<n;i++)
  {
  p=new LNode;    //生成新节点*p 
  cin>>p->data;    //输入p结点的数据 
  p->next=L->next;L->next=p;  //将新结点*p插入到头结点之后  
  }
}

❗️注意:


因为每次插入都是在链表头部,所以应该逆位序输入数据。输入顺序和线性表中的逻辑顺序是相反的。


🌷尾插法建立单链表


🔺实现原理

1️⃣建立一个空表,生成新节点

2️⃣将数据存放到新节点的数据域中

3️⃣将新节点插入到当前链表的尾部


be82178843c4ff5fc4582ec5d570dc3f_watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQ2V5bGFuXw==,size_20,color_FFFFFF,t_70,g_se,x_16.png

💬 代码演示


void CreateList_R(LinkList &L,int n)
{
  L=new LNode;  //先建立一个带头结点的空链表 
  r=L;    //尾指针指向头结点 
  for(int i=0;i<n;i++)
  {
  p=new LNode;    //生成新节点*p 
  cin>>p->data;    //输入p结点的数据 
  p->next=NULL;r->next=p;  //将新结点*p插入尾结点*r之后  
  r=p 
  }
}


🌷按序号寻找结点值


🔺实现原理


1️⃣从第一个结点开始,顺指针next逐个向下搜索,直到找到第  i  个结点,


2️⃣若没有找到,返回最后一个指针域NULL


💬 代码演示


LNode *GetElem(LinkList L,int i)
{
  int j=1;    //计数,初始为1 
  LNode *p=L->next;  //头结点指针赋值给p 
  if(i==0)
  return L;   //若i为 0,返回头指针 
  if(i<1)
  return NULL;  //若i无效,则返回 NULL 
  while(p&&j<i)   //从第一个元素开始找,直到第i个结点 
  {
  p=p->next;
  j++;
  }
  return p;    //返回第 i 个结点的指针 
}


🌷按值寻找结点


🔺实现原理


1️⃣从第一个结点开始,顺指针next逐个向下搜索数值,直到找到符合的数值


2️⃣若没有找到,返回最后一个指针域NULL


💬 代码演示


LNode *LocateElem(LinkList L,ElemType e)
{
  LNode *p=L->next;
  while(p!=NULL&&p->data!=e)  //从第一个结点开始查找数据域为 e 的结点 
  {
  p=p->next;
  }
  return p;     //找到后返回该结点指针,否则返回NULL 
}


🌷插入结点


将值为 x 的新结点插到单链表的第 i 个位置。


🔺实现原理


1️⃣调用按序号查找函数,查找第i-1个结点,假设返回结点为*p


2️⃣令新结点*s的指针域指向*p的后续结点


3️⃣令结点*p的指针域指向新的插入的结点*s


💬 代码演示


p=GetElem(L,i-1)  //查找插入位置的前驱结点 
s->next=p->next;  //新结点*s的指针域指向*p的后续结点
p->next=s;    //令结点*p的指针域指向新的插入的结点*s


🌷删除结点


将单链表的第 i 个结点删除。


🔺实现原理


1️⃣调用按序号查找函数,查找第 i 个结点,假设返回结点为*p


2️⃣将*p指针域next指向下下位结点

570efbc858ab53fbafde0b763ddba5e0_watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQ2V5bGFuXw==,size_20,color_FFFFFF,t_70,g_se,x_16.png


❓提出问题


之前讲到的链表结点中只有一个指向后续结点的指针域,若从某个结点出发只能顺指针向后一个一个寻查其他结点。若要访问某个结点的前驱节点,只能从头开始遍历。


有什么方法可以克服单链表这种单向性的缺点呢?


答案就是循环链表,双向链表


🌺循环链表


🔺实现原理


将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表。由此,从循环链表中任意一个结点出发都能找到其他结点。

e37a592d4f1fec6e75a2e9606f8f9b5f_watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQ2V5bGFuXw==,size_20,color_FFFFFF,t_70,g_se,x_16.png


对于这个循环链表,可以用O(1)的时间访问第一个结点,但是要访问最后一个结点,却需要O(n)时间,因为需要将单链表全部扫描一遍。


❓有没有什么方法可以用O(1)的时间由链表指针访问到最后一个结点呢?


我们需要对这个循环链表进行改造,不用头指针,而是用指向终端结点的尾指针来表示循环链表,此时查找开始结点和终端结点都为O(1)的时间了。

7bd7239dffefecbda8ee848ae8bff842_watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZSh5YWwQ2V5bGFuXw==,size_20,color_FFFFFF,t_70,g_se,x_16.png



🌷循环链表与单链表的差别


他们的差别在于:


当链表遍历时,判别终止的条件不同。假设当前指针为p,在单链表中判断是否到达终点条件为【p!= NULL】或【p->!= NULL】,循环链表的判断条件为【p->next!= L】


🌺双向链表


🔺实现原理


双链表的结点中有两个指针域,一个指向后一结点,一个指向前一结点,结点结构如图所示

0e5314fd7746b6ef47fef125cdc66714_watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQ2V5bGFuXw==,size_20,color_FFFFFF,t_70,g_se,x_16.png


💬 代码演示


双链表结点:


typedef struct DNode
{
  ElemType data;
  struct DNode *prior,*next;
}DNode, *DLinklist;


🌷双向链表插入


在双链表中p所指的结点之后插入结点*s

561f07ca17f9bef2cbbf03680c323f17_cf0ee0b17d3d4f5fb57de5f29b357ea6.png



💬 代码演示


s->next=p->next;            //将结点*插入到结点*p之后
p->next->prior=s;
s->prior=p;
p->next=s;


🌷双向链表删除


删除双链表中结点*p的后续结点*q

f8f7b932b9c25d38688b56626f65ade0_watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQ2V5bGFuXw==,size_20,color_FFFFFF,t_70,g_se,x_16.png


💬 代码演示


p->next=q->next;        //操作一
q->next->prior=p;       //操作二
free(p);                //释放结点空间


🌺线性表与链表的选择


在实际应用中,由于它们各有自己的优缺点,使用哪一种存储结构,应该根据具体问题具体分析,通常从空间和时间两个方向作比较分析。


🌷空间性能的比较


顺序表的存储空间必须提前分配,元素个数受到限制。一但存储空间装满了就不能扩容,若继续添加元素,就会出现内存溢出。若提前分配空间过大,可能导致大量空间被闲置,若提前分配空间过小,可能造成溢出。


链表不需要为其提前分配空间,只要内存空间允许,链表就能无限增加元素。


🌷时间性能的比较


🌹存取元素的效率


顺序表是由数组实现的,它可以顺序存取,也可以随机存取。


链表是一种顺序存取结构,只能从表头顺序存取元素


🌹逻辑结构与物理结构


采用顺序存储时,逻辑上相邻的元素,对应的物理元素位置也相邻


采用链式存储时,逻辑上相邻的元素,物理存储位置不一定相邻,对应的逻辑关系是由指针链接来表示的


🌹查找的效率


对于按值找,两者的时间复杂度都为O(n)


对于按序号查找,顺序表可以随机访问,时间复杂度仅为O(1),链表的时间复杂度为O(n)


🌹插入或删除的效率


对于顺序表,进行插入或删除时,要移动后方大量的元素,时间复杂度为O(n)


对于链表,在进行插入或删除时,不需要移动前后元素,只需要修改指针,时间复杂度为O(1)


🌷选择总结


1.当难以估计线性表的长度或存储规模时,不应采用顺序表,应采用链表。


2.若需要经常进行插入,删除操作时,不应采用顺序表,应采用链表。


3.若需要经常按序号访问结点值时,不应采用链表,应采用线性表。


🌺每日金句


本人不才,如有错误,欢迎各位大佬在评论区指正。有帮助的话还请【关注➕点赞➕收藏】,不行的话我再努努力💪💪💪


相关文章
|
17天前
|
存储
顺序表和链表(2)
【10月更文挑战第23天】
顺序表和链表(2)
|
18天前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
45 4
|
18天前
|
存储 算法 数据管理
顺序表和链表(1)
【10月更文挑战第22天】
|
20天前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之顺序表【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找等具体详解步骤以及举例说明
|
20天前
|
存储 C语言
【数据结构】顺序表(c语言实现)(附源码)
本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
50 3
|
19天前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
20天前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
20天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之顺序表习题精讲【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找习题精讲等具体详解步骤以及举例说明
|
18天前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
38 0
|
1月前
|
存储
[数据结构] -- 双向循环链表
[数据结构] -- 双向循环链表
22 0