今天在看了快速排序的思路后尝试自己写了一个快排实现算法,发现结果不对,调试了很久最后结果还是有点问题。
1、快速排序错误写法1 之 循环中交换元素后立即移动了左、右指针
下面贴出我有问题的代码,你能看出来哪里有问题吗?
public static void main(String[] args) {
int [] array = {6, 1, 2, 7, 9, 3 , 4, 5, 10, 8, 20, 12, 90, 110, 11};
quickSort(array, 0 , array.length - 1);
for (int i : array) {
System.out.print(i + " ");
}
}
public static void quickSort(int [] array, int start, int end) {
//取下标0的元素作为基点,实现数组左边子数组元素元素全部小于基点,数组右边子数组元素全部大于基点
if (start >= end) {
return;
}
int pivot = array[start];
int i = start;
int j = end;
while (i < j) {
while(array[j] >= pivot && i < j) {
j--;
}
while(array[i] <= pivot && i < j) {
i++;
}
if (i < j) {
int temp = array[j];
array[j] = array[i];
array[i] = temp;
i++;
j--;
}
}
int temp = array[j];
array[j] = pivot;
array[start] = temp;
quickSort(array, start, j - 1);
quickSort(array, j + 1, end);
}
运行结果为:
1 2 3 4 5 6 7 8 9 10 11 12 110 20 90
可以看到,结果数组中后半部分居然不是有序的。
后来参照网上的写法,发现错误代码在if条件内部交换i,j位置的元素后,让i自增加1了,即i++,让j自减减1了,即j--。假如把i++;j--注释掉,再运行一遍,发现结果正常了。
1 2 3 4 5 6 7 8 9 10 11 12 20 90 110
不禁让我好奇这是为什么呢?
我们来看这么一个原始数组A = [20, 12, 90, 110, 11]
1、首先选取pivot=A[0];
2、设置两个指针,一个指针 l 指向A[0],并且方向向右;另一个指针 r 指向A[length -1],并且方向向左;
3、r 从右往左遍历,直到找到一个比pivot小的元素;等 r 达到指定位置后,让 l 从左往右遍历,直到找到一个比pivot大的元素。
千万注意两点:
1、必须让指针 r 先移动,r 移动完后 l 才能开始移动;
2、两个指针移动比较时,遇到等于pivot的元素也要继续移动,因为我们要实现的是让pivot左边的子数组都小于pivot,右边的子数组都大于pivot。
4、这样目前指针 l 指向的元素是90, r指向的元素是11, 交换着两个元素内容;数组变成
[20, 12, 11, 110, 90]
5、假如在交换完之后,立马 l++; r--; 这时候 l==r了,现在 r 指向的数110, l 指向的也是110;
9、接着再循环时,循环条件不满足,就退出整个while循环了。
10、然后交换数组第一个元素和array[j]的值,就变成[110, 12, 11, 20, 90]。
11、很明显,上面出错了。
我们知道,必须先执行r指针的循环,等它结束后才能执行 l 指针的循环(下面有解释原因)。如果你在交换数据后立马执行l++; r--; 这就相当于l 和 r 指针同时移动了一步,并且没有先后顺序。
上面会导致 r 指针在(array[j] >= pivot && i < j)条件中提前触发i>=j这个条件,导致r指针还没挪到正确位置就被迫结束了。
2、快速排序错误写法2 之 左指针先与右指针移动
让 l 指针先移动
public static void main(String[] args) {
int [] array = {6, 1, 2, 7, 9, 3 , 4, 5, 10, 8, 20, 12, 90, 110, 11};
//int [] array = {3, 6, 7, 2, 4, 9, 1, 5};
quickSort(array, 0 , array.length - 1);
for (int i : array) {
System.out.print(i + " ");
}
}
public static void quickSort(int [] array, int start, int end) {
//取下标0的元素作为基点,实现数组左边子数组元素元素全部小于基点,数组右边子数组元素全部大于基点
if (start >= end) {
return;
}
int pivot = array[start];
int i = start;
int j = end;
while (i < j) {
while(array[i] <= pivot && i < j) {
i++;
}
while(array[j] >= pivot && i < j) {
j--;
}
if (i < j) {
int temp = array[j];
array[j] = array[i];
array[i] = temp;
//i++;
//j--;
}
}
int temp = array[j];
array[j] = pivot;
array[start] = temp;
quickSort(array, start, j - 1);
quickSort(array, j + 1, end);
}
运行结果为
1 2 5 3 4 6 7 8 9 20 10 11 110 12 90
上面仅仅只是把l 和 r指针对应的循环调整了下顺序,运行结果就错误了。
3、为什么必须右指针r先移动?
假如对[6, 1, 2, 7, 9]进行从小到大的排序,6作为基准数,如果从左边开始,i首先到达一个比6大的数,这个数为7,这时候再从左边走,但这时7到最后一个数之间已经没有比6小的数,所以6只能与7进行替换,我们要做的是从小到大的排序,这样把一个大于6的数排到前边,显然是错误的,所以应从右边进行,这样即使右边到达某个数后,这个数左边即使没有大于基准数的数,那也是把这个比基准数小的数替换到首位,达到基准数左边都比右边小的效果。
4、快速排序正确写法
package shuzu;
public class quickSort {
public static void main(String[] args) {
int [] array = {6, 1, 2, 7, 9, 3 , 4, 5, 10, 8, 20, 12, 90, 110, 11};
//int [] array = {3, 6, 7, 2, 4, 9, 1, 5};
quickSort(array, 0 , array.length - 1);
for (int i : array) {
System.out.print(i + " ");
}
}
public static void quickSort(int [] array, int start, int end) {
//取下标0的元素作为基点,实现数组左边子数组元素元素全部小于基点,数组右边子数组元素全部大于基点
if (start >= end) {
return;
}
int pivot = array[start];
int i = start;
int j = end;
while (i < j) {
while(array[j] >= pivot && i < j) {
j--;
}
while(array[i] <= pivot && i < j) {
i++;
}
if (i < j) {
int temp = array[j];
array[j] = array[i];
array[i] = temp;
}
}
int temp = array[j];
array[j] = pivot;
array[start] = temp;
quickSort(array, start, j - 1);
quickSort(array, j + 1, end);
}
public static void swap(int [] array, int index1, int index2) {
int temp = array[index1];
array[index1] = array[index2];
array[index2] = temp;
}
}
5、总结
快速排序注意点:
1、右指针必须比左指针先移动;
2、在循环中交换数据后,不要移动两个指针,否则不满足右指针比左指针先移动的效果;
3、左指针移动时,保证在第一个大于pivot指向的元素大,等于都不行。同理右指针移动。