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