可以从此进入实时观察排序的执行过程:入口
先建立swap函数,用于两个数之间的交换。
void swap(int &a,int &b)
{
int temp=a;
a=b;
b=temp;
}
1. 冒泡排序
算法思想:
从后往前(或者从前往后)两两比较相邻元素的值,若为逆序则交换,直到序列比较完。就是把小的元素往前调或者把大的元素往后调。共进行n-1次冒泡。
稳定性:
由于序列的相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
最好时间复杂度为:O(n) //原本就有序
平均(最坏)时间复杂度为:O(n2) //原本为逆序
空间复杂度为:O(1)
代码:
//冒泡排序
void BubbleSort(int A[],int n){
for(int i=0;i<n-1;i++){ //共进行n-1次冒泡
bool flag=false; //用于表示本次冒泡是否发生交换
for(int j=n-1;j>i;j--) //进行一次冒泡
if(A[i-1]>A[i]){ //如果为逆序
swap(A[j-1],A[j]) //交换
flag=true;
}
if(flag=false)
return; //若此次没有发生数据交换,则已经有序
}
}
2. 选择排序
算法思想:
每一趟在待排序的元素中选取关键字最小(或最大)的元素加入到有序子序列中。需进行n-1趟处理。
稳定性:
在一趟选择,如果一个元素比当前元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中两个5的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法。
时间复杂度为:O(n2)
空间复杂度为:O(1)
代码:
//简单选择排序
void SelectSort(int A[],int n){
for(int i=0;i<n-1;i++){ //一共进行n-1趟
int min=i; //记录最小元素
for(int j=i+1;j<=n-1;j++){ //在一轮从A[i]至A[n-1]中选择最小元素
if(A[j]<A[min]) min=j; //更新最小元素位置
if(min!=i) swap(A[min],A[i]); //与当时最小元素交换
}
}
}
3. 插入排序
算法思想:
每次按序把一个待排序的的记录按其关键字的大小插入到前面已经有序的子序列中,直到全部记录已经插入完成。
稳定性:
由temp<A[j];可知,若两元素相等时不需交换位置,该算法为稳定的排序算法。
时间复杂度为:O(n2)
空间复杂度为:O(1)
//直接插入排序
void InsertSort(int A[],int n){
for(int i=1;i<n;i++) //将后面的元素插入已经排好的序列
if(A[i]<A[i-1]){ //如果元素A[i]小于前一个元素
temp=A[i]; //用temp暂存A[i]
for(int j=i-1;j>=0&&temp<A[j];j--) //检查前面已经排好序的元素,直到找到要插入的位置(即A[j]<temp 时位置j+1)
A[j+1]=A[j]; //将所有大于temp的元素依次后移
A[j+1]=temp; //插入元素temp到指定位置
}
}
4. 希尔排序
算法思想:
是直接插入排序的一种改进,又称“缩小增量排序”,先将待排序表分割成如L[i,i+d,i+2d,…,i+kd]的子表,对各个子表分别进行直接插入排序,然后依次缩小增量d,重复上述过程,直到d=1为止。
稳定性:
由于一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
时间复杂度为: O(n1.3)~O(n2)
空间复杂度为: O(1)
//希尔排序
void ShellSort(int A[],int n){
for(int d=n/2;d>=1;d=d/2) //设置每次排序的步长
for(int i=d+1;i<=n;i++){
A[0]=A[i]; //A[0]为暂存单元,不能当做哨兵,因为不一定经过它,
for(int j=i-d;j>0&&A[0]<A[j];j-=d) //循环找到指定位置
A[j+d]=A[j]; //将大于A[0]的元素依次后移
A[j+d]=A[0]; //执行插入操作
}
}
5. 快速排序
算法思想:
首先设定一个分界值,通过该分界值将数组分成左右两部分。使得左边部分中各元素都小于分界值,而右边部分中各元素都大于或等于分界值。然后递归对左右两个子表进行如此划分,直到左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
稳定性:
由于是从右边向左查找直到找到比枢轴小的元素,从左边向右查找直到找到比枢轴大的元素,交换时会改变相同元素的前后顺序,所以快速排序是不稳定的排序算法。
平均(最好)时间复杂度为: O(nlog2n)//每次划分较为平均
最坏时间复杂度为: O(n2) //原本有序
最好空间复杂度为: O(log2n) //每一次都平分数组的情况
最坏空间复杂度为: O(n) //退化为冒泡排序的情况
//快速排序
int Partition(int A[],int low,int high){
int pivot=A[low]; //选择第一个元素作为枢轴
while(low<high){ //用low和high确定最后枢轴的位置
while(low<high&&A[high]>=pivot) high--; //从右边向左查找直到找到比枢轴小的元素
A[low]=A[high]; //将比枢轴小的元素插到左边
while(low>high&&A[low]<=pivot) low++; //从左边向右查找直到找到比枢轴大的元素
A[high]=A[low]; //将比枢轴大的元素插到右边
}
A[low]=pivot; //将枢轴插到最终的位置
return low; //返回枢轴的位置
}
void QuickSort(int A[],int low,int high){
if(low<high){ //递归跳出的条件
int pivotpos=Partition(A,low,high); //进行依次快排划分
QuickSort(A,low,pivotpos-1); //划分左子表
QuickSort(A,pivotpos+1,high); //划分右子表
}
}
6. 堆排序
堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。小于时为小根堆,大于时为大根堆。
算法思想:,
以大根堆为例,首先建立大根堆:把所有非终端结点都检查一遍,若不满足大根堆的要求,则进行调整,将当前结点与更大的一个孩子互换,如互换破坏了下一级的堆,则采用相同的方法继续往下调整。
接下来基于大根堆进行排序:每一趟将堆顶元素加入有序的子序列(即与待排序序列中的最后一个元素交换),并将待排序元素序列再次调整为大根堆。直到所有元素有序。
稳定性:
由于堆排序中记录的比较和交换都是跳跃进行的,因此堆排序也是一种不稳定的排序算法。
时间复杂度为: O(nlog2n) //建立过程O(n)+排序过程O(nlog2n)
空间复杂度为: O(1)
//堆排序
//堆的建立
void BuildMaxHeap(int A[],int len){
for(int i=len/2;i>0;i--) //从后往前调整所有非终端结点
HeadAdjust(A,i,len);
}
void HeadAdjust(int A[],int k,int len){
A[0]=A[k]; //A[i]暂存子树的根节点
for(int i=k*2,i<=len,i=i*2){ //从沿着key较大的子结点向下筛选
if(i<len&&A[i]<A[i+1])
i++; //取key较大的结点的下标
if(A[i]<=A[0]) break; //若大于孩子结点,筛选结束
else{
A[k]=A[i]; //将A[i]调整到双亲结点上面
k=i; //修改k的值到移动的结点上面,方便下一次筛选
}
}
A[k]=A[0]; //将被筛选的值放入最终的位置,完成一轮筛选
}
//堆的排序
void HeadSort(int A[],int len){
BuildMaxHeap(A,len); //建立堆
for(int i=len;i>1;i--){ //n-1趟数据交换和堆的建立
swap(A[1],A[i]); //将堆顶元素和堆底元素交换
HeadAdjust(A,1,i-1); //把剩余的元素整理成堆
}
}
7. 归并排序
算法思想:
采用分治法,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。又是将两个有序表合并成一个有序表,称为二路归并。
稳定性:
由if(B[i]<=B[j]) A[k]=B[i++];可知两元素相等时不需交换位置,该算法为稳定的排序算法。
时间复杂度为: O(nlog2n) //跟二叉树的高度有关
空间复杂度为: O(n) //递归
//归并排序
int *B=new int[n]; //使用辅助数组临时存储数组元素
//将已经有序的两部分A[low]...A[mid]和A[mid+1]...A[high]归并为一个有序序列。
void Merge(int A[],int low,int mid,int high){
int i,j,k;
for(int key=low;k<=high;k++)
B[k]=A[k]; //将A中的元素复制到B中临时存储。
for(int i=low;j=mid+1;i<=mid;j=mid+1;j<=high;k++){ //依次从两个有序序列中挑选较小的元素放入A中。
if(B[i]<=B[j]) A[k]=B[i++];
else A[k]=B[j++]; //将较小的值放入数组A中。
}
while(i<=mid) A[k++]=B[i++]; //剩下多余的元素直接存入。
while(j<=high) A[k++]=B[j++];
}
void MergeSort(int A[],int low,int high){
if(low<high){ //递归跳出的条件
int mid=(low+high)/2; //从中间划分
MergeSort(A,low,mid); //对左半部分进行归并排序
MergeSort(A,mid+1,high); //对右半部分进行归并排序
Merge(A,low,mid,high); //进行归并
}
}