Algorithm 如何计算更复杂算法(如快速排序)的顺序(大O)

Algorithm 如何计算更复杂算法(如快速排序)的顺序(大O),algorithm,complexity-theory,big-o,Algorithm,Complexity Theory,Big O,我知道关于大O符号有很多问题,我已经检查过了: 举几个例子 我凭“直觉”知道如何计算n,n^2,n

我知道关于大O符号有很多问题,我已经检查过了:

举几个例子

我凭“直觉”知道如何计算
n
n^2
n
logn
nlogn
nlogn
,等等

我的意思是,我知道快速排序是
n log n
(平均)。。但是,为什么?合并/梳理等也一样

谁能用一种不太数学化的方式解释一下,你是怎么计算的


主要原因是我将要进行一次重要的面试,我很肯定他们会要求这种东西。我已经研究了几天了,每个人似乎都有关于冒泡排序为何为n^2的解释,或者(对我来说)在

上无法阅读的解释。你通常可以为每次运行时将空间/时间减半的算法声明logn。一个很好的例子是任何二进制算法(例如,二进制搜索)。您可以选择向左或向右,然后将搜索的空间轴分成两半。重复做一半的模式是log n.

当应用分而治之算法时,将问题划分为子问题,直到问题变得非常简单,变得微不足道,如果划分顺利,每个子问题的大小大约为n/2。这通常是big-O复杂性中出现的
log(n)
的起源:
O(log(n))
是分区顺利进行时所需的递归调用数。

查看此处给出的“电话簿”示例:

请记住,Big-O是关于规模的:随着数据集的增长,该算法需要多少操作

O(logn)通常意味着您可以在每次迭代中将数据集切成两半(例如二进制搜索)

O(logn)表示您正在对数据集中的每个项目执行O(logn)操作


我很确定‘O(n log n)’没有任何意义。或者,如果是这样的话,它会简化为O(n log n)。

对于某些算法,通过直觉获得运行时间的严格限制几乎是不可能的(例如,我认为我永远无法直觉地知道运行时间
O(n log n)
,我怀疑有人会期望你这样做)。如果你能得到你的手,你会发现一个相当彻底的处理渐近符号,这是适当的严格,而不是完全不透明


如果算法是递归的,一种推导边界的简单方法是写出一个递归,然后开始求解它,或者迭代,或者使用或其他方法。例如,如果您不想对它过于严格,那么获取QuickSort运行时间的最简单方法是通过主定理——QuickSort需要将数组划分为两个相对相等的子数组(应该很直观地看到这是
O(n)
),然后对这两个子数组递归调用快速排序。然后,如果我们让
T(n)
表示运行时间,我们有
T(n)=2T(n/2)+O(n)
,根据主方法,它是
O(n log n)

,我将尝试直观地分析为什么Mergesort是n log n,如果你能给我一个n log n算法的例子,我也可以完成它

Mergesort是一个排序示例,它通过重复拆分元素列表直到只有元素存在,然后将这些列表合并在一起。每个合并中的主要操作是比较,每个合并最多需要n个比较,其中n是两个列表组合的长度。从这一点,你可以推导出递推式,并很容易地解决它,但我们将避免这种方法

而不是考虑Mergesort将如何表现,我们将采取一个列表,并拆分它,然后采取一半,并再次分裂,直到我们有n个长度为1的分区。我希望很容易看出,在我们将列表拆分为n个分区之前,这种递归只会进入log(n)深度

现在我们已经知道这n个分区中的每一个都需要合并,那么一旦这些分区被合并,下一个级别将需要合并,直到我们再次得到长度为n的列表。有关此过程的简单示例,请参阅wikipedia的图表

现在考虑这个过程所花费的时间,我们将有日志(N)级别,在每个级别,我们将不得不合并所有的列表。事实证明,每个级别需要n个时间来合并,因为我们每次将合并总共n个元素。然后,如果将比较操作视为最重要的操作,则可以很容易地看到,使用mergesort对数组进行排序需要n log(n)个时间

如果有什么不清楚或我跳过了某个地方,请让我知道,我可以尝试更详细

编辑第二个解释:

让我想想,如果我能更好地解释这一点

问题被分解成一组较小的列表,然后对较小的列表进行排序和合并,直到您返回到现在已排序的原始列表

当你分解这些问题时,你会有几个不同的大小级别,首先你会有两个大小列表:n/2,n/2,然后在下一个级别你会有四个大小列表:n/4,n/4,n/4,n/4在下一个级别你会有n/8,n/8,n/8,n/8,n/8,n/8,n/8,n/8这会一直持续到n/2^k等于1(每个细分是长度除以2的幂,并不是所有长度都可以被4整除,所以它不会这么漂亮)。这是重复除以2,最多可以继续对数2(n)次,因为2^(对数2(n))=n,所以再除以2将产生一个大小为零的列表

现在需要注意的重要一点是,在每个层次上,我们都有n个元素
 6   2    0   4    1   3     7   5
  2 6      0 4      1 3       5 7
    0 2 4 6            1 3 5 7
         0 1 2 3 4 5 6 7
def function(data, n):
    if n <= constant:
       return do_simple_case(data, n)
    if some_condition():
       function(data[:n/2], n / 2) # Recurse on first half of data
    else:
       function(data[n/2:], n - n / 2) # Recurse on second half of data
def function(data, n):
    if n <= constant:
       return do_simple_case(data, n)
    part1 = function(data[n/2:], n / 2)      # Recurse on first half of data
    part2 = function(data[:n/2], n - n / 2)  # Recurse on second half of data
    return combine(part1, part2)