几种trivial排序方法的分析

来源:互联网 发布:南京大学软件学院考研 编辑:程序博客网 时间:2024/04/30 17:18

一提到排序可能很多人就立即想到大名鼎鼎的快速排序,不过今天我想对几种O(n2)的排序方法做一个简单的分析,他们是选择排序(selection sort),插入排序(insertion sort)和冒泡排序(bubble sort),经过分析我们会知道在某些场合下,他们是有价值的,速度上也可以秒杀快排。

 

选择排序:首先找到数组中最小的元素,将其与位置上第一的元素交换位置;然后找到第二小的元素,将其与位置上第二的元素交换位置;持续这样的操作直到数组完全被排序。

 

插入排序:从前向后扫描数组中的每个元素,通过不断将当前元素插入到该元素之前的序列中来保持扫描过的序列是有序的,这样处理完最后一个元素后,整个数组就是有序的了。

 

冒泡排序:不断从最后一个位置向前扫描数组,每次扫描的长度减1,在扫描的过程中遇到相邻的元素不满足排序规则就将它们交换,直到扫描长度降为1。

 

下面是这几种排序的c和c++的源码:

选择排序c版本:

插入排序c版本:

冒泡排序c版本:

 

选择排序c++版本:

插入排序c++版本:

冒泡排序c++版本:

 

我们分析复杂度一般使用比较次数作为度量,可是在某些条件下,元素移动实际上是时间的瓶颈,所以下面分别从两个方面分析这几种排序的复杂度,在这里我们令数组的长度为n。

 

首先看选择排序,在扫描的过程中,每个内层循环都要完整的扫描剩余的数组,这样比较的次数为n+(n-1)+……+1=n(n-1)/2,大概是n2/2。这说明选择排序的比较次数并不依赖于数组的排列,也就是说一个趋近于有序的数组使用选择排序并没有得到额外的好处。但是选择排序的优点在于它固定的使用了n-1次交换操作,所以在元素移动的复杂度上,选择排序是线性的。

 

再来看插入排序,插入排序平均看来需要n2/4次比较,我们可以这样分析,每次插入操作时按平均意义的话元素应该向前移动一半的距离,而这样每个元素都移动一半的距离就相当于选择排序一半的比较次数。同时我们还可以看出插入排序的比较次数和元素移动次数几乎是一样的,所以元素交换的平均次数也是n2/4。这样看来插入排序好像并没有什么优势可言,相比选择排序还有一方面可以到达线性复杂度来说,插入排序两方面都是二次的。不过大家注意刚才的分析是平均意义上的,考虑数组如果已经有序的话,实际上插入排序无论从比较还是元素移动都是线性的。换句话说,插入排序可以利用到数组未排序前的结构。关于这个问题的精确分析,参考后面的附言。

 

最后是冒泡排序, 冒泡排序相比其他两个排序几乎没有优势,即使从平均意义上讲,它的比较复杂度和元素移动复杂度相比最坏情况也没有任何提升,都是大概n2/2。最坏情况很容易分析,第k次冒泡要比较并交换n-k次,所以总的来说是n(n-1)/2。而平均意义感觉可能会好一些,但是并不是那样的,分析的过程比较复杂,这里就不列出了。所以一般来说,冒泡排序都要比选择排序和插入排序要慢。

 

结论:1.在移动元素占主要时间的应用中选择排序是非常好的选择 。

         2.对于几乎有序的数组进行排序应该使用插入排序。

以上的情况从复杂度上是可以击败快速排序的,这里没有给出具体的实验结果。

 

附言:

我们来精确的分析一下插入排序的比较次数,首先介绍两个概念“逆序”和“逆序数”,如果数组中某两个元素的位置上的次序和他们的大小上的次序是相反的,那么这就叫做一个逆序,数组中所有的逆序的总数叫做这个数组的逆序数。对于数组中某一个元素来说,它与位置上在它前面的所有元素的逆序的个数就是前面比他大的那些元素的个数,而考虑插入排序的内循环,当前元素被比较的次数最多和前面比他大的那些元素的个数大1。这就说明插入排序的内循环的复杂度与对于某个元素与其前面元素的逆序个数是相同的,我们将所有的元素都考虑进来就能得出结论:数组的逆序数与插入排序的比较次数是几乎相同的。

原创粉丝点击