分而治之:快数排序
从归并排序到快速排序:
- 归并排序:简化分解,侧重合并
- 快速排序:侧重分解,简化合并
快速排序的分解:
快速排序侧重分解过程,其分解的主要方法是数组划分。
数组划分:
基本思想:
- 任选元素x作为分界线,称为主元(pivot)
- 交换重排,满足x左侧元素小于右侧
实现方法:
- 选取固定位置主元x(一般是首元素或尾元素)
- 维护两个部分的右端点变量i,j
- 考察数组元素A,只和主元比较
若A[j] ≤ x,则交换 A[j] 和 A[i+1] ,i与j右移;
若A[j] > x,则j右移.
数组划分代码:
void splitArr(int *arr, int len){
int i = -1;
int x = arr[len-1]; //arr[len-1]为主元
int temp;
for(int j=0; j<len; j++;){
if(arr[j] <= x){
temp = arr[i+1];
arr[i+1] = arr[j];
arr[j] = temp;
i++;
}
else j++;
}
temp = arr[i+1];
arr[i+1] = arr[len-1];
arr[len-1] = temp; //最后把arr[len-1]放到位置i+1
}
固定主元的时间复杂度:
- 最好情况:O(nlogn)
- 最坏情况:O(n2)
如此看,快速排序并不优于归并排序。反思最差情况,数组划分时选取固定位置主元,可以针对性构造最差情况;所以,如果数组划分时选取随机位置的主元,就无法构造针对性最差情况了。
随机选取主元:
随机化数组划分代码:
#include <cstdlib> //rand()函数的头文件
#include <ctime> //time()函数的头文件
int rand(int l,int r){ //生成随机种子
srand(time(NULL));
return rand()%(r-l+1)+l;
}
void splitArr(int *arr, int len){
int i = -1;
int k = rand(0,len-1); //选取随机数
int x = arr[k]; //arr[k]为主元
int temp;
for(int j=0; j<len; j++){
if(arr[j] <= x){
temp = arr[i+1];
arr[i+1] = arr[j];
arr[j] = temp;
i++;
}
else j++;
}
temp = arr[i+1];
arr[i+1] = arr[k];
arr[k] = temp; //最后把arr[k]放到位置i+1
}
算法分析图:
随机化快速排序代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <ctime>
using namespace std;
const int N = 1001;
int a[N];
int rand(int l,int r)//生成随机种子
{
srand(time(NULL));
return rand()%(r-l+1)+l;
}
void Quick_sort(int left,int right){
if(left >= right) return;
//随机选取主元,对于原数组有序程度高的情况下,时间复杂度会退化成O(n^2) 这时采用随机化算法使得复杂度稳定在O(nlogn)
int k = rand(left,right);
int x = a[k];//主元
int i = left;
int j = right;
swap(a[left],a[k]);//将主元放到首位置
for(int j=left+1; j<=right; j++){
if(a[j] <= x){
swap(a[i+1],a[j]);
i++;
}
else j++;
}
/*
while(i!=j){
while(a[j]>=x && i<j)
j--;
while(a[i]<=x && i<j)
i++;
swap(a[i],a[j]);
}*/
swap(a[left],a[i]);//将主元交换到分界
Quick_sort(left,i-1);
Quick_sort(i+1,right);
}
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
Quick_sort(0,n-1);
for(int i=0;i<n;i++)
printf("%d ",a[i]);
return 0;
}