Python Big-O缩放-验证和表示(案例:列表的唯一性)

Python Big-O缩放-验证和表示(案例:列表的唯一性),python,list,big-o,Python,List,Big O,我有三种不同的方法来检查列表中条目的唯一性,理论上O(N)、O(N log N)和O(N**2)的比例不同,而N是列表的长度 我确实理解为什么这些方法应该像O(N)、O(N logn)和O(N**2)那样缩放,但我无法从数值上证明这一点 这个想法很简单,就是使用随机列表条目和不同长度多次运行每个方法。然后绘制时间与列表长度的关系图(即N)。我预计每种方法/N的最坏情况应该与理论预测类似,但事实并非如此 守则: import time import random import matplotlib

我有三种不同的方法来检查列表中条目的唯一性,理论上O(N)、O(N log N)和O(N**2)的比例不同,而N是列表的长度

我确实理解为什么这些方法应该像O(N)、O(N logn)和O(N**2)那样缩放,但我无法从数值上证明这一点

这个想法很简单,就是使用随机列表条目和不同长度多次运行每个方法。然后绘制时间与列表长度的关系图(即N)。我预计每种方法/N的最坏情况应该与理论预测类似,但事实并非如此

守则:

import time
import random
import matplotlib.pyplot as plt


#O(N**2) - each loop is O(N)
def is_unique1(alist):
    for i in range(len(alist)):
        for j in range(i+1,len(alist)):
            if alist[i] == alist[j]:
                return False
    return True

#O(N log N) - as sort() is O(N log N)
def is_unique2(alist):
    copy = list(alist)
    copy.sort()
    for i in range(len(alist)-1):
        if copy[i] == copy[i+1]:
            return False
    return True    

#O(N) - as set is O(N)
def is_unique3(alist):
    aset = set(alist)
    return len(aset) == len(alist)


times = []
lengths = []
scale = 1.5

for i in range(1,10):
    print('range:',10**i,'to',10**(i+1),'values calc:',int(10**(i/scale)))

for j in range(1,10):
    for i in range(1,int(10**(j/scale))):
        random.seed(42)
        a = str(random.randint(10**j,10**(j+1)))
        start = time.time()
        is_unique3(a)
        end = time.time()
        times.append(end-start)
        lengths.append(len(a))


print(min(times),max(times))

plt.scatter(lengths,times,s=5)
plt.ylabel('process time (s)')
plt.xlabel('N (length of list)')
plt.title('is_unique3')
plt.grid()
plt.ylim(0.9*min(times),1.1*max(times))
#plt.yscale('log')
plt.show()
结果是:

不幸的是,我根本看不到理论预期和数值计算之间的对应关系

认为一个人能做到这一点只是幻想吗?我这样做是不对的吗?我是否必须检查每个可能的列表条目以获得正确的缩放比例

我很困惑,希望你能给我任何提示

######编辑 谢谢你的评论!我改变了收集每个进程的时间的方式,并决定平均每(现在)100次运行一个列表长度。我试着每100次跑的最长时间,但结果看起来仍然是随机的。修改后的代码段:

import time
import random
import numpy as np
import matplotlib.pyplot as plt


#O(N**2) - each loop is O(N)
def is_unique1(alist):
    for i in range(len(alist)):
        for j in range(i+1,len(alist)):
            if alist[i] == alist[j]:
                return False
    return True

#O(N log N) - as sort() is O(N log N)
def is_unique2(alist):
    copy = list(alist)
    copy.sort()
    for i in range(len(alist)-1):
        if copy[i] == copy[i+1]:
            return False
    return True    

#O(N) - as set is O(N)
def is_unique3(alist):
    aset = set(alist)
    return len(aset) == len(alist)

times = []
lengths = []
times_mean = []
#times_max = []

for j in range(500,10000,1000):
    lengths.append(j)
    for i in range(1,100):
        a = []
        for i in range(1,j):
            a.append(random.randint(0,9))
        start = time.perf_counter()
        is_unique2(a)
        end = time.perf_counter()
        times.append(end-start)
    times_mean.append(np.mean(times))
    #times_max.append(np.max(times))

#print(min(times),max(times))
#print(len(lengths),len(times_mean))

plt.scatter(lengths,times_mean,s=5, label='mean')
#plt.scatter(lengths,times_max,s=5, label='max')
plt.legend(loc='upper left')
plt.ylabel('process time (s)')
plt.xlabel('N (length of list)')
plt.title('is_unique2')
plt.grid()
plt.ylim(0.9*min(times_mean),1.1*max(times_mean))
#plt.yscale('log')
plt.show()
结果是:

虽然方法2和方法3看起来会在某种程度上按O(N)或O(N log N)进行缩放-我希望不是偶然的-方法1看起来仍然是垃圾,甚至不接近O(N**2)。事实上,我认为这种方法,作为一个双循环,是最坏的一英里


我还缺少更一般的东西吗?

为了改进您的实验,您应该使用相同的数据集来评估所有长度的所有函数。目前,您正在为每个要测试的n长度和每个要测试的函数生成一个新数据集。这将导致不确定的结果


此外,大o仅为函数提供一个上限,而不是一个精确的数字,它可能会更低(如果所有元素都是唯一的,则在函数中),但决不会高于上限。

为了改进实验,您应该使用相同的数据集来评估所有长度的所有函数。目前,您正在为每个要测试的n长度和每个要测试的函数生成一个新数据集。这将导致不确定的结果


此外,大o只给出了函数的上限,而不是一个精确的数字,它可能会更低(在函数中,如果所有元素都是唯一的),但永远不会超过上限。

您只能将长度增加到10,增量为1。在这么短的输入时间内,您仍然在很大程度上被固定开销所覆盖,相邻值之间的区别相当小(
n
vs.
n log n
在任何情况下都不会清楚地显示相邻值)。如果您想超越淹没可见结果的固定开销,并且有足够的工作差异,即使在解释器性能抖动很小的情况下(使用
time.time()会加剧),也可以尝试运行输入大小为100的测试,然后重复加倍(200400800等)
而不是一个更合适的计时机制)。

您只需将长度增加到10,增量为1。在这么短的输入时间内,您仍然在很大程度上被固定开销所覆盖,相邻值之间的区别相当小(
n
vs.
n log n
在任何情况下都不会清楚地显示相邻值)。如果您想超越淹没可见结果的固定开销,并且有足够的工作差异,即使在解释器性能抖动很小的情况下(使用
time.time()会加剧),也可以尝试运行输入大小为100的测试,然后重复加倍(200400800等)
而不是更合适的计时机制)。

谢谢您的评论。我修改了代码,它在一定程度上起到了帮助作用。仍然为一种方法复制O(N**2)缩放似乎仍然遥不可及。@Nikolaij:您意识到您的
O(N**2)
解决方案从短路中获益最大,对吗?只有当输入事实上是唯一的(或者非唯一元素第一次出现在列表的后面)时,您才会看到预期的big-O行为。
n log n
n
解决方案在开始寻找副本之前,都会完成所有算法上最昂贵的工作,因此它们的时间会更加一致。非常正确。到目前为止我还没有完全意识到。谢谢你的评论。我修改了代码,它在一定程度上起到了帮助作用。仍然为一种方法复制O(N**2)缩放似乎仍然遥不可及。@Nikolaij:您意识到您的
O(N**2)
解决方案从短路中获益最大,对吗?只有当输入事实上是唯一的(或者非唯一元素第一次出现在列表的后面)时,您才会看到预期的big-O行为。
n log n
n
解决方案在开始寻找副本之前,都会完成所有算法上最昂贵的工作,因此它们的时间会更加一致。非常正确。到目前为止,我还没有完全意识到。看到您更新的代码后,第一个图表反映了这样一个事实:1000中数字为0到9的列表的概率为0,并且在索引较低的情况下发现重复列表和短路的概率越高,列表越大。您需要一个比0..9中的数字更大的数据样本,也许更好的样本只是对1..n的数字列表进行洗牌,并引入一个低概率的相等值,例如1/n。看到您的更新代码,第一个图反映了一个事实,即1000中数字为0到9的列表的概率为0,并且在索引较低的情况下发现重复的第一个和短路的概率越高,列表越大。你需要一个比数字更大的数据样本