秒懂插入排序与选择排序

插入排序原理:它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

插入排序核心:假设第一个元素排好,之后的元素对排好的部分从后向前比较并逐一移动。

插入排序实现:

[cpp]  view plain  copy
  1. void insertion_sort(int a[], int n)  
  2. {  
  3.     int i,j,tmp;  
  4.     for (i = 1; i < n; i++) {  
  5.         tmp = a[i];  
  6.         for (j = i - 1; j >= 0 && a[j] > tmp; j--) {  
  7.             a[j+1] = a[j];  
  8.         }  
  9.         a[j+1] = tmp;  
  10.     }  
  11. }  

插入排序过程动画:



选择排序:


选择排序和插入排序类似,都是in-place comparison sort。


选择排序核心思想:从未排好的部分的第一个作为最小(最大)的,然后依次和剩余的比较,如果有更小(更大)的,记下下标,以此作为新的最小(最大)值,继续比较,一趟结束后,然后和第一个进行交换。


选择排序实现:

[cpp]  view plain  copy
  1. void selection_sort(int a[], int n)  
  2. {  
  3.     int i, j, k;  
  4.     for (i = 0; i< n-1; i++) {  
  5.         k = i;  
  6.         for (j = i+1; j < n; j++) {  
  7.             if (a[k] > a[j])  
  8.                 k = j;  
  9.         }  
  10.         if (k != i) {  
  11.             int tmp = a[i];  
  12.             a[i] = a[k];  
  13.             a[k] = tmp;  
  14.         }  
  15.     }  
  16. }  

选择排序过程动画:







思想

插入排序的思想有点像打扑克抓牌的时候,我们插入扑克牌的做法。想象一下,抓牌时,我们都是把抓到的牌按顺序放在手中。因此每抓一张新牌,我们都将其插入到已有的排好序的手牌当中,注意体会刚才的那句话。也就是说,插入排序的思想是,将新来的元素按顺序放入一个已有的有序序列当中。

举个例子可能更容易理解一些,假设有这样一系列数字:

  8 2 4 9 3 6  首先我们考虑数字2,假设后面的数字不存在(手中只有一张8,又抓来了2),那么显然2应该放在8的前面。

  2 8 4 9 3 6  又抓来了一张4,现在大家都知道应该怎么办了吧?

  2 4 8 9 3 6  又来了个9,没错,正好不用换顺序

  2 4 8 9 3 6  同样的道理,考虑3该放的位置,显然放在2和4的中间

  2 3 4 8 9 6  最后一个也是一样,最后得到从小到大的序列

  2 3 4 6 8 9  完成排序

怎么样,这下全明白了吧,其实很简单。

代码

复制代码
        printline("before sort:", v);
for (int i=1; i<v.size(); i++){
int key = v[i];
int j = i-1;
while (j >= 0 && v[j] > key){
v[j+1] = v[j];
j--;
}
v[j+1] = key;

}
printline("after sort:", v);
复制代码

代码中,key相当于每次要比较的元素。

分析

插入排序的思路很简单,很清晰,是一种最常见最简单的排序方法。但是可以看出,由于需要两层循环,外层循环n-1次,内层循环每次递增一次。当输入完全从小到大有序时,只需要常数的时间,这当然是最好的情况。但是我们不能期望输入,当输入完全逆序时,最坏的情况就出现了,显然时间复杂度是O(n*n)的。我们都很清楚,这个时间复杂度在排序中并不能算好的。这也是为什么插入排序虽然简单,但并没有被广泛应用的原因所在。




简单选择排序

此文章前半部分--》》简单选择排序是另一位大哥写的博客:http://blog.csdn.net/hguisu/article/details/7776068

但是我这想要说的是后半部分双向选择排序,因为这个排序虽然那位大哥也写了,但是bug大多,而且下面评论的人有回复和纠正,但是似乎纠正的代码也是错的,所以才想着写这篇博客。

思想:

在要排序的一组数中,选出最小(或者最大)的个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后个数)比较为止。


操作方法:

第一趟,从n 个记录中找出关键码最小的记录与第一个记录交换;

第二趟,从第二个记录开始的n-1 个记录中再选出关键码最小的记录与第二个记录交换;

以此类推.....

第i 趟,则从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换,

直到整个序列按关键码有序。

算法实现:

[java]  view plain  copy
  1. public int[] selectSort(int[] arr) {  
  2.   
  3.         int tmp;  
  4.         int minIndex = 0// 接受Minnum返回的最小值的下标  
  5.         for (int i = 0; i < arr.length; i++) {  
  6.             minIndex = Minnum(arr, i, arr.length);  
  7.             if (minIndex != i) { // 判断这个下标是不是就是当前待排序的下标,其实这个判断也可以不要,但是想着为了程序的美,最好加上  
  8.                 // Swap(arr[minIndex], arr[i]);  
  9.                 tmp = arr[minIndex];  
  10.                 arr[minIndex] = arr[i];  
  11.                 arr[i] = tmp;  
  12.             }  
  13.         }  
  14.         return arr;  
  15.     }  

简单选择排序的改进——二元选择排序

简单选择排序,每趟循环只能确定一个元素排序后的定位。我们可以考虑改进为每趟循环确定两个元素(当前趟最大和最小记录)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需进行[n/2]趟循环即可。具体实现如下:

算法实现一:那位大哥的写法

[java]  view plain  copy
  1. void SelectSort(int r[],int n) {    
  2.     int i ,j , min ,max, tmp;    
  3.     for (i=1 ;i <= n/2;i++) {      
  4.         // 做不超过n/2趟选择排序     
  5.         min = i; max = i ; //分别记录最大和最小关键字记录位置    
  6.         for (j= i+1; j<= n-i; j++) {    
  7.             if (r[j] > r[max]) {     
  8.                 max = j ; continue ;     
  9.             }      
  10.             if (r[j]< r[min]) {     
  11.                 min = j ;     
  12.             }       
  13.       }      
  14.       //该交换操作还可分情况讨论以提高效率    
  15.       tmp = r[i-1]; r[i-1] = r[min]; r[min] = tmp;    
  16.       tmp = r[n-i]; r[n-i] = r[max]; r[max] = tmp;     
  17.     
  18.     }     
  19. }    

i从0开始,不然第一个数据,就没有参与比较, 这是漏洞之一

算法实现二:评论大哥(暂且这么叫吧)的写法

[java]  view plain  copy
  1. public void selectTwoSort(int a[], int n) {  
  2.         int i;  
  3.         for (i = 0; i <= n / 2; i++) { // i从0开始,不然第一个数据,就没有参与比较  
  4.             int min = i;  
  5.             int max = n - i - 1;// 最大数据指针应该这么设置吧!  
  6.             int tmp = 0;  
  7.             int j;  
  8.             for (j = i + 1; j < n - i - 1; j++) {  
  9.                 if (a[j] < a[min]) {  
  10.                     min = j;  
  11.                     continue;  
  12.                 }  
  13.                 if (a[j] > a[max]) {  
  14.                     max = j;  
  15.                 }  
  16.             }  
  17.             // 放置最小值  
  18.             tmp = a[min];  
  19.             a[min] = a[i];  
  20.             a[i] = tmp;  
  21.             // 放置最大值  
  22.             tmp = a[max];  
  23.             a[max] = a[n - i - 1];  
  24.             a[n - i - 1] = tmp;  
  25.         }  
  26.     }  

这里的漏洞是
[java]  view plain  copy
  1. int max = n - i - 1;// 最大数据指针应该这么设置吧!  
因为max值本来就是在比较下重动态更新的,所以将

[java]  view plain  copy
  1. int max = i;  
还是等于 i 到  n - i - 1 之间的任何一个都是无所谓的;

最大的bug还是运行这两个算法基本上都会有错误,也许是因为那两位大哥举例子用的数组的缘故,所以没被发现程序思想没错,实现的方式也没错,想要解决bug需要解决两点

第一点:选出min和max的位置

按照上面的算法写的话,我举个例子

数组:10    8     9    7    

i = 0 ; //从第一个数开始

[java]  view plain  copy
  1.  for (j = i + 1; j < n - i - 1; j++) {  
  2.                 if (a[j] < a[min]) {  
  3.                     min = j;  
  4.                     continue;  
  5.                 }  
  6.                 if (a[j] > a[max]) {  
  7.                     max = j;  
  8.                 }  
  9.   }  
这里错的地方是因为 j = i + 1 ,从上面的数组很容易看出当第一位数是这组待排序的数中最大的时候,j = i + 1 就会直接跳到第一个数的后面去选择后面中的最大最小,所以当第一位不管是这组数最大还是最小值时,此上面两种算法绝对出错

换而言之,解决问题的办法就是将第一位数也拉进来比较。

[java]  view plain  copy
  1. j = i ;    //这样就好了  

第一点:min和max的位置都出来了, 关键的地方是先和第n - i - 1个位置交换max呢?还是先和第 i 个位置交换min呢

为什么要这么考虑?下面我同样举个例子

还是数组:[...排好序的数..] 10   8   7   5   6   9 [...排好序的数...],无疑此数组中max的位置是10的下表, min无疑是5的位置,假如按照上面大哥的写法,无论什么情况都是先交换最小的,然后再交换最大的数组一趟排序后的结果是[...排好序的数..9]  8   7   10   6  [5...排好序的数...]因为5的位置先和10的位置交换,但是max的值还是原先10的位置,也就是交换后5的位置,之后再将max位置上的5和n - i - 1位置上的9交换.由于排好序的数是不会参与比较的,所以此算法无论如何都是得不到正确结果的,这里只举出了这一种特殊的交换位置,还有两种特殊的交换位置自己想象一下立马就可以出来了

好了,到了这里,问题全部跑出来了,

因为要考虑交换位置的情况,所以代码的臃肿度立马提升了不少,下面给出我自己的写的代码,如果以后想到更好的解决办法,会优化的,

如果是那位大哥大姐能有更好的想法,好心的话也可以回复一下我可怜

算法实现三:

[java]  view plain  copy
  1. <pre name="code" class="java">public int[] selectSort(int[] arr, int n) {  
  2.   
  3.         int tmp;  
  4.         int minIndex, maxIndex;  
  5.         for (int i = 0; i <= n / 2; i++) {  
  6.             minIndex = i;  
  7.             maxIndex = i;  
  8.             for (int j = i; j < n - i - 1; j++) {  
  9.                   
  10.                 if (arr[j] > arr[maxIndex]) {  
  11.                     maxIndex = j;  
  12.                     continue;  
  13.                 }  
  14.                 if (arr[j] < arr[minIndex]) {  
  15.                     minIndex = j;  
  16.                       
  17.                 }  
  18.                   
  19.             }  
  20.             if (maxIndex == i && minIndex == n - i - 1) {  
  21.                 //特殊交换位置一,当最大的交换位置和最小的交换位置都在最前面和最后面  
  22.                 tmp = arr[maxIndex];  
  23.                 arr[maxIndex] = arr[minIndex];  
  24.                 arr[minIndex] = tmp;  
  25.             } else  
  26.             if (maxIndex == i) {  
  27.                 //特殊交换位置二,当最大的交换位置是最前面  
  28.                 // Swap(arr[n - i], arr[maxIndex]) ;  
  29.                 tmp = arr[maxIndex];  
  30.                 arr[maxIndex] = arr[n - i - 1];  
  31.                 arr[n - i - 1] = tmp;  
  32.   
  33.                 // Swap(arr[i], arr[minIndex]) ;  
  34.                 tmp = arr[minIndex];  
  35.                 arr[minIndex] = arr[i];  
  36.                 arr[i] = tmp;  
  37.             } else  
  38.             if (minIndex == n - i - 1) {  
  39.                 //特殊交换位置三,当最小的交换位置是最后面  
  40.                 // Swap(arr[i], arr[minIndex]) ;  
  41.                 tmp = arr[minIndex];  
  42.                 arr[minIndex] = arr[i];  
  43.                 arr[i] = tmp;  
  44.   
  45.                 // Swap(arr[n - i], arr[maxIndex]) ;  
  46.                 tmp = arr[maxIndex];  
  47.                 arr[maxIndex] = arr[n - i - 1];  
  48.                 arr[n - i - 1] = tmp;  
  49.                   
  50.             } else {  
  51.                 //除了上面三种特殊交换,就剩普通交换了,普通交换随便哪个先交换都行  
  52.                 // Swap(arr[i], arr[minIndex]) ;  
  53.                 tmp = arr[minIndex];  
  54.                 arr[minIndex] = arr[i];  
  55.                 arr[i] = tmp;  
  56.   
  57.                 // Swap(arr[n - i], arr[maxIndex]) ;  
  58.                 tmp = arr[maxIndex];  
  59.                 arr[maxIndex] = arr[n - i - 1];  
  60.                 arr[n - i - 1] = tmp;  
  61.             }  
  62.             sortTest.dispaly(arr);   //自己写的输出函数  
  63.         }  
  64.         return arr;  
  65.     }  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值