冒泡是交换排序的基础算法,而快排是交换排序的高级算法。快排给我的感觉跟插入排序的感觉很像,都有点挖坑填数的感觉。不同的是插入是一个一个元素的定位,少了分而治之.
快速排序主要思路是: 挖坑填数 + 分治法(Divided-and-ConquerMethod).
分治法相对来说,还是很好理解,从字面就可以看出,分而治之的意思,以某个标准,或者说是以数组某个元素为中介,将数组划分为两部分,分别进行排序.
而挖坑填数,则是理解的难点,尤其是习惯了冒泡算法的交换之后,思路很不容易扭转过来.借用网上的一个例子:
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Value | 72 | 6 | 57 | 88 | 60 | 42 | 83 | 73 | 48 | 85 |
第一步, 选择一个pivotpos枢轴点. 其实不要被枢轴这两个看起来高大上的字眼给唬住.理解了的话, 其实就是任意选取一个数值而已.一般我们默认就选第一个.
将a[0]=72拿到一边去.数组变为:
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Value | XX | 6 | 57 | 88 | 60 | 42 | 83 | 73 | 48 | 85 |
然后a[0]就空出来了,变成一个等着被填的坑.
第二步,从右至左遍历数组,寻找比a[0]=72小的第一个数字,也就是48. 将48 填进72空出来的坑.于是数组变为:
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Value | 48 | 6 | 57 | 88 | 60 | 42 | 83 | 73 | XX | 85 |
第三步,从左至右遍历数组,寻找比a[0]=72大的第一个数字,也就是88,将88填入刚才空出来的坑:
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Value | 48 | 6 | 57 | XX | 60 | 42 | 83 | 73 | 88 | 85 |
循环第二步, 从右到左找小数,为42,填坑:
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Value | 48 | 6 | 57 | 42 | 60 | XX | 83 | 73 | 88 | 85 |
循环第三步,从左到右找大数,到坑的index为止,a[0-4]都比72小.没有选择了,于是枢轴点a[0]=72入坑:
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Value | 48 | 6 | 57 | 42 | 60 | 72 | 83 | 73 | 88 | 85 |
到此,第一个数字的位置确定下来了.左边都是比它小的,右边都是比它大的.再以72为中点,将数组两边分别看作是一个数组.再次用刚才的方法排序.即可完成最终的排序.我想这也是枢轴点这个词的由来吧.
继续算法,对于左边部分:
48拿出来,42放进去:
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Value | 42 | 6 | 57 | XX | 60 | 72 | 83 | 73 | 88 | 85 |
57放进去,48最后入坑:
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Value | 42 | 6 | 48 | 57 | 60 | 72 | 83 | 73 | 88 | 85 |
接下来,再次对48左右两边分而治之:
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Value | 6 | 42 | 48 | 57 | 60 | 72 | 83 | 73 | 88 | 85 |
待左边排序完成,对最早的枢轴点72右边进行排序:
83拿出来,73比它小,入坑:
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Value | 6 | 42 | 48 | 57 | 60 | 72 | 73 | 83 | 88 | 85 |
当划分出来的数组长度为1时,递归结束。
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Value | 6 | 42 | 48 | 57 | 60 | 72 | 73 | 83 | 85 | 88 |
当划分出来的数组长度为1时,递归结束。遍历完成,标为黄色的数字是被选为枢轴的数字。
而其中运用到了递归分别对两端再次遍历,就是常说的分治法.晚点补上代码实现.
public class MyQuickSort {
/**
* 分治法, 确定每个枢轴的位置.
*
* @param a
* @param low
* @param high
* @return
*/
private static int partition(int[] a, int low, int high) {
int pivot = a[low];
while (low < high) {
while (low < high && a[high] >= pivot) { // 找到右边第一个比枢轴小的值,循环退出.
high--;
}
a[low] = a[high];
while (low < high && a[low] <= pivot) { // 左边找到第一个比枢轴大的值,循环退出.
low++;
}
a[high] = a[low];
}
a[low] = pivot;
return low;
}
/**
* 快速排序, 用递归不断迭代出每次枢轴两边的数组部分.
*
* @param a
*/
public static void quickSort(int[] a, int low, int high) {
if (low < high) { //不加会堆溢出.
int pivotPos = partition(a, low, high);
quickSort(a, low, pivotPos - 1);
quickSort(a, pivotPos + 1, high);
}
}
/**
* main(),测试用.
*
* @param args
*/
public static void main(String[] args) {
int[] a = new int[]{72, 6, 57, 88, 60, 42, 83, 73, 48, 85};
quickSort(a, 0, a.length - 1);
for (int i : a) {
System.out.print(i + " ");
}
}
}