本次主要记录一些经典的排序算法,其中包括冒泡排序、直接选择排序、插入排序、归并排序、快速排序、堆排序、希尔排序、桶排序以及计数排序和基数排序。首先会给出这些排序算法的基本思想,然后给出实现的代码,最后会给出其时间复杂度。
1:冒泡排序
思想:
(1):比较相邻的前后两个元素,如果后面的数据小于前面的数据,则交换这两个数据的位置。这样经过一次遍历,最小的元素将在第0个位置,属于”冒泡”.
(2):重复第一步,依次将第二小…的元素排列到数组的顶端。
// 交换数据的三种方法 void swap(int &t1, int &t2){ int tmp = t1; t1 = t2; t2 = tmp; }
<pre name="code" class="cpp">// 使用异或进行交换 void swap2(int &a, int &b){ if(a != b){ /// 防止swap2(a, a)自身交换自身 a ^= b; b ^= a; a ^= b; } } void swap3(int &a, int &b){ if(a != b){ a = a + b; b = a - b; a = a - b; } }
#include <iostream> using namespace std; // 可以进行优化 设置监视哨,当该趟没有进行任何交换时,则退出 时间复杂度为n^2 void swap(int &t1, int &t2){ int tmp = t1; t1 = t2; t2 = tmp; } void bubbleSort(int r1[], int n){ for(int i = 0; i < n; i++) { for(int j = n-1; j > i; j--){ if(r1[j] < r1[j-1]){ swap(r1[j], r1[j-1]); } } } } int main(){ int a[10] ={6,5,4,7,2,4,1,8,5,10}; bubbleSort(a, 10); cout <<"冒泡排序后: "; for(int i = 0; i < 10; i++) { cout << a[i] << " "; } cout << endl; return 0; }
时间复杂度:O(n^2) 为稳定的排序算法
注意:可以进行优化设置监视哨,当该趟没有进行任何交换时,则退出。
2:直接选择排序
思想:第一次从R[0]~R[n-1]中选取最小值,与R[0]交换,第二次从R[1]~R[n-1]中选取最小值,与R[1]交换,....,第i次从R[i-1]~R[n-1]中选取最小值,与R[i-1]交换,.....,第n-1次从R[n-2]~R[n-1]中选取最小值,与R[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列.
代码:
#include <iostream> using namespace std; // 使用临时变量进行交换 void swap(int &a, int &b){ int tmp = a; a = b; b = tmp; } void display(int a[], int n){ for(int i = 0; i < n; i++){ cout << a[i] << " "; } cout << endl; } void SelectSort(int a[], int n){ for(int i = 0; i < n-1; i++) { int minIndex = i; int minElement = a[minIndex]; for(int j = i+1; j < n; j++) { if(a[j] < minElement){ minElement = a[j]; minIndex = j; } } swap3(a[i], a[minIndex]); } } int main(){ int a[10] ={6,5,4,7,2,4,1,8,5,10}; SelectSort(a, 10); display(a, 10); return 0; }
时间复杂度:O(n^2)。 为不稳定的排序算法
3:插入排序
思想:(1)初始时,a[0]自成1个有序区,无序区a[1,..n-1]。令i=1
(2)令temp=a[i],j从i-1到0,如果a[j]>temp 则a[j+1]=a[j],这样就将a[i]并入当前的有序区a[0,…i-1]中形成a[0,..i]的有序区间。
(3)i++并重复第二步直到i==n-1。排序完成。
insertSort.h
#ifndef INSERTSORT #define INSERTSORT #include "sortAlg.h" class InsertSort:public SortAlg{ public: InsertSort(int *is, const int &len):SortAlg(is, len){} void sort(){ for(int i = 1; i < length; i++){ int j = i-1, tmp = sortElement[i]; while(sortElement[j] > tmp && j >= 0){ sortElement[j+1] = sortElement[j]; j--; } sortElement[j+1] = tmp; } } }; #endif
平均时间复杂度为O(n^2),如果该序列已经排序好了,此时效率最高为O(n)。是稳定的排序算法。
4:归并排序
思想:(1)如果只有一个元素则已经排序好,返回。(2)分别归并排序A[1,…n/2]和A[n/2+1,…n]。
(1) 对两个已经归并好的序列进行归并O(n).
代码:
#include <iostream> using namespace std; void mergeSort(int r1[], int r2[], int s, int m , int t){ int i = s, j = m+1; int k = s; while(i <= m && j <= t){ if(r1[i] <= r1[j]) r2[k++] = r1[i++]; else r2[k++] = r1[j++]; } if(i > m){ while(j <= t) r2[k++] = r1[j++]; } if(j > t){ while(i <= m) r2[k++] = r1[i++]; } for(int n = s; n <= t; n++){ /// 注意 这个是必不可少 r1[n] = r2[n]; } } void merge(int r1[], int r2[], int s, int t){ if(s < t){ int m = (s+t)/2; merge(r1, r2, s, m); // 归并排序A[1,,n/2] merge(r1, r2, m+1, t); // 归并排序A[n/2+1,, n] mergeSort(r1, r2, s, m, t); // 对已排序的两个子序列进行归并 } } void display(int *a, int length){ for(int i = 0; i < length; i++){ cout << a[i] << " "; } cout << endl; } int main(){ int r1[10] = {3,2,5,1,7,4,9,7,8,7}, r2[10]; merge(r1, r2, 0, 9); display(r2, 10); return 0; }
时间复杂度:O(nlgn). 为稳定的排序算法
5:快速排序
思想:(1)通常选择一个元素作为主元x(通常为第一个元素,随机化快排则是随机选择一个元素,然后与第一个元素进行交换)。将数组A中小于x的元素放在其左边,大于等于x的元素放在其右边。<这步划分通常有两种方法,书中是挖坑填数,从两边进行。而另外一种方法则是从一边开始,代码也给出来了>。
(2)对左右子序列分别进行快速排序即可。
代码:
#include <iostream> #include <ctime> using namespace std; void swap(int &t1, int &t2){ int tmp = t1; t1 = t2; t2 = tmp; } // MIT视频中划分方法 只从一边进行 int Partition(int r1[], int p, int q){ int i = p, j = p+1; int pivot = r1[p]; for(j = p+1; j <= q; j++){ if(r1[j] < pivot){ i++; swap(r1[i], r1[j]); } } swap(r1[i], r1[p]); return i; } // 书中划分方法 从两边进行分划 挖坑填数 分而治之法 不稳定排序算法 int Partition2(int r1[], int p, int q){ int i = p, j = q; int pivot = r1[p]; while(i < j){ while(j > i && r1[j] > pivot) j --; if(j > i) r1[i++] = r1[j]; while(i < j && r1[i] < pivot) i++; if(i < j) r1[j--] = r1[i]; } r1[i] = pivot; return i; } // 随机化的快速排序算法 int randomPartition(int r1[], int p, int q){ srand((unsigned)time(0)); int randInt = rand()%(q-p+1)+p; // 随机选择一个元素作为主元 swap(r1[p], r1[randInt]); int i = p, j = p+1; int pivot = r1[p]; for(j = p+1; j <= q; j++){ if(r1[j] < pivot){ i++; swap(r1[i], r1[j]); } } swap(r1[i], r1[p]); return i; } void QuickSort(int r1[], int p, int q){ if(p < q){ int m = Partition(r1, p, q); //int m = randomPartition(r1, p, q); QuickSort(r1, p, m-1); QuickSort(r1, m+1, q); } } int main(){ int a[10] ={6,5,4,7,2,4,1,8,5,10}; cout <<"排序前: "; for(int i = 0; i < 10; i++){ cout << a[i] << " "; } cout << endl; QuickSort(a, 0, 9); cout <<"快排后: "; for(int i = 0; i < 10; i++) { cout << a[i] << " "; } cout << endl; return 0; }
平均时间复杂度为O(nlgn)。当序列已经有序,此时为最坏情况,时间复杂度为O(n^2)。为不稳定的排序算法。
6:堆排序
堆排序首先需要理解的就是堆的存储,删除,插入及堆化的过程(可以参考:http://blog.csdn.net/morewindows/article/details/6709644)。堆排序的思想就是将已经建好的堆(小堆举例)的根元素与最后一个元素互换,然后对除最后一个元素的堆进行堆化变为二叉堆,这样最后一个元素就是最小元素,重复这个操作,此时得到的数组即为降序排列的数组。
代码:
#include <iostream> using namespace std; void display(int a[], int n){ for(int i = 0; i < n; i++){ cout << a[i] << " "; } cout << endl; } void swap(int &a, int &b){ int tmp = a; a = b; b = tmp; } // 用数组来存储堆, 插入元素相当于在数组最后插入元素 向上进行堆化 void minHeapFixUp(int a[], int i){ int tmp = a[i]; int j = (i-1)/2; while(j >= 0 && i!= 0){ // 没有i!=0 会出现死循环 if(a[j] <= tmp) break; a[i] = a[j]; i = j; j = (i-1)/2; // -1/2 == 0 } a[i] = tmp; } // 在最小堆中添加元素 void minHeapAddNumber(int a[], int n, int x){ a[n-1] = x; minHeapFixUp(a, n-1); } // 删除元素都是删除第0个元素,并将最后一个元素放在第0个元素的位置 进行堆化 void minHeapFixDown(int a[], int i, int n){ // 向下 删除元素 int j = 2*i +1; int tmp = a[i]; while(j < n){ if(j+1 < n && a[j+1] < a[j]) j++; if(tmp <= a[j]) break; a[i] = a[j]; i = j; j = j*2+1; } a[i] = tmp; } // 在最小堆中删除数 void minHeapDeleteNumber(int a[], int n){ swap(a[0], a[n-1]); minHeapFixDown(a, 0, n-2); } // 堆化数组 建立最小堆 void makeMinHeap(int a[], int n){ for(int i = n/2 - 1; i >= 0; i--){ minHeapFixDown(a, i, n); } } // 堆排序 void minHeapSortToDes(int a[], int n){ for(int i = n-1; i > 0; i--){ swap(a[0], a[i]); minHeapFixDown(a, 0, i); } } int main(){ int a[11] = {9, 12, 17,30, 50, 20, 60, 65, 4, 19}; // 建堆 makeMinHeap(a, 10); display(a, 10); // 添加元素 minHeapAddNumber(a, 11, 3); display(a, 11); // 删除元素 //minHeapDeleteNumber(a, 11); //display(a, 11); // 堆排序 minHeapSortToDes(a, 11); display(a, 11); return 0; }
时间复杂度为O(nlgn)。为不稳定的排序算法。
7:希尔排序
思想:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。
步长一般以n/2开始,每次减半,直到最后为1.
代码:
#include <iostream> using namespace std; void ShellSort(int a[], int n){ for(int gap = n/2; gap > 0; gap --){ // 步长 for(int i = gap; i < n; i++){ int temp = a[i]; int j = i-gap; while(j >= 0 && a[j] > temp){ // 进行直接插入排序 a[j+gap] = a[j]; j -= gap; } a[j+gap] = temp; } } } void display(int a[], int n){ for(int i = 0; i < n; i++){ cout << a[i] << " "; } cout << endl; } int main(){ int a[10] ={6,5,4,7,2,4,1,8,5,10}; ShellSort(a, 10); display(a, 10); return 0; }
时间复杂度一般要取决于步长,在O(nlgn)与O(n^2)之间,为不稳定排序。
8:桶排序
桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。
例如要对大小为[1..1000]范围内的n个整数A[1..n]排序,可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储(10..20]的整数,……集合B[i]存储((i-1)*10,i*10]的整数,i = 1,2,..100。总共有100个桶。然后对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]中。 然后再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任何排序法都可以。最后依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这样就得到所有数字排好序的一个序列了。
假设有n个数字,有m个桶,如果数字是平均分布的,则每个桶里面平均有n/m个数字。如果对每个桶中的数字采用快速排序,那么整个算法的复杂度是O(n+m*n/m*log(n/m))=O(n+nlogn-nlogm)
从上式看出,当m接近n的时候,桶排序复杂度接近O(n)
当然,以上复杂度的计算是基于输入的n个数字是平均分布这个假设的。这个假设是很强的,实际应用中效果并没有这么好。如果所有的数字都落在同一个桶中,那就退化成一般的排序了。
#include <iostream> using namespace std; // MIT视频中划分方法 只从一边进行 int Partition(int r1[], int p, int q){ int i = p, j = p+1; int pivot = r1[p]; for(j = p+1; j <= q; j++){ if(r1[j] < pivot){ i++; swap(r1[i], r1[j]); } } swap(r1[i], r1[p]); return i; } void QuickSort(int r1[], int p, int q){ if(p < q){ int m = Partition(r1, p, q); //int m = randomPartition(r1, p, q); QuickSort(r1, p, m-1); QuickSort(r1, m+1, q); } } // 每个桶中的元素 为一个数组 并且记录了该桶中存放元素的数目 struct bucket{ int node[10]; // 应该为待排序数组的大小 这里简单的定义为了10 很局限 int count; }; void BucketSort(int r1[], int n){ // 桶的大小为10 bucket* pBucket = new bucket[10]; // 初始化桶 for(int i = 0; i < 10; i++){ (pBucket+i)->count = 0; } int maxNum = r1[0], minNum = r1[0]; for(int i = 1; i < n; i++){ if(r1[i] > maxNum) maxNum = r1[i]; if(r1[i] < minNum) minNum = r1[i]; } // 将数据放入桶中 for(int i = 0; i < n; i++) { int index = ((r1[i]-minNum)/(maxNum-minNum))*9; (pBucket+index)->node[(pBucket+index)->count] = r1[i]; (pBucket+index)->count++; } // 对每个桶进行快排 或者其它排序方法 int k = 0; for(int i = 0; i < 10; i++){ QuickSort((pBucket+i)->node, 0, (pBucket+i)->count-1); for(int j = 0; j < (pBucket+i)->count; j++){ r1[k++] = (pBucket+i)->node[j]; } } delete []pBucket; } void display(int a[], int n){ for(int i = 0; i < n; i++){ cout << a[i] << " "; } cout << endl; } int main(){ int a[10] ={6,5,4,7,2,4,1,8,5,10}; BucketSort(a, 10); display(a, 10); return 0; }
时间复杂度O(n),为不稳定排序算法
9:计数排序和基数排数
这两个排序算法都是线性时间排序算法,计数排序需假设输入数据为一个小区间内的整数,为小规模的数字。而基数排序一般用来在线性时间内处理大范围的数据。
计数排序的代码:
#include <iostream> using namespace std; void CountSort(int r1[], int c[], int n, int k, int r2[]){ for(int i = 0; i < k; i++) c[i] = 0; for(int i = 0; i < n; i++) c[r1[i]]++; // 统计每个元素出现的次数 for(int i = 1; i < k; i++) c[i] = c[i-1] + c[i]; // 统计小于等于该元素的个数 即该元素即将放在r2中的位置 //for(int i = n-1; i >= 0; i--){ // 为了保证该算法是稳定的 for(int i = 0; i < n; i++){ r2[c[r1[i]] - 1] = r1[i]; c[r1[i]] = c[r1[i]] - 1; } } int main(){ int a[10] = {2,3,1,4,5,4,2,3,0,3}, b[10]; int c[6]; cout << "排序前: "; for(int i = 0; i < 10; i++){ cout << a[i] <<" "; } cout << endl; CountSort(a, c, 10, 6, b); cout << "计数排序后: "; for(int i = 0; i < 10; i++){ cout << b[i] <<" "; } cout << endl; return 0; }
基数排序代码可能局限于实际的应用,先从个位排序,再到十位,最后到高位等,可以对每位采用桶排序。
它们的时间复杂度都为O(n)
总结:(1)冒泡排序、直接选择排序、插入排序、归并排序、快速排序、堆排序、希尔排序为基于比较的排序算法,其平均时间复杂度不会低于O(nlgn)。而计数和基数排序都为线性时间排序算法。桶排序比较特殊
(2) 冒泡排序、直接选择排序、插入排序的平均时间复杂度为O(n^2)。归并排序、快速排序、堆排序的平均时间复杂度为O(nlgn),为渐进最好的比较排序算法。桶排序、计数排序及基数排序为O(n)
(3)直接选择排序、快速排序、堆排序、希尔排序(跨跃步长交换)和桶排序都为不稳定的排序算法,他们都跨跃交换了。其它的都为稳定排序算法。
参考文献
1:http://blog.csdn.net/column/details/algorithm-easyword.html白话白话经典算法系列
2:http://blog.csdn.net/houapple/article/details/6480100桶排序