冒泡是交换排序的基础算法,而快排是交换排序的高级算法。快排给我的感觉跟插入排序的感觉很像,都有点挖坑填数的感觉。不同的是插入是一个一个元素的定位,少了分而治之.
快速排序主要思路是: 挖坑填数 + 分治法(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 + " "); } } }