快速排序的时间复杂度为 O(nlogn),算是很快的一种排序方法。
先来看看快排的实现原理(按递增):
在数组中,随便找一个数,称其为基准数,后面的原理实现便围绕它展开。
简单来说,就是把基准数左边所有比它大的数放到它的右边,再把右边所有比它大的数放到它的左边(顺序可互换),然后利用了分治法。
这里采用对基准数挖坑再移植填补的方法---挖坑法:
首先挖出基准数(挖出可理解为在数组中它的位置没有存放数了),从基准数的左边往右开始遍历,找出第一个比它大的数x,挖出来填到基准数的位置,此时基准数可以移植到x的位置,基准数的位置就更新了,然后再重复上述步骤:挖 —> 找 —> 填 —> 移,不断重复,直至基准数的左边找不比它小的数(用while循环可搞定)。此时基准数移到了某个位置,再把右边所有比它大的数放到它的左边,道理和上述的一样,从右边往左开始遍历,挖 —> 找 —> 填 —> 移,再不断重复直至右边找不比它大的数。
这像是一种从左右两边分别先后向中间逼近,把基准数可以移的位置空间不断压缩直至满足左小右大的条件的方法,注意基准数最后放下的位置不一定是中间。
//---一般把在边界的值作为基准数,便于限定区间实现分治和指针移动
//---这里区间设为[n,m],把最右值a[m]作为基准数
---设key为基准数
while(n<m){//---n为从左往右的指针,m为从右往左的指针
while(n<m&&a[n]<=key){
++n;
}//---找(左)
a[m]=a[n];//---挖-填-把找到的数填到基准值的位置
//a[n]=-1;找到的数的位置成了新的坑,基准数的位置更新到此处为a[n]
while(n<m&&a[m]>=key){
--m;
}//---找(右)
a[n]=a[m];//---a[n]是上一步基准数所更新的位置,这里就把找到的数填到基准值的位置a[n]
//a[m]=-1;
}
//---当n>=m证明已经无数可找,满足左小右大
//---此时就可以把最新挖的坑填上被挖出来的基准数
a[n]=key;
return n;//---返回基准数位置供分治法判断是否满足左右只有各一个数
//---小步骤完成---
此时满足了基准数左边的数都比右边小,虽然但是,此时左右的数都是乱序的(坏情况下)。那么就需要 分治法 来使得基准数左后两边各剩一个数(这样便有序了,当然,每分一次都要重新找基准数,基准数取右边界值)。当左或右存在多个数时,开始分治。
void quickSort(int* a,int l,int r){//----递归方式
if(l<r){//---如果l>=r,说明这层的基准数已经没地方移动,即满足左一个数 右一个数。
int mid=sortFast(a,l,r);//---左右区间的分割线取决于上一层挖坑法移动完后的基准数位置
quickSort(a,l,mid-1);
quickSort(a,mid+1,r);
}
}
void quickSort2(int* a ,int l,int r){//----非递归方式,用到栈或者队列,原理一样
stack<int>s;
s.push(r);
s.push(l);
while(!s.empty()){//---非空时,存在移动条件
int begin=s.top();
s.pop();
int end=s.top();
s.pop();
int mid=sortFast(a,begin,end);//---挖坑法
if(begin<end){//---当左边界小于右边界,基准值可以移动时,继续下一步分治
if(begin<mid){
s.push(mid-1);
s.push(begin);
}
if(end>mid){
s.push(end);
s.push(mid+1);
}
}
}
}
对于基准数的选取,为尽量避免最坏情况的出现(如给出的是倒序的数组)使得时间复杂度变高,可以采用在头、中、尾三数取中间值的方法(就简单地判断一下):
int find(int *a,int begin,int end){//---头中尾-三数取中,避免最坏情况
int mid = (begin + end) / 2;
if (a[begin] < a[mid])
{
if (a[mid] < a[end])
return mid;
else if (a[begin]>a[end])
return begin;
else return end;
}
else
{
if (a[mid] > a[end])
return mid;
else if (a[begin] < a[end])
return begin;
else return end;
}
}
在挖坑法中调用三数取中法:
int sortFast(int *a,int n,int m){
int mid=find(a,n,m);//---三数取中法
swap(a[mid],a[m]);//---基准数始终取右边界,则需要和右边界交换一下
int key=a[m];//---key为基准数
while(n<m){
while(n<m&&a[n]<=key){
++n;
}
a[m]=a[n];
while(n<m&&a[m]>=key){
--m;
}
a[n]=a[m];
}
a[n]=key;
return n;
}
完整代码和程序执行过程数组每步的变化:( -1代表挖的坑 )
#include<iostream>
#include<stack>
#include<algorithm>
#include<cstring>
using namespace std;
#define len 6
int find(int *a,int begin,int end){//---头尾中-三数取中,避免最坏情况
int mid = (begin + end) / 2;
if (a[begin] < a[mid])
{
if (a[mid] < a[end])
return mid;
else if (a[begin]>a[end])
return begin;
else return end;
}
else
{
if (a[mid] > a[end])
return mid;
else if (a[begin] < a[end])
return begin;
else return end;
}
}
int sortFast(int *a,int n,int m){
int mid=find(a,n,m);
swap(a[mid],a[m]);
int key=a[m];
while(n<m){
while(n<m&&a[n]<=key){
++n;
}
a[m]=a[n];
while(n<m&&a[m]>=key){
--m;
}
a[n]=a[m];
}
a[n]=key;
return n;
}
void quickSort(int* a,int l,int r){//----递归
if(l<r){
int mid=sortFast(a,l,r);
quickSort(a,l,mid-1);
quickSort(a,mid+1,r);
}
}
void quickSort2(int* a ,int l,int r){//----非递归
stack<int>s;
s.push(r);
s.push(l);
while(!s.empty()){
int begin=s.top();
s.pop();
int end=s.top();
s.pop();
int mid=sortFast(a,begin,end);
if(begin<end){
if(begin<mid){
s.push(mid-1);
s.push(begin);
}
if(end>mid){
s.push(end);
s.push(mid+1);
}
}
}
}
int main(){
int a[6]={1, 578, 36, 51, 48, 5};
int b[6]={6,5,1,3,2,4};
char c[6]={'d','a','c','e','f','b'};
cout<<"---对a排序---\n待排数组:";for(int i=0;i<len;i++)cout<<a[i]<<' ';cout<<endl;
quickSort(a,0,len-1);//---len事先宏定义了为6
for(int i=0;i<len;i++)cout<<a[i]<<' ';cout<<endl;
return 0;
}
附上图的调试代码:
#include<iomanip>
int sortFast(int *a,int n,int m){
int mid=find(a,n,m);
swap(a[mid],a[m]);
int key=a[m];
cout<<"基准数: "<<key<<endl;
a[m]=-1;
cout<<"挖基准数: ";for(int i=0;i<len;i++)cout<<setw(3)<<a[i]<<' ';cout<<endl;
while(n<m){
while(n<m&&a[n]<=key){
++n;
}
a[m]=a[n];
// a[n]='_';
a[n]=-1;
cout<<"左边交换: ";for(int i=0;i<len;i++)cout<<setw(3)<<a[i]<<' ';cout<<endl;
while(n<m&&a[m]>=key){
--m;
}
a[n]=a[m];
// a[m]='_';
a[m]=-1;
cout<<"右边交换: ";for(int i=0;i<len;i++)cout<<setw(3)<<a[i]<<' ';cout<<endl;
}
a[n]=key;
cout<<" 补坑 : ";for(int i=0;i<len;i++)cout<<setw(3)<<a[i]<<' ';cout<<endl;
return n;
}
以上对代码的分析为个人的自学和理解,图解可看其它大佬的博客。(当然,代码不止一种写法)