本文对各种排序做一个总结(这里不简述具体的过程),包括冒泡排序、选择排序、快速排序、归并排序、堆排序。分别从时间复杂度、空间复杂度、算法的最坏情况以及稳定性方面分析,递归的尽量会附带非递归实现。
1. 冒泡排序
冒泡排序是一种很简单的排序算法,这里直接上代码(从小到大排序):
bool flag = true;//如果在某一趟排序中数列已经有序,则结束排序过程
for(int i = 0;i < n-1 && flag; ++i){
flag = false;
for(int j = 0; j < n-i-1; ++j){//减一是为了实现g[j+1]
if(g[j] > g[j+1]){
swap(g[j], g[j+1]);
flag = true;
}
}
}
时间复杂度:时间复杂度是O(n^2),假设排序的数有n个,遍历一趟的复杂度是O(n),需要遍历n-1次,所以时间复杂度为O(n^2)。
空间复杂度:O(1),因为不需要额外的存储空间。
最坏情况:要排序的数是逆序排好序的。
稳定性:冒泡排序是稳定的算法,因为它实现相邻两个元素之间的交换,没有改变元素的相对位置,所以满足稳定性。
2. 选择排序
选择排序是一种比较好理解的排序,可以说是冒泡排序的兄弟了,只是排序方式不一样,下面直接给出代码:
//选择排序
void Select_Sort(int a[], int n){
int Min;
for(int i = 0;i < n-1; ++i){//n-1次选择就可以了
Min = i;
for(int j = i+1; j < n; ++j){//从i - n之间选择一个最小的放在 i 处。
if(a[j] < a[Min]){
Min = j;
}
}
if(Min != i){
swap(a[i], a[Min]);
}
}
}
时间复杂度:O(n^2),因为每次遍历的复杂度是O(n)的,一共遍历n-1次,所以复杂度是O(n^2)。
空间复杂度:O(1)
最坏情况:貌似排序的数和复杂度没关系,属于佛系。
稳定性:选择排序是不稳定的排序,例如有5 8 5 2 9 ,5个元素按从小到大排序,当第一趟排序后形成2 8 5 5 9 ,两个5的相对位置最终排好序后发生了变化。
3. 快速排序
快速排序(Quick Sort)使用分治法策略。它的基本思想是:选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分;其中一部分的所有数据都比另外一部分的所有数据都要小。然后,再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。快速排序经常容易被改造用在其它的一些方面,如求数组中第k大的数。快排的基准值的选取会对排序结果有一定影响。快速排序是面试中常考的类型,需要关注非递归的方式。
这里不对算法进行相应描述,直接看代码(其中基准值是随机选取的):
/*快速排序
* l ----- 序列左边下标
* t ----- 序列右边下标
* a[] --- 数组
*/
int Partition(int a[], int l, int t){//划分
int i = l;
int j = t;
int x = a[i];
while(i < j){
while(i < j && a[j] > x)
j--;
if(i < j)
a[i++] = a[j];
while(i < j && a[i] < x)
i++;
if(i < j)
a[j--] = a[i];
}
a[i] = x;
return i;
}
void Quick_Sort(int a[], int l, int t){
if(l < t){
int rm = l + rand()%(t - l + 1);//基准值是在随机选取的
swap(a[l], a[rm]);
int mid = Partition(a, l, t);
Quick_Sort(a, l, mid-1);//递归调用
Quick_Sort(a, mid+1, t);//递归调用
}
}
非递归代码:
typedef pair<int, int> Pair;
int Partition(int a[], int lt, int rt){//划分
int i = lt;
int j = rt;
int rm = i + rand()%(j - i + 1);//基准值是在随机选取的
swap(a[i], a[rm]);
int x = a[i];
while(i < j){
while(i < j && a[j] > x)
j--;
if(i < j)
a[i++] = a[j];
while(i < j && a[i] < x)
i++;
if(i < j)
a[j--] = a[i];
}
a[i] = x;
return i;
}
void Quick_Sort(int a[], int l, int t){
stack<Pair>S;
S.push(Pair(l, t));
while(!S.empty()){
int lt = S.top().first;
int rt = S.top().second;
S.pop();
int mid = Partition(a, lt, rt);
if(mid+1 < rt)
S.push(Pair(mid+1, rt));
if(lt < mid-1)
S.push(Pair(lt, mid-1));
}
}
时间复杂度:快速排序最坏时间复杂度是O(n^2),在(1)数组中的元素全部相同,(2)数组已经排好序或逆序(这要看选取的基准值,如果总是选取最左边的作为基准值则是O(n^2))这两类情况下快速排序会达到最坏的情况。从这里可以看出选取基准值很重要,如果基准值随机选取,快排很难达到最坏情况。平均时间复杂度是O(n*log2(n)),可以这样理解:快排是采用分治的策略,可以把这个过程看成一个二叉树,则遍历一层的时间复杂度是O(n),树的高度为log2(n+1),则复杂度为O(n*log2(n))。
空间复杂度:O(logn)~O(n),这里看的是递归的深度。
稳定性:快排是一种不稳定的算法,因为在选取基准值相互交换的时候,元素的相对位置会变化。
4. 归并排序
归并排序算法是典型的分治算法,其算法思想是先将数组对半分开,然后将得到的两个数组再对半分开,一直到只有一个元素为止,然后再进行两两合并,最后合并为一个大的数组,具体思路可看下图。
代码实现:
void Merge(int a[], int lt, int rt, int p[]){//p是临时数组
int mid = (rt - lt)/2 + lt;
int i = lt, j = mid + 1;
int k = 0;
while(i <= mid && j <= rt){
if(a[i] <= a[j]){
p[k++] = a[i++];
}else {
p[k++] = a[j++];
}
}
while(i <= mid){
p[k++] = a[i++];
}
while(j <= rt){
p[k++] = a[j++];
}
for(i = 0; i < k; ++i){
a[lt+i] = p[i];
}
}
void MergeSort(int a[], int lt, int rt, int p[]){
if(lt < rt){
int mid = (rt - lt)/2 + lt;
MergeSort(a, lt, mid, p);
MergeSort(a, mid+1, rt, p);
Merge(a, lt, rt, p);
}
}
时间复杂度:归并排序最好、最坏、平均时间复杂度都是O(nlogn),同样可以把归并排序的过程看成一个完全二叉树(这里要和快速排序区分),划分的时间复杂度为O(logn),每层合并的时间复杂度为O(n),所以总的时间复杂度为O(nlogn)。
最坏情况:复杂度和排序的数的顺序无关,佛系。
空间复杂度:O(n),用到一个n的辅助数组。
稳定性:归并排序是稳定的。
5. 堆排序
堆排序(Heap_Sort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。常用于求前1-k的所有数字。
堆排序的代码如下:
//向下调整
void Adjust_Heap(int a[], int idx, int Len){
while(idx*2+1 < Len){
int temp = idx*2+1;
if(temp+1 < Len && a[temp+1] > a[temp]){
temp++;
}
if(a[idx] < a[temp]){
swap(a[idx], a[temp]);
idx = temp;
}else break;
}
}
void Heap_Sort(int a[], int n){
//建立大顶堆
for(int i = (n-1)/2; i >= 0; --i){
Adjust_Heap(a, i, n);
}
//排序
for(int i = 0;i < n; ++i){
swap(a[0], a[n-i-1]);
Adjust_Heap(a, 0, n-i-1);
}
}
时间复杂度:建堆的复杂度是O(n)(这个需要计算一下),排序的复杂度是n * lgn 所以是O(nlog2(n))。
空间复杂度:O(1)
稳定性:堆排序是不稳定的。举个栗子:1 2 2 的情况就不满足(从小到大排序),因为排序后两个2的相对位置变了。
Reference :
https://blog.csdn.net/jiajing_guo/article/details/69388331