十大排序及C++实现

No.1 冒泡排序

  从左至右依次消除相邻逆序对。

 1 void swap(int &a, int &b)
 2 {
 3     int temp = a;
 4     a = b;
 5     b = temp;
 6 }
 7 void bubble_sort(int *array,int length)
 8 {
 9     for( int i=0; i<length-1; i++ )
10     {
11         for( int j=0; j<length-i-1; j++ )
12         {
13             if( array[j]>array[j+1] )
14             {
15                 swap(array[j],array[j+1]);
16             }
17         }    
18     }    
19 }
View Code

  每次内层循环后最大值都来到未排序数组最右端,就像大泡泡依次向上冒出。

  双重循环,时间复杂度O(n^2)。

  冒泡排序简单易理解,但每次都不管三七二十一直接两重循环。一个优化方法是每次内层循环都判断剩余数组是否有序,若有序则排序完成。

 1 void bubble_sort(int *array,int length)
 2 {
 3     for( int i=0; i<length-1; i++ )
 4     {
 5         bool is_sorted = true;
 6         for( int j=0; j<length-i-1; j++ )
 7         {
 8             if( array[j]>array[j+1] )
 9             {
10                 swap(array[j],array[j+1]);
11                 is_sorted = false;
12             }
13         }    
14         if( is_sorted )    break;
15     }    
16 } 
View Code

 


 

 

No.2 选择排序

  每次选择最小值,将其与未排序数组最左端交换。即每次内层循环都在未排序数组中使用FindMin()函数,并与未排序数组左端交换。

 1 void select_sort(int *array,int length)
 2 {
 3     for( int i=0; i<length-1; i++ )
 4     {
 5         int min = i; //array[i,...,length]最小值下标 
 6         for( int j=i+1; j<length; j++ )
 7         {
 8             if( array[min]>array[j] ){
 9                 min = j;
10             }
11         }
12         swap(array[i],array[min]);
13     }
14 }
View Code

  双重循环,时间复杂度O(n^2)。

 


 

 

No.3 插入排序

  算法过程类似于打扑克抓手牌,每次从牌堆抓一张牌后,从右至左在手牌中找到合适位置插入新手牌,保持手牌有序。

 1 void insert_sort( int *array, int length )
 2 {
 3     for( int i=1; i<length; i++ )
 4     {
 5         int j = i - 1;
 6         int key = array[i]; /*暂时保存新的手牌 */ 
 7         while( j>=0 && array[j]>key )/*找到合适位置*/ 
 8         {
 9             array[j+1] = array[j];
10             j--;
11         }
12         array[j+1] = key; /*将新手牌放入合适位置*/ 
13     }
14 }
View Code

  在最好情况即有序情况下时间复杂度为外层循环O(n),在最坏情况下即逆序情况下时间复杂度为双重循环O(n^2)。

 


 

 

No.4 希尔排序

  插入排序的改进版本,又称为缩减增量排序。其思想是每次用增量ht(>=1)作为间隔将数组划分为子问题进行插入排序,每次排序后增量缩减,

重复上述过程,直到增量减为1,此时的排序与插入排序相同。

 1 void shell_sort( int *array, int length )
 2 {
 3     /*计算初始增量*/
 4     int gap = 1;
 5     while( gap<length )    gap = gap * 3 + 1;
 6     /*以增量为间隔排序*/
 7     while( gap>0 )
 8     {
 9         for( int i=gap; i<length; i++ )
10         {
11             /*当gap=1时程序与插入排序相同*/
12             int j = i - gap;
13             int key = array[i];
14             while( j>=0 && array[j]>key )
15             {
16                 array[j+gap] = array[j];
17                 j -= gap;
18             }
19             array[j+gap] = key;
20         }
21         gap /= 3; /*增量递减*/
22     }
23 }
View Code

  通过将完全无序的初始序列,通过缩减增量逐步将序列有序,最后一步的插入排序只需交换个别元素位置,使时间复杂度优于插入排序。

  增量的初值及缩减规则没有最优标准。

 


 

 

No.5 归并排序

  分治法思想,即划分,排序,合并。每次将序列两两分组,直至序列长度为1,接着逐步合并子序列并保持合并序列有序。

 1 const int INF = 10000000;
 2 void merge(int *array, int p, int q, int r )
 3 {
 4     int length1 = q - p + 1, length2 = r - q;
 5     int *A = new int[length1+1];
 6     int *B = new int[length2+1]; //临时数组
 7     
 8     for( int i=p; i<=q; i++ )
 9     {
10         A[i-p] = array[i];    
11     }    
12     for( int i=q+1; i<=r; i++ )
13     {
14         B[i-q-1] = array[i];
15     }
16     A[length1] = B[length2] = INF; //为统一操作 
17     
18     int i = 0, j = 0;
19     for( int k=p; k<=r; k++ )
20     {
21         if( A[i]<B[j] )
22         {
23             array[k] = A[i];
24             i++; 
25         }
26         else
27         {
28             array[k] = B[j];
29             j++;
30         }
31     }
32     
33     delete []A;    delete []B; //释放内存 
34 }
35 
36 void merge_sort( int *array, int p, int r )
37 {
38     if( p<r )
39     {
40         int q = (p+r)/2;
41         merge_sort(array,p,q);
42         merge_sort(array,q+1,r);
43         merge(array,p,q,r);
44     }
45 }
View Code

  算法每次将问题划分为两个相等的子问题,合并与排序同时进行,需要O(n)时间。

  T(n)  = 2T(n/2) + O(n)  =  T(nlogn)。

 


 

 

No.6 快速排序

  分治法思想,每次在序列中选取一个基准值,经过一次排序操作后,位于基准值左边的元素不大于基准值、位于基准值右边的元素不小于基准值。

接着在基准值左右两边分别选取新的基准值重复操作,直到左右元素个数不大于1,序列排序完成。

  基准值的选取影响算法时间的好坏,这里统一选取数组的最右端作为基准值。

  找到基准值后对元素的划分有多种方法。

  单边扫描法:从左至右遍历元素,若元素小于基准值,则与左端元素交换。

 1 int patition( int *array, int p, int r )
 2 {
 3     int x = array[r]; //以最右端元素作为基准值
 4     int i = p - 1;
 5     for( int j=p; j<r; j++ )
 6     {
 7         if( array[j]<x )
 8         {/*将小于基准值元素放在左边*/
 9             i++;
10             swap(array[i],array[j]);    
11         }    
12     }
13     swap(array[i+1],array[r]);
14     return i+1; 
15 }
16 
17 void quick_sort( int *array, int p, int r )
18 {
19     if( p<r )
20     {
21         int q = patition(array,p,r);
22         quick_sort(array,p,q-1);
23         quick_sort(array,q+1,r);
24     }
25 }
View Code

  双边扫描法:先从左至右找到大于基准值元素,再从右至左找到小于基准值元素,交换元素,重复上述过程直至左右指针相遇。

 1 int patition( int *array, int p, int r )
 2 {
 3     int x = array[r]; //以最右端元素作为基准值
 4     int i = p, j = r - 1;
 5     while( i<=j )
 6     {/* 保证i左边的元素<=x,j右边元素(不包括r)>=x */
 7         while( i<=j && array[i]<=x )    i++;
 8         while( i<=j && array[j]>=x )    j--;
 9         if( i<=j )
10         {
11             swap(array[i],array[j]);    
12         }    
13     } 
14     /* 此时array[i]>x */
15     swap(array[r],array[i]);
16     return i; 
17 }
18 
19 void quick_sort( int *array, int p, int r )
20 {
21     if( p<r )
22     {
23         int q = patition(array,p,r);
24         quick_sort(array,p,q-1);
25         quick_sort(array,q+1,r);
26     }
27 }
View Code

  快排在最坏情况下:每次划分后一段元素个数为0,另一端为length-1 时间复杂度为O(n^2),在一般情况下时间复杂度为O(nlogn)。

 


 

 

No.7 堆排序

  利用数据结构排序,每次取堆顶元素。

  自己的思路是利用堆插入元素构建堆,接着依次取出堆顶元素,实现排序。

 1 const int Max_N = 10000;
 2 
 3 int sz = 0;//堆元素个数 
 4 int heap[Max_N]; //
 5 
 6 void swap(int &a, int &b)
 7 {
 8     int temp = a;
 9     a = b;
10     b = temp;
11 }
12 
13 void push( int x )
14 {
15     //自己节点编号
16     int i = sz++;
17     
18     while( i>0 )
19     {
20         //父亲节点编号 
21         int p = (i-1)/2;    
22         
23         //如果没有大小颠倒则推出
24         if( heap[p]<=x )    break;
25         
26         //把父亲节点数值放下来,将自己下标与之交换
27         heap[i] = heap[p];
28         i = p; 
29     } 
30     
31     heap[i] = x;
32 }
33 
34 int pop()
35 {
36     //最小值 
37     int ret = heap[0];
38     
39     //提到根的数值
40     int x = heap[--sz];
41     
42     //从根开始向下交换
43     int i = 0;
44     while( i*2+1<sz )
45     {
46         int a = 2*i+1, b = 2*i+2;
47         if( b<sz && heap[b]<heap[a] )    a = b; /*a为孩子较小值下标*/
48         
49         //如果没有大小颠倒则退出 
50         if( heap[a]>=x )    break;
51         
52         //将儿子提上来 
53         heap[i] = heap[a]; 
54         i = a; 
55     } 
56     
57     heap[i] = x;
58     
59     return ret;
60 }
61 
62 void heap_sort( int *array, int length )
63 {
64     for( int i=0; i<length; i++ )
65     {
66         push(array[i]);
67     }
68     
69     for( int i=0; i<length; i++ )
70     {
71         array[i] = pop();
72     }
73 }
View Code

  这种思路比较耗时,构建堆过程中每一个元素都要从堆尾到堆顶做交换比较。

  堆排序思路:先将元素加入堆中,再对堆非叶子节点做调整以构建堆,接着依次重复将堆顶下沉到堆尾(与堆尾交换)、重建堆建构,实现排序。

 1 void adjust_heap(int *heap,int i,int length)
 2 {
 3     int x = heap[i];
 4     int sz = length;
 5     
 6     while( i*2+1<sz )
 7     {
 8         int a = i*2+1, b = i*2+2;
 9         if( b<sz && heap[b]>heap[a] )
10         {
11             a = b;/*a为孩子较大值下标*/
12         }
13         
14         //如果没有大小颠倒,退出 
15         if( heap[a]<=x )    break;
16         
17         //将孩子提上来
18         heap[i] = heap[a];
19         i = a; 
20     }
21     
22     heap[i] = x;
23 }
24 
25 void heap_sort( int *array, int length )
26 {
27     /*调整构建大顶堆*/
28     //第一个非叶子节点是(length-1)/2是因为它是最后一个节点的父亲 
29     for( int i=(length-1)/2; i>=0; i-- )
30     {/*从第一个非叶子节点从下至上,从右至左调整结构*/
31         adjust_heap(array,i,length);
32     }
33     
34     for( int i=length-1; i>0; i-- )
35     {
36         /*将堆顶元素下沉*/
37         swap(array[0],array[i]);
38         adjust_heap(array,0,i);//重写调整堆结构 
39     }
40 }
View Code

  对于每个元素其调整操作最多需要O(logn),共有n个元素,所以堆排序时间复杂度为O(nlogn)。

  堆排序实际上也是一种选择排序,是一种树形选择排序。普通排序在选择最小值过程中已经与此小值做过了比较,但没有保存,所以每次都要重新比较一遍。

而堆排序利用树形结构保存了中间比较的结果,减少比较次数。

 


 

 

No.8 计数排序

  不同于上述算法的是计数排序不是基于比较的排序算法。算法的思路是用一个数组count[ i ] : 记录在数组中小于等于元素 i 的个数,即元素 i 的升序排名。

 1 void count_sort( int *array, int length )
 2 {
 3     int max = array[0];
 4     for( int i=1; i<length; i++ )
 5     {
 6         max = max < array[i] ? array[i] : max; 
 7     }
 8     
 9     int *count = new int[max+1]; //count[0,...,max]
10     memset(count,0,sizeof(int)*(max+1));
11     for( int i=0; i<length; i++ )
12     {
13         /*记录 array[i] 出现的次数*/
14         count[ array[i] ]++;
15     }
16 
17     for( int i=1; i<=max; i++ )
18     {
19         /* 记录 i 在array 中的排名*/
20         count[i] = count[i-1] + count[i];
21     }
22     
23     int *temp = new int[length];
24     for( int i=0; i<length; i++ )
25     {
26         /* 以array[i]的排名放入临时数组 --因为数组从0开始*/
27         temp[ --count[array[i]] ] = array[i];
28     }
29     
30     for( int i=0; i<length; i++ )
31     {
32         /* 将排序后元素放回原数组 */
33         array[i] = temp[i];
34     }
35     
36     delete []count;    delete []temp;
37 }
View Code

  计数排序采用用空间换时间的方法,使用简单,时间复杂度为O(n+max)。同时具有局限性,比如数值的差距很大,那么有大量的空间没有被使用、因为使用

数组下标计数,所以只适用于整数。

  计数排序适用于一定范围内的整数排序,且时间复杂度能达到O(n)。

 


 

 

No.9 桶排序

  桶排序可以看作是计数排序的优化,算法将待排数据放入一定大小(数值范围)的有序桶里,再对桶里数据进行排序(可自己选择排序算法),

最后按序从桶中取出数据。

 1 const int BUCKET_SIZE = 5;//默认用5个桶 
 2 
 3 void bucket_sort( int *array, int length )
 4 {
 5     /*最大/最小值*/
 6     int max_element,min_element;
 7     max_element = min_element = array[0];
 8     for( int i=1; i<length; i++ )
 9     {
10         max_element = max( max_element, array[i] );
11         min_element = min( min_element, array[i] );
12     }
13     
14     /*差值*/
15     int dif = max_element - min_element;
16     /*用二维向量作为桶*/
17     vector<int> bucket_list[BUCKET_SIZE];
18     
19     /*单个桶的区间值*/
20     float section = (float)dif/(BUCKET_SIZE);
21     
22     /*数据入桶*/
23     for( int i=0; i<length; i++ )
24     {
25         /*(当前数-min)/区间-1 -> 桶的下标*/
26         int num = (int)((array[i]-min_element)/section)-1;
27         if( num<0 )    num = 0;
28         bucket_list[num].push_back(array[i]);    
29     } 
30     
31     //桶内排序
32     for(int i=0; i<BUCKET_SIZE; i++ )
33     {
34         sort(bucket_list[i].begin(),bucket_list[i].end());
35     }
36     
37     int index = 0;
38     for( int i=0; i<BUCKET_SIZE; i++ )
39     {
40         for( int j=0; j<bucket_list[i].size(); j++ )
41         {
42             array[index++] = bucket_list[i][j];
43         }
44     }
45 }
View Code

  桶排序是非比较整数排序。

  最优情况为数据均匀分布于桶中,最坏情况为所有数据在一个桶中,此时的时间复杂度取决于桶内排序算法的选取。所以桶的大小与个数选取十分重要。

  当所有数值都有唯一对应的桶时,此时无需做桶内排序,可以看作是计数排序。

 


 

 

No.10 基数排序

  非比较整数排序,其思想为按位数逐步划分数据。

 1 const int size = 10;
 2 void radix_sort( int *array, int length )
 3 {    
 4     int max_element = array[0];
 5     for( int i=1; i<length; i++ )
 6     {
 7         max_element = max( max_element, array[i] );    
 8     } 
 9     
10     int pow = 1;
11     while( true )
12     {
13         vector<int> bucket_list[size];
14         
15         for( int i=0; i<length; i++ )
16         {
17             int radix = (array[i]/pow)%10; /*当前排序位*/
18             bucket_list[ radix ].push_back( array[i] ); 
19         }
20         
21         int index = 0;
22         for( int i=0; i<size; i++ )
23         {
24             for( int j=0; j<bucket_list[i].size(); j++ )
25             {
26                 array[index++] = bucket_list[i][j];//放回数据 
27             }
28         }
29         
30         pow *= 10;
31         if( pow>max_element )    break;
32     }
33 }
View Code

  基数排序可以看作是固定10个桶的桶排序,其排序规则是按照位数,不断的将数据放入桶和从桶中取出数据。

  可以按位数从左向右排序,也可从右向左。

 


 

 

主要参考:https://mp.weixin.qq.com/s/Qf416rfT4pwURpW3aDHuCg

 

  

posted @ 2021-01-17 17:14  代码改变头发  阅读(116)  评论(0编辑  收藏  举报