特点
选取一个中心或者基准,一般选择第一个。
然后将比基准大的元素搬到后面,比基准小的元素搬在后面,此时基准左边的元素(左子表)都小于基准,右边的元素(右子表)都大于基准。
然后将左子表继续按照上述步骤进行操作,选取新基准,小的在前,大的放后。右边的序列相同。
直到序列的长度为1时,结束操作。
整个过程是一个递归的思想,递归结束条件是子序列长度为1。
基本思想:
通过一趟排序,将排序记录分割成独立的两部分,其中一部分记录的比另一部分关键字小,则可对这两部分记录进行排序,以达到整个序列有序。
具体实现:
选取一个基准,所以元素与之比较,小的调在其左边,打的调到其右边。
实例:
元素从下标1开始存放,0号位置用来存放基准/界点,low和high是需要排序的区间
数据移动过程:
low指向的位置空着(选择第一个元素作为基准),并且前面需要放比49小的值,那么移动high,查看指向的值是否比49小,如果比49大,high–,否则就将high指向的元素搬到前面low指向的空闲位置,此时high指向位置空闲;
high指向的位置空闲,那么需要在前面找一个比基准49大的元素,low向后移动
此时,low位置空闲,需要找一个比49小的元素,搬过来放进去,移动high:
此时,high指向的位置空闲,需要找一个比49大的元素,放在high位置,low向后移动:
此时,low位置空闲,需要找一个较小的,比49小的元素放进去,移动high指针,发现找不到比49小的元素:
low == high时,该位置实际上就是基准49的最终位置。
最后,继续对子表A和子表B用相同的方法进行划分:
当76和27都放在了各自位置后,继续对76的左子序,右子序,和27的左子序,右子序继续进行上述操作。
总结:
每一趟子表的形成是采用从两头向中间交替式逼近法
前面空了从后面搬
后面空了从前面搬
直到low == high
代码:
void print(int *arr,int length){
for(int i =0;i<length;++i){
std::cout<<arr[i]<<" ";
}
std::cout<<std::endl;
}
//确定基准的最终的位置,基准位置确定后,相当于将原表划分成了左右两个子表
int Partition(int *arr,int low,int high){
arr[0] = arr[low];
int base = arr[0];
while (low < high)
{
while(low<high&&arr[high]>=base) --high;
arr[low] = arr[high];
while(low<high&&arr[low]<=base) ++low;
arr[high] = arr[low];
}
arr[low] = arr[0];
return low;
}
void QSort(int *arr,int low,int high){
int base;
if(low < high){
base = Partition(arr,low,high);
QSort(arr,low,base-1); //左子表进行排序
QSort(arr,base+1,high);//右子表进行排序
}
}
int main(){
int arr[] = {1,234,33,2,34,3,3,4,88,999,12345,0,1};
int length = sizeof(arr)/sizeof(arr[0]);
QSort(arr,1,length);
print(arr,length);
return 0;
}
效率分析
时间复杂度:
平均时间是 O(nlog2n)
QSort() O(log2n)
Partition() O(n)
空间复杂度:
辅助空间使用了系统栈,栈的长度取决于调用的深度,平均情况下需要O(logn)的栈空间,最坏需要O(n)
(有对递归快排进行优化的,使用用户空间的数据结构栈进行实现)
稳定性:
不稳定
适用场景:
数据越乱,排序越快
不适用于基本有序或者原本有序的场景。
本文中使用的图片来自于公开课程,十分感谢老师生动讲解,地址:快速排序