大话数据结构03-线性表
1.开场白
线性表: 零个或多个数据元素的有限序列。比方说拔河比赛的某一方,线性表强调前方和后方的唯一性。若将线性表记为 (a1,…, a'.1, at, a'+1,…, an) ,则表中 a'.1 领先于 剑, a, 领 先于 a,吨,称 a'.1 是 a,的直接前驱元素, a'+1是 a,的直接后继元素。当 1=1 , 2,…, 0-1 时, a,1 有且仅有一个直接后继,当 i=2, 3 ,…, 0 时, a,有且仅有一个直接前驱。比方说十二星座。
以线性表元素的个数 o (0)0) 定义为线性表的长度,当 0=0 时,称为空表。
在较复杂的线性表中 , 一个数据元素可以由若干个数据项组成。
2.线性表的抽象数据类型
线性表应该有一些什么 样的操作呢? 初始化,比较,清空,添加,删除等操作,也就是抽象的数据类型来有效完成。
我们对于 union 操作,用到了前面线性表基本操作 ListLength、GetE坠m、 LocateElem、 Listlnsert 等,可见,对于复杂的个性化的操作,其实就是把基本操作组合起来实现的。
2.1. 线性袤的顺序存储结构
雄性亵的顺序存储结构,指的是用一段地址连续的存储单元依次 存储线性褒的触据元素。
既然线性表的每个数据元素相同,那就可以用一个数组来表示。即把第一个数据元素存到数组下 标为 0 的位置中,接着把线性表相邻的元素存储在数组中相邻的位置。 随着数据的插入,我们线性表的长度开始变大,不过续性袤的当 前长度不能超过存储容量,即数组的长度
由上图代码可知:描述顺序存储结构需要三个属性:
1)存储空间的起始位置:数组 dara,色的存储位置就是存储空间的存储位置。
2)线性袤的最大存储容量: 数组长度 Ma.xSize。
3)线性表的当前长度: Jengtb.
两个概念"数组的长度"和"续性表的长度"需要区分一下。线性袤的长度是线性表中数据元素的个数,随着线性表插入和删除操作的进行, 这个量是变化的。
在任意时刻,线性表的长度应该小于等于数组的长度。
2.2.地址计算方法
C 语言中的数组却是从 0 开始第一个下标的,于是线性表的第 i 个元素是要存储在数组 下标为 i-1 的位置,即数据元素的序号和存放它的数组下标之间存在对应关系(
2.3.顺序存储结构的插入与删除
插入操作
- 如果插入位置不合理,抛出异常;
- 如果线性表长度大于等于数组长度,则抛出异常或动态增加容量;
- 从最后一个元素开始向前遍历到第 i 个位置,分别将它们都向后移动一个位 置;
- 将要插入元素填入位置 i 处; • 表长加 1
删除操作
- 如果删除位置不合理,抛出异常i
- 取出删除元素;
- 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一 个位置;
- 表长减
顺序存储结构的优缺点
2.4.线性表的链式存储结构
当插入和删除时,就要移动大量元素,仔细分析后,发现原因就在于相邻两元素的存储位置也具有邻居关系。在顺序结构中,每个数据元素只需要存数据元素信息就可以了。现在链式结构中,除了要存数据元素信息外, 还要存储它的线性表后继元素的存储地址。
为了表示每个数据元素 al 与其直接后继数据元素 al+1 之间的逻辑关系, 对 数据元素刮来说, 除了存储其本身的信息之外,还需存储一个指示其直接后继的信息 (即直接后继的存储位置)。我们把存储数据元素信息的域称为数据域, 把存储直接后继位置的域称为指针域。 指针域中存储的信息称做指针或链。 这两部分信息组成数据 元素 ai 的存储映像,称为结点 (Node);n 个结点 (al 的存储映像) 链结成一个链衰,即为线性表 (a1, a2,…, aD) 的链 式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表,单链表正是通过每个结点的指针域将线性表的数据元素按其逻辑次序链接在一起。
总得有个头有个尾,链表也不例外。我们把链表中第一个结点 的存储位置叫做头指针,那么整个链表的存取就必须是从头指针开始进行了 。 之后的每一个结点,其实就是上一个的后继指针指向的位置。最后一个结点,它的指针指向哪里?, 最后一个, 当然就意味着直接后继不存在了,所以我们规定,线性链表的最后一 个结点指针为"空n(通常用 NULL或 ^符号表示)
为了更加方便地对链装进行操作,会在单链表的第一个结点前附设一 个结点,称为头结点。头结点的数据域可以不存储任何信息,谁叫它是第-个呢,有 这个特权。也可以存储如线性表的长度等附加信息,头结点的指针域存储指向第一个结点的指针。
头指针与头结点的异同
3.单链表
3.1.单链表的读取
线性表的顺序存储结构中,我们要计算任意一个元素的存储位置是很容易的。 但在单链表中,由于第 i 个元素到底在哪?没办法一开始就知道,必须得从头开始找。因此,对干单链表实现获取第 i 个元素的数据的操作 GetElem,在算法上,相对要麻烦一些。
- 获得链裴第 i 个数据的算法思路:
1)声明一个结点 p 指向链表第一个结点,初始化 j 从 1 开始;
2)当 j<i 时,就遍历链裴,让 p 的指针向后移动,不断指向下一结点, j 累加 1;
3)若到链表末尾 p 为空,则说明第 i 个元素不存在;
4)否则查找成功,返回结点 p 的数据。
实现代码算法如下:
由于单链袤的结构中没有定义表长,所以不能事先知道要循环多少次,因此也就 不方便使用 for 来控制循环。其主要核心思想就是 "工作指针后移',这其实也是很多 算法的常用技术。
此时就有人说,这么麻烦,这数据结构有什么意思!还不如顺序存储结构呢。世间万物总是两面的,有好自然有不足,有差自然就有优势。下面我们来看 一下在单链表中的如何实现 u插入'和"删除。
3.2.单链表的插入与删除
假设存储元素 e 的结点为 S ,要实现结点 p、 p->next 和 s 之间逻辑关系的变化,只需将结点 s 插入到结点 p 和 p->next 之间即可。
用不着惊动其他结点,只需要让 s->next和 p->next 的指针做一点改变即可。
s->next=p->next; p->next=s;
解读这两句代码,也就是说让 p 的后继结点改成 s 的后继结点,再把结点 s 变成 p 的后继结点。且先后顺序不可颠倒,对于插入或删除数据越频繁的操作,单链表的效率优势就越是明显。
3.3.单链表的整表创建
回顾一下, 顺序存储结构的创建,其实就是一个数组的初始化,即声明一个类型 和大小的数组并赋值的过程。而单链表和顺序存储结构就不一样,它不像顺序存储结 构这么集中,官可以很散,是一种动态结构。对于每个链表来说,它所占用空间的大 小和位置是不需要预先分配划定的,可以根据系统的情况和实际的需求即时生成。
- 所以创建单链表的过程就是一个动态、生成链表的过程。即从"空袭"的初始状态 起,依次建立各元素结点,并逐个插入链表。
单链衰整表创建的算法思路:
1)声明一结点 p 和计数器变量 i;
2)初始化一空链表 L;
3)让 L 的头结点的指针指向 NULL,即建立一个带头结点的单链表;
4)循环:
• 生成一新结点赋值给 p;
• 随机生成一数字赋值给 p 的数据域 p->data;
• 将 p 插入到头结点与前一新结点之间。
实现有两个算法:
1)头插法:始终让新结点在第一的位 置。我也可以把这种算法简称为头插法。
2)尾插法:每次新结点都插在终端结点的后面,这种 算法称之为尾插法。
3.4.单链表的整表删除
当我们不打算使用这个单链表时,我们需要把它销毁,其实也就是在内存中将官 释放掉,以便于留出空间给其他程序或软件使用。
单链表整表删除的算法思路如下:
1)声明一结点 p和 q;
2)将第一个结点赋值给 p;
3)循环:
• 将下一结点赋值给 q; • 释放 p; • 将 q 赋值给 p。
总结如下:
1)若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结 构。若需要频繁插入和删除时,宜采用单链裴结构。比如说游戏开发中, 对于用户注册的个人信息,除了注册时插入数据外,绝大多数情况都是读 取,所以应该考虑用顺序存储结构 。而游戏中的玩家的武器或者装备列 表,随着玩家的游戏过程中,可能会随时增加或删除,此时再用顺序存储 就不大合适了,主事链表结构就可以大展拳脚。当然,这只是简单的类比, 现实中的软件开发,要考虑的问题会复杂得多。
2)当线性表中的元素个数变化较大或者根本不知道有多大肘,最好用单链表 结构, 这样可以不需要考虑存储空间的大小问题。而如果事先知道线性表 的大致长度,比如一年 12 个月,一周就是星期一至星期日共七天,这种用 顺序存储结构效率会高很多。
4.静态链表
对于一些语言,如 Basìc、 Fortran 等早期的编程高级语言,由于没有指 针,链表结构按照前面我们的讲法,它就没法实现了。怎么办呢?
有人就想出来用数组来代替指针,来描述单链表。真是不得不佩服他们的智慧, 我们来看着他是怎么做到的。
首先我们让数组的元素都是由两个数据域组成, 也钮 和 cur。也就是说,数组的每 个下标都对应一个 也ta 和一个 curo 数据域也ta,用来存放数据元素, 也就是通常我 们要处理的数据;而游标 cur 相当于单链表中的 next 指针,存放该元素的后继在数组
中的下标。
我们把这种用数组描述的链表叫做静态链表,这种描述方法还有起名叫做游标实现法。
我们对数组第一个和最后一个元素作为特殊元素处理,不存数据。我们通常 把未被使用的数组元素称为备用链表。而数组第一个元素,即下标为 0 的元素的 cur 就存放备用链袤的第一个结点的下标i 而数组的最后一个元素的 cur 则存放第一个有 数值的元素的下标,相当于单链表中的头结点作用,当整个链表为空时,则为 02
假设我们已经将数据存入静态链袭,比如分别存放着"甲"、 a乙'、 e丁"、"戊'、 u己"、"庚n 等数据,则它将处于下图
此时"甲"这里就存有下一元素"乙n 的游标 2,"乙"则存有下一元素"丁'的 下标 3。而"庚"是最后一个有值元素,所以它的 cur 设置为 0。而最后一个元素的 cur 则因"甲'是第一有值元素而存有宫的下标为 1. 而第一个元素则因空闲空间的第 一个元素下标为 7 ,所以宫的 cur 存有 7。
4.1.静态链表的插入操作
4.2.静态链袤的删除操作