排序算法学习笔记
排序是最基本最常见的算法,你在编程的过程中会发现许多算法都是基于排序算法的转变。
C++实现
----------------------------------
排序算法的分类如下图所示,我们这里仅仅介绍除基数排序外的内部排序算法,最简单最常见的7种排序算法。
注*
排序算法的稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,则称这种排序算法是稳定的;否则称为不稳定的。
------------------------------------------------------
选择排序(Simple Selection Sort)
我认为最直观的排序方法。
基本思想
在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
排序实例
C++实现初始状态 [49 38 65 97 76 13 27 49]第一趟排序后 13 [49 65 97 76 38 27 49]第二趟排序后 13 27 [65 97 76 49 38 49]第三趟排序后 13 27 38 [97 76 49 65 49]第四趟排序后 13 27 38 49 [76 97 65 49]第五趟排序后 13 27 38 49 49 [97 65 76]第六趟排序后 13 27 38 49 49 65 [97 76]第七趟排序后 13 27 38 49 49 65 76 [97]
template <typename T> void sim_sel_sort(std::vector<T>& a) { for(int i=0; i<a.size()-1; ++i){ int min=i; //设a[i]是其后面最小的数 for(int j=i+1; j<a.size(); ++j){ //找出a[i]及其后面最小的数 if(a[j]<a[min]) min=j; } T tmp=a[i]; //交换a[i]和最小的数 a[i]=a[min]; a[min]=tmp; } }--------------------------------------------------------
冒泡排序(Bubble Sort)
这是C程课中教的第二种排序方法。
基本思想
对当前还未排好序的范围内的全部数,自上而下地对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。第一趟冒泡把最大数移到最上面,第二趟把第二大的数移到第二的位置……以此类推,直到没有任何一对数字需要比较。直观来看,气泡由大到小依次上浮。
排序实例
C++实现
template <typename T> void bubble_sort(std::vector<T>& a) { for(int i=a.size(); i>0; i--){ //需要排序的部分上限i for(int j=0; j<i-1; j++){ //需要排序的部分[0,i] if(a[j]>a[j+1]){ //后面比前面小,交换 T tmp=a[j]; a[j]=a[j+1]; a[j+1]=tmp; } } } }-----------------------------------------
插入排序(Straight Insertion Sort)
基本思想
在第p趟,将位置p上的元素向左移动至它前面的正确位置上,这样就保证了位置0至位置p的元素已经排好序了。当最后一趟进行完毕,所有的元素就排好序了。
排序实例
C++实现
template <typename T> void str_ins_sort(std::vector<T>& a) { for(int i=1; i<a.size(); i++){ //a[i]需要移到正确的位置去 int ins=i; T tmp=a[i]; for(int j=i; j>0; j--){ if(a[j-1]>tmp){ a[j]=a[j-1]; //比a[i]大的都后移一位 ins=j-1; //找出a[i]应该插入的位置 } else break; } a[ins]=tmp; //插入正确位置 } }---------------------------------
希尔排序(Shell Sort)
希尔排序是就像分组的直接插入排序,又称缩小增量排序。
基本思想
希尔排序需要一个增量序列d1, d2, …… dt,其中dt=1, d(k)>d(k+1)。所有距离为d1的倍数的记录放在同一个组中,先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1,即所有记录放在同一组中进行直接插入排序为止。dt=1保证能够实现排序正确。
排序实例
C++实现
template <typename T> void shell_sort(std::vector<T>& a) { for(int gap=a.size()/2; gap>0; gap/=2){ //增量序列 for(int i=gap; i<a.size(); i++){ //a[i]需要移到该组正确的位置去 int ins=i; T tmp=a[i]; for(int j=i; j>=gap; j-=gap){ //a[i]只与该组的数相比 if(a[j-gap]>tmp){ a[j]=a[j-gap]; //比a[i]大的都后移gap位 ins=j-gap; //找出a[i]应该插入的位置 } else break; } a[ins]=tmp; //插入正确位置 } } }
堆排序(Heap Sort)
堆排序的思想来自于二叉堆,以下讨论假设利用大顶堆。
基本思想
将待排序列造成一个大顶堆,此时整个序列的最大值就是堆顶的根结点,将它与堆数组末尾元素交换,然后将剩余的n-1个元素重新构造成一个大顶堆。再将新大顶堆的根结点和末尾元素交换,如此反复进行,就得到一个左小右大的有序序列。
排序实例
原序列
大顶堆
排序过程中
排序后
C++实现
//无序序列调整为一个大顶堆,a[0]~a[n-1]为堆,此处与二叉堆不同,二叉堆a[1]~a[n] template <typename T> void heap_adjust(std::vector<T>& a, int node, int n) { for(int child=2*node+1; child<n; child=2*child+1){ if(child!=n-1 && a[child]<a[child+1]) child++; if(a[node]<a[child]){ std::swap(a[node],a[child]); node=child; } else break; } } //堆排序 template <typename T> void heap_sort(std::vector<T>& a) { //无序序列调成大顶堆堆 for(int i=a.size()/2; i>=0; i--) heap_adjust(a, i, a.size()); //根结点与末尾元素交换,DeleteMax,并把剩下的元素重新调成大顶堆 for(int j=a.size()-1; j>0; j--){ std::swap(a[0],a[j]); //根结点与末尾元素交换,DeleteMax heap_adjust(a, 0, j); //把剩下的元素重新调成大顶堆 } }---------------------------------------
归并排序(Merge Sort)
归并排序是递归思想的范例。
基本思想
假设初始序列有n个元素,可以看成n个有序的子序列,每个子序列长度为1,然后两两合并,得到[n/2]个长度为2(或1)的有序子序列;再两两归并,……,如此重复,直至得到一个长度为n的有序序列位置。
排序实例
C++实现
//归并 template <typename T> void merge(std::vector<T>& a, std::vector<T>& tmpArray, int LeftBegin, int RightBegin, int RightEnd) { int LeftEnd=RightBegin-1; int LeftPos=LeftBegin; int RightPos=RightBegin; int tmpPos=LeftBegin; //把a中元素有序地复制到tmpArray while(LeftPos<=LeftEnd && RightPos<=RightEnd){ if(a[LeftPos]<a[RightPos]) tmpArray[tmpPos++]=a[LeftPos++]; else tmpArray[tmpPos++]=a[RightPos++]; } while(LeftPos<=LeftEnd) tmpArray[tmpPos++]=a[LeftPos++]; while(RightPos<=RightEnd) tmpArray[tmpPos++]=a[RightPos++]; //把tmpArray复制到a中对应位置 int n=RightEnd-LeftBegin+1; for(int i=0; i<n; i++) a[LeftBegin+i]=tmpArray[LeftBegin+i]; } //含有4个参数的归并排序函数 template <typename T> void merge_sort_tmp(std::vector<T>& a, std::vector<T>& tmpArray, int begin, int end) { if(begin<end){ int center=(begin+end)/2; merge_sort_tmp(a, tmpArray, begin, center); //对左半边进行递归 merge_sort_tmp(a, tmpArray, center+1, end); //对右半边进行递归 merge(a, tmpArray, begin, center+1, end); //将左半边和右半边使用merge函数归并 } } //只含有1个参数的归并排序 template <typename T> void merge_sort(std::vector<T>& a) { std::vector<T> tmpArray(a.size()); merge_sort_tmp(a, tmpArray, 0, a.size()-1); }-----------------------------------------
快速排序(Quick Sort)
快速排序是在实践中最快的已知排序算法。
基本思想
取待排序列中的一个元素p,称为枢纽元pivot,将待排序列分为2个部分,一部分所有元素均≤p,另一部分所有元素均≥p。对分好的两部分继续进行上述操作,递归操作,至两部分元素数目很少(约为10),对齐使用插入排序。这样整个整个序列就排好序了。
排序实例
C++实现
//从3个数中找中值,并把min, mid, max放到a[left],a[right-1],a[right]上 template <typename T> const T& median3(std::vector<T>& a, int left, int right) { //3个数,比较一遍,大者往右,小者往左,结果为min, mid, max int center=(left+right)/2; if(a[center]<a[left]) std::swap(a[center],a[left]); if(a[right]<a[left]) std::swap(a[right],a[left]); if(a[right]<a[center]) std::swap(a[right],a[center]); //把枢纽元放在right-1的位置上 std::swap(a[center],a[right-1]); return a[right-1]; } //含有3个参数的快速排序 template <typename T> void quick_sort_tmp(std::vector<T>& a, int left, int right) { if(right-left>=10){ T pivot=median3(a, left, right); //计算后枢纽元放在a[right-1] int i=left, j=right-1; while(i<j){ while(a[i]<pivot) //i左移,移过比pivot小的a[i] i++; while(a[j]>pivot) //j左移,移过比pivot大的a[j] j++; //当i和j都停止时,i指向一个比pivot大的元素,j指向一个比pivot小的元素 if(i<j) //如果i<j,a[i]和a[j]交换 std::swap(a[i], a[j]); else //如果i和j已经交错,跳出循环 break; } std::swap(a[i], a[right-1]); //把枢纽元a[right-1]放到正确的位置i处 quick_sort_tmp(a, left, i-1); quick_sort_tmp(a, i+1, right); } else//对于小数组(N=10左右),插入排序比快速排序好些 str_ins_sort(a); } //含有1个参数的快速排序 template <typename T> void quick_sort(std::vector<T>& a) { quick_sort_tmp(a, 0, a.size()-1); }