排序算法的分类如下图所示,我们这里仅仅介绍除基数排序外的内部排序算法,最简单最常见的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);
}