《数据结构》 您所在的位置:网站首页 数据恢复知识点总结 《数据结构》

《数据结构》

2023-10-13 13:15| 来源: 网络整理| 查看: 265

写在前面的话:

      适应范围:《数据结构》复习总结系列适用于考研、期末考试、考前复习,小白新手

      本系列参考书目: 《数据结构:c语言版》(严蔚敏)

      关于写这个系列的原因:本人今年考研上岸,加上在校期间学习《数据结构》这门课时候,对数据结构有了好感,对这门课考试的考点把握还不错,所以不想荒废自己的知识,就计划用这种方式总结起来。如果有理解不到位的地方,欢迎在评论指出。我会尽量把知识点讲清楚后,按照考点进行总结。这个系列计划按照知识点总结后+考点总结,同时配有少部分习题(之后会放出大量习题的链接,可以自行练习下载)的方式进行,我会加快更新速度,有不理解的问题也可以在评论提出,大家一起学习进步嗷。

第二章 线性表

    本章开始讨论线性结构及其逻辑结构、存储结构和相关运算。线性表作为最基本、最简单、也是最常用的一种数据结构,本章所涉及知识点在接下来的章节学习时也常用到。同时也是数据结构的重点内容。

     本章涉及的算法题实现起来较为容易且代码量较少,但需考虑所设计代码的性能,因此要熟知线性表的各种基本操作,务必在本章打好基础。

【考点】①线性表的定义;

               ②线性表的顺序存储表示;

               ③线性表的链式存储表示(包括单链表、循环链表、双向链表等);

               ④顺序表与链表的比较;

               ⑤线性表的应用(专门章节进行讲解)

【本章大纲】

【目录】

第二章 线性表

一、线性表的定义

1.1 相关概念术语

   1.1.1 线性表

   1.1.2 空表

   1.1.3 相关名词

1.2 线性表的特点

二、线性表的顺序存储

2.1 顺序表示相关概念

2.2 顺序表的存储结构表示

2.3 顺序表中基本操作的实现

2.3.1 顺序表的初始化

2.3.2 顺序表的取值

2.3.3 顺序表的查找

2.3.4 顺序表的插入

2.3.5 顺序表的删除

三、线性表的链式存储

3.1 单链表链式表示的相关概念

 3.1.1 结点

 3.1.2 链式存储结构

 3.1.3 首元结点、头结点、头指针的区分

 3.1.4 其他说明

3.2 单链表的存储结构表示

3.3 单链表中基本操作的实现

3.3.1 单链表的初始化

3.3.2 单链表的取值(根据位置)

3.3.3 单链表的按值查找

3.3.4 单链表的插入(⭐⭐⭐)

3.3.5 单链表的删除(⭐⭐⭐)

3.3.6 单链表的创建(⭐⭐⭐)

3.3 循环链表

3.4双向链表

  3.4.1 双向链表的相关概念

  3.4.2 双向链表的插入(⭐⭐⭐)

  3.4.3 双向链表的删除(⭐⭐⭐)

四、顺序表与链表的比较

一、线性表的定义 1.1 相关概念术语    1.1.1 线性表

     【例】在月份表中(1,2,3,.... ,11,12)中就是一个线性表,表中数据元素是单个数字,虽然数据元素的不相同,但他们拥有相同的数据特性。

    【定义】如上例子,由n个具有相同特性的数据元素的有限序列称为线性表。

   1.1.2 空表

    【定义】线性表中元素个数n定义为线性表的长度,当n=0时为空表。

    【例】建立一张2022学年学生期末成绩表,而现在还未举行期末考试,不知道学生的成绩,因此此表没有数据元素为一张空表。

   1.1.3 相关名词

     【例】字母表(A,B,C,D,E,F,G,H)

     【定义】在字母表中,A被称为线性起点,H被称为线性终点。以C而言,B为起直接前驱,D为其直接后继。

1.2 线性表的特点

    (1)集合中必存在唯一的一个“第一元素”。

    (2)集合中必存在唯一的一个 “最后元素” 。

    (3)除最后一个元素之外,均有唯一的后继。

    (4)除第一个元素之外,均有唯一的前驱。

二、线性表的顺序存储 2.1 顺序表示相关概念

  【定义】顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素,称为线性表的顺序存储结构)。通常使用这种存储结构的线性表被称为顺序表,它的特点为逻辑上相邻的数据元素,在物理位置上也是相邻的。

  【例】

     如图,只要确定了存储线性表的起始位置,线性表中的任一元素的位置都可随机存取。因此,线性表的顺序存储结构是一种随机存取。

2.2 顺序表的存储结构表示

      【算法描述】

#define MAXSIZE 100; //声明顺序表的最大长度 typedef struct { ElemType *elem; //线性表的基地址(初始地址) int Length; //顺序表的长度 }SqList; 2.3 顺序表中基本操作的实现 2.3.1 顺序表的初始化

     【算法思想】①为顺序表L分配一个maxsize大小的数组康健,并使基地址指向此空间的基地址

                           ②将表的长度置为0,即置为空表

    【算法描述】

status InitList (SqList &L){ L.elem = new ElemType[MAXSISE]; //为顺序表分配一个maxsize大小的空间 if (!L.elem) exit (OVERFLOW); //存储分配失败 L.length=0; //设置空表长度为0 return OK; } 2.3.2 顺序表的取值

     【算法思想】判段取值位置 i 是否合法

           ①合法时,将第i 个元素即L.elem[i-1]所对应的值赋给参数e,使参数e返回数据元素 i 的值;

           ②不合法时,返回EROR。

    【算法分析】查找操作是根据指定元素位置进行查找的,因此取值算法的时间复杂度为O(1)。

   【算法描述】此算法也体现出顺序表的随机存取的特点。

int GetElem(SqList L,int i,ElemType &e) { if (iL.length) return ERROR; //判断i值是否合理,若不合理,返回ERROR e=L.elem[i-1]; //第i-1的单元存储着第i个数据 return OK; } 2.3.3 顺序表的查找

   【算法思想】当查找数字时,如图

      若查找数字66,依旧从初始地址进行查找,向后逐一查询,直至查找到最后一个元素,依旧不符合时,返回ERROR。

   【算法分析】通过上图中的示例,可以看出顺序表上查找一个数据元素,其时间主要消耗在比较数据上,而比较数据元素的次数取决于所要插入的位置,假设在等概率情况下每个元素被查找到的概率为1/n,因此

所以,顺序表按值查找算法的时间复杂度为O(n)。

   【算法描述】

int LocateELem(SqList L,ElemType e) { for (i=0;i< L.length;i++) if (L.elem[i]==e) return i+1; //查找成功,返回其序号 return ERROR; //查找失败,返回ERROR } 2.3.4 顺序表的插入

   【算法思想】(1)判断插入位置i是否合法;判断顺序表的存储空间是否已满。                         (2)将第n至第i位的元素依次向后移动一一个位置,空出第i个位置,如下图。                         (3)将要插入的新元素e放入第i个位置。                         (4)表长加1,插入成功返回OK。

     【算法分析】 通过上图中的示例,可以看出顺序表上插入一个数据元素的主要特点是先移动后插入,因此时间主要消耗在移动数据上,而移动数据元素的次数取决于所要插入的位置。据观察,插入第i个元素的位置,需要移动n-i+1次。

     假设在等概率情况下每个位置被插入的概率为1/(n+1),其n+1是因为元素除了插入在n个元素的位置,还有可能直接插入最后一个元素的后面,所以有n+1种可能性。

因此平均移动次数

所以,顺序表的插入算法的时间复杂度为O(n)。

  【算法描述】

Status ListInsert_Sq(SqList &L,int i ,ElemType e){ if(iL.length+1) return ERROR; //i值不合法 if(L.length==MAXSIZE) return ERROR; //当前存储空间已满 for(j=L.length-1;j>=i-1;j--) L.elem[j+1]=L.elem[j]; //插入位置及之后的元素后移 L.elem[i-1]=e; //将新元素e放入第i个位置 L.length++; //表长增1 return OK; } 2.3.5 顺序表的删除

   【算法思想】(1)判断删除位置i是否合法(合法值为1si≤n)。                         (2)将欲删除的元素保留在e中。                         (3)将第i+ 1至第n位的元素依次向前移动一个位置。                         (4)表长减1,删除成功返回OK。

   【算法分析】  通过上图中的示例,可以看出顺序表上删除一个数据元素的主要特点是先删除后移动,因此时间主要消耗也在移动数据上,而移动数据元素的次数取决于所要删除元素的位置。据观察,插入第i个元素的位置,需要移动n-i次。

     假设在等概率情况下每个位置被插入的概率为1/n。因此平均移动次数

所以,顺序表的插入算法的时间复杂度为O(n)。

   【算法描述】

Status ListDelete_Sq(SqList &L,int i,Elemtype e){ if((iL.length)) return ERROR; //i值不合法 e=L.elem[i]; for (j=i;jnext=NULL;   //头结点的指针域置空   return OK; } 3.3.2 单链表的取值(根据位置)

【算法思想】对于链表的查找,要从链表的头指针出发,顺着链域next逐个结点往下搜索,直至搜索到第i个结点为止,所以链表不是随机存取结构,取值步骤如下:

                 (1)从L->next顺链扫描,用指针p指向当前扫描到的结点,p初值p = L->next。     

                 (2)j 做计数器,累计当前扫描过的结点数,j 初值为1。

                 (3)当p指向扫描到的下一结点时,计数器j 加1。当j = i时,p所指的结点就是要找的第i个结点;当P=NULL时,取值失败。

【算法分析】

     该算法的主页操作为比较 i 与 j 并后移指针 p 。而while循环体中的语句频度主要与位置 i 有关。

     假设在等概率情况下,每个元素的取值概率相等,即都为1/n。①当1≤ i ≤ n时,频度为 i - 1,取值成功;②若 i>n,则频度为n,取值失败。因此改算法最坏时间复杂度为O(n)。

     平均查找长度ASL=(n-1)/2,因此平均时间复杂度为O(n)。

【算法描述】

Status GetElem_L(LinkList L,int i,ElemType &e){ p=L->next;j=1; //初始化 while(p&&jnext; ++j; } if(j==i) e=p->data; //取第i个元素的数据域 else return ERROR; //第i个元素不存在 return OK; } 3.3.3 单链表的按值查找

【算法思想】从第一个结点起,依次和e相比较:

                 (1)如果找到一个其值与e相等的数据元素,则返回其在链表中的“位置”或地址;

                 (2)如果查遍整个链表都没有找到其值和e相等的元素,则返回0或“NULL”。

【算法分析】

        该算法的执行时间与待查找的值e相关,类似于单链表的取值,因此时间复杂度也为O(n).

【算法描述】

LNode *LocateELem_L (LinkList L,Elemtype e) { LNode *p; p=L->next; //初始化,p指向首元结点 while(p &&p->data!=e) //顺序扫描 p=p->next; //p指向下个结点 return p; //查找成功返回值为e的结点地址p,查找失败p为null } 3.3.4 单链表的插入(⭐⭐⭐)

 【算法思想】此算法应注意指针指向顺序,避免造成断链:

                    (1)找到ai-1存储位置p(插入点的直接前驱)(图步骤①) ;

                    (2)生成一个新结点*s (图步骤②);

                    (3)将新结点*s的数据域置为x(图步骤③);

                    (4)新结点*s的指针域指向结点ai (图步骤④);

                    (5)令结点*p的指针域指向新结点*s (图步骤⑤)。

  【算法分析】此算法需要在插入第 i 个结点之前,先找的第 i-1 个结点,因此也需顺序找到此结点的位置,其时间复杂度与单链表的取值算法相同为O(n)。

  【算法描述】

Status ListInsert_L(LinkList &L,int i,ElemType e){ p=L;j=0; while(p&&jnext;++j;} //寻找第i−1个结点,使p结点指向该结点 if(!p||j>i−1)return ERROR; //i大于表长 + 1或者小于1,位置不合理 s=new LNode; //生成新结点*s s->data=e; //将结点*s的数据域置为e s->next=p->next; //将结点*s的指针域指向结点ai p->next=s; //将结点*p的指针域指向结点*s return OK; }//

3.3.5 单链表的删除(⭐⭐⭐)

 【算法思想】(1)找到ai-1存储位置p(删除结点的直接前趋)(图步骤①);

                      (2)保存要删除的结点的值(图步骤②);

                      (3)令p->next指向ai的直接后继结点(图步骤③);

                      (4)释放结点ai的空间(图步骤④)。

【算法分析】此算法同样也类似于插入算法,时间复杂度也为O(n)。

【算法描述】

Status ListDelete_L(LinkList &L,int i,ElemType &e){ p=L;j=0; while(p->next &&jnext; ++j; } if(!(p->next)||j>i-1) return ERROR; //删除位置不合理 q=p->next; //临时保存被删结点的地址以备释放 e=q->data; //保存删除结点的数据域 p->next=q->next; //改变删除结点前驱结点的指针域 delete q; //释放删除结点的空间 return OK; } 3.3.6 单链表的创建(⭐⭐⭐)

① 前插法创建单链表

【算法思想】从一个空表开始,重复读入数据:

                    (1)生成新结点;

                    (2)将读入数据存放到新结点的数据域中;

                    (3)将该新结点插入到链表的前端。

  【算法分析】当有n个数据时,需插入n次,因此时间复杂度为O(n)。

  【算法描述】

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

② 后插法创建单链表

【算法思想】

      (1)从一个空表L开始,将新结点逐个插入到链表的尾部,尾指针r指向链表的尾结点。

      (2)初始时,r同L均指向头结点。每读入一个数据元素则申请一个新结点,将新结点插入到尾结点后,r指向新结点。

【算法分析】当有n个数据时,需插入n次,因此时间复杂度为O(n)。

【算法描述】

void CreateList_L(LinkList &L,int n){ L=new LNode; L->next=NULL; r=L; //尾指针r指向头结点 for(i=0;i>p->data; //输入元素值 p->next=NULL; r->next=p; //插入到表尾 r=p; //r指向新的尾结点 } } 3.3 循环链表

  【特点】表中最后一个结点的指针域指向头结点,整个链表如同一个环形。正是这样的结构,我们可以从任意一个结点找到表中其他的结点。

  【注意】

     (1)循环链表与单链表在大多数情况下的操作是相同的,两者的差别在于:

        当单链表在遍历时判断当前指针p是否指向表尾结点的终止条件为p !=null 或p->next!=null;而循环链表为p!=L或p->next !=L。

     (2)对循环链表,有时不给出头指针,而给出尾指针,可以更方便的找到第一个和最后一个结点。

3.4双向链表   3.4.1 双向链表的相关概念

 【特点】在双向链表中,每个结点pᵢ拥有两个指针域,一个指向直接后继(pᵢ->next),另一个指向直接前驱(pᵢ->prior)。正是这种结构,在查找某个结点的直接前驱不在需要从头遍历。 

【注意】B->next->prior = B->prior->next = B

  3.4.2 双向链表的插入(⭐⭐⭐)

  【算法思想】注意不要因为步骤不对,造成断链问题。

  【算法描述】

Status ListInsert_DuL(DuLinkList &L,int i,ElemType e){ if(!(p=GetElemP_DuL(L,i))) return ERROR; //确定第i个元素的位置指针p 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; }   3.4.3 双向链表的删除(⭐⭐⭐)

  【算法思想】

  【算法描述】

Status ListDelete_DuL(DuLinkList &L,int i,ElemType &e){ if(!(p=GetElemP_DuL(L,i))) //确定第i个元素的位置指针p return ERROR; e=p->data; p->prior->next=p->next; //修改被删除结点的前驱结点的后继指针 p->next->prior=p->prior; //修改被删除结点的后继结点的前驱指针 delete p; //释放空间 return OK; } 四、顺序表与链表的比较

      存储结构

顺序表

链表

空间

存储空间

预先分配,会导致空间闲置或溢出现象

动态分配,不会出现存储空间闲置或溢出现象

存储密度

不用为表示结点间的逻辑关系而增加额外的存储开销,存储密度等于1

需要借助指针来体现元素间的逻辑关系,存储密度小于1

时间

存取元素

随机存取,按位置访问元素的时间复杂度为O(1)

顺序存取,按位置访问元素时间复杂度为O(n)

插入、删除

平均移动约表中一半元素,时间复杂度为O(n)

不需移动元素,确定插入、删除位置后,时间复杂度为O(1)

适用情况

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

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

① 长度变化较大

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

【注】①存储密度为数据元素本身所占用的存储量与整个结点结构所占用的存储量之比。

           ②两种存储方式各有优缺点,对于频繁进行插入删除操作的线性表,宜采用链表作为存储结构;为节约存储空间,宜采用顺序表作为存储结构。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有