第三章 列表

目录

A.循秩访问

具体实现

B.接口与实现

从静态到动态:(为什么要学习列表)

什么是列表

call - by - position 循位置访问

列表类的实现(操作集):

节点的操作集:

 节点类的实现:

列表类的操作集:

列表类的实现 

列表类的初始化(init)

C.无序列表

插入与基于复制的构造

插入操作:

基于复制的构造:

删除和析构 :

删除的实现:

析构:

查找:

去重(唯一化操作):


A.循秩访问

列表作为和向量一样是线性的数据结构,我们首要关心的是,他能否可以用[ ] 来便捷的访问我们需要的内存单元呢? 

首先来说是可以实现的:

具体实现

 同向量的重载不太一样,多了很多内容。这里的列表其实就是链表 ,每个节点node由一个指针succ和 data 构成。那么它的时间复杂度很明显看出来是O(r),比起向量O(1)的速度,确实效率低下。具体来分析一下它的平均时间复杂度:

假设有这么多的数据0~n:

 访问第一个元素的时候需要 while循环一次 ,第二个两次。。。。。。 可以看出来如果逐个访问完n个元素 ,会花费 O(n^2) 的时间复杂度,那么访问其中随机的某一元素花费的时间复杂度 就为O(n^2) / n = O(n).


B.接口与实现

从静态到动态:(为什么要学习列表)

列表和向量都是线性的数据结构,它们是对称互补的,体现于动态操作和静态操作中彼此不同的效率。

首先来了解什么是动态静态的操作,读取操作为静态 写入操作为动态。 

除了操作分动态静态,数据元素的存储与组织也分:

 前者以向量作为代表,数据空间的大小创立之初就已然确定,物理地址与逻辑上的次序是一一对应,可以根据秩很快进行访问。那么它在进行静态操作的时候,则具有很大优势,元素都被存放在与逻辑相应的内存单元、有明确的物理地址,进行get() ,search() 操作只需要O(1) 时间。反过来 ,在应用动态的操作insert 、remove 起来就力不从心了,回忆代码逻辑可以知道,这两个操作一个插入一个元素,然后原区间整体往后移,一个移除一个元素然后整体往前移,时间复杂度均是O(n)

为了弥补在动态操作上的这种不足,我们引进了列表这种动态的数据结构,来扬长避短。


什么是列表

 值得细说的是,对于列表,我们总能像下图那样,有个入口在首节点处进入,然后通过 引用 跳转到下一节点,不断引用跳转至末节点,用秩对应的话,首节点就是rank 0 末节点是 rank n-1,在逻辑上是顺序对应的,但是物理地址上他们却并不一定是紧邻的,不过并不影响他们的逻辑顺序,因为有引用彼此相邻。


call - by - position 循位置访问

在之前第A章的部分,已经说明了如何通过列表实现循秩访问,但是效率太低了 是O(n)。

向量因为它下图中的性质可以很方便用循秩访问:

 而列表:

 对于循位置访问,有个很有意思的比喻:

循位置访问主要是关注节点与节点之间,尤其关注于相邻节点之间的关系即相互引用、相互访问,从而提高效率。


列表类的实现(操作集):

先有节点才有列表。

节点的操作集:

 节点类的实现:

 从这个节点定义前驱后继可以看出来,这会是一个双向的链表,目的是后面实现删除的操作。

列表类的操作集:

列表类的实现 

这个实现只是一个大体的框架,学习的重点是要 好好领会下图:

 引入了 头哨兵 header尾哨兵 trailer 是 和头节点 first 末节点 last  互为前驱后继,对他们的理解好坏直接影响后面接口功能的实现的理解。不过相信邓的课,大家上完第二章之后都应该很熟悉哨兵这个概念了。对应秩的话可以认为是 -1 0 n-1 n 。头尾元素是与生俱来的 但是是 不可见的 (invisble )而且二者不相同,而首末元素,不见得不相同甚至可能不存在,但是是可见的 (visble)。

列表类的初始化(init)

 为头尾元素申请一个节点使其真实存在,并互联它们。现在初始化是一个空集的状态像下图。


C.无序列表

插入与基于复制的构造

插入操作:

主要由下图的两个函数实现: 

 依次介绍一下参数,以了解如何使用,

意为:在p节点 前插入 data e 。函数体中的实现也很容易看懂,调用P节点的insertaspred 函数,把e 作为p 的前驱插入。

下图是插入作前驱的具体实现,建造一个新的节点x ,由于是调用P节点的insertaspred 函数,这里的pred 是 p 的pred ,this指针是p本身,这里是将明确新的节点x 的前驱和后继。后面则是,进行互联。

 整个函数效果如下图:

 整个操作就是一个微创手术一样,可以回想一下向量的插入操作,与向量大张旗鼓不同,这里仅对相邻的节点进行操作 互联,花费的时间也仅是O(1) ,这就是列表在写入这种动态操作的优势体现。即便p节点是首节点 first 或者是 last 末节点也无所谓,操作一样是安全的,因为我们列表初始化的时候,引入了头尾哨兵节点。

基于复制的构造:

结合参数表,这个函数的意思是,从节点p开始,随后将后面连续n个节点复制进新列表进行构造。

函数体的内容也很好理解,init() 先构建一个空列表,insertaslast 其实是insertasbefore (trailer,p->data),这里就体现出了头尾哨兵有多好用了。


删除和析构 :

删除的实现:

 代码的实现 没什么难理解的部分可以结合下图一起理解:


析构:

主要是要两步,第一步将列表的可见部分的节点全部删除主要是通过remove 头哨兵的后继完成,第二步将头尾两节点释放。


查找:

思路主要是仿照向量的find()函数:

如同下图,有一个列表L ,以p节点作为基准,在这个p的真前驱中逐个比对,所谓真前驱即不包括p本身。

 具体代码如下:

 一点小细节在于,参数表的顺序是 find (e,n,p),意为在p的n个真前驱中寻找,先n再p,如果写成find(e,p,n) 意为 在p的真后继中寻找。函数体内容很简单不细说了,当有多个重复元素的时候会停在秩最大的那一项。

去重(唯一化操作):

首先对于这个去重操作,我们对列表有以下的理解:

 前面绿色的部分是不会存在重复元素的,可以认为是算法的不变性,紫色则是非空后缀,我们以e作为目标在前缀不断寻找,无论查找成功与否,问题规模最少减一。

那么去重的具体实现有:

 首先是一个平凡情况处理,如果元素数量仅有1 自然无重复。可以紧接着注意到   可以认为这是一个初始化,当p节点是首节点的时候,它的前驱是头哨兵,自然没重复 满足了 不变性。函数体中的while循环体里,要认识到,find()是会返回命中的节点的指针的若查找失败则返回null。q若存在即说明 q是有重复元素的节点,则remove ,至于q不存在时r++,则说明将当前节点p放入绿色区域中,在下一轮循环中进行寻找。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值