快速排序
上次的快排理解的不深刻,这次梳理一下它的核心:
(1)选择一个标志值(通常为数组第一个元素)
(2)从后向前进行一次遍历,找到一个小于标志值的数,i++,与标志值互换
(3)从前向后进行一次遍历,找到一个大于标志值的数,j--,与标志值互换
(4)重复(2)(3)步骤,直到左边数全部小于标志值,右边数全部大于标志值
(5)将左右两侧的小于大于部分再次调用该方法,继续进行快排。
上代码方便观看:
public void quickSort (int firstSub, int lastSub) { int i = firstSub; int j = lastSub; int key = numArr[i]; while(i < j) { while(i < j && numArr[j] >= key) j--; if(i < j){ numArr[i] = numArr[i] + numArr[j]; numArr[j] = numArr[i] - numArr[j]; numArr[i] = numArr[i] - numArr[j]; i++; } while(i < j && numArr[i] <= key) i++; if(i < j){ numArr[j] = numArr[i] + numArr[j]; numArr[i] = numArr[j] - numArr[i]; numArr[j] = numArr[j] - numArr[i]; j--; } } if(i > firstSub)quickSort(firstSub , i - 1); if(j < lastSub)quickSort(i + 1, lastSub); }
举个例走一遍它的算法流程:
比如有一个无序数组:8 12 5 9 2 33 45 4 10 6 (看起来尽可能杂乱无章嗷)
1、第一步,我们选择数组首个元素作为标志数,意思是通过这个数来将整个数组分成两部分,通过一系列比较移动后,得到一个左侧全部小于这个数,右侧全部大于这个标志数的数组。
2、第二步、我们需要两个下标(前下标i,后下标j),因为快排方法是通过右到左一次找小于标志数的值换位,再从左向右找到一个大于该标志数的值换位,所以要有连两个下标来记录找到的符合要求的元素值的下标。同时,也要靠这两个下标来进行判断是否已经完成了一个整个数组的查询,比如方法中,我们始终判断后下标大于前下标,如果不大于,说明后下表找到是前下标已经找过的元素了。所以我们通过比较前下标与后下标来判断什么时候执行完毕跳出来。
接下来就是循环判断了,只要后下标大于前下标,就会一直进行循环。
3、首先从右向左找小于标志数的元素值,找到后,后下标值与 前下标i(存储的是标志数的下标) 进行换位操作。
前: 8 12 5 9 2 33 45 4 10 6
后: 6 12 5 9 2 33 45 4 10 8
直接找到了 6 < 8 ,换位。
4、接着从左向右寻找大于标志数的元素值,找到后,前下标i 与 后下标j(标志数的下标位置) 进行换位操作。
前: 6 12 5 9 2 33 45 4 10 8
后: 6 8 5 9 2 33 45 4 10 12
12 > 8,换位。
5、然后这是一个循环过程,接着找,重复 3 和 4 ,类似这样:
6 4 5 9 2 33 45 8 10 12 (右向左找小)
6 4 5 8 2 33 45 9 10 12 (左向右找大)
6 4 5 2 8 33 45 9 10 12 (右向左找小)
6、好啦,到这一步完后,再次判断后就会跳出循环,因为每次都会 i++ 或 j--,i < j 会跳出。
可以很明显的看出,8 为标志数,左侧都小于8,右侧都大于8,大家可以自己在纸上试试这个变换过程哦,不是很复杂~
接着,我们再次调用快排方法,这回只会传入前部分的前下标后下标,和后部分的前下标后下标。
7、又会对两个部分进行 3 和 4 的操作,像这样:
前部分:
6 4 5 2 (6标志数)
2 4 5 6 (右向左找小)
2 4 5 6 (左向右找大,然而没有,搞定跳出)
后部分:
33 45 9 10 12 (33标志数)
12 45 9 10 33 (右向左找小)
12 33 9 10 45 (左向右找大)
12 10 9 33 45 (右向左找小)
8、这个例子举得不好。。。后半部分执行到这还得进行一次快排:
12 10 9 (12标志数)
10 9 12 (右向左找小)
10 9 12 (左向右找大,都大,跳出,左侧部分再进行一次快排)
相信大家看到这都理解快排的原理了吧,但是它还有不稳定,大神们也对它进行了诸多的改进,比如对标志数进行随机选择等等。希望对学算法的各位有所帮助吧!