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

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

🌺链表


🌷链表的定义


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


为了表示每个数据元素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.若需要经常按序号访问结点值时,不应采用链表,应采用线性表。


🌺每日金句


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


相关文章
|
1月前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
57 4
|
1天前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
28天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
53 5
|
1月前
|
存储
顺序表和链表(2)
【10月更文挑战第23天】
顺序表和链表(2)
|
1月前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
49 0
|
1月前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
68 0
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
210 9
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
37 1
|
1月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
1月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。

热门文章

最新文章