前言:
快排作为世界十大经典算法之一,其重要性就不再多谈了。 我在学习快排过程中有一个很大的疑惑是为什么在进行从左到右和从右到左的扫描时,如果左右都等于pivot基准值还要进行一次交换。这其实是因为一些其他场景的处理导致的一些没法避免的小问题。下面我们详细分析他真正想处理的问题是什么。
注意:快排有非常多的写法,我们只能针对一段代码中的扫描和什么时候停止扫描进行元素交换的逻辑进行分析。不能说本文的解析适用于所有人写的快排。
话不多说,先上java代码:
public class Solution {
public void sortArray(int[] A) {
if (A == null) return;
helper(A, 0, A.length - 1);
}
private void helper(int[] A, int left, int right) {
if (left >= right) {
return;
}
int pivot = A[left + (right - left) / 2];
int i = left, j = right;
while (i <= j) {
while (i <= j && A[i] < pivot) {
i++;
}
while (i <= j && A[j] > pivot) {
j--;
}
if (i <= j) {
int tmp = A[i];
A[i] = A[j];
A[j] = tmp;
i++;
j--;
}
}
helper(A, left, j);
helper(A, i, right);
}
}
分析为何左右都等于pivot时还要做一次交换
这里从代码来看也就是说为什么while (i <= j && A[i] < pivot)
不能写成while (i <= j && A[i] <= pivot)
- 这里我们使用举例法, 比如一个A数组是[1,1,1]如果A[i] <= pivot都要继续扫描不停止,那么i会一直增长到3,大于j导致后面的while循环条件不满足。 后面去递归处理子问题时会成为helper(A, 0, 2)的情况,因为j都没有机会移动就被i跑到j右边去了,这就导致了无限循环永远结束不了的bug。
- 我们再举个例子, 比如数组A是[2,3,1],我们还假设A[i] <= pivot时不停止 。又出现了上面的情况,无限循环
- 但并不是所有的数组情况都会出错,比如如果换一个数组A[3,2,1,4,5]并还假设A[i] <= pivot时不停止那么是可以得到正确结果的。我们可以详细来画一下。
假设while (i <= j && A[i] <= pivot)时扫描继续,不停止,i继续执行++操作
[3,2,1,4,5]
i p j
i j
[1,2,3,4,5]
i
j
j
此时因为 i >= j不再满足所以不进行交换和while循环, 第一轮结束
然后会有递归的两个式子:
helper(A, 0, 0); //left == right 直接就return了
helper(A, 1, 4);
[1,2,3,4,5]
i p j
i
j
此时因为 i >= j不再满足所以不进行交换和while循环, 第二轮结束
然后会有递归的两个式子:
helper(A, 1, 2); //left == right 直接就return了
helper(A, 3, 4);
(1) 先看 helper(A, 1, 2);
[1,2,3,4,5]
i j
p
i
j
此时因为 i >= j不再满足所以不进行交换和while循环, 第三轮第一组结束
然后会有递归的两个式子:
helper(A, 1, 1); //left == right 直接就return了
helper(A, 2, 2); //left == right 直接就return了
(2) 再看 helper(A, 3, 4);
[1,2,3,4,5]
i j
p
j i
此时因为 i >= j不再满足所以不进行交换和while循环, 第三轮第二组结束
然后会有递归的两个式子:
helper(A, 3, 3); //left == right 直接就return了
helper(A, 4, 4); //left == right 直接就return了
这样看来等于pivot时不交换对于这个序列没有影响
- 再举一个会出错的例子[5,4,3,2]。我们也可以详细来画一下。
[5,4,3,2]
i p j
[2,4,3,5]
i j
p
i
此时因为 i >= j不再满足所以不进行交换和while循环, 第一轮结束
然后会有递归的两个式子:
helper(A, 0, 2);
helper(A, 3, 3); //left == right 直接就return了
然后我们只用看helper(A, 0, 2);
[2,4,3,5]
i j
i(//越界)
此时j都没机会动i就大于j了, while循环结束。继续进入递归的式子
helper(A, 0, 2);
helper(A, 3, 2); //left >= right 直接就return了
然后helper(A, 0, 2); 就无限循环导致stack overflow栈内存溢出了