第三章 线性表

来源:互联网 发布:作品集 知乎 编辑:程序博客网 时间:2024/06/06 01:56

第三章 线性表

线性表,(简称表)就是一组元素序列的抽象。一个线性表是某类元素的一个集合,还记录着元素之间的一种顺序关系。python语言里的内置类型list和tuple都已具体的方式支持程序里的这类需要,他们都可以看作线性表的实现。

一、线性表概念和表ADT

1)从实现者角度,必须考虑①如何把该结构内部的数据组织好;②如何提供一套有用而且必要的操作,并有效实现这些操作。
2)从使用者角度,必须考虑该结构提供的操作,如何有效使用。
研究表数据结构的实现问题:
① 计算机内存特点,以及保存元素和元素顺序信息的需要
②各种重要操作的效率。
基于各方面考虑,两种线性表基本的实现模型:
1)顺序表:将元素顺序放在大块连续存储区;元素间的顺序关系由存储顺序自然表示。
2)链接表:将元素放在通过链接构造起来的一系列存储块里。即链表!

二、顺序表的实现

元素之间的逻辑顺序关系通过元素在存储区里的物理位置表示,隐式表示元素间的关系。

python的list

首先tuple元组是不变的表,不支持改变其内部状态的任何操作,
list的基本实现技术:
初始建立空表时,系统分配容纳8个元素的存储区,插入到满,换一块4倍大的存储区;当存储到50000时,改变策略,缓存储区容量加倍。避免出现过多的空闲的存储位置。
几个操作:list.clear() 清空表。
list.reverse() 修改表自身,将其元素倒置,首尾互换。复杂度为O(n)。
list.sort() 对表中元素排序。第九章讨论,最好的排序算法的平均和最坏情况时间复杂度都是O(n log n)。python的排序算法就是这样。

顺序表的简单总结

顺序表的优点和缺点都在于其元素存储的集中方式和连续性。
优点:定位元素访问O(1),简单操作效率高。
缺点:添加/删除,操作效率低,操作代价高。
这样的表结构不够灵活,不容易调整和变化。如果线性表巨大,顺序表需要巨大块的连续存储空间,会不容易存储管理。

三、链接表

用链接关系显式表示元素之间的顺序关联。
基本思想:把表中的元素分别存储在一批独立的存储块里;能够保证从组成表结构中的任一个结点可找到与其相关的下一个节点;在前一个节点里用连接的方式,显示地记录与下一节点之间的关联。
建立复杂的链接结构,C等语言实现比较麻烦。但是在python中,对于链接结构有关的高级技术的支持非常全面。

链表的构造分两部分:一个是结点的对象;第二个是链接的对象
结点Node:由两个属性,到三个属性 (elem,next)–>(prev , elem , next)
链表List:由一个属性,到两个属性(head)–>(head , rear)

单链表–>单链表变形(增加尾结点)–>循环单链表(首尾相连)–>双链表(前后互联)

单链表

单链表,单项链接表,链表的结点是一个二元组。表元素保存着数据项,链接域保存同下一个表里的下一个结点的标识。
常见形式的单链表:与表里的n个元素对应的n个结点通过链接形成一条结点链。
基本链表操作:
创建空链表、删除链表、判断是否为空
加入元素操作:
表首端插入,一般情况的插入
删除元素:
删除表首元素、一般情况的删除
扫描定位和遍历,链表操作的复杂度
单链表类的实现
Lnode类实现结点
LList类实现链表,构造内部的结点对象Node
这里写图片描述
在该节定义一个简单的链表类。可以根据需要为LList()增加操作,定位插入,删除等。

单链表的变形和操作

考虑到前面单项列表的后端插入操作较为复杂,因此考虑添加尾结点引用。
1. 初始化方法:首先初始化LList对象的数据域,调用LList类的初始化函数,再初始化尾结点为None;
2. 前端插入操作:空表,rear和head都是None,建立结点rear=head;非空表,建立结点,self._head = LNode(elem,self._head)
3. 尾端插入操作:空表,前端添加=后端添加;非空,建立结点,rear后移,self.rear.next=LNode(elm) self.rear=self.rear.next
4. 弹出末尾元素:rear指向最后一个结点,但是无法寻找前一个结点。故head找到rear时,e=p.next.elem取出rear=p p.next=None

类的设计的内在一致性原则:

python对不同方法之间的关系并没有任何约束,也不对这样的一组方法定义做任何相互关系方面的检查。这些方法需要相互协调,保持一致。
一般情况,设计一个类时需要考虑一套统一的规则,类的初始化方法建立起的对象应满足这些规则。

循环单链表

单链表另一变形–循环链表。最后一个结点的next域不用None,而是指向表的第一个结点。
由于循环链表里的结点连成一个圈,表头,表尾从表的形态上无法区分。
与普通单链表的差异在于扫描循环的结束控制
1. 前端加入元素:就是在首尾结点之间加入新的结点(首),尾结点引用更新。
2. 尾端加入元素:也是在首尾节点之间加入新的结点(尾),首结点应用更新。
3. 输出表元素:循环结束控制。

双链表

单链表,单方向链接,扫描和逐步操作。增加了尾结点构成循环链表,在删除/添加操作时只能在首端为O(1)和尾端添加是O(1),其他都是O(n)。因此两段插入删除都高效完成,必须修改,加入另一方向的链接,得到了双向链接表,双链表。
双链表:从双链表中的任一结点出发,可以找到其前后相邻的结点。
1. 结点删除:p.prev.next=p.next ; p.next.prev=p.prev.第一句为前结点指定后一个结点 前—>后;第二句为后节点指定前结点为该店的前结点。即删除一个元素,就是让该元素相邻的两个互相连接即可。
2. 构造链表:头尾结点指针,最后指向首尾结点

循环双链表

将双链表的首尾两个结点互联:表尾结点的next域指向表的首结点,表首结点的prev域指向尾结点
这里写图片描述

两个链表的操作

1.列表的反转:将截断列表的首结点的next设置为前面的结点。最后将head设置为之前生成的新列表。(通过next设置为之前的结点,完成反转)
2.列表的排序:
①list类型有一个sort方法,可以完成list的元素排序。list.sort()方法完成递增排序;
②也可以用标准函数sorted(object)对各种序列进行排序,返回排序好的列表。
插入排序:从尚未处理的元素中取出插入排序片段,保持仍然正确排序,反复进行直到都加入了排序的片段时,排序完成。
3.排序算法
①列表的插入排序
伪代码:按顺序从第i(>1)个开始取A,取完之后和之前的元素按顺序遍历比较,A小于B则之前的元素后移留空,反复该步骤直至A>B时,将取出的元素赋值到该位置

for i in range(1, len(lst)):  # 暂定排序元素    x = lst[i]    j = i    while j > 0 and x < lst[j - 1]:  # 遍历        lst[j] = lst[j - 1]  # 交换        j -= 1    lst[j] = x

②单链表的插入排序:两种思路,移动表中元素,或者调整节点之间的连接关系
第一种思路:
移动表元素伪代码:取第i(>1)个节点的元素,将前面的结点与该元素比较,满足就后移指针;不满足就插入,将排序好的剩余部分逐步后移。

    def sortm(self):        if self.head is None:            return        crt = self.head.next        while crt is not None:            x, p = crt.elem, self.head            while p is not crt and p.elem <= x:                p = p.next            while p is not crt:                y = p.elem                p.elem = x                x = y                p = p.next            crt.elem = x            crt = crt.next

第二种思路:
调整链接的方式实现插入排序。一个取下链表结点,将其插入一段元素递增的结点链中的正确位置。

    def sort(self):        if self.head is None:            return        last = self.head        crt = last.next        while crt is not None:            p = self.head            q = None            while p is not crt and p.elem <= crt.elem:                q = p                p = p.next            if p is crt:                last = crt            else:                last.next = crt.next                crt.next = p                if q is None:                    self.head = crt                else:                    q.next = crt            crt = last.next

不同链表简单总结

  1. 基本单链表
    通过一个方向链接构造起来。
    首端插入和删除操作需要O(1);定位和尾端操作都需要O(n)的时间
  2. 增加尾结点的单链表
    首端的插入和删除操作都是O(1);尾端的插入O(1);但尾端的删除是O(n)。
  3. 循环单链表
    首尾端的插入删除都是O(1);需要注意结束判断的问题!
  4. 双链表
    每个节点两个方向的链接。高效的找到前后结点。首尾端的插入和删除都是O(1)。循环双链表也是如此。

单链表:遍历,检索都只能表头开始,需要O(n)。
双链表:遍历,检索都可以从表头和表尾开始,需要O(n)。
对应的循环列表:遍历和检索可以从任一点开始。但要注意结束条件。

链接表的优点:
①不需要修改表的元素,表结构容易调整和修改
②整个表由小的存储块构成,容易构成安排和管理。
链接表的缺点:
定位访问需要线性时间,与顺序表相比最大劣势;
单链表尾端删除不是常量时间,双链表增加第二个链接域,可以实现两端的高效插入和删除;
查询时扫描O(n),双链表可以实现O(1);
为存储一个元素需要多一个连接域,存储代价;
双链表可以提高链表操作的灵活性,但需要增加两个连接域

四、表的应用

list自实现,list的pop,链表的pop实现。

本章总结

线性表:基本运算和抽象数据类型。两种实现技术:顺序表和链接表。
比较简答的数据结构,n个数据元素的有限序列,各个元素在表中有特定的排列位置和前后顺序关系
1. 顺序表:插入删除操作代价高,存储代价也可能会很大。
2. 按位置访问点代价高,调整结构灵活;引入尾结点,建立循环,增加反向链接,都可以直接提高操作的效率,但要付出存储的代价。
3. 可以认为,顺序表作为链表的一个结点。因为一个结点可以保存多个数据。
4. 重点是链接表的操作,面向对象,继承已有的类,构造新的类似功能的类。

原创粉丝点击