Python 列表与链表的区别详解

目录
  • python 列表和链表的区别
  • 列表的实现机制
  • 链表
  • 链表与列表的差异

python 列表和链表的区别

python 中的 list 并不是我们传统意义上的列表,传统列表——通常也叫作链表(linked list)是由一系列节点来实现的,其中每个节点都持有一个指向下一节点的引用。

class Node:
	def __init__(self, value, next=None):
		self.value = value
		self.next = next

接下来,我们就可以将所有的节点构造成一个列表了:

>>> L = Node("a", Node("b", Node("c", Node("d"))))
>>> L.next.next.value
'c'

这是一个所谓的单向链表,双向链表的各节点中还需要持有一个指向前一个节点的引用

但 python 中的 list 则与此有所不同,它不是由若干个独立的节点相互引用而成的,而是一整块单一连续的内存区块,我们通常称之为“数组”(array),这直接导致了它与链表之间的一些重要区别。

例如如果我们要按既定的索引值对某一元素进行直接访问的话,显然使用数组会更有效率。因为,在数组中,我们通常可以直接计算出目标元素在内存中的位置,并对其进行直接访问。而对于链表来说,我们必须从头开始遍历整个链表。

但是具体到 insert 操作上,情况又会有所不同。对于链表而言,只要知道了要在哪里执行 insert 操作,其操作成本是非常低的,无论该列表中有多少元素,其操作时间大致上是相同的。而数组就不一样了,它每次执行 insert 操作都需要移动插入点右边的所有元素,甚至在必要的时候,我们可能还需要将这些列表元素整体搬到一个更大的数组中去。

也正因如此,append 操作通常会采取一种被称为动态数组或‘向量'的指定解决方案,其主要想法是将内存分配的过大一些,并且等到其溢出时,在线性时间内再次重新分配内存。但这样做似乎会使 append 变得跟 insert一样糟糕。其实不然,因为尽管这两种情况都可能会迫使我们去搬动大量的元素,但主要不同点在于,对于 append 操作,发生这样的可能性要小得多。事实上,如果我们能确保每次所搬入的数组都大过原数组一定的比例(例如大20%甚至100%),那么该操作的平均成本(或者说得更确切一些,将这些搬运开销均摊到每次 append 操作中去)通常是常数!

数组数据是连续的,一般需要预先设定数据长度,不能适应数据动态的增减,当数据增加是可能超过预设值,需要要重新分配内存,当数据减少时,预先申请的内存未使用,造成内存浪费。链表的数据因为是随机存储的,所以链表可以动态的分配内存,适应长度的动态变化

1.数组的元素是存放在栈中的,链表的元素在堆中
2.读取操作:数组时间复杂度为O(1),链表为O(n)
3.插入或删除操作:数据时间复杂度为O(n),链表为O(1)

列表
关于列表的存储:

列表开辟的内存空间是一块连续的内存,把这个内存等分成几份(单位是字节),他是连续存储的。
如果一个列表长度已满,再append添加元素的话,会在内存中重新开辟一个2倍的内存空间以存储新元素,原列表内存会被清除。

列表与链表复杂度:

按元素值查找:
     按顺序查找,复杂度是一样的。
     按二分查找,链表没法查找.
按下标查找:
     列表是O( 1 )
     链表是O(n)
在某元素后插入:
     列表是O(n)
     链表是O( 1 )
删除某元素:
     列表是O(n)
     链表是O( 1 )

链表------->列表相对应的数据结构
链表是一种线性数据结构(与树形结构相对),不是进行连续存储的。
链表中每一个元素都是一个对象,每个对象称为一个节点,包含有数据域key和执行下一个节点的指针next。通过各个节点之间的相互连接,最终串联成一个链表。
1、存储的过程中,需要先创建节点,然后进行定义。

# 节点定义:
class  Node( object ):
   def  __init__( self ,item):
	     self .item  =  item  # 数据域
	     self . next  =  None  # 指针域

n1  =  Node( 1 )
n2  =  Node( 2 )
n3  =  Node( 3 )

n1. next  =  n2
n2. next  =  n3
# 通过 n1 找到n3的值
print (n1. next . next .item)

只保留头结点,执行第一个位置,剩下的都是next去指定。

2、链表遍历:(头节点的变动)

# 节点定义:
class  Node( object ):
   def  __init__( self ,item):
	     self .item  =  item  # 数据域
	     self . next  =  None  # 指针域

n1  =  Node( 1 )
n2  =  Node( 2 )
n3  =  Node( 3 )

n1. next  =  n2
n2. next  =  n3
# 通过 n1 找到n3的值
print (n1. next . next .item)

3、链表节点的插入和删除操作(非常方便,时间复杂度低)

插入:

p  =  Node( 5 )  # 要插入的值
curNode  =  Node( 1 )  # 标志位
# 顺序不能乱,否则就找不到原链表中的下一个值
p. next  =  curNode. next  # 指定插入值之后的值为标志位之后的值
curNode. next  =  p   # 然后再把原先的链next指向改成插入的值

删除:

curNode 代表当前值
p  =  curNode. next  # 表示要删除的数
curNode. next  =  p. next  # 重新指定建立链表
del  p 删除数

4、建立链表(单链表)

1)头插法:是在head头节点的位置后插入数;得到的链表与原先的列表顺序是相反的。

def  createLinkListF(li):
  l  =  Node()  # 始终指向头节点
   for  num  in  li:
     s  =  Node(num)
     s. next  =  l. next
     l. next  =  s
   return  l

2)尾插法:在链表的尾巴上插入。相当于是追加,必须时刻记住尾巴在哪儿

def  createLinkListR(li):
  l  =  Node()
  r  =  l  # r指向尾节点
   for  num  in  li:
     s  =  Node(num):
     r. next  =  s
     r  =  s  # 重新指定尾节点

双链表
双链表中每个节点有两个指针:一个指向后面节点,一个指向前面节点。

1、节点定义:

class  Node( object ):
   def  __init__( self ,item = None ):
     self .item  =  item    # 记录当前值
     self . next  =  None    # 记录下一个值
     self .prior  =  None   # 记录前置的一个值

2、双链表节点的插入和删除

curNode  =  Node( 1 )  # 取一数据作为标志位

1)插入:

p  =  Node( 2 )  # 要插入的数
p. next  =  curNode. next  # 指定插入数的next 是 当前数的next
curNode. next .prior  =  p  # 指定插入数的下一个数的 前置数为当前的数值
p.prior  =  curNode  # 插入数的前置数为 标志位
curNode. next  =  p  # 指定,标志位的next数是当前要插入的数

2)删除:

p  =  curNode. next  # 标志位的下一个数,要删除的数
curNode. next  =  p. next  # 将next指向下一个数
p. next .prior  =  curNode # 将要删除数的下一个数的前置数改为标志位
del  p  # 删除当前数

3、建立双链表

尾插法:
def  createLinkListR(li):
  l  =  Node()
  r  =  l
   for  num  in  li:
     s  =  Node(num)
     r. next  =  s
     s.prior  =  r
     r  =  s
   return  l,r

单链表逆置

循环反转单链表。在循环的方法中,使用pre指向前一个节点,cur指向当前节点,每次把cur->next指向pre即可。

# 创建节点
class  Node( object ):

     def  __init__( self ,item = None , next = None ):
         self .item  =  item  # 数据域
         self . next  =  next  # 指针域 

# 循环逆置方法
def  revLinkList(link):

     if  link  is  None  or  link. next  is  None :
         return  link

     pre  =  link  # 记录当前节点的值
     cur  =  link. next  # 记录下一节点的值
     pre. next  =  None  # 先将当前节点的next指向定为None

     while  cur:  # 链表中一直有值
         tmp  =  cur. next  # 获取cur的下一个值,临时赋值给tmp
         cur. next  =  pre  # 将cur值指向pre
         pre  =  cur  # 重新指定
         cur  =  tmp

     return  pre  # 把当前值返回

#应用
link  =  Node( 1 , Node( 2 , Node( 3 , Node( 4 , Node( 5 , Node( 6 , Node( 7 , Node( 8 , Node( 9 )))))))))
r  =  revLinkList(link):   # 链表逆置之后,得到的head值
while  r:
     print ( "{0}---->" . format (r.item))  # 输出逆置后的当前值
     r  =  r. next  # 获取下一个,重新赋给r,然后交给上边输出

列表的实现机制

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

基于下标(位置)的高效元素访问和更新,时间复杂度应该是O(1);为满足该特征,应该采用顺序表技术,表中元素保存在一块连续的存储区中。
允许任意加入元素,而且在不断加入元素的过程中,表对象的标识(函数id得到的值)不变。为满足该特征,就必须能更换元素存储区,并且为保证更换存储区时 list 对象的标识 id 不变,只能采用分离式实现技术。

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

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

列表的实现可以是数组或者链表。列表是一种顺序表,顺序表一般是数组。列表是一个线性表,它允许用户在任何位置进行插入、删除、访问和替换元素。

列表的实现是基于数组或者基于链表结构,当使用列表迭代器的时候,双向链表结构比单链表结构更快。
Python 中的列表英文名是 list,因此很容易与 C 语言中的链表搞混了,因为在 C 语言中大家经常给链表命名为 list。事实上 CPython,也是我们常见的用 C 语言开发的 Python 解释器,Python 语言底层是 C 语言实现的)中的列表根本不是列表。在 CPython 中列表被实现为长度可变的数组。

从细节上看,Python 中的列表是由其他对象的引用组成的连续数组,指向这个数组的指针及其长度被保存在一个列表头结构中。这就意味着,每次添加或删除一个元素时,由引用组成的数组需要改变大小(重新分配)。幸运的是,Python在创建这些数组时采用了指数分配,所以并不是每次操作都要改变数组的大小。但是,也因为这个原因添加或者取出元素是平摊复杂度较低。不幸的是,在普通链表上“代价很小”的其他一些操作在 Python 中计算复杂度相对较高。

总的来说,Python中的列表是一个动态的顺序表,而顺序表大多是由数组实现的。

链表

链表是由许多相同数据类型的数据项按照特定的顺序排列而成的线性表。链表中的数据项在计算机的内存中的位置是不连续且随机的,然而列表是连续的。链表数据的插入和删除是很方便的,但数据的查找效率较低,不能像列表一样随机读取数据。
链表由一个一个的结点构成,每个结点由数据域和“指针域”组成,数据域存储数字,“指针域”指向下一个结点所在的内存地址。(因为Python 中没有指针这一概念的,这里的指针是一种指向)

class Node(object):
    """节点"""
    def __init__(self, elem):
        self.elem = elem
        self.next = None

链表封装的一系列操作:

class SingleLinkList(object):
    """单链表"""
    def __init__(self, node=None):
        self.__head = node

    def is_empty(self):
        """链表是否为空"""
        return self.__head == None

    def length(self):
        """链表长度"""
        # cur游标,用来移动遍历节点
        cur = self.__head
        # count记录数量
        count = 0
        while cur != None:
            count += 1
            cur = cur.next
        return count

    def travel(self):
        """遍历整个链表"""
        cur = self.__head
        while cur != None:
            print(cur.elem, end=" ")
            cur = cur.next
        print("")

    def add(self, item):
        """链表头部添加元素,头插法"""
        node = Node(item)
        node.next = self.__head
        self.__head = node

    def append(self, item):
        """链表尾部添加元素, 尾插法"""
        node = Node(item)
        if self.is_empty():
            self.__head = node
        else:
            cur = self.__head
            while cur.next != None:
                cur = cur.next
            cur.next = node

    def insert(self, pos, item):
        """指定位置添加元素
        :param  pos 从0开始
        """
        if pos <= 0:
            self.add(item)
        elif pos > (self.length()-1):
            self.append(item)
        else:
            pre = self.__head
            count = 0
            while count < (pos-1):
                count += 1
                pre = pre.next
            # 当循环退出后,pre指向pos-1位置
            node = Node(item)
            node.next = pre.next
            pre.next = node

    def remove(self, item):
        """删除节点"""
        cur = self.__head
        pre = None
        while cur != None:
            if cur.elem == item:
                # 先判断此结点是否是头节点
                # 头节点
                if cur == self.__head:
                    self.__head = cur.next
                else:
                    pre.next = cur.next
                break
            else:
                pre = cur
                cur = cur.next

    def search(self, item):
        """查找节点是否存在"""
        cur = self.__head
        while cur != None:
            if cur.elem == item:
                return True
            else:
                cur = cur.next
        return False

链表与列表的差异

Python 中的 list(列表)并不是传统意义上的列表,传统的意义上的列表就是链表,不同地方在于链表在数据存储方面更加的自由,其带有指示下一个结点未知的指向,可以随意的存储数据。而列表则只能分配在一段连续的存储空间里,且是作为一个整体,这就大大限制了数据的变更操作,但其在进行一些基础简单的操作时,时间复杂度极低。

list(列表):动态的顺序表
链表:存储地址分散的,需要“指针”指向的线性表

链表插入删除效率极高,达到O(1)。对于不需要搜索但变动频繁且无法预知数量上限的数据,比如内存池、操作系统的进程管理、网络通信协议栈的 trunk 管理等。甚至就连很多脚本语言都有内置的链表、字典等基础数据结构支持。

列表是一个线性的集合,它允许用户在任何位置插入、删除、访问和替换元素。
列表实现是基于数组或基于链表结构的。当使用列表迭代器的时候,双链表结构比单链表结构更快。
有序的列表是元素总是按照升序或者降序排列的元素。

实现细节
python中的列表的英文名是list,因此很容易和其它语言(C++, Java等)标准库中常见的链表混淆。事实上CPython的列表根本不是列表(可能换成英文理解起来容易些:python中的list不是list)。在CPython中,列表被实现为长度可变的数组。

可参考《Python高级编程(第2版)》

从细节上看,Python中的列表是由对其它对象的引用组成的连续数组。指向这个数组的指针及其长度被保存在一个列表头结构中。这意味着,每次添加或删除一个元素时,由引用组成的数组需要该标大小(重新分配)。幸运的是,Python在创建这些数组时采用了指数分配,所以并不是每次操作都需要改变数组的大小。但是,也因为这个原因添加或取出元素的平摊复杂度较低。

不幸的是,在普通链表上“代价很小”的其它一些操作在Python中计算复杂度相对过高。

利用 list.insert(i,item) 方法在任意位置插入一个元素——复杂度O(N)
利用 list.pop(i) 或 list.remove(value) 删除一个元素——复杂度O(N)

列表的算法效率
可以采用时间复杂度来衡量:

index() O(1)
append O(1)
pop() O(1)
pop(i) O(n)
insert(i,item) O(n)
del operator O(n)
iteration O(n)
contains(in) O(n)
get slice[x:y] O(k)
del slice O(n)
set slice O(n+k)
reverse O(n)
concatenate O(k)
sort O(nlogn)
multiply O(nk)

O括号里面的值越大代表效率越低

到此这篇关于Python 列表与链表的区别详解的文章就介绍到这了,更多相关Python 列表与链表区别内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Python数据结构与算法之列表(链表,linked list)简单实现

    Python 中的 list 并不是我们传统(计算机科学)意义上的列表,这也是其 append 操作会比 insert 操作效率高的原因.传统列表--通常也叫作链表(linked list)--通常是由一系列节点(node)来实现的,其每一个节点(尾节点除外)都持有一个指向下一个节点的引用. 其简单实现: class Node: def __init__(value, next=None): self.value = value self.next = next 接下来,我们就可使用链表的结构来

  • python的链表基础知识点

    python中的链表(linked list)是一组数据项的集合,其中每个数据项都是一个节点的一部分,每个节点还包含指向下一个节点的链接.链表有两种类型:单链表和双链表. 链表的数据结构如下图所示: 在链表中删除操作可以通过修改指针来实现,如下图所示: 插入则是调整,插入点的前后两个指针的指向关系,如下图所示: 在python中每个变量都是指针,例如: 用内置数据结构(list,dict,tuple等)的嵌套/组合,它们隐式地包含了指向/嵌套关系,如graph[u][v]={w0,w1..}类的

  • Python栈的实现方法示例【列表、单链表】

    本文实例讲述了Python栈的实现方法.分享给大家供大家参考,具体如下: Python实现栈 栈的数组实现:利用python列表方法 代码如下: # 列表实现栈,利用python列表方法 class listStack(object): def __init__(self): self.items = [] def is_empty(self): return self.items == 0 def size(self): return len(self.items) def top(self)

  • python/golang 删除链表中的元素

    先用使用常规方法,两个指针: golang实现: type Node struct { value int next *Node } type Link struct { head *Node tail *Node lenth int } // 向链表中添加元素 func (link *Link) add(v int) { if link.lenth == 0 { // 当前链表是空链表 link.head = &Node{v, nil} link.tail = link.head link.l

  • Python 列表与链表的区别详解

    目录 python 列表和链表的区别 列表的实现机制 链表 链表与列表的差异 python 列表和链表的区别 python 中的 list 并不是我们传统意义上的列表,传统列表--通常也叫作链表(linked list)是由一系列节点来实现的,其中每个节点都持有一个指向下一节点的引用. class Node: def __init__(self, value, next=None): self.value = value self.next = next 接下来,我们就可以将所有的节点构造成一个

  • Python之is与==的区别详解

    Python有两个用于相等比较的运算符,"is"和"=="(等于).在这篇文章中,我将教你们两者之间的区别,以及通过几个简单地例子说明什么时候使用它们. 当我还是一个孩子的时候,我们的邻居家有两只双胞胎猫. 这两只猫看起来看起来完全一样 - 同样的木炭毛,同样敏锐的绿眼睛.撇开一些个性怪癖,单从它们的外表根本无法区分它们.但他们确确实实是两只不同的猫,两个不同的生物,尽管它们看起来完全一样. 就如相等和相同在意思上是有差异的,理解这种差异对理解Python中的操作符

  • 基于Python __dict__与dir()的区别详解

    Python下一切皆对象,每个对象都有多个属性(attribute),Python对属性有一套统一的管理方案. __dict__与dir()的区别: dir()是一个函数,返回的是list: __dict__是一个字典,键为属性名,值为属性值: dir()用来寻找一个对象的所有属性,包括__dict__中的属性,__dict__是dir()的子集: 并不是所有对象都拥有__dict__属性.许多内建类型就没有__dict__属性,如list,此时就需要用dir()来列出对象的所有属性. __di

  • Python列表倒序输出及其效率详解

    Python列表倒序输出及其效率 方法一 使用Python内置函数reversed() for i in reversed(arr): pass reversed返回的是迭代器,所以不用担心内存问题. 方法二 使用range()倒序 for i in range(len(arr) - 1, -1, -1): pass 方法三 先使用list自带的reverse()函数,再用range()循环 arr.reverse() for i in range(len(arr)): pass 因为要先倒序,

  • python列表生成器常用迭代器示例详解

    目录 列表生成式基础语法 1. 使用列表生成式,一行解决for循环 2. 双层循环 3. 加判断语句,条件过滤 4. 加入函数 5. 常见几种迭代器:range. zip . enumerate . filter . reduce 列表生成式基础语法 [exp for iter_var in iterable (if conditional)] 原理: 首先迭代 iterable 里所有内容,每一次迭代,都把iterable里相应的内容放在iter_var中,再把表达式exp应用该iter_va

  • Python列表append()函数使用方法详解

    目录 1. 基本使用 2. 任意类型元素 3. 列表同步 3.1 原理剖析 3.2 解决方案 4. append()与extend()的区别 总结 1. 基本使用 append() 函数可以向列表末尾添加元素 语法 list.append( element ) 参数 element:任何类型的元素 实例:向列表末尾添加一个元素 name_list = ['zhangsan', 'lisi', 'wangwu'] name_list.append('zhaoliu') print(name_lis

  • Python列表与元组的异同详解

    前言 "列表(list)与元组(tuple)两种数据类型有哪些区别"这个问题在初级程序员面试中经常碰到,超出面试官预期的答案往往能加不少印象分,也会给后续面试顺利进行提供一定帮助,这道题主要考察候选人对Python基本数据结构的掌握程度,属于简单类型的送分题,那么该如何来回答才不至于丢分? 相同点:都是序列类型 回答它们的区别之前,先来说说两者有什么相同之处.list 与 tuple 都是序列类型的容器对象,可以存放任何类型的数据.支持切片.迭代等操作 >>> foo

  • Python中is和==的区别详解

    Python中有很多运算符,今天我们就来讲讲is和==两种运算符在应用上的本质区别是什么. 在讲is和==这两种运算符区别之前,首先要知道Python中对象包含的三个基本要素,分别是:id(身份标识).type(数据类型)和value(值). is和==都是对对象进行比较判断作用的,但对对象比较判断的内容并不相同.下面来看看具体区别在哪. ==比较操作符和is同一性运算符区别 ==是python标准操作符中的比较操作符,用来比较判断两个对象的value(值)是否相等,例如下面两个字符串间的比较:

  • python多线程与多进程及其区别详解

    前言 个人一直觉得对学习任何知识而言,概念是相当重要的.掌握了概念和原理,细节可以留给实践去推敲.掌握的关键在于理解,通过具体的实例和实际操作来感性的体会概念和原理可以起到很好的效果.本文通过一些具体的例子简单介绍一下python的多线程和多进程,后续会写一些进程通信和线程通信的一些文章. python多线程 python中提供两个标准库thread和threading用于对线程的支持,python3中已放弃对前者的支持,后者是一种更高层次封装的线程库,接下来均以后者为例. 创建线程 pytho

  • Python中__repr__和__str__区别详解

    看下面的例子就明白了 class Test(object): def __init__(self, value='hello, world!'): self.data = value >>> t = Test() >>> t <__main__.Test at 0x7fa91c307190> >>> print t <__main__.Test object at 0x7fa91c307190> 看到了么?上面打印类对象并不是很

随机推荐