快速排序
关于快速排序(Quick Sort)
快速排序(Quick Sort)是一种分治算法(Divide-and-Conquer)
分治的思想是将一个大问题分解成一个个小问题,各个击破,合并子问题的解得到大问题的解。
具体快速排序的思路是: 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小。然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。参见百科
举栗子
还是栗子有助于思维啊,我们要对一个长度为n(n=7)的数组array 进行排序:
array = [6, 2, 7, 3, 8, 9, 6]
第1级排序(第1级递归)
我们将<=6的值排到6的左边,>=6的数排到6的右边。我们设
leftPos = 0, rightPos = 6, key = array[leftPos] (及key=6 )
以下分析可以参照更下面些的代码
Step 1.1.1: leftPos = 0, rightPos = 6, 从后往前,寻找比key小的数, 并交换位置
[3, 2, 7, 6, 8, 9, 6] 各个变量状态leftPos:0, rightPos:3, key:6
Step 1.1.2: 从前往后,寻找比key大的数, 并交换位置
[3, 2, 6, 7, 8, 9, 6] 各个变量状态leftPos:2, rightPos:3, key:6
//[While A]第一次循环结束,继续[While A],第二次循环开始:
Step 1.2.1: 从后往前,寻找比key小的数, 并交换位置(状态:leftPos:2, rightPos:3, key:6)
[3, 2, 6, 7, 8, 9, 6] 各个变量状态leftPos:2, rightPos:2, key:6
Step 1.2.2: 从前往后,寻找比key大的数, 并交换位置
[3, 2, 6, 7, 8, 9, 6] 各个状态不变, 因为leftPos == rightPos
//[While A]结束, 因为leftPos == rightPos, 届时leftPos==rightPos,key:6, array leftPos左边的数都 <= key(6), leftPos右边的数都 >= key(6)
递归调用quickSort, 以便用同样的方式, 分别处理leftPos 左边的数据 & leftPos右边的数据
第一轮递归得到的数组就是
array = [3, 2, 6, 7, 8, 9, 6]
第2级排序(第2级递归)
就是数据规模更小的,解法相同的子问题咯。对array[3] (也就是6)的左右两边分别排序,
及看作一下两个子数组的求解
subArr1 = [3, 2], subArr1 = [7, 8, 9, 6]
分别以array[0] (subArr1[0], 值为3), array[3] (subArr2[0], 值为7)为key得到
array = [2, 3, 6, 6, 7, 9, 8]
嗯,以上大概就是这么个意思。subArr1 和 subArr1 的处理是同一级的,但代码执行的时候会先把subArr1递归处理完,才能轮到subArr2。
Talk is cheap, Show code
下面就是java版本的递归实现了,有注释,附带一些时间复杂度的思考,有问题的话欢迎互相交流咯。
/**
* 快速排序array的某个区间[i,j]
* 递归实现, 平均时间复杂度O(nLog(n))
* @param array
* @param start
* @param end
* e.g. 举例分析
* [6, 2, 7, 3, 8, 9, 6]
*
* Step 1.1.1: leftPos = 0, rightPos = 6, 从后往前,寻找比key小的数, 并交换位置
* [3, 2, 7, 6, 8, 9, 6] 各个变量状态leftPos:0, rightPos:3, key:6
* Step 1.1.2: 从前往后,寻找比key大的数, 并交换位置
* [3, 2, 6, 7, 8, 9, 6] 各个变量状态leftPos:2, rightPos:3, key:6
*
* //第一次循环结束,继续[While A],第二次循环开始:
* Step 1.2.1: 从后往前,寻找比key小的数, 并交换位置(状态:leftPos:2, rightPos:3, key:6)
* [3, 2, 6, 7, 8, 9, 6] 各个变量状态leftPos:2, rightPos:2, key:6
* Step 1.2.2: 从前往后,寻找比key大的数, 并交换位置
* [3, 2, 6, 7, 8, 9, 6] 各个状态不变, 因为leftPos == rightPos
* //[While A]结束, 因为leftPos == rightPos, 届时leftPos==rightPos,key:6, array leftPos左边的数都 <= key(6), leftPos右边的数都 >= key(6)
*
* 递归调用quickSort, 以便用同样的方式, 分别处理leftPos 左边的数据 & leftPos右边的数据
*/
public static void quickSort(int array[], int i, int j) {
//边界条件
if (null == array || array.length < 2 || i < 0 || j >= array.length || i>=j) {
return;
}
int leftPos = i;
int rightPos = j;
int key = array[i];
while(leftPos < rightPos) {//[While A]
/** 从后往前,寻找比key小的数. =是必须的,以确保rightPos能往左移 */
while(array[rightPos] >= key && rightPos > leftPos) {//[While A.1], 比较最多执行m1次(m1 = j - i + 1)
--rightPos;
}
if (rightPos > leftPos) {
/* 交换位置. 这种跳跃式的交换, 不能保证相同值的顺序不变, 注定快速排序是一种不稳定的排序*/
// swap(array, rightPos, leftPos);
array[leftPos] = array[rightPos];// 因为key==array[leftPos]
array[rightPos] = key;//key==array[rightPos]
}
/** 从前往后,寻找比key大的数 */
while(array[leftPos] <= key && rightPos > leftPos){//[While A.2], 比较最多执行m2次(m2 = j - i + 1)
//实际上[While A.1] + [While A.2] 判断语句最大总次数为(j - i + 1 + 1)次记为m, 所以记[While A]的时间复杂度为O(m)
++leftPos;
}
if (rightPos > leftPos) {
/* 交换位置 */
// swap(array, rightPos, leftPos);
array[rightPos] = array[leftPos];//因为key==array[rightPos]
array[leftPos] = key;
}
}
{//这两段代码看做一个代码块, 平均情况下调用log(n)次
quickSort(array, i, leftPos - 1);
quickSort(array, leftPos + 1, j);
}
}
测试代码:
int[] array2 = { 12, 12, 5, 25, 6, 8, 17, 9, 3, 22, 2, 65, /*12,*/ 72, 13, 12 };
// int[] array2 = {6, 2, 7, 3, 8, 9, 6};
quickSort(array2, 0, array2.length - 1);
//LogHelper.v(array2);
测试结果
pos 0:2
pos 1:3
pos 2:5
pos 3:6
pos 4:8
pos 5:9
pos 6:12
pos 7:12
pos 8:12
pos 9:13
pos 10:17
pos 11:22
pos 12:25
pos 13:65
pos 14:72
That’s all! 好啦,结束鸟!

253

被折叠的 条评论
为什么被折叠?



