数据结构-顺序表和单链表python

本文探讨了Python中的顺序表与单链表,介绍了它们的结构、Python实现、操作特性,以及在不同场景下的优缺点。重点比较了顺序表的O(1)访问速度与链表的动态管理灵活性,以及各自在插入和删除操作中的时间复杂度。
摘要由CSDN通过智能技术生成

1 顺序表

1.1 顺序表的形式和结构

在这里插入图片描述

上图表示的是顺序表的基本形式,数据元素本身连续存储,每个元素所占的存储单元大小固定相同,元素的下标是其逻辑地址,而元素存储的物理地址(实际内存地址)可以通过存储区的起始地址Loc (e0)加上逻辑地址(第i个元素)与存储单元大小(c)的乘积计算而得,即:

Loc(ei) = Loc(e0) + c*i

故,访问指定元素时无需从头遍历,通过计算便可获得对应地址,其时间复杂度为O(1)

如果元素的大小不统一,则须采用图b的元素外置的形式,将实际数据元素另行存储,而顺序表中各单元位置保存对应元素的地址信息(即链接)。由于每个链接所需的存储量相同,通过上述公式,可以计算出元素链接的存储位置,而后顺着链接找到实际存储的数据元素。注意,图b中的c不再是数据元素的大小,而是存储一个链接地址所需的存储量,这个量通常很小。

图b这样的顺序表也被称为对实际数据的索引,这是最简单的索引结构

1.2 python的顺序表

  • 概念

Python中的listtuple两种类型采用了顺序表的实现技术,具有前面讨论的顺序表的所有性质。

tuple是不可变类型,即不变的顺序表,因此不支持改变其内部状态的任何操作,而其他方面,则与list的性质类似。

  • list的基本实现技术

Python标准类型list就是一种元素个数可变的线性表,可以加入和删除元素,并在各种操作中维持已有元素的顺序(即保序),而且还具有以下行为特征:

  • 基于下标(位置)的高效元素访问和更新,时间复杂度应该是O(1);

    为满足该特征,应该采用顺序表技术,表中元素保存在一块连续的存储区中。

  • 允许任意加入元素,而且在不断加入元素的过程中,表对象的标识(函数id得到的值)不变。

    为满足该特征,就必须能更换元素存储区,并且为保证更换存储区时list对象的标识id不变,只能采用分离式实现技术。

在Python的官方实现中,list就是一种采用分离式技术实现的动态顺序表。这就是为什么用list.append(x) (或 list.insert(len(list), x),即尾部插入)比在指定位置插入元素效率高的原因。

在Python的官方实现中,list实现采用了如下的策略:在建立空表(或者很小的表)时,系统分配一块能容纳8个元素的存储区;在执行插入操作(insert或append)时,如果元素存储区满就换一块4倍大的存储区。但如果此时的表已经很大(目前的阀值为50000),则改变策略,采用加一倍的方法。引入这种改变策略的方式,是为了避免出现过多空闲的存储位置。

1.3 顺序表的操作

1.3.1 顺序表的数据插入

在这里插入图片描述

  • prepend(头部插入)
    可以明显的知道,因为顺序表元素是连续的,如果你想要头部插入一个元素,则需要将之前所有元素整体往后移动,因此时间复杂度无疑为O(n)
  • append(尾部插入)
    尾部的话则只需要扩容在尾部加入元素即可,因此时间复杂度为O(1)
  • insert(中间插入)
    中间插入参考头部插入,后面的元素也需要往后移动,因此时间复杂度也为O(n)

1.3.2 删除数据

  • 删除表尾数据

时间复杂度为O(1)

  • 删除不为表尾数据

时间复杂度为O(n)

1.3.3 访问指定index元素

访问指定元素时无需从头遍历,通过计算便可获得对应地址,其时间复杂度为O(1)

2.单链表

从上面顺序表的特性我们可以知道,由于顺序表的空间地址是连续的,所有做插入操作或者删除操作的话,后面的元素需要做移动操作,可能时间复杂度会达到O(n),当我们的程序需要做这种频繁的操作时,我们就不适合用这种数据结构了,而链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。这里我们先介绍一下单链表。

2.1 链表的定义

链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续存储数据,而是在每一个节点(数据存储单元)里存放下一个节点的位置信息(即地址)

下面是图示意:
在这里插入图片描述

可以看到每一个节点都有一个数据区和链接区,数据区可以是任何数据结构(元祖,字典,列表),链接区则保存一个地址指向下一个元素

2.2 单向链表

2.2.1 定义

单向链表也叫单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。

2.2.2 python实现单链表

  • 节点实现
class SingleNode(object):
    """单链表的结点"""
    def __init__(self,item):
        # _item存放数据元素
        self.item = item
        # _next是下一个节点的标识
        self.next = None

由于python没有指针的概念,因此用一个类来模拟表示一个节点,self.item保存值,self.next保存地址

  • 基本功能实现
    • is_empty() 链表是否为空
    • length() 链表长度
    • travel() 遍历整个链表
class SingleLinkList(object):
    """单链表"""
    def __init__(self):
        # 初始头节点为None
        self._head = None

    def is_empty(self):
        """判断链表是否为空,如果头节点为None,说明这个列表是为空的"""
        return self._head == None

    def length(self):
        """链表长度"""
        # cur初始时指向头节点
        cur = self._head
        count = 0
        # 尾节点指向None,当未到达尾部时
        while cur != None:
            count += 1
            # 将cur后移一个节点
            cur = cur.next
        return count

    def travel(self):
        """遍历链表"""
        cur = self._head
        # 判断最后一个节点是否为None,如果为None说明已经遍历到尾了
        while cur != None:
            print(cur.item)
            cur = cur.next
        print ""
  • 头插入操作实现
    def add(self, item):
        """头部添加元素"""
        # 先创建一个保存item值的节点
        node = SingleNode(item)
        # 将新节点的链接域next指向头节点,即_head指向的位置
        node.next = self._head
        # 将链表的头_head指向新节点
        self._head = node

新建一个节点,改变头节点的指向

  • 尾插入实现
    def append(self, item):
        """尾部添加元素"""
        node = SingleNode(item)
        # 先判断链表是否为空,若是空链表,则将_head指向新节点
        if self.is_empty():
            self._head = node
        # 若不为空,则遍历找到尾部,将尾节点的next指向新节点
        else:
            cur = self._head
            while cur.next != None:
                cur = cur.next
            cur.next = node
  • 中间插入
    def insert(self, pos, item):
        """指定位置添加元素"""
        # 若指定位置pos为第一个元素之前,则执行头部插入
        if pos <= 0:
            self.add(item)
        # 若指定位置超过链表尾部,则执行尾部插入
        elif pos > (self.length() - 1):
            self.append(item)
        # 找到指定位置
        else:
            node = SingleNode(item)
            count = 0
            # pre用来指向指定位置pos的前一个位置pos-1,初始从头节点开始移动到指定位置
            pre = self._head
            while count < (pos - 1):
                count += 1
                pre = pre.next
            # 先将新节点node的next指向插入位置的节点
            node.next = pre.next
            # 将插入位置的前一个节点的next指向新节点
            pre.next = node
  • 删除操作
    def remove(self, item):
        """删除节点"""
        cur = self._head
        pre = None
        while cur != None:
            # 找到了指定元素
            if cur.item == item:
                # 如果第一个就是删除的节点
                if not pre:
                    # 将头指针指向头节点的后一个节点
                    self._head = cur.next
                else:
                    # 将删除位置前一个节点的next指向删除位置的后一个节点
                    pre.next = cur.next
                break
            else:
                # 继续按链表后移节点
                pre = cur
                cur = cur.next

3.顺序表和单链表进行对比

  • 顺序表特点:顺序表的优点是访问元素的时间复杂度比较小,缺点在于顺序表的空间必须是连续的,如果说一旦动态的改变,整个存储区都得改变。

  • 链表的特点:可以对离散的存储空间得到充分的利用,但是它的缺点为在于额外的开销比较大,利用链表之后,你对于存储的操作时(add,insert,append)时间复杂度会比顺序表小,可以说是空间复杂度换时间复杂度的做法

操作链表顺序表
访问元素O(n)O(1)
在头部插入/删除O(1)O(n)
在尾部插入/删除O(1)O(1)
在中间插入/删除O(1)O(n
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值