1. 序言:
鲁迅先生笔下孔乙己的茴香豆四种写法的故事让大家常引得大家哄堂大笑,诚然我们不需要掌握茴香豆的四种写法,也无需掌握hello world的各种写法,但作为一个技术人员,还是需要懂得一题多解甚至一解多样,锤炼技术才是我们端好这碗饭的不二法门。
快速排序是一种最为常用的排序方法,其主要思想“分治”更是一种必须掌握的关键算法。快速排序代码简单,但其边界处理和关键字处理是一项比较麻烦的内容,今天来梳理一下快速排序的多种写法,以便于各位找到一种最容易理解和接受的写法。
2. 四种写法
2.1 先移后判换两端法
- 代码
public static void quickSort(int[] nums, int l, int r){
if(l >= r) return;
int i = l - 1, j = r + 1; //先定位到边界两侧
int key = nums[l];
while(i < j){
while(nums[++i] < key); //先移动再与关键字判断
while(nums[--j] > key); //先移动在与关键字判断
if(i < j)
swap(nums, i, j); //交换两侧值
}
quickSort(nums, l, j);
quickSort(nums, j + 1, r);
}
- 简单解释
- i, j定位置于到边界以外以便先移动后判断。
- 大while循环可完成交换,使得key左侧的值大于等于key,右侧则小于key。
- 小while循环为需要交换的元素定位位置,也即:在左侧找到一个大于key的数,在右侧找到一个小于key的数。
- 两个小while找到位置后,swap函数完成交换。
- 重复上述两个过程。
- 最终满足条件,因此继续划分左子序列和右子序列。
2.2 先判后移换两端法
public static void quickSort(int[] nums, int l, int r){
if(l >= r) return;
int i = l, j = r;
int key = nums[l];
while(i < j){
while(i < j && nums[j] >= key) j--;
while(i < j && nums[i] <= key) i++;
if(i < j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
nums[l] = nums[i];
nums[i] = key;
quickSort(nums, l, j);
quickSort(nums, j + 1, r);
}
- 简单解释
- i, j定位置于到边界以便先判断后移动。
- 由于判定方式都添加了 = 号,因此key值最终不在正确的位置,所以需要最后将其还原到正确的位置,加了后面两行。
- !!!必须将其回归到正确位置
2.3 先判后移步步换两端法
public static void quickSort(int[] nums, int l, int r){
if(l >= r) return;
int i = l, j = r;
int key = nums[l];
while(i < j){
while(i < j && nums[j] >= key) j--;
if(i < j){
swap(nums, i, j);
i++;
}
while(i < j && nums[i] <= key) i++;
if(i < j){
swap(nums,i, j);
j--;
}
}
quickSort(nums, l, i - 1);
quickSort(nums, j + 1, r);
}
- 简单解释
- 从边界出发,因此同样先判后移
- 与2.1不同,这里每次发生位置不对就进行移动,移动之后,另一侧的那个元素位置是正确的,因此也移动一个位置
- 又开始另一侧的判别和移动
2.4 填坑法
public static void quickSort(int[] nums, int l, int r){
if(l >= r) return;
int i = l, j = r;
int key = nums[l];
while(i < j){
while(i < j && nums[j] >= key) j--;
nums[i] = nums[j];
while(i < j && nums[i] < key) i++;
nums[j] = nums[i];
}
nums[i] = key;
quickSort(nums, l, i - 1);
quickSort(nums, i + 1, r);
}
- 简单解释
- 先把关键位置“挖掉”,也即先用key取出来
- 先从右边向左扫,找到比key小的数“挖掉”放入“坑”中,这样右边出现一个新的“坑”
- 然后从左往右扫,找到比key大的数“挖掉”放入“坑”中,这样左边出现一个新的“坑”
- 我们发现,填坑的过程就是将大于或小于key的数分类的过程,填坑完毕即可完成这一次划分
- 最后将key值放回最后的坑中,我们发现,这个值出现在正确的位置,因此递归划分左子序列和右子序列
3. 总结
- 注意取出的key值最终是否在正确的位置上
- 注意是先判定后移动还是先移动后判定,这里引发是否与key值的比较取“=”的问题
- 先移后判不取=,先判后移取=,填坑法一边取一边不取
- 填坑法需要先处理“初始坑”相反的一侧的部分,否则,无法“入坑”