八大排序的实现
一:插入排序
插入排序是一种简单直观的排序方法,其基本思想在于每次将一个待排序记录,按其关键字大小插入到前面已经排序好的子序列中,直至全部记录插入完成。
由此引申三种重要排序:直接插入排序,折半插入排序,希尔排序
1:直接插入排序
1)查找A[i]在A[1...i-1]中的位置k
2)A[k...i-1]中所有元素后移一位
3)将A[i]复制到A[k]
void InsertSort(int A[],int n){int i,j;for(i=2;i<=n;i++){ //依次将A[2...n]插入到前面已经排好的序列 if(A[i].key
空间复杂度:O(1)
时间复杂度:n-1趟插入插入,每趟操作都分为比较关键词和移动元素,而比较关键词和移动元素取决于待排序表的初始状态。最好情况——表中元素有序,此时只需比较,无需移动,时间复杂度为O(n),而平均时间复杂度O(n^2)
稳定性:稳定
适用性:适用于顺序存储和链式存储的线性表(PS:大部分排序算法都仅适用于顺序存储的线性表)
2:折半插入排序
对直接插入排序做适当改进即可,在查找有序子表时进行折半查找即可
void InsertSort(int A[],int n){int i,j,low,mid,high;for(i=2;i<=n;i++){ //依次将A[2...n]插入到前面已经排好的序列 A[0]=A[i] ;//将A[i]缓存在A[0];low=i,high=i-1; while(low<=high){ mid=(low+high)/2; //取中间点 if(A[mid].key>A[0].key) low=mid-1; //查左子表 else high=mid+1;//查右子表 }//(low>high跳出)for(j=i-1;j>=high+1;j--)A[j+1]=A[j];A[high+1]=A[0]; //插入操作 }
}
显然,折半插入仅仅减少了比较的次数,约为O(nlogn),比较次数与待排序表的初始状态无关,仅取决于表中元素个数n。
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
3:希尔排序
上面讲解可以看出,直接插入排序算法适用于基本有序的排序表和数量不大的排序表。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
1)插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。
2)但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位
基本思想:
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2
对顺序表做希尔排序,与直接插入排序的相比较:
1)前后记录位置的增量是dk,不是1
void BubbleSort(int A[],int n){//升序为例for(int i=0;i=0;j--){ if(A[j-1].key>A[j].key)swap(A[j-1],A[j]) ; //逆序则交换flag=true; }if(flag==false) return ; //本趟未交换,则表已经有序,return}
}
时间复杂度:O(1)
空间复杂度:最好O(n)(表有序),平均O(n^2)
稳定性:显然是稳定的
2:快速排序
快排是对冒泡排序的改进,思想是基于分治的:
1)在待排表A[1...n]中取一个元素pivot为基准,
2)待排表元素小于pivot放A[1..K-1], 待排表元素大于等于pivotA[k+1..n],pivot元素放在最终位
这个过程称为一趟快排,然后对两子表递归的进行上述过程,直到每部分只有一个元素或为空为止
#include //不稳定排序
#include
using namespace std;
const int maxn=100;
void Quicksort(int *a,int left,int right){if(left>right) return ;int i=left,j=right;int temp=a[left];//主元 while(i!=j){ while(i=temp) j--;while(i=pivot) high--;A[low] =A[high];while(lowmaxn) n=maxn;printf("Please input the number of numbers you want to sort:\n");while(~scanf("%d",&n)){for(int i=0;i
测试用例截图:

分析:
时间复杂度:快排的运行时间与划分是否对称有关,最坏情况是初始排列表基本有序或者基本逆序,此时时间复杂度为O(n^2),平均时间复杂度O(nlogn)
空间复杂度:快排是递归的,需要借助一个递归栈来保存每一层递归调用必要信息,其容量与递归深度一致,最坏情况进行n-1次递归调用,栈深度为O(n),平均栈深度O(logn).因此空间复杂度最坏O(n),平均O(logn)
稳定性:不稳定。例如A={3,2,2},一趟快排后 A={3,2,2},最终排序序列也为A={2,2,3}
注意:在快速排序中,每一趟并不产生有序子列,但每一趟后将一个元素(基准元素)放到最终位置。
三:选择排序
1:简单选择排序
基本思想:每一趟(例如第i趟)在后面n-i+1个待排序元素中选取最小的关键字元素,最为有序子列的第i个元素,n-1趟可排序完成。
//简单选择排序
void SelectSort(int A[],int n){for(int i=0;i
时间复杂度:元素间的比较次数与序列初始状态无关,始终为n(n-1)/2,所以时间复杂度为O(n^2)
空间复杂度:O(1)
稳定性:不稳定,例如A={2,2,1},最终排序结果为A={1,2,2},含有相同关键字的元素相对位置发生变化。
2:堆排序(划重点)
/*
* 堆排序是一种复杂度为Nlog(N)的排序算法。对应二叉堆。
* 二叉堆是一颗完全二叉树,一般可以直接用数组实现。它的特点:
* 1. 父节点的键值总是大于等于(或小于等于)任何一个子节点的值。
* 2. 每一个节点的左右子树都是一个二叉堆。
* 当父节点的值都大于等于子节点的值,这样的二叉堆叫做大顶堆。
* 当父节点的值都小于等于子节点的值,叫做小顶堆
*/
以大根堆为例:
#include
#include
using namespace std;
const int maxn=100;void AdjustDown(int A[],int k,int len){//函数AdjustDown()将元素k向下进行调整A[0]=A[k]; //A[0]暂存for(int i=2*k;i<=len;i*=2){if(iA[i]) break; //筛选结束else{A[k]=A[i]; //将A[i]调整到双亲结点 k=i; //修改k值,继续向下调整 } }//forA[k]=A[0]; //被筛选结点放到最终位置
}void BuildMaxhead(int A[],int len){for(int i=len/2;i>0;i--) //从i=A[n/2]~1,反复调整AdjustDown(A,i,len);
} void HeadSort(int A[],int len){BuildMaxhead(A,len); //初始建堆for(int i=len;i>1;i--){ //n-1趟交换和建堆过程swap(A[i],A[1]); AdjustDown(A,1,i-1);//把剩余i-1个元素整理成堆}
} int main(){int n;int a[maxn];if(n>maxn) n=maxn;printf("Please input the number of numbers you want to sort:\n");while(~scanf("%d",&n)){for(int i=1;i<=n;i++)scanf("%d",&a[i]);HeadSort(a,n);printf("sorted result:\n");for(int i=1;i<=n;i++)printf("%d ",a[i]);}return 0;
}
测试用例截图:
分析:
时间复杂度:建堆时间O(n),之后有n-1次向下调整,向下调整复杂度O(h),最好、最坏、平均时间复杂度都是O(nlogn)
空间复杂度:O(1)
稳定性:不稳定
注意:每一趟都能将一个元素放在最终位置
四:归并排序和基数排序
1:归并排序
核心思想:通过先递归的分解数列,再合并数列就完成了归并排序。
假定排序表有n个记录,则可看作是n个有序的子表,每个字表长度为一,然后两两归并,得到[n/2](向上取整)个长度为2或为1的有序表;两两归并.....如此重复,直至合并为一个长度为n的有序表,这种排序为二路归并。
例如:
初始关键字 49 38 65 97 76 13 27
一趟后: 38 49 65 97 13 76 27
两趟后: 38 49 65 97 13 27 76
三趟后: 13 27 38 49 65 76 97
以二路归并为例:
//归并排序
//通过先递归的分解数列,再合并数列就完成了归并排序。
#include
#include
using namespace std;
int n;
int *B=(int *)malloc((n+1)*sizeof(int));// 辅助数组
void Merge(int A[],int low,int mid,int high){for(int i=low;i<=high;i++){B[i]=A[i];}int i,j,k;for( i=low,j=mid+1,k=i;i<=mid && j<=high;k++){if(B[i]<=B[j]) //比较 B 的左右两段 ,稳定排序 ,谁小放谁 A[k]=B[i++];else A[k]=B[j++];}//跳出时左半段走完或者右半段走完 while(i<=mid) A[k++]=B[i++];while(j<=high) A[k++]=B[j++];
}void MergeSort(int A[],int low,int high){if(low>n;int A[n]; for(int i=0;i>A[i];MergeSort(A,0,n-1);for(int i=0;i
测试用例截图:

分析:
时间复杂度:每一趟归并时间复杂度O(n),共进行logn(以2为底,向上取整)趟。因此时间复杂度O(nlogn)
空间复杂度:O(n)
稳定性:Merge()不改变相同关键字记录的相对次序,因此是稳定的
注意:一般而言,对与N个元素进行K-路归并排序时,排序趟数m满足K^m=N,从而m=logk N(m为整书,结果向上取整)
2:基数排序
采用多关键字排序(基于关键字各位的大小进行排序),借助“分配”和“收集”两种操作对关键字进行排序
代码后续给出!!!
时间复杂度:O(d(n+r)),与序列初始状态无关
空间复杂度:一趟排序需要存储空间r(r个队列),以后重复使用,所以空间复杂度O(r)
复杂度总结:

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
