Reference
快排其实比归并写起来要容易一些。
核心原理就是选取一个基准数,然后把比他小的放到左边,比他大的放到右边,然后再同样处理两边,递归,最终排好序。
实际操作时并没有这么做,我们忽略基准数,从两边开始检查,把左边较大的和右边较小的交换,最后两者相遇时再把基准数交换过来。这样相对位置关系可以保证。
核心代码:
swap函数不是必需的,图个省事,C++中可以方便地使用Reference来交换内存中的两个元素,C的话用指针也可以做到,不过看起来没这么自然。
inline void swap(int &a,int &b)
{
int tmp=a;
a=b;
b=tmp;
}
void quickSort(int arr[],int L,int R)
{
if(L>=R) return;//由于递归调用自己,出口要写在最开始
int s=arr[L];//假定基准数是数列最左边的数
int i=L;//左右的两个哨兵
int j=R;
while(i!=j)//注意最外层的while检查只会在内层循环都走完后才会检查
//实际上内层的两个while跳出时可以统一写成i!=j,因递归出口保证了i一定比j小,j减小时一定会遇到i
{
//右边的哨兵必须先出发,这样才能保证最后如果两者相遇在中间,脚下的元素一定是比基准数小的
//因为右哨兵只会在比基准数小的元素处停下
//保证最后把基准数和相遇点的数交换时,大小关系正确
while(arr[j]>=s&&i<j)//右边的哨兵开始,遇到大的元素就跳过,在小的元素处停下来。
//i<j(或i!=j)是防止整个都比基准数大导致循环无法跳出以及访问越界的
j--;
while(arr[i]<=s&&i<j)//右边的哨兵找到了,左边的哨兵就出发,如果没找到,会使i=j=L。
i++;
if(i<j)//两者没有相遇时才交换
swap(arr[i],arr[j]);
}
swap(arr[i],arr[L]);//交换相遇点的元素和基准数
/*有两种情况:
1.两者在中间相遇,由于是右边的哨兵先出发,相遇点一定比基准数小,正常交换。
2.右边的哨兵跑完了整个数组都没找到比基准数小的,此时i=j=L,由于基准数也是左边界,交换自己,没问题。
例子:6,7,8,9
由于右边的哨兵的位置限制了左边的哨兵的行动,不存在左边的哨兵跑过界的情况。*/
quickSort(arr,L,i-1);//再分别处理左右边
quickSort(arr,i+1,R);
}
测试代码:
#include <iostream>
using namespace std;
void printArray(int a[],int L,int R)
{
while(L<=R)
cout<<a[L++]<<' ';
cout<<endl;
}
inline void swap(int &a,int &b)
{
int tmp=a;
a=b;
b=tmp;
}
void quickSort(int arr[],int L,int R)
{
if(L>=R) return;
int s=arr[L];
int i=L;
int j=R;
int tmp;
while(i!=j)
{
while(arr[j]>=s&&i<j)
j--;
while(arr[i]<=s&&i<j)
i++;
if(i<j)
swap(arr[i],arr[j]);
}
swap(arr[i],arr[L]);
quickSort(arr,L,i-1);
quickSort(arr,i+1,R);
}
int main(int argc, char const *argv[])
{
int arr[]={12,11,10,9,8,7,6,5,4,3,2,1,0};
quickSort(arr,0,12);
printArray(arr,0,12);
return 0;
}
结果:0 1 2 3 4 5 6 7 8 9 10 11 12