【问题标题】:Cost of list functions in PythonPython中列表函数的成本
【发布时间】:2010-12-04 01:18:26
【问题描述】:

基于this older thread,看起来Python中列表函数的成本是:

  • 随机访问:O(1)
  • 在前面插入/删除:O(n)
  • 插入/删除到后面:O(1)

谁能确认这在 Python 2.6/3.x 中是否仍然适用?

【问题讨论】:

    标签: python performance list


    【解决方案1】:

    没错,在前面插入会强制所有元素移动到位。

    collections.deque 提供类似的功能,但针对两侧插入进行了优化。

    【讨论】:

    • 这是否意味着底层数据结构是一个数组?如果它是一个列表,任何地方的插入,包括在前面,都应该是一个 O(1) 操作,对吧?
    【解决方案2】:

    看看here。这是针对不同类型列表的 PEP。指定的版本是 2.6/3.0。

    追加(插入到后面)是O(1),而插入(其他任何地方)是O(n)。所以是的,看起来这仍然是真的。

    Operation...Complexity
    Copy........O(n) 
    Append......O(1)
    Insert......O(n) 
    Get Item....O(1)
    Set Item....O(1)
    Del Item....O(n) 
    Iteration...O(n)
    Get Slice...O(k)
    Del Slice...O(n)
    Set Slice...O(n+k)
    Extend......O(k) 
    Sort........O(n log n)
    Multiply....O(nk)
    

    【讨论】:

    • 感谢 Ryeguy——感谢它。同样来自 kaizer.se 引用的 TimeComplexity 文档: x in s............ O(n) min(s), max(x)... O(n)
    • @ryeguy:惊讶地发现插入和删除是 O(n) 操作。列表数据结构的重点不是将插入和删除的时间复杂度降低到 O(1) 吗?从上面看,底层数据结构似乎更像是一个数组。我错过了什么吗?
    • @AKE 见array list。在数据结构的不同实现中存在权衡。在典型的 O(1) 插入/删除列表中,您通常将 Get Item 设置为 O(n)。
    • @MikeS:是的,我明白了。在提出这个问题之后,我发现处理器架构也使实际实现的选择有所不同。例如,具有高速缓存内存行为的处理器,其中每次内存访问都会拉回与相邻内存的高速缓存行,这意味着实际上从缓存中保存的相邻内存地址的 O(n) 插入/删除实际上可能比 O(1 ) 从内存中随机分布的位置访问内存。
    • 检查一个整数是否存在于数字列表中的复杂性是多少? >>> 3 in lis
    【解决方案3】:

    Python 3 主要是一种进化变化,在数据结构及其复杂性方面没有大的变化。

    Python 集合的规范来源是 Wiki 上的 TimeComplexity

    【讨论】:

      【解决方案4】:

      Fwiw,如果需要,有一个更快的(对于某些操作...插入 O(log n))list implementation,称为 BList。 BList

      【讨论】:

        【解决方案5】:

        我知道这篇文章很旧,但我最近自己做了一个小测试。 list.insert() 的复杂度似乎是 O(n)

        代码:

        '''
        Independent Study, Timing insertion list method in python
        '''
        import time
        
        def make_list_of_size(n):
            ret_list = []
            for i in range(n):
                ret_list.append(n)
            return ret_list
        
        #Estimate overhead of timing loop
        def get_overhead(niters):
            '''
            Returns the time it takes to iterate a for loop niter times
            '''
            tic = time.time()
            for i in range(niters):
                pass #Just blindly iterate, niter times
            toc = time.time()
            overhead = toc-tic
            return overhead
        
        def tictoc_midpoint_insertion(list_size_initial, list_size_final, niters,file):
            overhead = get_overhead(niters)
            list_size = list_size_initial
            #insertion_pt = list_size//2 #<------- INSERTION POINT ASSIGMNET LOCATION 1
            #insertion_pt = 0 #<--------------- INSERTION POINT ASSIGNMENT LOCATION 4 (insert at beginning)
            delta = 100
            while list_size <= list_size_final:
                #insertion_pt = list_size//2 #<----------- INSERTION POINT ASSIGNMENT LOCATION 2
                x = make_list_of_size(list_size)
                tic = time.time()
                for i in range(niters):
                    insertion_pt = len(x)//2 #<------------- INSERTION POINT ASSIGNMENT LOCATION 3
                    #insertion_pt = len(x) #<------------- INSERTION POINT ASSIGN LOC 5 insert at true end
                    x.insert(insertion_pt,0)
                toc = time.time()
                cost_per_iter = (toc-tic)/niters #overall time cost per iteration
                cost_per_iter_no_overhead = (toc - tic - overhead)/niters #the fraction of time/iteration, #without overhead cost of pure iteration                                              
                print("list size = {:d}, cost without overhead = {:f} sec/iter".format(list_size,cost_per_iter_no_overhead))
                file.write(str(list_size)+','+str(cost_per_iter_no_overhead)+'\n')
                if list_size >= 10*delta:
                    delta *= 10
                list_size += delta
        
        def main():
            fname = input()
            file = open(fname,'w')
            niters = 10000
            tictoc_midpoint_insertion(100,10000000,niters,file)
            file.close()
        
        main()
        

        查看 5 个可以插入的位置。成本当然取决于列表的大小,以及您与列表开头的距离(即必须重新组织多少内存位置)

        忽略情节的左图

        【讨论】:

          猜你喜欢
          • 2011-05-24
          • 2018-01-28
          • 2019-06-28
          • 1970-01-01
          • 2011-05-19
          • 2013-02-19
          • 2014-11-18
          • 2019-03-26
          • 1970-01-01
          相关资源
          最近更新 更多