您的位置:首页 > 理论基础 > 数据结构算法

数据结构与算法之--简单排序:冒泡、选择和插入

2017-07-08 18:44 721 查看
三种简单排序算法

  排序是最常见的算法,本文将介绍三种简单排序算法:冒泡,选择和插入排序。三种算法基本都在数组内部操作数据,所以空间复杂度为O(N),时间复杂度都为O(N^2),需要说明的是,虽说时间复杂度均为O(N^2),但具体来说,O(N^2)仅仅是指用于数据值比较次数的量级,但是交换和拷贝数据的次数是不同的,所以三种排序算法的性能在这里有重要区别。

说明:为了描述方便,以下均以int类型数列为例,数据量为N,排序方式是从小到大。

冒泡排序

  冒泡排序是最容易想到的排序方法,操作上就是两两比较和交换,具体来说,首先,从左到右两两比较,若左边的值大,就交换两个数字,这样,一轮下来,数列中的最大值就被交换到了最右边。然后,把最右边的数晾在一边,对前N-1个数重复一轮比较和交换。重复这个过程直到倒数第二大的数被换到了第二的位置。分析可知,这里需要比较的次数是:(N-1)+(N-2)+...+1 = N(N-1)/2,分析需要交换的次数,这里假设比较悲剧的情况,即一开始数据是从大到小排列的,每次都需要交换,则交换次数是N(N-1)/2,一般不会这么背,平均来说,需要交换的次数是N(N-1)/4

1     /**
2      * 一层层地,一次次地交换局部最值,实现把局部最值放到局部的最后边
3      * Time Complexity: O(N^2)
4      */
5     public static int[] bubbleSort(int[] input) {
6         if (input.length <= 1) {
7             return input;
8         }
9         //  compare And Swap
10         for (int outer = input.length - 1; outer > 0; outer--) {
11             for (int inner = 0; inner < outer; inner++) {
12                 int prev = input[inner];
13                 int next = input[inner + 1];
14                 if (prev > next) {
15                     input[inner] = next;
16                     input[inner + 1] = prev;
17                 }
18             }
19         }
20         return input;
21     }


选择排序

  选择排序的比较过程和冒泡排序一样,但是把最值交换到数据最右边的过程,不是通过逐个交换完成的,而是通过增加一个临时变量来记住最值的位置,一次扫描式的比较之后,找出最值的位置,然后把最值和队列最右边的值交换即可。比较次数依然是N(N-1)/2,但交换次数最多只有N-1次,记住最值位置的临时变量的拷贝最多N-1次,平均(N-1)/2。

1     /**
2      * 一层层地找出局部最值,然后交换一次把局部最值放到局部的最后边
3      * Time Complexity: O(N^2)
4      */
5     public static int[] selectSort(int[] input) {
6         if (input.length <= 1) {
7             return input;
8         }
9         //  compare then record and Swap
10         for (int outer = input.length - 1; outer > 0; outer--) {
11             int maxIdx = 0;
12             for (int inner = 1; inner <= outer; inner++) {
13                 if (input[maxIdx] < input[inner]) {
14                     maxIdx = inner;
15                 }
16             }
17             if (maxIdx != outer) {
18                 int outerVal = input[outer];
19                 input[outer] = input[maxIdx];
20                 input[maxIdx] = outerVal;
21             }
22         }
23         return input;
24     }


插入排序

  插入排序的思考起点是数据是局部有序的,假如在某个数右边的数据都是有序的,那么,以这个数为起点,向左扫描,不断把数字插入到右边有序的数据列中去,当有序列表扩张到整个数列时,这个数列就是有序的。这里需要说明一下插入操作:把要插入的数字复制到临时变量,然后和有序列中的数字按顺序逐个比较,若有序列中的数字小于待插值,则把这个数字向左挪动一个位置(挪动之后,原来的位置留下一个空位),反之,则把待插值插入这个数字的左边(左边数字挪动后留下的空位或者待插入数字原来的位置)。比较次数:插入排序在最背的情况下,需要N(N-1)/2次,平均需要N(N-1)/4次挪动数据的次数与比较次数类似:最差[b]N(N-1)/2,平均N(N-1)/4次,另外,临时变量需要N-1次拷贝[/b]。在数列比较有序的情况下用插入排序更快,但假如原始顺序与将要排的顺序相反的话,就需要大量挪动数据,变得很慢。

参考代码如下:

1     /**
2      * 遍历把值插入已有的局部有序序列中,逐步实现有序序列的扩张直到整个序列有序。
3      * 优点:局部有序时,几乎不需要挪动数据,直接插入;
4      * 这同时也是其缺点,即当顺序正好与想要的顺序大致相反时,需要大量地挪动数据,此时效率甚至低于冒泡法。
5      * Time Complexity: O(N^2)
6      */
7     public static int[] insertSortFromRight(int[] input) {
8         if (input.length <= 1) {
9             return input;
10         }
11         // 从右往左扫描,右边局部有序
12         for (int insertIdx = input.length - 2; insertIdx >= 0; insertIdx--) {
13             int insertVal = input[insertIdx];
14             int orderedIdx = insertIdx + 1;
15             for (; orderedIdx < input.length; orderedIdx++) {
16                 if (insertVal <= input[orderedIdx]) {
17                     break;
18                 } else {
19                     input[orderedIdx - 1] = input[orderedIdx];
20                 }
21             }
22             input[orderedIdx - 1] = insertVal;
23         }
24         return input;
25     }


三种排序比较

三种排序算法比较

算法比较次数交换次数拷贝次数
冒泡N(N-1)/2N(N-1)/4,最差N(N-1)/20
选择N(N-1)/2N-1(N-1)/2
插入N(N-1)/4,最差N(N-1)/20N(N-1)/4+(N-1),最差N(N-1)/2+(N-1)
说明:一次交换等于三次拷贝。

总结

三种算法中,冒泡和选择实现方式简单,而插入法稍微复杂;

性能上,选择法比冒泡法快,而插入法在原始数据有序度高时最快,原始顺序与排序顺序相反时,性能下降明显
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐