神奇少年的数据结构学习笔记二(线性表)

目录

1.线性表的定义

2.线性表的顺序存储结构

3.线性表的链式存储结构

3.1  线性表链式存储结构定义:

3.2  单链表的操作

3.4  静态链表

3.5  循环链表

3.6  双向链表


1.线性表的定义

零个或多个数据元素的有限序列

a1>a2>a3>a4>a5>a6

首先是一个序列,元素之间是有顺序的,若元素存在多个,则第一个元素无前驱,最后一个元素无后驱,其他每个元素都有且只有一个前驱和后驱;线性表强调元素个数是有限的;每一个元素都是相同的数据类型;

2.线性表的顺序存储结构

线性表的顺序存储结构:指用一段地址连续的存储单元依次存储线性表的数据结构

线性表(a1,a2,............,an)的顺序结构:

线性表的顺序存储结构所需要的三个属性

  1. 存储空间的起始位置:数组data,它的存储位置就是存储空间的存储位置
  2. 线性表的最大容量:数组的长度Maxsize
  3. 线性表的当前长度:length

注意:数组的长度是存放线性表的存储空间的长度,存储分配后这个量一般是不变的。线性表的长度是线性表中的元素个数,随着增删的操作会变化的;在任意时刻,线性表的长度应该小于等于数组的长度;

由于我们数数都是从1开始,线性表也是这样定义,起始是1,但是c#中数组的位置是从下标0处开始的,于是线性表的第i个元素是要存储在下标为i-1的位置,即数据元素的序号和存放它的数组下标之间存在对应关系,如图:

用数组存储顺序表意味着要分配固定长度的数组空间,由于线性表可以进行插入和删除操作,所以分配的数组空间要大于等于当前线性表的长度;

存储器中的每个存储单元都有自己的编号,这个编号称为地址

当我们占用位置后,占用的第一个位置确定后,后面的位置都是可以计算的。假设占用c个存储单元,那么线性表中第i+1个元素的存储位置和第i个数据元素的存储位置满足下列关系(LOC表示获得存储位置的函数)

LOC(a_{i+1})=LOC(a_{i})+c

所以对于第i个元素ai的存储位置可以由a1推出

LOC(a_{i})=Loc(a_{1})+(i-1)*c

通过这个公式,可以算出线性表任意位置的地址,不管是哪一个,都是相同的时间。那么我们对于线性表位置的存入或取出数据,对计算机来说都是相同的时间,它的存取时间性能O(1).我们通常把具有这一特点的存储结构称为随机存储结构

  1. 查询操作:就是返回线性表的元素在数组下标 n-1处的值
  2. 插入操作:插入位置不合理抛出异常>线性表长度大于或等于数组长度,抛异常或动态增加容量>从最后一个元素开始向前遍历到i处,分别都向后移动一个位置>要插入的值写到i处>表长加1
  3. 删除操作:删除位置不合理抛出异常>取出删除元素>从删除元素开始遍历到最后一个元素位置,分别都向前移动一个位置>表长减1

可以看出顺序线性表每次操作插入或删除时的时间复杂度为O(n)

线性表顺序结构的优缺点:

优点:

  • 无须为表中元素之间的逻辑关系而增加额外的存储空间
  • 可以快速的存取表中任一位置的元素

缺点:

  • 插入和删除操作需要移动大量元素
  • 当线性表长度变化较大,难以确定存储空间的容量
  • 造成存储空间的“碎片”

3.线性表的链式存储结构

由于顺序存储结构在插入和删除时需要移动大量元素,为了解决这个问题所以有了链式存储结构

由于顺序存储结构插入和删除时需要移动大量元素是因为相邻的两个元素的存储位置也具有邻居关系,它们在内存中的位置也是挨着的,中间没有空隙所以无法快速插入,删除后出现了空隙又需要弥补;为了解决这个问题就不考虑相邻位置,而只让每个元素知道它下一个元素的位置在哪里,这样,我们在知道第一个元素时,就知道第二个元素的位置,所有的位置都可以通过遍历找出来;

3.1  线性表链式存储结构定义:

为了表示每个元素a_{i}与其直接后继数据元素a_{i+1}之间的逻辑关系,对于数据元素a_{i}来说,除了存储本身信息外,还需要存储一个指示其直接后继的存储位置。我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称指针域。指针域存储的信息称做指针或链。这两部分信息组成数据元素a_{i}的存储映像,称为结点(Node)

n个结点链结成一个链表,即为线性表(a_{1},a_{2},...,a_{n})的链表式存储结构,因为此链表的每个结点只包含一个指针域,所以叫单链表

链表中的第一个结点的存储位置叫做头指针,最后一个结点指针为“空”

有时为了更方便对链表进行操作,会在单链表的第一个结点前附设一个结点,称为头结点。头结点的数据域可以不存储任何信息,也可以存储如线性表长度等附加信息。

头指针和头结点的异同

 头指针

  • 头指针是指向链表第一个结点的指针,若链表有头结点则是指向头结点的指针
  • 头指针有标识作用,所以常用头指针冠以链表的名字
  • 无论链表是否为空,头指针均不为空。头指针是链表的必要元素

头结点

  • 头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义
  • 有了头结点,对第一元素结点前插入和删除第一结点其操作和其他结点的操作一样
  • 头结点不一定是链表必须要素

3.2  单链表的操作

读取       O(n):

  1. 声明一个指针p指向链表的第一个结点,初始化j从1开始
  2. 当j小于1时就遍历链表,让p的指针向后移动,不断向下一结点,j累加1   
  3.   若到链表末尾p为空,则说明i结点不存在
  4. 查找成功返回p结点的数据   

插入         查询一次O(n) 操作O(1): 

  1. 声明一个指针p指向链表表头结点,初始化j从1开始
  2. 当j小于1时就遍历链表,让p的指针向后移动,不断向下一结点,j累加1 
  3. 若到链表末尾p为空,则说明i结点不存在
  4. 查找成功,在系统中生成一个空结点s,将要插入的数据元素赋值给s>data(数据域)
  5. 给p的后继结点赋值给s,s结点替换为p的后继结点

删除和插入操作差不多,我就不说了,只是一个是新加一个空结点替换指针域,一个是去掉一个结点(给要删除的结点s指针域赋值给指向s的结点,然后回收结点)

创建单链表:

头插法:循环插入的时候 每次新增的都是第一个,依次向前

尾插法:循环插入的时候 每次新增的都是依次向后

删除单链表:

  1. 申明结点p和q
  2. 将第一个结点赋值给p
  3. 循环:将下一结点赋值给q,释放p,将q赋值给p.

单链表和顺序存储结构优缺点

  存储分配方式 时间性能 空间性能
顺序存储结构 一段连续的存储单元依次存储线性表的数据

查找     O(1)

插入和删除

平均移动表长一半的元素

时间复杂度  O(n)

需要预分配存储空间,分大了浪费,分小了会溢出
单链表结构 链式存储结构,用一组任意的存储单元存放线性表的元素

查找 O(n)

插入和删除

在找出某位置的指针后

时间复杂度 O(1)

不需要分配存储空间,只要有就可以分配,元素个数也不限制

3.4  静态链表

由于早期的一些编程高级语言,没有指针的概念,所以无法实现链表结构,有人想出来用数组代替指针,来描述单链表

给数组的元素都是两个数据域组成,data和cur,也就是说数组每一个下标对应一个data和cur, data存放数据元素(数据域),cur存放该元素的后继在数组中的下标(指针);我们给这种用数组描述的链表叫做静态链表,这种描述方法叫游标实现法;

为了方便插入数据,我们通常会把数组建立得大一些,以便有空闲空间可以便于插入时不至于溢出。另外数组的第一个元素和最后一个元素作为特殊元素处理,不存储数据。通常给未被使用的数组元素称为备用链表。数组第一个元素(下标0)的cur存放备用链表的第一个结点的下标;数组最后一个元素的cur存放第一个有数值的元素的下标,相当於单链表中的头结点

静态链表的操作

插入:给要插入的值赋值给备用链表第一个元素(第一个元素的cur值)p>找到要插入的位置>将要插入的位置的指针赋值给p>将要插入的位置的指针指向p>更新第一个元素的cur值

静态链表优缺点

优点:在插入删除操作时,不需要移动元素,改进了顺序存储结构在插入删除操作要移动大量元素的缺点;

缺点:没有解决连续存储分配带来的表长难以确定的问题;失去了顺序存储结构随机存取的特性;

删除操作类似插入,其他操作和线性表基本操作差不多,我就不写了;主要是因为静态链表是给没有指针的高级语言设计的一种实现单链表能力的方法;我们一般是用不上的,主要是理解思想;

3.5  循环链表

将单链表中的终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表简称循环链表;.

比如将A和B两个循环链表合并,只需要将A的尾指针指向B的第一个结点(因为合并后只需要一个头结点的,B的头结点相当于被释放了),将B的尾指针指向A的头结点,

3.6  双向链表

在单链表中,有了指针,每次查找下一结点的时间复杂度为O(1),但是查找上一节点,最坏的时间复杂度是O(n)

为了克服这个问题所以有了双向链表,在单链表的每个结点中,再设置一个指向其前驱结点的指针域。所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前继。

双向链表也可以有循环链表,双向链表在插入和删除时,需要修改两个指针变量的值,由于每个结点都是两个指针域,有效的提高算法的性能,是一种空间换取时间;

双向链表操作

插入:假设插入结点s到p于p1之间 > 给p赋值给s的前驱指针 > 把p1赋值给s的后驱指针> 把s赋值给p1的前驱指针>s赋值给p的后驱指针

删除:假设删除p和p1之间结点s > 给p1赋值给p的后驱指针 > 把p赋值给p1的前驱指针

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章