快速排序在面试中经常用于考察面试者的代码能力,以下是我个人对如何手撕快排的一些理解:
原理:
快速排序的解决分为两个部分,分区(partition)和递归(recurse)。
分区是主要进行排序的功能,递归用于控制分区的次数。
分区的思想是:选定一个数,将所有小于这个数的数组元素都放在它的左侧,同理所有大于这个数的数组元素都放在它的右侧。此时,这个数在数组中就是有序的,它处于它应该在的位置。接下来对其左侧的区间进行分区:同样也是选定一个数……,右侧区间同理。一直进行分区这个操作,直到这个区间只有一个数,也就代表这个区间的父区间排序完毕。了解了快速排序的流程后,不难看出它和二叉树的遍历很像,每个区间二分进行分区并排序,就是二叉树遍历所有子节点并打印。
实现思路:
int partition(int l, int r){ // 分区函数
int mid = nums[r]; // 选取最右侧的数组元素为界
int fast = l, slow = l; // 定义快慢指针
while(fast < r){
if (nums[fast] < mid){
swap(nums[fast], nums[slow]); // 将比mid小的放在slow指针的左边
slow++; // slow右移,此时slow左侧的元素都比mid小
}
fast++;
}
swap(nums[r], nums[slow]); // 将mid与slow交换,此时该数组以mid为界,左边都比mid小,右边都比mid大
// 打印调试过程
// for (int i = l;i < r;i++) cout << nums[i] << ' ';
// cout << endl;
return slow; // 返回mid的索引
}
void qsort(int l, int r){
if (r > l){
int mid = partition(l, r); // mid已经有序,以mid为基准
qsort(l, mid - 1); // 对于mid左边的区域进行分区
qsort(mid + 1, r); // 对于mid右边的区域进行分区
} else return;
}
流程理解:
首先,我们输入一个数组,例如:
int nums[6] = {5, 3, 1, 6, 2, 4};
为了方便函数读取,我们将这个数组设置为全局变量(如果将nums设置在main函数中,在定义函数时同时将数组的地址传入也可)。
易知数组的l,r分别为0,5,在主函数中执行:
qsort(0, 5);
此时进行第一次的partition,我们默认取区间右边界的数为mid,也就是将4作为界。然后定义快慢指针,初始值都是l,使用while循环,每次循环快指针都往右走,一直走到r结束。
在快指针往右走的过程中,判断其遍历到的每一个数,如果这个数比mid小,那么就将这个数与慢指针指向的数位置互换。位置互换后,慢指针再右移一位。
以上这个操作,将使得原区间中所有比mid小的数,都在慢指针的左侧。慢指针所在的位置,就是mid应该处于的位置。所以最后将mid与慢指针指向的数位置交换,分区操作就完成了。
接下来以mid为界,不断划分区间进行递归:
qsort(l, mid - 1);
qsort(mid + 1, r);
直到划分为长度为1的区间,此时排序就算完成了,最后输出即可。