最近在面试过程中,发现很多公司还是很注重基础的,对于基本的排序要求掌握的比较扎实,能够任意写出各种排序,对各种排序性能进行比较,那就随手总结一下吧。
排序分类一览
类型一 交换排序
冒泡排序
特点:稳定,每一次都会有一个数字到最终的位置
void swap(int &a,int &b)
{
int c = a;
a = b;
b = c;
}
void bubbleSort(int a[],int num)
{
for(int i=0;i<num-1;i++)
for(int j=0;j<num-i-1;j++)
if(a[j]>a[j+1])swap(a[j],a[j+1]);
}
快速排序
特点:不稳定,每次中间的数字会有序,如果数据量太大的话,可能会爆栈。
int qivotPosition(int a[],int low,int high)
{
int temp = a[low];
while(low<high)
{
while(low<high&&a[high]>=temp)high--;
a[low] = a[high];
while(low<high&&a[low]<=temp)low++;
a[high] = a[low];
}
a[low] = temp;
return low;
}
void quickSort(int a[],int low,int high)
{
if(low<high)
{
int qivot = qivotPosition(a,low,high);
quickSort(a,low,qivot-1);
quickSort(a,qivot+1,high);
}
}
比较
排序方法 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|
冒泡排序 | O(n2) | O(1) | 是 |
快速排序 | O(nlogn) | O(logn) | 否 |
类型二 插入排序
直接插入排序
特点:稳定,前面的一部分总是有序的,数据量比较小的时候适用。
void directInsert(int a[],int num)
{
int temp;
for(int i=0; i<num-1; i++)
{
if(a[i]>a[i+1])
{
int j;
temp = a[i+1];
for(j=i; temp<a[j]&&j>=0; j-- )
a[j+1] = a[j];
a[j+1] = temp;
}
}
}
希尔排序
特点:不稳定,跨步长有序
void shellSort(int a[],int num)
{
int temp;
for(int dk=num/2; dk>=1; dk/=2)
for(int i=dk; i<=num; i++)
{
if(a[i]<a[i-dk])
{
temp = a[i];
int j;
for(j=i-dk;temp<a[j]&&j>=0;j-=dk)
a[j+dk] = a[j];
a[j+dk] = temp;
}
}
}
比较
排序方法 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|
直接插入排序 | O(n2) | O(1) | 是 |
希尔排序 | O(n1.3) | O(1) | 否 |
类型三 选择排序
简单选择排序
特点:不稳定,每次都能有一个有序
void selectSort(int a[],int num)
{
for(int i=0;i<num-1;i++)
{
int min = i;
for(int j=i+1;j<num;j++)
if(a[j]<a[min])min = j; //记录最小值
if(min!=i)
swap(a[i],a[min]);
}
}
堆排
特点:堆排分为大顶堆和小顶堆,本例是以大顶堆为例,每次调整以后都可以找到最大值。最为重要的是堆排在最坏的情况下也可以保证O(nlogn)的时间复杂度,且空间复杂度为O(1)。数据量较大的时候比较适合选择。
Note
本例中的数据有特殊性 a[1]-a[num] 其中a[0]为临时存储容器
步骤如下
1.首先将序列构造成大根堆(位于根节点的一定是当前序列的最大值)
2.取出当前大顶堆的根节点,将其与序列末尾元素进行交换
3.对交换后的n-1个序列元素进行调整,使其满足大顶堆的性质
4.重复2.3步骤,直至堆中只有1个元素为止
void adjustDown(int a[],int k,int num);
void buildMaxHeap(int a[],int num)
{
for(int i=num/2;i>0;i--)
adjustDown(a,i,num);
}
void adjustDown(int a[],int k,int num)
{
a[0] = a[k];
for(int i=2*k;i<=num;i*=2)
{
if(i<num&&a[i]<a[i+1])
i++;
if(a[0]>=a[i])break;
else
{
a[k] = a[i];
k = i;
}
}
a[k] = a[0];
}
void heapSort(int a[],int num)
{
buildMaxHeap(a,num);
for(int i=num;i>1;i--)
{
swap(a[i],a[1]);
adjustDown(a,1,i-1);
}
}
比较
排序方法 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|
简答选择排序 | O(n2) | O(1) | 否 |
堆排序 | O(nlogn) | O(1) | 否 |
类型四 归并排序
归并排序
特点:稳定,唯一速度快的排序中稳定的排序算法,但付出了空间的代价。局部有序,进而全局有序。
int b[100];
void merge(int a[],int low,int mid,int high)
{
int i,j,k;
for(k=low;k<=high;k++)
{
b[k] = a[k];
}
for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++)
{
if(b[i]<=b[j])
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<high)
{
int mid = (low+high)/2;
mergeSort(a,low,mid);
mergeSort(a,mid+1,high);
merge(a,low,mid,high);
}
}
比较
排序方法 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|
归并排序 | O(nlogn) | O(n) | 是 |
类型五 基数排序
基数排序
步骤如下:
1.分配:将L[i]中的元素取出,首先确定个位上的数字,根据该数字分配到与之序号相同的桶中。
2.收集:当序列中所有的元素都分配到对应的桶中,再按照顺序依次将桶中的元素收集成新的一个待排序的序列L[];
3.对新形成的序列L[]重复执行分配和收集工作,对元素中的十位、百位…直到分配完该序列中的最高位,则排序结束。总体应用了一种“桶排”的思想。
比较
排序方法 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|
基数排序 | O(d(n+r)) | O® | 是 |
代码实现
上述排序的完整的可执行版本:
#include <iostream>
using namespace std;
//交换排序
//冒泡 稳定 每一次都会有一个数字到最终的位置
void swap(int &a,int &b)
{
int c = a;
a = b;
b = c;
}
void bubbleSort(int a[],int num)
{
for(int i=0;i<num-1;i++)
for(int j=0;j<num-i-1;j++)
if(a[j]>a[j+1])swap(a[j],a[j+1]);
}
//快排 不稳定 每次中间的数字会有序
//数据量中等时选用 数量太大可能会爆栈
int qivotPosition(int a[],int low,int high)
{
int temp = a[low];
while(low<high)
{
while(low<high&&a[high]>=temp)high--;
a[low] = a[high];
while(low<high&&a[low]<=temp)low++;
a[high] = a[low];
}
a[low] = temp;
return low;
}
void quickSort(int a[],int low,int high)
{
if(low<high)
{
int qivot = qivotPosition(a,low,high);
quickSort(a,low,qivot-1);
quickSort(a,qivot+1,high);
}
}
//插入排序
//直接插入 稳定 前面的一部分总是有序的
//数据量比较小的时候适用
void directInsert(int a[],int num)
{
int temp;
for(int i=0; i<num-1; i++)
{
if(a[i]>a[i+1])
{
int j;
temp = a[i+1];
for(j=i; temp<a[j]&&j>=0; j-- )
a[j+1] = a[j];
a[j+1] = temp;
}
}
}
//希尔排序 不稳定 跨步长有序
//步长的起始值为num/2
void shellSort(int a[],int num)
{
int temp;
for(int dk=num/2; dk>=1; dk/=2)
for(int i=dk; i<=num; i++)
{
if(a[i]<a[i-dk])
{
temp = a[i];
int j;
for(j=i-dk;temp<a[j]&&j>=0;j-=dk)
a[j+dk] = a[j];
a[j+dk] = temp;
}
}
}
//选择排序 不稳定
//简单选择排序 每次都能有一个有序
void selectSort(int a[],int num)
{
for(int i=0;i<num-1;i++)
{
int min = i;
for(int j=i+1;j<num;j++)
if(a[j]<a[min])min = j; //记录最小值
if(min!=i)
swap(a[i],a[min]);
}
}
//堆排 因为最后的交换 所以也是不稳定排序
//分为大顶堆和小顶堆 本例以大顶堆为例 每次调整之后都可以找到最大值
//本例中的数据有特殊性 a[1]-a[num] 其中a[0]为临时存储容器 所以数组声明需要a[num+1]
void adjustDown(int a[],int k,int num);
void buildMaxHeap(int a[],int num)
{
for(int i=num/2;i>0;i--)
adjustDown(a,i,num);
}
void adjustDown(int a[],int k,int num)
{
a[0] = a[k];
for(int i=2*k;i<=num;i*=2)
{
if(i<num&&a[i]<a[i+1])
i++;
if(a[0]>=a[i])break;
else
{
a[k] = a[i];
k = i;
}
}
a[k] = a[0];
}
void heapSort(int a[],int num)
{
buildMaxHeap(a,num);
for(int i=num;i>1;i--)
{
swap(a[i],a[1]);
adjustDown(a,1,i-1);
}
}
//归并排序 稳定排序 局部有序,进而全局有序
int b[100];
void merge(int a[],int low,int mid,int high)
{
int i,j,k;
for(k=low;k<=high;k++)
{
b[k] = a[k];
}
for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++)
{
if(b[i]<=b[j])
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<high)
{
int mid = (low+high)/2;
mergeSort(a,low,mid);
mergeSort(a,mid+1,high);
merge(a,low,mid,high);
}
}
int main()
{
int num;
int a[100];
/*
//堆排的输入输出
while(cin>>num)
{
for(int i=1;i<=num;i++)
cin>>a[i];
heapSort(a,num);
for(int i=1;i<=num;i++)
cout<<a[i]<<" ";
cout<<endl;
}*/
while(cin>>num)
{
//cin>>num; //一个数组的长度
for(int i=0;i<num;i++)
{
cin>>a[i];
}
//bubbleSort(a,num);
//quickSort(a,0,num-1);
//directInsert(a,num);
//shellSort(a,num);
//selectSort(a,num);
mergeSort(a,0,num-1);
for(int i=0;i<num;i++)
cout<<a[i]<<" ";
cout<<endl;
}
return 0;
}
性能对比
其中有几个算法比较特殊,归并排序是唯一一个效率较高,却稳定的算法,因为它付出了空间的代价;堆排序与归并的最好、最差时间复杂度都可以是O(nlogn),且堆排的空间复杂度仅为O(1)。