快速排序的思想近似于BST里根节点与左右节点的关系。
大致思想是选择一个轴值,然后把数组里比轴值小的放在左边,比轴值大的放在右边,通过递归处理两边的数组达到总体排序的效果。
主框架如下:
template<class E>
void qsort(E A[], int i, int j) {
if (j <= i) return; //不处理数组大小0和1的情况
int pivotindex = findpivot(A, i, j); //找一个轴值
swap(A, pivotindex, j); //把轴值放到数组末尾
int k = partition<E>(A, i - 1, j, A[j]); //k保存交换最后的左边界
swap(A, k, j); //交换左边界和轴值,相当于把轴值插入到排序好的位置
qsort<E>(A, i, k - 1); //轴值往左排序
qsort<E>(A, k + 1, j); //轴值往右排序
}
findpiovt函数找一个轴值
影响快排时间复杂度最大的就是轴值的选择,对于未排序的数组而言,轴值的选择最好是随机的,这里简单的取数组中间的值作为轴值。
template<class E> int findpivot(E A[], int i, int j) {return (i + j) / 2;}
partition函数用来交换数组轴值左右的元素
不需要保证交换后数组是有序的,这交给接下来分隔后的数组去做。
template<class E>
int partition(E A[], int l, int r, E& pivot) {
do {
while (A[++l] < pivot); //左边界往右移动直到找到大于轴值的值
while ((r != 0) && (A[--r] > pivot)); //右边界往左移动直到找到小于轴值的值
swap(A, l, r); //交换数组里的这两个值
} while (l < r); //左右边界交错即停止
swap(A, l, r); //交错后会有一次不必要的交换,这里把它调整过来
return l; //返回此时的左边界
}
测试代码如下:
#include<iostream>
using namespace std;
template<class E>
void swap(E A[], int a, int b) {
int temp = A[a];
A[a]=A[b];
A[b]=temp;
}
template<class E>
int partition(E A[], int l, int r, E& pivot) {
do {
while (A[++l] < pivot);
while ((r != 0) && (A[--r] > pivot));
swap(A, l, r);
} while (l < r);
swap(A, l, r);
return l;
}
template<class E> int findpivot(E A[], int i, int j) {return (i + j) / 2;}
template<class E>
void qsort(E A[], int i, int j) {
if (j <= i) return;
int pivotindex = findpivot(A, i, j);
swap(A, pivotindex, j);
int k = partition<E>(A, i - 1, j, A[j]);
swap(A, k, j);
qsort<E>(A, i, k - 1);
qsort<E>(A, k + 1, j);
}
int main() {
int arr[] = {3,2,1,4,4,6,3,2,1,5,7,2,1,6,8,10};
qsort(arr,0,(sizeof(arr)/sizeof(int))-1);
for(auto i : arr){
cout<<i<<' ';
}
}
说一下时间复杂度
一般认为快排的时间复杂度为θ(nlogn),最差情况就是轴值的选择没有很好的分隔数组,选择了最大或最小的那个数,那么在下一次递归排序中需要排序的数组大小和原来差不多大,此时的时间复杂度就变为O(n²)。