排序算法大致可分为两种:html
一种是比较排序,时间复杂度O(nlogn) ~ O(n^2),主要有:冒泡排序,选择排序,希尔排序,插入排序,归并排序,堆排序,快速排序等。ios
另外一种是非比较排序,时间复杂度能够达到O(n),主要有:计数排序,基数排序,桶排序等。c++
排序算法的稳定性:git
排序算法稳定性的简单形式化定义为:若是Ai = Aj,排序前Ai在Aj以前,排序后Ai还在Aj以前,则称这种排序算法是稳定的。通俗地讲就是保证排序先后两个相等的数的相对顺序不变。算法
对于不稳定的排序算法,只要举出一个实例,便可说明它的不稳定性;而对于稳定的排序算法,必须对算法进行分析从而获得稳定的特性。须要注意的是,排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下能够变为稳定的算法,而稳定的算法在某种条件下也能够变为不稳定的算法。api
例如,对于冒泡排序,本来是稳定的排序算法,若是将记录交换的条件改为A[i] >= A[i + 1],则两个相等的记录就会交换位置,从而变成不稳定的排序算法。数组
其次,说一下排序算法稳定性的好处。排序算法若是是稳定的,那么从一个键上排序,而后再从另外一个键上排序,前一个键排序的结果能够为后一个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位排序后元素的顺序在高位也相同时是不会改变的。数据结构
1、冒泡排序架构
冒泡排序是一种极其简单的排序算法,也是我所学的第一个排序算法。它重复地走访过要排序的元素,依次比较相邻两个元素,若是他们的顺序错误就把他们调换过来,直到没有元素再须要交换,排序完成。这个算法的名字由来是由于越小(或越大)的元素会经由交换慢慢“浮”到数列的顶端。ide
尽管冒泡排序是最容易了解和实现的排序算法之一,但它对于少数元素以外的数列排序是很没有效率的。
冒泡排序算法的运做以下:
1 #include<bits/stdc++.h>
2 using namespace std; 3
4 int a[]={6, 5, 3, 1, 8, 7, 2, 4}; 5
6 void BubbleSort(int a[],int n){ 7 for(int i=0;i<n-1;i++){ 8 for(int j=0;j<n-1-i;j++){ 9 if(a[j]>a[j+1]){ 10 int temp=a[j]; 11 a[j]=a[j+1]; 12 a[j+1]=temp; 13 } 14 } 15 } 16 } 17
18 int main(){ 19 ios::sync_with_stdio(false); 20 int n=sizeof(a)/sizeof(int);//sizeof()求的是字节数
21 cout<<n<<endl; 22 BubbleSort(a,n); 23 for(int i=0;i<n;i++){ 24 cout<<a[i]<<" "; 25 } 26 cout<<endl; 27 return 0; 28 }
2、冒泡排序的改进--鸡尾酒排序
鸡尾酒排序,也叫定向冒泡排序,是冒泡排序的一种改进。此算法与冒泡排序的不一样处在于从低到高而后从高到低,而冒泡排序则仅从低到高去比较序列里的每一个元素。他能够获得比冒泡排序稍微好一点的效能。
以序列(2,3,4,5,1)为例,鸡尾酒排序只须要访问一次序列就能够完成排序,但若是使用冒泡排序则须要四次。可是在乱数序列的状态下,鸡尾酒排序与冒泡排序的效率都不好劲。
1 #include<bits/stdc++.h>
2 using namespace std; 3
4 int a[]={6, 5, 3, 1, 8, 7, 2, 4}; 5
6 void CocktailSort(int a[],int n){ 7 int left=0,right=n-1; 8 while(left<right){ 9 for(int i=left;i<right;i++){ 10 if(a[i]>a[i+1]){ 11 int temp=a[i]; 12 a[i]=a[i+1]; 13 a[i+1]=temp; 14 } 15 } 16 right--; 17 for(int i=right;i>left;i--){ 18 if(a[i-1]>a[i]){ 19 int temp=a[i]; 20 a[i]=a[i-1]; 21 a[i-1]=temp; 22 } 23 } 24 left++; 25 } 26 } 27
28 int main(){ 29 ios::sync_with_stdio(false); 30 int n=sizeof(a)/sizeof(int);//sizeof()求的是字节数
31 cout<<n<<endl; 32 CocktailSort(a,n); 33 for(int i=0;i<n;i++){ 34 cout<<a[i]<<" "; 35 } 36 cout<<endl; 37 return 0; 38 }
3、选择排序
选择排序也是一种简单直观的排序算法。它的工做原理很容易理解:初始时在序列中找到最小(大)元素,放到序列的起始位置做为已排序序列;而后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到全部元素均排序完毕。
注意选择排序与冒泡排序的区别:冒泡排序经过依次交换相邻两个顺序不合法的元素位置,从而将当前最小(大)元素放到合适的位置;而选择排序每遍历一次都记住了当前最小(大)元素的位置,最后仅需一次交换操做便可将其放到合适的位置。
选择排序是不稳定的排序算法,不稳定发生在最小元素与A[i]交换的时刻。
好比序列:{ 5, 8, 5, 2, 9 },一次选择的最小元素是2,而后把2和第一个5进行交换,从而改变了两个元素5的相对次序。
1 #include<bits/stdc++.h>
2 using namespace std; 3
4 int a[]={6, 5, 3, 1, 8, 7, 2, 4}; 5
6 void SelectionSort(int a[],int n){ 7 for(int i=0;i<n;i++){ 8 int ans=i; 9 for(int j=i+1;j<n;j++){ 10 if(a[j]<a[ans]){ 11 ans=j; 12 } 13 } 14 if(ans!=i){ 15 int temp=a[i]; 16 a[i]=a[ans]; 17 a[ans]=temp; 18 } 19 } 20 } 21
22 int main(){ 23 ios::sync_with_stdio(false); 24 int n=sizeof(a)/sizeof(int);//sizeof()求的是字节数
25 cout<<n<<endl; 26 SelectionSort(a,n); 27 for(int i=0;i<n;i++){ 28 cout<<a[i]<<" "; 29 } 30 cout<<endl; 31 return 0; 32 }
4、插入排序
插入排序是一种简单直观的排序算法。它的工做原理很是相似于咱们抓扑克牌
对于未排序数据(右手抓到的牌),在已排序序列(左手已经排好序的手牌)中从后向前扫描,找到相应位置并插入。
插入排序在实现上,一般采用in-place排序(即只需用到O(1)的额外空间的排序),于是在从后向前扫描过程当中,须要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
具体算法描述以下:
插入排序不适合对于数据量比较大的排序应用。可是,若是须要排序的数据量很小,好比量级小于千,那么插入排序仍是一个不错的选择。 插入排序在工业级库中也有着普遍的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序做为快速排序的补充,用于少许元素的排序(一般为8个或如下)。
1 #include<bits/stdc++.h>
2 using namespace std; 3
4 int a[]={6, 5, 3, 1, 8, 7, 2, 4}; 5
6 void InsertionSort(int a[],int n){ 7 for(int i=1;i<n;i++){ 8 int temp=a[i]; 9 int j=i-1; 10 while(j>=0&&a[j]>temp){ 11 a[j+1]=a[j]; 12 j--; 13 } 14 a[j+1]=temp; 15 } 16 } 17
18 int main(){ 19 ios::sync_with_stdio(false); 20 int n=sizeof(a)/sizeof(int);//sizeof()求的是字节数
21 cout<<n<<endl; 22 InsertionSort(a,n); 23 for(int i=0;i<n;i++){ 24 cout<<a[i]<<" "; 25 } 26 cout<<endl; 27 return 0; 28 }
5、插入排序的改进--二分插入排序
对于插入排序,若是比较操做的代价比交换操做大的话,能够采用二分查找法来减小比较操做的次数,咱们称为二分插入排序
当n较大时,二分插入排序的比较次数比直接插入排序的最差状况好得多,但比直接插入排序的最好状况要差,所当以元素初始序列已经接近升序时,直接插入排序比二分插入排序比较次数少。二分插入排序元素移动次数与直接插入排序相同,依赖于元素初始序列。
1 #include<bits/stdc++.h>
2 using namespace std; 3
4 int a[]={6, 5, 3, 1, 8, 7, 2, 4}; 5
6 void InsertionSortDichotomy(int a[],int n){ 7 for(int i=1;i<n;i++){ 8 int temp=a[i]; 9 int left=0,right=i-1; 10 while(left<=right){ 11 int mid=(left+right)/2; 12 if(a[mid]>temp) 13 right=mid-1; 14 else
15 left=mid+1; 16 } 17 for(int j=i-1;j>=left;j--){ 18 a[j+1]=a[j]; 19 } 20 a[left]=temp; 21 } 22 } 23
24 int main(){ 25 ios::sync_with_stdio(false); 26 int n=sizeof(a)/sizeof(int);//sizeof()求的是字节数
27 cout<<n<<endl; 28 InsertionSortDichotomy(a,n); 29 for(int i=0;i<n;i++){ 30 cout<<a[i]<<" "; 31 } 32 cout<<endl; 33 return 0; 34 }
6、插入排序更高效的改进--希尔排序
希尔排序,也叫递减增量排序,是插入排序的一种更高效的改进版本。希尔排序是不稳定的排序算法。
希尔排序是基于插入排序的如下两点性质而提出改进方法的:
希尔排序经过将比较的所有元素分为几个区域来提高插入排序的性能。这样可让一个元素能够一次性地朝最终位置前进一大步。而后算法再取愈来愈小的步长进行排序,算法的最后一步就是普通的插入排序,可是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。
假设有一个很小的数据在一个已按升序排好序的数组的末端。若是用复杂度为O(n^2)的排序(冒泡排序或直接插入排序),可能会进行n次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据,因此小数据只需进行少数比较和交换便可到正确位置。
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
希尔排序是不稳定的排序算法,虽然一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不一样的插入排序过程当中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱。
好比序列:{ 3, 5, 10, 8, 7, 2, 8, 1, 20, 6 },h=2时分红两个子序列 { 3, 10, 7, 8, 20 } 和 { 5, 8, 2, 1, 6 } ,未排序以前第二个子序列中的8在前面,如今对两个子序列进行插入排序,获得 { 3, 7, 8, 10, 20 } 和 { 1, 2, 5, 6, 8 } ,即 { 3, 1, 7, 2, 8, 5, 10, 6, 20, 8 } ,两个8的相对次序发生了改变。
1 #include<bits/stdc++.h>
2 using namespace std; 3
4 int a[]={6, 5, 3, 1, 8, 7, 2, 4}; 5
6 void ShellSort(int a[],int n){ 7 int h=0; 8 while(h<=n){ 9 h=h*3+1; 10 } 11 while(h>=1){ 12 for(int i=h;i<n;i++){ 13 int j=i-h; 14 int temp=a[i]; 15 while(j>=0&&a[j]>temp){ 16 a[j+h]=a[j]; 17 j-=h; 18 } 19 a[j+h]=temp; 20 } 21 h=(h-1)/3; 22 } 23 } 24
25 int main(){ 26 ios::sync_with_stdio(false); 27 int n=sizeof(a)/sizeof(int);//sizeof()求的是字节数
28 cout<<n<<endl; 29 ShellSort(a,n); 30 for(int i=0;i<n;i++){ 31 cout<<a[i]<<" "; 32 } 33 cout<<endl; 34 return 0; 35 }
7、归并排序
归并排序是建立在归并操做上的一种有效的排序算法,效率为O(nlogn),1945年由冯·诺伊曼首次提出。
归并排序的实现分为递归实现与非递归(迭代)实现。递归实现的归并排序是算法设计中分治策略的典型应用,咱们将一个大问题分割成小问题分别解决,而后用全部小问题的答案来解决整个大问题。非递归(迭代)实现的归并排序首先进行是两两归并,而后四四归并,而后是八八归并,一直下去直到归并了整个数组。
归并排序算法主要依赖归并(Merge)操做。归并操做指的是将两个已经排序的序列合并成一个序列的操做,归并操做步骤以下:
归并排序除了能够对数组进行排序,还能够高效的求出数组小和(即单调和)以及数组中的逆序对,详见这篇博文。
1 #include <stdio.h>
2 #include <limits.h>
3
4 // 分类 -------------- 内部比较排序 5 // 数据结构 ---------- 数组 6 // 最差时间复杂度 ---- O(nlogn) 7 // 最优时间复杂度 ---- O(nlogn) 8 // 平均时间复杂度 ---- O(nlogn) 9 // 所需辅助空间 ------ O(n) 10 // 稳定性 ------------ 稳定
11
12
13 void Merge(int A[], int left, int mid, int right)// 合并两个已排好序的数组A[left...mid]和A[mid+1...right]
14 { 15 int len = right - left + 1; 16 int *temp = new int[len]; // 辅助空间O(n)
17 int index = 0; 18 int i = left; // 前一数组的起始元素
19 int j = mid + 1; // 后一数组的起始元素
20 while (i <= mid && j <= right) 21 { 22 temp[index++] = A[i] <= A[j] ? A[i++] : A[j++]; // 带等号保证归并排序的稳定性
23 } 24 while (i <= mid) 25 { 26 temp[index++] = A[i++]; 27 } 28 while (j <= right) 29 { 30 temp[index++] = A[j++]; 31 } 32 for (int k = 0; k < len; k++) 33 { 34 A[left++] = temp[k]; 35 } 36 } 37
38 void MergeSortRecursion(int A[], int left, int right) // 递归实现的归并排序(自顶向下)
39 { 40 if (left == right) // 当待排序的序列长度为1时,递归开始回溯,进行merge操做
41 return; 42 int mid = (left + right) / 2; 43 MergeSortRecursion(A, left, mid); 44 MergeSortRecursion(A, mid + 1, right); 45 Merge(A, left, mid, right); 46 } 47
48 void MergeSortIteration(int A[], int len) // 非递归(迭代)实现的归并排序(自底向上)
49 { 50 int left, mid, right;// 子数组索引,前一个为A[left...mid],后一个子数组为A[mid+1...right]
51 for (int i = 1; i < len; i *= 2) // 子数组的大小i初始为1,每轮翻倍
52 { 53 left = 0; 54 while (left + i < len) // 后一个子数组存在(须要归并)
55 { 56 mid = left + i - 1; 57 right = mid + i < len ? mid + i : len - 1;// 后一个子数组大小可能不够
58 Merge(A, left, mid, right); 59 left = right + 1; // 前一个子数组索引向后移动
60 } 61 } 62 } 63
64 int main() 65 { 66 int A1[] = { 6, 5, 3, 1, 8, 7, 2, 4 }; // 从小到大归并排序
67 int A2[] = { 6, 5, 3, 1, 8, 7, 2, 4 }; 68 int n1 = sizeof(A1) / sizeof(int); 69 int n2 = sizeof(A2) / sizeof(int); 70 MergeSortRecursion(A1, 0, n1 - 1); // 递归实现
71 MergeSortIteration(A2, n2); // 非递归实现
72 printf("递归实现的归并排序结果:"); 73 for (int i = 0; i < n1; i++) 74 { 75 printf("%d ", A1[i]); 76 } 77 printf("\n"); 78 printf("非递归实现的归并排序结果:"); 79 for (int i = 0; i < n2; i++) 80 { 81 printf("%d ", A2[i]); 82 } 83 printf("\n"); 84 return 0; 85 }
8、堆排序
堆排序是指利用堆这种数据结构所设计的一种选择排序算法。堆是一种近似彻底二叉树的结构(一般堆是经过一维数组来实现的),并知足性质:以最大堆(也叫大根堆、大顶堆)为例,其中父结点的值老是大于它的孩子节点。
咱们能够很容易的定义堆排序的过程:
堆排序是不稳定的排序算法,不稳定发生在堆顶元素与A[i]交换的时刻。
好比序列:{ 9, 5, 7, 5 },堆顶元素是9,堆排序下一步将9和第二个5进行交换,获得序列 { 5, 5, 7, 9 },再进行堆调整获得{ 7, 5, 5, 9 },重复以前的操做最后获得{ 5, 5, 7, 9 }从而改变了两个5的相对次序。
1 #include <stdio.h>
2
3 // 分类 -------------- 内部比较排序 4 // 数据结构 ---------- 数组 5 // 最差时间复杂度 ---- O(nlogn) 6 // 最优时间复杂度 ---- O(nlogn) 7 // 平均时间复杂度 ---- O(nlogn) 8 // 所需辅助空间 ------ O(1) 9 // 稳定性 ------------ 不稳定
10
11
12 void Swap(int A[], int i, int j) 13 { 14 int temp = A[i]; 15 A[i] = A[j]; 16 A[j] = temp; 17 } 18
19 void Heapify(int A[], int i, int size) // 从A[i]向下进行堆调整
20 { 21 int left_child = 2 * i + 1; // 左孩子索引
22 int right_child = 2 * i + 2; // 右孩子索引
23 int max = i; // 选出当前结点与其左右孩子三者之中的最大值
24 if (left_child < size && A[left_child] > A[max]) 25 max = left_child; 26 if (right_child < size && A[right_child] > A[max]) 27 max = right_child; 28 if (max != i) 29 { 30 Swap(A, i, max); // 把当前结点和它的最大(直接)子节点进行交换
31 Heapify(A, max, size); // 递归调用,继续从当前结点向下进行堆调整
32 } 33 } 34
35 int BuildHeap(int A[], int n) // 建堆,时间复杂度O(n)
36 { 37 int heap_size = n; 38 for (int i = heap_size / 2 - 1; i >= 0; i--) // 从每个非叶结点开始向下进行堆调整
39 Heapify(A, i, heap_size); 40 return heap_size; 41 } 42
43 void HeapSort(int A[], int n) 44 { 45 int heap_size = BuildHeap(A, n); // 创建一个最大堆
46 while (heap_size > 1) // 堆(无序区)元素个数大于1,未完成排序
47 { 48 // 将堆顶元素与堆的最后一个元素互换,并从堆中去掉最后一个元素 49 // 此处交换操做颇有可能把后面元素的稳定性打乱,因此堆排序是不稳定的排序算法
50 Swap(A, 0, --heap_size); 51 Heapify(A, 0, heap_size); // 重新的堆顶元素开始向下进行堆调整,时间复杂度O(logn)
52 } 53 } 54
55 int main() 56 { 57 int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 };// 从小到大堆排序
58 int n = sizeof(A) / sizeof(int); 59 HeapSort(A, n); 60 printf("堆排序结果:"); 61 for (int i = 0; i < n; i++) 62 { 63 printf("%d ", A[i]); 64 } 65 printf("\n"); 66 return 0; 67 }
9、快速排序
快速排序是由东尼·霍尔所发展的一种排序算法。在平均情况下,排序n个元素要O(nlogn)次比较。在最坏情况下则须要O(n^2)次比较,但这种情况并不常见。事实上,快速排序一般明显比其余O(nlogn)算法更快,由于它的内部循环能够在大部分的架构上颇有效率地被实现出来。
快速排序使用分治策略(Divide and Conquer)来把一个序列分为两个子序列。步骤为:
快速排序是不稳定的排序算法,不稳定发生在基准元素与A[tail+1]交换的时刻。
好比序列:{ 1, 3, 4, 2, 8, 9, 8, 7, 5 },基准元素是5,一次划分操做后5要和第一个8进行交换,从而改变了两个元素8的相对次序。
1 #include<bits/stdc++.h>
2 // 分类 ------------ 内部比较排序 3 // 数据结构 --------- 数组 4 // 最差时间复杂度 ---- 每次选取的基准都是最大(或最小)的元素,致使每次只划分出了一个分区,须要进行n-1次划分才能结束递归,时间复杂度为O(n^2) 5 // 最优时间复杂度 ---- 每次选取的基准都是中位数,这样每次都均匀的划分出两个分区,只须要logn次划分就能结束递归,时间复杂度为O(nlogn) 6 // 平均时间复杂度 ---- O(nlogn) 7 // 所需辅助空间 ------ 主要是递归形成的栈空间的使用(用来保存left和right等局部变量),取决于递归树的深度,通常为O(logn),最差为O(n) 8 // 稳定性 ---------- 不稳定
9
10 void Swap(int A[], int i, int j) 11 { 12 int temp = A[i]; 13 A[i] = A[j]; 14 A[j] = temp; 15 } 16
17 int Partition(int A[], int left, int right,int n) // 划分函数
18 { 19 int pivot = A[right]; // 这里每次都选择最后一个元素做为基准
20 int tail = left - 1; 21 printf("%d %d %d\n",pivot,left,right); // tail为小于基准的子数组最后一个元素的索引
22 for (int i = left; i < right; i++) // 遍历基准之外的其余元素
23 { 24 if (A[i] <= pivot) // 把小于等于基准的元素放到前一个子数组末尾
25 { //printf("%d %d\n",tail,i);
26 Swap(A, ++tail, i); 27 for (int i = 0; i < n; i++) 28 { 29 printf("%d ", A[i]); 30 }printf("\n"); 31
32 } 33 } 34 Swap(A, tail + 1, right); // 最后把基准放到前一个子数组的后边,剩下的子数组既是大于基准的子数组
35 for (int i = 0; i < n; i++) 36 { 37 printf("%d ", A[i]); 38 } printf("\n"); // 该操做颇有可能把后面元素的稳定性打乱,因此快速排序是不稳定的排序算法
39 return tail + 1; // 返回基准的索引
40 } 41
42 void QuickSort(int A[], int left, int right,int n) 43 { 44 if (left >= right) 45 return; 46 int pivot_index = Partition(A, left, right,n); // 基准的索引
47 QuickSort(A, left, pivot_index - 1,n); 48 QuickSort(A, pivot_index + 1, right,n); 49 } 50
51 int main() 52 { 53 int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 }; // 从小到大快速排序
54 int n = sizeof(A) / sizeof(int); 55 QuickSort(A, 0, n - 1,n); 56 printf("快速排序结果:"); 57 for (int i = 0; i < n; i++) 58 { 59 printf("%d ", A[i]); 60 } 61 printf("\n"); 62 return 0; 63 }
10、计数排序
计数排序用到一个额外的计数数组C,根据数组C来将原数组A中的元素排到正确的位置。
通俗地理解,例若有10个年龄不一样的人,假如统计出有8我的的年龄不比小明大(即小于等于小明的年龄,这里也包括了小明),那么小明的年龄就排在第8位,经过这种思想能够肯定每一个人的位置,也就排好了序。固然,年龄同样时须要特殊处理(保证稳定性):经过反向填充目标数组,填充完毕后将对应的数字统计递减,能够确保计数排序的稳定性。
计数排序的步骤以下:
计数排序的时间复杂度和空间复杂度与数组A的数据范围(A中元素的最大值与最小值的差加上1)有关,所以对于数据范围很大的数组,计数排序须要大量时间和内存。
例如:对0到99之间的数字进行排序,计数排序是最好的算法,然而计数排序并不适合按字母顺序排序人名,将计数排序用在基数排序算法中,可以更有效的排序数据范围很大的数组。
1 #include<iostream>
2 using namespace std; 3
4 // 分类 ------------ 内部非比较排序 5 // 数据结构 --------- 数组 6 // 最差时间复杂度 ---- O(n + k) 7 // 最优时间复杂度 ---- O(n + k) 8 // 平均时间复杂度 ---- O(n + k) 9 // 所需辅助空间 ------ O(n + k) 10 // 稳定性 ----------- 稳定
11
12
13 const int k = 100; // 基数为100,排序[0,99]内的整数
14 int C[k]; // 计数数组
15
16 void CountingSort(int A[], int n) 17 { 18 for (int i = 0; i < k; i++) // 初始化,将数组C中的元素置0(此步骤可省略,整型数组元素默认值为0)
19 { 20 C[i] = 0; 21 } 22 for (int i = 0; i < n; i++) // 使C[i]保存着等于i的元素个数
23 { 24 C[A[i]]++; 25 } 26 for (int i = 1; i < k; i++) // 使C[i]保存着小于等于i的元素个数,排序后元素i就放在第C[i]个输出位置上
27 { 28 C[i] = C[i] + C[i - 1]; 29 } 30 int *B = (int *)malloc((n) * sizeof(int));// 分配临时空间,长度为n,用来暂存中间数据
31 for (int i = n - 1; i >= 0; i--) // 从后向前扫描保证计数排序的稳定性(重复元素相对次序不变)
32 { 33 B[--C[A[i]]] = A[i]; // 把每一个元素A[i]放到它在输出数组B中的正确位置上 34 // 当再遇到重复元素时会被放在当前元素的前一个位置上保证计数排序的稳定性
35 } 36 for (int i = 0; i < n; i++) // 把临时空间B中的数据拷贝回A
37 { 38 A[i] = B[i]; 39 } 40 free(B); // 释放临时空间
41 } 42
43 int main() 44 { 45 int A[] = { 15, 22, 19, 46, 27, 73, 1, 19, 8 }; // 针对计数排序设计的输入,每个元素都在[0,100]上且有重复元素
46 int n = sizeof(A) / sizeof(int); 47 CountingSort(A, n); 48 printf("计数排序结果:"); 49 for (int i = 0; i < n; i++) 50 { 51 printf("%d ", A[i]); 52 } 53 printf("\n"); 54 return 0; 55 }
11、桶排序
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的肯定。桶排序 (Bucket sort)的工做的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每一个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。
桶排序不是比较排序,不受到O(nlogn)下限的影响,它是鸽巢排序的一种概括结果,当所要排序的数组值分散均匀的时候,桶排序拥有线性的时间复杂度。
桶排序最好状况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,由于其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。
1 #include<iostream>
2 using namespace std; 3
4 // 分类 ------------- 内部非比较排序 5 // 数据结构 --------- 数组 6 // 最差时间复杂度 ---- O(nlogn)或O(n^2),只有一个桶,取决于桶内排序方式 7 // 最优时间复杂度 ---- O(n),每一个元素占一个桶 8 // 平均时间复杂度 ---- O(n),保证各个桶内元素个数均匀便可 9 // 所需辅助空间 ------ O(n + bn) 10 // 稳定性 ----------- 稳定
11
12 /* 本程序用数组模拟桶 */
13 const int bn = 5; // 这里排序[0,49]的元素,使用5个桶就够了,也能够根据输入动态肯定桶的数量
14 int C[bn]; // 计数数组,存放桶的边界信息
15
16 void InsertionSort(int A[], int left, int right) 17 { 18 for (int i = left + 1; i <= right; i++) // 从第二张牌开始抓,直到最后一张牌
19 { 20 int get = A[i]; 21 int j = i - 1; 22 while (j >= left && A[j] > get) 23 { 24 A[j + 1] = A[j]; 25 j--; 26 } 27 A[j + 1] = get; 28 } 29 } 30
31 int MapToBucket(int x) 32 { 33 return x / 10; // 映射函数f(x),做用至关于快排中的Partition,把大量数据分割成基本有序的数据块
34 } 35
36 void CountingSort(int A[], int n) 37 { 38 for (int i = 0; i < bn; i++) 39 { 40 C[i] = 0; 41 } 42 for (int i = 0; i < n; i++) // 使C[i]保存着i号桶中元素的个数
43 { 44 C[MapToBucket(A[i])]++; 45 } 46 for (int i = 1; i < bn; i++) // 定位桶边界:初始时,C[i]-1为i号桶最后一个元素的位置
47 { 48 C[i] = C[i] + C[i - 1]; 49 } 50 int *B = (int *)malloc((n) * sizeof(int)); 51 for (int i = n - 1; i >= 0; i--)// 从后向前扫描保证计数排序的稳定性(重复元素相对次序不变)
52 { 53 int b = MapToBucket(A[i]); // 元素A[i]位于b号桶
54 B[--C[b]] = A[i]; // 把每一个元素A[i]放到它在输出数组B中的正确位置上 55 // 桶的边界被更新:C[b]为b号桶第一个元素的位置
56 } 57 for (int i = 0; i < n; i++) 58 { 59 A[i] = B[i]; 60 } 61 free(B); 62 } 63
64 void BucketSort(int A[], int n) 65 { 66 CountingSort(A, n); // 利用计数排序肯定各个桶的边界(分桶)
67 for (int i = 0; i < bn; i++) // 对每个桶中的元素应用插入排序
68 { 69 int left = C[i]; // C[i]为i号桶第一个元素的位置
70 int right = (i == bn - 1 ? n - 1 : C[i + 1] - 1);// C[i+1]-1为i号桶最后一个元素的位置
71 if (left < right) // 对元素个数大于1的桶进行桶内插入排序
72 InsertionSort(A, left, right); 73 } 74 } 75
76 int main() 77 { 78 int A[] = { 29, 25, 3, 49, 9, 37, 21, 43 };// 针对桶排序设计的输入
79 int n = sizeof(A) / sizeof(int); 80 BucketSort(A, n); 81 printf("桶排序结果:"); 82 for (int i = 0; i < n; i++) 83 { 84 printf("%d ", A[i]); 85 } 86 printf("\n"); 87 return 0; 88 }
12、基数排序
基数排序是按照低位先排序,而后收集;再按照高位排序,而后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
基数排序的时间复杂度是O(n * dn),其中n是待排序元素个数,dn是数字位数。这个时间复杂度不必定优于O(n log n),dn的大小取决于数字位的选择(好比比特位数),和待排序数据所属数据类型的全集的大小;dn决定了进行多少轮处理,而n是每轮处理的操做数目。
若是考虑和比较排序进行对照,基数排序的形式复杂度虽然不必定更小,但因为不进行比较,所以其基本操做的代价较小,并且若是适当的选择基数,dn通常不大于log n,因此基数排序通常要快过基于比较的排序,好比快速排序。因为整数也能够表达字符串(好比名字或日期)和特定格式的浮点数,因此基数排序并非只能用于整数排序。
1 #include<iostream>
2 using namespace std; 3
4 // 分类 ------------- 内部非比较排序 5 // 数据结构 ---------- 数组 6 // 最差时间复杂度 ---- O(n * dn) 7 // 最优时间复杂度 ---- O(n * dn) 8 // 平均时间复杂度 ---- O(n * dn) 9 // 所需辅助空间 ------ O(n * dn) 10 // 稳定性 ----------- 稳定
11
12 const int dn = 3; // 待排序的元素为三位数及如下
13 const int k = 10; // 基数为10,每一位的数字都是[0,9]内的整数
14 int C[k]; 15
16 int GetDigit(int x, int d) // 得到元素x的第d位数字
17 { 18 int radix[] = { 1, 1, 10, 100 };// 最大为三位数,因此这里只要到百位就知足了
19 return (x / radix[d]) % 10; 20 } 21
22 void CountingSort(int A[], int n, int d)// 依据元素的第d位数字,对A数组进行计数排序
23 { 24 for (int i = 0; i < k; i++) 25 { 26 C[i] = 0; 27 } 28 for (int i = 0; i < n; i++) 29 { 30 C[GetDigit(A[i], d)]++; 31 } 32 for (int i = 1; i < k; i++) 33 { 34 C[i] = C[i] + C[i - 1]; 35 } 36 int *B = (int*)malloc(n * sizeof(int)); 37 for (int i = n - 1; i >= 0; i--) 38 { 39 int dight = GetDigit(A[i], d); // 元素A[i]当前位数字为dight
40 B[--C[dight]] = A[i]; // 根据当前位数字,把每一个元素A[i]放到它在输出数组B中的正确位置上 41 // 当再遇到当前位数字同为dight的元素时,会将其放在当前元素的前一个位置上保证计数排序的稳定性
42 } 43 for (int i = 0; i < n; i++) 44 { 45 A[i] = B[i]; 46 } 47 free(B); 48 } 49
50 void LsdRadixSort(int A[], int n) // 最低位优先基数排序
51 { 52 for (int d = 1; d <= dn; d++) // 从低位到高位
53 CountingSort(A, n, d); // 依据第d位数字对A进行计数排序
54 } 55
56 int main() 57 { 58 int A[] = { 20, 90, 64, 289, 998, 365, 852, 123, 789, 456 };// 针对基数排序设计的输入
59 int n = sizeof(A) / sizeof(int); 60 LsdRadixSort(A, n); 61 printf("基数排序结果:"); 62 for (int i = 0; i < n; i++) 63 { 64 printf("%d ", A[i]); 65 } 66 printf("\n"); 67 return 0; 68 }
参考资料:
http://www.cnblogs.com/eniac12/p/5329396.html