插入排序与冒泡排序算法


85

我试图了解一些排序算法,但是我正努力查看气泡排序和插入排序算法的区别。

我知道两者都是O(n 2),但是在我看来,冒泡排序只是将每次通过时数组的最大值冒泡到顶部,而插入排序只会使每次通过时将最小值沉到底部。他们不是在做完全相同的事情,只是朝不同的方向做吗?

对于插入排序,比较/潜在交换的次数从零开始,并且每次都增加(即0、1、2、3、4,...,n),但对于冒泡排序,会发生相同的行为,但是在结束时排序(即n,n-1,n-2,... 0),因为气泡排序在排序时不再需要与最后一个元素进行比较。

尽管如此,似乎人们普遍认为插入排序通常更好。谁能告诉我为什么?

编辑:我主要是对算法工作方式的差异感兴趣,而不是它们的效率或渐进复杂性。


1
在其他地方对此进行了充分的记录:例如,参见en.wikipedia.org/wiki/Sorting_algorithm。在这里重复是没有意义的,一个好的答案将是广阔的。
Bathsheba

@Bathsheba有75位赞成票的人和88k位已查看问题的人似乎不同意;)
差距

@parsecer:哈!现在,我将不得不复习答案。当前获得最高投票的答案很有用;不确定其他人。这是由于投票降低而失去的一些代表点。公认答案中的断言“这就是为什么插入排序比气泡排序更快的原因”不一定是正确的。
巴尔谢巴

@Bathsheba哦,不
parsecer

Answers:


38

在第i个迭代中的冒泡排序中,您总共有ni-1个内部迭代(n ^ 2)/ 2,但是在插入排序中,第i步具有最多i个迭代,但是平均而言,i / 2,因为您可以停止内部循环在找到当前元素的正确位置之后,再进行操作。所以你有(从0到n的总和)/ 2,共(n ^ 2)/ 4;

这就是为什么插入排序比气泡排序更快的原因。


2
我认为,算法解释在网上无处不在。
sasha.sochka 2013年

3
是的,但是OP似乎仍未在机制上有所区别
UmNyobe

15
好吧,你可以假设我了解基本原理。我想要的是一个比较,这确实很好。因此,想法是,尽管插入排序导致第ith个元素下降,而冒泡排序导致其冒泡,插入排序并不会导致其下降到最底部,而只是导致其下降到正确的位置已经排序的部分。因此,它所做的比较/交换较少。那正确吗?
米格韦尔2013年

1
什么?“所以,您有(从0到n的总和)/ 2,共(n ^ 2)/ 4总和”请进行一些爆炸!0 + 1 + 2 + 3 + 4 + 5 = 15; 15/2 = 7.5; 7.5 * 4 = 30; sqrt(30)=胡言乱语
约翰·史密斯,

@JohnSmith我相信答案中有轻微错误。从1到n的和是n *(n + 1)/ 2,因为它是一个三角数。查找三角数以获取更多说明。这样除以2就是n *(n + 1)/ 2。
CognizantApe

121

插入排序

i迭代之后,对第一个i元素进行排序。

在每次迭代中,下一个元素都会冒泡通过已排序的部分,直到到达正确的位置:

sorted  | unsorted
1 3 5 8 | 4 6 7 9 2
1 3 4 5 8 | 6 7 9 2

4冒泡进入排序部分

伪代码:

for i in 1 to n
    for j in i downto 2
        if array[j - 1] > array[j]
            swap(array[j - 1], array[j])
        else
            break

气泡排序

i迭代之后,最后的i元素是最大的且有序的。

在每次迭代中,筛选未排序的部分以找到最大值。

unsorted  | biggest
3 1 5 4 2 | 6 7 8 9
1 3 4 2 | 5 6 7 8 9

5从未排序的部分冒出

伪代码:

for i in 1 to n
    for j in 1 to n - i
         if array[j] > array[j + 1]
             swap(array[j], array[j + 1])

请注意,如果在外部循环的迭代之一期间未进行任何交换,则典型实现会提前终止(因为这意味着对数组进行排序)。

区别

在插入中,将排序元素冒泡到已排序的部分,而在冒泡排序中,将最大值冒出未排序的部分。


10
谢谢,这很清楚!我认为我需要强调的主要事情是插入排序中的break语句意味着它可以尽早终止每个迭代:即,当它在已排序部分中找到其位置时。冒泡排序要求交换继续进行,直到未排序部分中的最大元素到达已排序部分为止,因此永远不会尽早终止。不过,这是一个很棒的总结,因此+1
Migwell

4
我认为这应该是最好的答案:)
Adelin 2014年

2
为了清楚起见,为每个算法的教学价值主循环不变量加1 。遗憾的是,它没有明确包含复杂度的比较(表示为n的函数),无论如何,我认为它比可接受的答案更好,因为从中我可以看到区别。
Honza Zidek

我可以问一下,为什么要在每个步骤中交换插入伪代码中的项目吗?if(a [j-1]> a [j])则a [j] = a [j-1] ELSE if(a [j-1] <e && a [j]> e)比a [j] = e; break; ,其中e是需要排序的项目。使用此解决方案,您无需交换已排序的项目,而只需复制它们即可。期待您的解释,因为我有些困惑。
Karoly

@Karoly,我选择了我的版本,因为它更简单。您的速度稍快一些,指出这一点很好。维基百科描述了这两个版本。
汤姆(Tom),2016年

16

另一个区别是,我在这里没有看到:

气泡排序每个交换3个值分配:您必须首先构建一个临时变量以保存要向前推送的值(第1个),而不是将另一个交换变量写入刚刚保存该值的位置(第2个),然后您必须在其他第三个位置(第3个)写临时变量。您必须对每个位置都进行此操作-您要继续-将变量排序到正确的位置。

使用插入排序,只要您到达变量的正确位置,就可以将变量排序为临时变量,然后将所有变量向后放在该位置前面1个位置。这使得每个点1个值assignement。最后,将临时变量写入现场。

这也大大减少了价值分配。

这不是最强的速度优势,但我认为可以提及。

我希望,我表示自己可以理解,如果不能,对不起,我不是一个幼稚的英国


1
“然后将所有变量放在该点的前面1个点向后” –难道这还需要大量的赋值来移动数据吗?(假设数据是连续存储的,而不是链表)
Mark K Cowan

@MarkKCowan,是的,在那儿,插入排序按上述用户的说明按“位置”进行分配。基本上,插入排序可以在内部循环中用一个分配来编写,而Bubblesort在内部循环中可以用3个分配来编写。
JSQuareD

9

插入排序的主要优点是它是在线算法。您不必一开始就拥有所有值。在处理来自网络或某些传感器的数据时,这可能很有用。

我有种感觉,这将比其他常规n log(n)算法更快。因为复杂度n*(n log(n))例如是从流(O(n))中读取/存储每个值,然后对所有值(O(n log(n)))进行排序,从而导致O(n^2 log(n))

相反,使用“插入排序”需要O(n)从流中读取值O(n)并将值放置到正确的位置,因此O(n^2)仅此一种。另一个优点是,您不需要用于存储值的缓冲区,而在最终目标中对它们进行排序。


如果可以有序遍历数据而不是简单地扫描数组,则可以更有效地进行即时排序。例如,在收到元素时将其插入二叉树。这样一来,您O(n log(n))就可以在整个过程中的每一步完成总的工作,以得到一个排序的集合。(任意点的有序遍历为O(m))。如果您只需要最后的排序结果,但想将排序计算与数据的传输时间重叠,那么使用Heap可能会很好。(并且就地工作,就像插入排序)。
彼得·科德斯

无论如何,气泡排序和插入排序都不是理想的选择,因为问题大小足够大,对于O(f(n))复杂度类而言,其实现比实现细节和常量因素更重要。
彼得·科德斯

更正:堆不利于此。当您按排序顺序删除元素时,它会执行大多数排序工作,这就是为什么增长是如此便宜的原因。这里的目标是在最后一个元素到达时完成大部分工作。
彼得·科德斯

无论如何,如果您确实需要维护一个用于n插入的排序数组,那么实际上归结为哪种算法最适合对几乎排序的数组进行排序,在该算法的顶部有一个未排序的元素。在几乎排序的情况下,O(n log(n))有很多排序算法O(n),因此您需要sum(M=1..n, O(M * log(M)) )工作并不是事实。确实可以O(n^2 log(n)),但是选择正确的算法将是他们的O(n^2)全部工作。不过,插入排序是最有效的方法。
彼得·科德斯

7

冒泡排序不是在线的(它无法对输入流进行排序而不知道会有多少个项目),因为它并不能真正跟踪已排序元素的全局最大值。插入项目后,您需要从一开始就开始冒泡


4

仅当有人从大量数字中查找前k个元素时,好气泡排序才比插入排序好。即,在k次迭代后的气泡排序中,您将获得前k个元素。但是,在插入排序中进行了k次迭代之后,它仅确保对这k个元素进行了排序。


2

虽然这两种排序都是O(N ^ 2).Inserting排序中的隐藏常量要小得多,隐藏常量是指实际执行的基本操作数。

什么时候插入排序有更好的运行时间?

  1. 数组几乎已排序-请注意,在这种情况下,插入排序执行的操作少于气泡排序。
  2. 数组的大小相对较小:插入排序是在元素周围移动元素以放置当前元素,这仅比元素数量少的冒泡排序更好。

注意插入排序并不总是比冒泡排序更好。要获得两全其美的效果,如果数组的大小较小,则可以使用插入排序,对于较大的数组则可以合并sort(或quicksort)。


2
如果元素的数量不多,气泡排序将如何更好?我的理解是,是在IS中滑动还是在BS中互换,将取决于所比较的元素是较大(IS)还是较小(BS),而不取决于元素数量。如果有错,请纠正我。
穆斯塔法

0

在所有情况下,气泡排序几乎都是无用的。在插入排序可能有太多交换的用例中,可以使用选择排序,因为它可以保证交换次数少于N次。因为选择排序比冒泡排序更好,所以冒泡排序没有用例。


0

每次迭代中的交换次数

  • 插入排序在每次迭代中最多进行1次交换
  • Bubble-sort在每次迭代中进行0到n次交换。

访问和更改已排序的部分

  • 插入排序访问(并在需要时更改)已排序的部分以找到要考虑的数字的正确位置。
  • 经过优化后,Bubble-sort不会访问已排序的内容。

是否在线

  • 插入排序在线。这意味着插入排序放置到适当位置之前一次只接受一个输入。它不必只进行比较adjacent-inputs
  • 冒泡排序不是在线的。它一次不操作一个输入。它在每次迭代中处理一组输入(如果不是全部)。冒泡排序adjacent-inputs在每次迭代中进行比较和交换

0

插入排序:

1.在插入排序不需要交换。

2.插入排序的时间复杂度在最佳情况下为Ω(n),在最坏情况下为O(n ^ 2)。

3.与气泡排序相比,复杂度更低。

4.示例:将书插入图书馆,整理卡片。

冒泡排序:1.冒泡排序需要交换。

2,最佳情况下气泡排序的时间复杂度为Ω(n),最坏情况下为O(n ^ 2)。

3.比插入排序更复杂。


1
为何不需要交换?它交换元素以将元素放置在正确的位置。而且我不会说气泡排序更为复杂。
parsecer

-1

插入排序可以恢复为“查找应该在第一个位置(最小)的元素,通过移动下一个元素留出一些空间,然后将其放置在第一个位置。好。现在查看应该在第2个元素。 ... ”等等...

冒泡排序的操作方式不同,可以恢复为“只要发现两个相邻元素的顺序错误,就可以交换它们”。


这确实对插入排序有所帮助,但是您对冒泡排序的说明不包括实际的循环,因此我无法真正比​​较它们。我插入排序也有效地具有以下规则:只要找到两个相邻的顺序错误的元素,就将它们交换,这就是循环操作的方式不同。
米格韦尔2013年

3
这样的选择不是吗?
哈罗德

哦,是的,^
米格威尔(Migwell),
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.