所以我刚刚学会了排序算法的泡泡,合并,插入,排序等等。他们在排序方法上看起来都非常相似,而我们的方法似乎只是微不足道的变化。那么他们为什么会产生这样不同的排序时间,例如O(n ^ 2)vs O(nlogn)作为例子
答案 0 :(得分:8)
"相似性" (?!)你看到的是完全虚幻的。
小学,O(N平方)接近,一遍又一遍地重复他们的工作,没有任何好处,对于"下一步",在"前一步骤& #34 ;.所以第一步需要时间与N成比例,第二步需要N-1,依此类推 - 得到的整数之和从1到N与N平方成正比。
例如,在选择排序中,您每次都在寻找I:N部分中的最小元素,其中我首先是0,然后是1,等等。这是(并且必须)通过检查所有这些来完成的因为以前没有任何关注,因为以前的任何优势都没有在后续通行证上承担任何较少量的工作。一旦找到最小元素,就将它与第I个元素交换,递增I,然后继续。 O(N平方)当然。
先进的O(N log N)方法结构巧妙,可以利用以前步骤中完成的后续工作步骤。与基本方法相比,这种差异是如此普遍和深刻,如果人们无法察觉,那主要是关于一个人的感知敏锐性,而不是关于方法本身: - )。
例如,在合并排序中,您在逻辑上将数组拆分为两个部分,0到半长,半长到长。一旦对每一半进行排序(通过相同的方式递归,直到长度足够短),两个半部合并,这本身就是一个线性子步骤。
由于你每次都减半,你显然需要一些与log N成比例的步骤,并且,因为每一步都是O(N),显然你得到了非常理想的O(N log N)结果
Python" timsort"是一个"自然的mergesort",即mergesort的一个变种,它被调整以利用数组的已经排序(或反向排序)的部分,它可以快速识别并避免花费任何进一步的工作。这并没有改变大O,因为那是关于最差 - 时间 - 但预期时间进一步崩溃,因为在如此多的真实情况下生活案例中存在一些部分排序 。
(注意,按照big-O的严格定义,quicksort根本不快 - 它的最坏情况与N平方成正比,当你碰巧选择一个可怕的转折点时每一次...... 预期时间明智的它很好,虽然没有蒂姆索特那么好,因为在现实生活中你反复选择灾难支点的情况非常罕见。但是,最差 - 例如,可能会发生! - )。
timsort
如此非常适合吹走即使是非常有经验的程序员。我不算数,因为我是发明家朋友蒂姆彼得斯和Python狂热分子,所以我的偏见是显而易见的。但是,请考虑......
...我记得一个"技术谈话"在谷歌举办timsort活动。坐在我前排的是Josh Bloch,当时也是一名Google员工和Java专家。在谈话的中途,他无法抗拒 - 他打开了他的笔记本电脑并开始黑客攻击,看看它是否可能像优秀的,尖锐的技术演示一样好。它似乎表明它将会出现。
因此,timsort
现在也是Java虚拟机(JVM)最新版本中的排序算法,但仅适用于用户定义的对象(基元数组仍按旧方式排序,quickersort [ *]我相信 - 我不知道哪些Java特性决定了这个"分裂"设计选择,我的Java-fu相当弱: - )。
[*]基本上是快速排序加上一些黑客进行枢轴选择以试图避免毒药案例 - 而且这也是Python在蒂姆·彼得斯给出这个不朽贡献之前使用过的东西几十年来他所做的许多重要事件。
对于具有CS背景的人来说,结果有时令人惊讶(像Tim,我有幸拥有远在学术背景,而不是在CS,但在EE,这有很大帮助:-)。例如,假设您必须维护一个始终在任何时间点排序的不断增长的数组,因为必须将新的传入数据点添加到数组中。
经典的方法是使用二分法O(log N)为每个新的传入数据点找到合适的插入点 - 但是,为了将新数据放在正确的位置,你需要改变后面的内容通过一个插槽,即O(N)。
使用timsort,你只需将新数据点附加到数组中,然后对数组进行排序 - 在这种情况下,对于timsort来说就是O(N)(因为它在利用已经非常棒的时候非常棒)第一批N-1项目的性质! - )。
你可以把timsort想象成推动"利用之前完成的工作"到了一个新的极端 - 不仅先前由算法本身完成的工作,而且还有现实数据处理的其他方面的其他影响(导致段被提前分类),都被利用到了这个目标。
然后我们可以进入铲斗排序和基数排序,它改变了话语平面 - 在传统的排序中限制了一个能够比较两个项目 - 通过利用项目'内部结构。
或者类似的例子 - 宾利在他不朽的书中提出的#34;编程珍珠" - 需要对数百万个唯一正整数进行排序,每个正整数限制为24位长。
他用一个16M位的辅助数组解决了它 - 毕竟只有2M字节 - 最初是全零:一个通过输入数组来设置辅助数组中的相应位,然后一个通过辅助数组到在找到1
时再次形成所需的整数 - 并且爆炸,O(N)[并且非常快速:-)]为这个特殊但重要的情况排序! - )