Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/html/70.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在Python中从列表中删除不必要的项时,为什么计算时间会减少_Python_Time Complexity_Fibonacci - Fatal编程技术网

在Python中从列表中删除不必要的项时,为什么计算时间会减少

在Python中从列表中删除不必要的项时,为什么计算时间会减少,python,time-complexity,fibonacci,Python,Time Complexity,Fibonacci,在过去的几天里,我一直在努力更好地理解计算复杂性以及如何改进Python代码。为此,我尝试了计算斐波那契数的不同函数,比较了如果我做了一些小的修改,脚本运行的时间 我使用一个列表计算斐波那契数,将列表中的元素-2和-1相加 我很困惑地发现,如果我在循环中添加一个.pop(),删除列表中不需要的元素,我的脚本运行速度会明显加快。我不明白这是为什么。循环中的每一步,计算机都会再做一件事。因此,我未经训练的直觉表明,这应该会增加计算时间。当列表很长时,“查找”是列表的最后一个元素吗 这是我的密码: i

在过去的几天里,我一直在努力更好地理解计算复杂性以及如何改进Python代码。为此,我尝试了计算斐波那契数的不同函数,比较了如果我做了一些小的修改,脚本运行的时间

我使用一个列表计算斐波那契数,将列表中的元素-2和-1相加

我很困惑地发现,如果我在循环中添加一个.pop(),删除列表中不需要的元素,我的脚本运行速度会明显加快。我不明白这是为什么。循环中的每一步,计算机都会再做一件事。因此,我未经训练的直觉表明,这应该会增加计算时间。当列表很长时,“查找”是列表的最后一个元素吗

这是我的密码:

import time
import numpy as np

def fib_stack1(n):
    """ Original function """
    assert type(n) is int, 'Expected an integer as input.'
    if n < 2:
        return n
    else:
        stack = [0, 1]
        for i in range(n-1):
            stack.append(stack[-1] + stack[-2])
        return stack[-1]

def fib_stack2(n):
    """ Modified function """
    assert type(n) is int, 'Expected an integer as input.'
    if n < 2:
        return n
    else:
        stack = [0, 1]
        for i in range(n-1):
            stack.append(stack[-1] + stack[-2])
            ### CHANGE ###
            stack.pop(-3)
            ##############
        return stack[-1] 


rec1 = []
rec2 = []
for _ in range(10):
    t1 = time.time()
    fib_stack1(99999)  
    t2 = time.time()
    rec1.append(t2-t1)
    t1 = time.time()
    fib_stack2(99999)  
    t2 = time.time()
    rec2.append(t2-t1)
print(np.array(rec1).mean())
print(np.array(rec2).mean())

列表
以连续方式将其元素存储在内存中

因此
list
对象的
append
方法需要不时调整分配的内存块大小(幸运的是,不是每次调用
append

有时,系统能够“就地”调整大小(在当前内存块之后分配更多内存),有时则不能:它必须找到一个足够大的连续内存块来存储新列表

当调整大小未“就位”时,需要复制现有数据。(请注意,列表大小减小时不会发生这种情况)

因此,如果复制列表时列表中的元素较少,则操作会更快


请注意,
list.append
仍然非常快。在列表末尾添加是最快的方法(与每次都必须移动元素以释放其“插槽”的插入相比)。

不,查找列表中的任何元素都是在相同的时间内完成的(在计算机科学中称为恒定时间行为)。添加对
pop
的调用确实会稍微增加每个循环迭代所需的工作量,但列表中的元素永远不会超过3个。在第一个版本中,列表在每次迭代中都会增长,这样的操作可能是完全免费的,也可能是非常昂贵的,这取决于列表在引擎盖下实际分配了多少额外内存,而这些内存是无法直接访问的

基本上,当您实例化一个列表时,会预先分配一些额外的空间,为将来的
append
s腾出空间,而代价是“浪费”空间。如果列表已满,则需要将其放大,以便进一步执行
append
s,因此这些特定的append比通常情况下要昂贵得多。如果数组末尾的内存中已经存在其他数据,则必须将列表元素中的所有数据(实际上只是指针)复制到一个新的内存位置,在这个位置上,整个新列表可以存储在一个连续的内存块中


有关列表增长行为的更多信息(仅在CPython中,因为这是特定于实现的),请参见,例如

维护列表的开销超过了额外的操作。这可以通过以下方式证明:

注意:

  • 在第一种情况下,python不知道您不需要旧值,因此它必须索引和存储所有以前的值,如其他人所述,包括在该列表上复制,因为它需要分配更多内存
  • 在第二种情况下,python不必存储旧值,因此自每次添加/pop以来,列表没有增加。它删除项目零,将所有较大的项目复制回一个空间,然后继续
  • 在第三种情况下(如下所述),我们删除中间列表,只需手动应用转换。这就是为什么
    fib\u stack2
    的元素大小之和等于
    fib\u stack3
    中组合的两个整数
如果不首先列出以下内容,则可以进一步改进:

def fib_stack3(n):
    """ Modified function """
    assert type(n) is int, 'Expected an integer as input.'
    if n < 2:
        return n
    else:
        second_anc, first_anc = 0, 1
        for i in range(n-1):
            second_anc, first_anc = first_anc, second_anc + first_anc
        return first_anc 
很明显,最后一个案例,我们根本不需要麻烦列出一个清单,就赢了

这是因为我们不再将列表向下移动,我们只是重复使用相同的两个整数。这是因为python允许单线交换

当列表很长时,“查找”是列表的最后一个元素吗

否,列表长度对查找速度没有影响。这些是数组列表,不是链表。这很可能与内存分配或缓存性能有关。垃圾收集器也参与其中

删除不需要的列表元素时,Python永远不必为列表分配更大的缓冲区。它还可以重用为
int
对象分配的内存,而不是从操作系统请求更多内存。考虑到整数有多大,重用它们的内存是一件大事。(内存分配的详细信息取决于Python版本和底层标准库分配器。Python 2有一个
int
s的自由列表,但不是
long
s;Python 3没有
int
s的自由列表。Python本身并不努力为大型对象重用分配,但底层分配器可能正在这样做有些东西。)

此外,当您必须不断分配新的整数时,尤其是像第99999个Fibonacci数这样大的整数,您不会从CPU的缓存中获得太多好处。主内存访问比缓存慢得多


最后,您的
fib_stack1
的分配模式(大量的分配,没有那么多的对象引用计数下降到0)触发Python的循环检测器系统,也称为垃圾收集器,它需要时间来运行,并且会触及大量不需要触及的内存,从而影响缓存性能。在我自己的测试中,特别是在Python3上,fib_stack1会暂时产生显著的加速。

简短的回答是肯定的。此外,您可以完全删除列表并查看进一步的改进
# Func      List Size (Sum of Elements Size)
fib_stack1: 120 (72)
fib_stack1: 90136 (4888568)
fib_stack1: 162720 (19034164)
fib_stack1: 260864 (42436332)
fib_stack1: 330256 (75095060)
fib_stack1: 418080 (117010332)
fib_stack1: 529224 (168182184)
fib_stack1: 595424 (228610568)
fib_stack1: 669896 (298295536)
fib_stack1: 753680 (377237048)

fib_stack2: 112 (48)
fib_stack2: 112 (1904)
fib_stack2: 112 (3752)
fib_stack2: 112 (5608)
fib_stack2: 112 (7456)
fib_stack2: 112 (9312)
fib_stack2: 112 (11160)
fib_stack2: 112 (13008)
fib_stack2: 112 (14864)
fib_stack2: 112 (16712)

fib_stack3: 48
fib_stack3: 1904
fib_stack3: 3752
fib_stack3: 5608
fib_stack3: 7456
fib_stack3: 9312
fib_stack3: 11160
fib_stack3: 13008
fib_stack3: 14864
fib_stack3: 16712
def fib_stack3(n):
    """ Modified function """
    assert type(n) is int, 'Expected an integer as input.'
    if n < 2:
        return n
    else:
        second_anc, first_anc = 0, 1
        for i in range(n-1):
            second_anc, first_anc = first_anc, second_anc + first_anc
        return first_anc 
fib_stack1 1.3875333309173583
fib_stack2 0.41049718856811523
fib_stack3 0.33348443508148196