[Data Structure]线性表Linear List2

Posted huisclos

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Data Structure]线性表Linear List2相关的知识,希望对你有一定的参考价值。

线性表

链接表(链表)

线性表实现的基本需要:

  • 能够找到表中的首元素(无论直接或间接,通常很容易做到)
  • 从表里的任一个元素出发,可以找到它之后的下一个元素

 显然,把表元素保存在连续的存储区里,自然满足这两个需求,顺序关联是隐含的。但满足这两种需求,并不一定要连续存储元素

实现线性表的另一方式是基于链接结构,用链接显式地表示元素之间的顺序关联。基于链接技术实现的线性表称为链接表或链表

实现链接表的基本想法:

  • 把表元素分别存储在一批独立的存储块(称为结点)里
    • 保证从一个元素的结点可找到与其相关的下一个元素的结点
    • 在结点里用链接的方式显式记录元素(结点)之间的关联
  • 这样,只要知道表中第一个结点,就能顺序找到表里其他元素

表的实现——链接表

 链接表有多种不同的组织方式。下面先讨论最简单的单链表,其中每个结点里记录着下一元素的结点的标识。

单链表

单链表结点的形式(链接域保存下一结点的标识)
技术图片

在单链表里,与表里的 n 个元素对应的 n 个结点通过链接形成一条结点链。从表里的任一个结点都可以找到保存下一个元素的结点

 要掌握一个单链表,就需要(也仅需要)掌握表的首结点,从它

  • 可以找到表的首元素(表里保存的数据)
  • 还可以找到表中下一结点的位置
    按同样方式继续下去,就可以找到表里的所有数据元素

表头变量:保存着链表第一个结点的标识(链接)的变量
技术图片

一个具体的表由一些具体结点构成

  • 每个结点(对象)有自己的标识(下面也常直接称其为链接)
  • 结点之间通过结点链接建立起顺序联系
  • 给表的最后一个结点(表尾结点)的链接域设置一个不会作为结点对象标识的值(Python 里自然应该用 None),称为空链接

通过判断是否空链接,可以知道是否已经到了表的结束

  • 在做检索表中元素的工作时,据此判断检索工作是否完成
  • 如果表头指针的值是空链接,说明“它所引用的表已经结束”。没有元素就已经结束,说明这个表是空表

 在实现算法时,我们并不需要关心具体的表里各结点的具体链接的值是什么(它们总保存在表结构里),只需要关心链表的逻辑结构

  • 链表的操作也只需根据链表的逻辑结构考虑和实现

## 单链表操作:基本操作
考虑链接表的几个基本操作:

  • 创建空链表:只需将表头变量设置为空链接
    在 Python 里将其设置为 None
  • 删除链表:丢弃表的所有结点,与具体环境有关
    • 在一些语言(如 C 语言)里需要做许多事情,释放所用存储
    • 在 Python 里,只需简单将表指针设 None,就丢掉了整个链表的所有结点。 Python 程序的存储管理系统会自动回收不用的存储
  • 判断表是否为空:将表头变量的值与空链接比较
    • 在 Python 里检查其值是否为 None
  • 判断表是否满:链接表不会满,除非存储空间用完

单链表操作:加入元素

给单链表加入元素的一些基本情况

  • 位置可以为首端,尾端,定位。不同位置的操作复杂性可能不同
  • 加入元素不需要移动已有数据,只需为新元素安排一个新结点,然后把新结点连接在表里所需的位置

通过修改链接,改变表的结构

首端加入: 1) 创建一个新结点存入数据; 2) 把原链表首结点的链接存入新结点的链接域; 3) 修改表头变量使之引用新结点
技术图片

尾端加入: 1) 创建一个新结点存入数据; 2) 表空时直接让表头变量引用这个新结点并结束,否则找到表尾结点; 3) 令表尾结点的链接域引用这一新结点,并将新结点的链接域设置为空链接
技术图片

定位加入: 1) 找到新结点加入位置的前一结点,不存在时结束; 2) 创建新结点存入数据; 3) 修改前一结点和新结点的链接域将结点连入
技术图片

 单链表操作:删除元素

删除元素,所用技术与加入元素类似

  • 首端删除:直接修改表头指针,使之引用当时表头结点的下一个结点。Python 系统里会自动回收无用对象的存储块,下同
  • 尾端删除:找到倒数第二个结点,将其链接域设置为空链接
  • 定位删除:找到要删除元素所在结点的前一结点,修改它的链接域将要求删除的结点从表中去掉
    技术图片

    单链表操作:扫描和遍历

    许多操作中需要扫描表里一个个结点,可能检查其中的元素,如
  • 这种操作的过程称为遍历,顺序检查一个数据结构的所有元素
  • 求表元素的个数
  • 在表中查找特定位置的元素,或查找满足某些条件的元素
    进行这类操作,都需要用一个(或几个)扫描变量
    有些表操作比较复杂,例如表元素排序
    (排序 待续)

    单链表操作:复杂性

     基本操作:
  • 创建空表: O(1)
  • 删除表:在 Python 里是 O(1)。当然存储管理也需要时间
  • 判断空表: O(1)

加入元素(都需要加一个 T(分配) 的时间):

  • 首端加入元素: O(1)
  • 尾端加入元素: O(n),因为需要找到表的最后结点
  • 定位加入元素: O(n),平均情况和最坏情况

删除元素:

  • 首端删除元素: O(1);尾端删除: O(n)
  • 定位删除元素: O(n),平均情况和最坏情况
  • 其他删除通常需要扫描整个表或其一部分, O(n) 操作

其他操作,如果需要扫描整个表或其一部分,都是 O(n) 操作。如

  • 求表的长度(表中元素个数)
  • 定位表中的元素;等等

一类典型的表操作是扫描整个表,对表中每个元素做同样的工作(即遍历操作)。例如,输出所有的元素值,将它们累积到一个变量里等。
这种工作可以通过一个循环完成
遍历操作的复杂性应该是 O(n) * T(元素操作)

有可能改造表的表示方式,提高一些操作的效率。例如,如果工作中经常需要求表长度,可以考虑采用下面结构(加一个表头对象):
技术图片
 这样,在加入/删除元素时需要维护个数记录,有得有失

## 单链表的Python实现
实现链接结构,需要定义相应的类,首先是表示结点的类
 下面是一个简单的结点类:

 class LNode : # 只定义初始化操作
     def __init__(self, elm, nxt):
       self.elem = elm
       self.next = nxt

 简单的使用代码(Python 允许直接访问对象的普通数据域):

llist1 = LNode(1, None); pnode = llist1
for i in range(2, 11):
   pnode.next = LNode(i, None)
   pnode = pnode.next
pnode = llist1
while pnode is not None:
   print(pnode.elem)
  pnode = pnode.next

单链表的实现:几个基本操作

基于结点 LNode 定义一种链接表类型,为此定义一个表类

class LList:
   def __init__(self):
      self.head = None

   def isEmpty(self):
      return self.head == None
    
   def prepend(self, elem):
       self.head = LNode(elem, self.head

LList 对象只有一个 head 域,指向表中的首结点。几个操作(方法):

  • 初始建立的表里没有结点(空表)
  • 根据 head 的值判断是否空表
  • prepend 在表首端加入一个包含新元素的(新)结点

单链表的实现:尾端加入

 append 在表最后加入一个包含新元素的结点

def append(self, elem):
   if self.head == None:
   self.head = LNode(elem, None)
   return
p = self.head
while p.next != None:
    p = p.next
p.next = LNode(elem, None)

注意,这里需要区分两种情况:

  • 如果加入新元素时原表为空,就用 head 记录新加的结点
  • 如果表不空,需要先通过循环找到当时表里的最后一个结点,然后用这个结点的 next 域记录新结点的链接
  • 复杂性(最坏情况)显然为 O(n)

单链表实现:首/尾端弹出

 首/尾端弹出元素的方法(删除操作与此类似)

def pop(self): # 首端弹出
   if self.head == None:
       raise ValueError
   e = self.head.elem
   self.head = self.head.next
   return e

def poplast(self): # 尾端弹出,显然复杂性为 O(n)
   if self.head == None: # empty list
      raise ValueError
   p = self.head
  if p.next == None: # list with only one element
     e = p.elem; self.head = None
     return e
   while p.next.next != None: # till p.next be the last node
   p = p.next
  e = p.next.elem; p.next = None
  return e

单链表实现:其他操作

def find(self, pred): # 在表里找到第一个满足 pred 的元素返回
    p = self.head
    while p != None:
       if pred(p.elem):
          return p.elem
         p = p.next
       return None

 def printall(self): # 输出表中所有元素
    p = self.head
   while p != None:
       print(p.elem)
       p = p.next

单链表的变形

单链表并非只有一种设计,可以根据需要和认识修改设计,例如

  • 前面实现的一个缺点是尾端加入操作的效率低
  • 实际中可能经常需要频繁地在表的两端加入元素

一种可能是采用下面的结构,给表对象增加一个对表尾结点的引用:
技术图片
 这样,在尾端加入元素,也能做到 O(1)

  • 注意:新设计的链表与前面单链表结构近似,结构变化应该只影响到表的变动操作,非变动操作不需要修改。有可能重用前面定义吗?

单链表的变形:带尾节点引用

面向对象技术支持基于已有的类(基类)定义新类(派生类)

  • 派生类继承其基类的所有功能(数据域和方法)
  • 派生类可以定义新的数据域,定义新的方法
  • 派生类可以重新定义基类里已定义的方法(覆盖已有方法)

回到链表,我们可以基于 LList 定义(具有前述结构的)新链表类

  • 让它继承 LList 的所有非变动操作
  • 增加一个尾结点引用域,重新定义表的变动操作

通过继承方式定义新类
class LList1(LList) :

Python 规定,定义时不注明基类,自动以公共类 object 作为基类
前面的 LNode 和 LList 都以 object 作为基类

LList1 定义为 LList 的派生类,覆盖 LList 的一些方法

class LList1(LList):
   def __init__(self):
      LList.__init__(self) # 调用 LList 的初始化方法
      self.rear = None

def prepend(self, elem):
     self.head = LNode(elem, self.head)
     if self.rear == None: # the empty list
     self.rear = self.head # rear points also to the new node

def append(self, elem):
     if self.head == None:
        self.prepend(elem) # call prepend, do the same
    else:
        self.rear.next = LNode(elem, None)
        self.rear = self.rear.next

 首端和尾端删除方法也需要覆盖

def pop(self):
     if self.head is None:
        raise ValueError
     e = self.head.elem
     if self.rear is self.head: # list with one node
        self.rear = None
     self.head = self.head.next
     return e

def poplast(self):
     return None # to be implemented or simply use this

带尾结点记录的单链表可以很好支持前/尾端加入和首端弹出元素























以上是关于[Data Structure]线性表Linear List2的主要内容,如果未能解决你的问题,请参考以下文章

data structure practice

[Data Structure & Algorithm] 线性表的查找

Data Structure 基本概念

线性回归(Linear Regression)

线性表(linear_list)

线性表(linear list)