快速排序从右边开始扫描和从左边开始扫描的区别
数组:
6 1 2 7 9 3 4 5 10 8
以6为基准数
先从右边开始扫描,到5退出循环
再从左边开始扫描,到7退出循环
交换5和7:
6 1 2 5 9 3 4 7 10 8
继续从7开始向右边扫描,到4退出循环
再从5开始向左边扫描,到9退出循环
交换4和9:
6 1 2 5 4 3 9 7 10 8
继续从9开始向右边扫描,到3退出循环
再从4开始向左边扫描,到3,这时左指针和右指针相遇停止扫描,然后基准数和左右指针交换换到中间变成:
3 1 2 5 4 6 9 7 10 8
public void quickSort2(int[] nums, int left, int right) {
if (left >= right) {
return;
}
int i = left, j = right;
//标杆
int base = nums[left];
while (i <= j) {
//右扫描找到比标杆小的数
while (nums[j] >= base && j > left) {
j--;
}
//左扫描找到比标杆大的数
while (nums[i] <= base && i < right) {
i++;
}
if (i >= j) {
break;
}
//左指针和右指针相遇时不用交换,左右指针相遇时在小的子数组最后一个
if (i < j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
//标杆复位,使得标杆左边的元素比标杆小,右边的元素比标杆大
nums[left] = nums[j];
nums[j] = base;
quickSort(nums, left, j - 1);
quickSort(nums, j + 1, right);
}
数组:
6 1 2 7 9 3 4 5 10 8
如果先从左边开始扫描:
先从左边开始扫描,到7退出循环
再从右边开始扫描,到5退出循环
交换7和5:
6 1 2 5 9 3 4 7 10 8
继续从5开始向左边扫描,到9退出循环
再从7开始向右边扫描,到4退出循环
交换9和4:
6 1 2 5 4 3 9 7 10 8
继续从4开始向左边扫描,到9退出循环
再从9开始向右边扫描,这时左指针和右指针相遇停止扫描,然后基准数和左右指针交换换到中间变成:
9 1 2 5 4 3 6 7 10 8
9 1 2 5 4 3 6 7 10 8,基准数左边的数不是都小于基准数
先从右边扫描最后停留的位置肯定是小于基准数的,因为右指针是找比基准数小的数,先找到比基准数小的数,左指针和右指针相遇的情况只能是右指针停止,然后左指针向左扫描直到和右指针相遇,所以相遇的位置肯定是比基准数小的,然后这个数和基准数交换,这个数在基准数左边并小于基准数符合快排规则
先从左边扫描最后停留的位置肯定是大于基准数的,因为左指针是找比基准数大的数,先停在比基准数大的数,左指针和右指针相遇的情况只能是左指针停止,然后右指针向左扫描直到和左指针相遇,所以相遇的位置肯定是比基准数大的,然后这个数和基准数交换,这个数在基准数左边但大于基准数不符合快排规则
所以要先从右边扫描
可以不从右边开始扫描,回到这步
6 1 2 5 4 3 9 7 10 8
继续从4开始向左边扫描,到9退出循环
再从9开始向右边扫描,这时左指针和右指针相遇停止扫描,这时左右指针所在的位置9是大于基准数的子数组的第一个数,所以右指针左移一位就能到达小于基准数的子数组的最后一个数3,然后这个数和基准数交换:
3 1 2 5 4 6 9 7 10 8
这时就符合快排的规则
public void quickSort1(int[] nums, int left, int right) { if (left >= right) { return; } int base = nums[left]; int i = left,j = right+1;//右指针初始化为right+1是为了防止最右边的数小于基准数时执行--j右指针左移一位去到right-1的位置,这个位置和基准数交换后基准数右边的数比基准数小不符合快排规则 while (true) { while (nums[++i] < base && i < right);//后面的判断是防止左指针越界 while (nums[--j] > base);//左右指针相遇时这步的右指针先减会左移一位找到比基准数小的数 if (i >= j) { break; } int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } nums[left] = nums[j]; nums[j] = base; quickSort1(nums,left,j-1); quickSort1(nums,j+1,right); }
public void quickSort(int[] nums, int left, int right) {
if (left >= right) {
return;
}
int i = left,j = right;
//标杆
int base = nums[left];
while (i <= j) {
//左扫描找到比标杆大的数
while (nums[i] <= base && i < right) {
i++;
}
//右扫描找到比标杆小的数
while (nums[j] >= base && j > left) {
j--;
}
//这步的作用是左右指针相遇时如果当前数大于基准数,右指针就要左移一位找到比基准数小的数
if (i == j && nums[j] > base) {
j--;
} }
if (i >= j) {
break;
}
//左指针和右指针相遇时不用交换
if (i < j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
//标杆复位,使得标杆左边的元素比标杆小,右边的元素比标杆大
nums[left] = nums[j];
nums[j] = base;
quickSort(nums,left,j-1);
quickSort(nums,j+1,right);
}
上面两段代码作用一样实现方式不同
先从左边扫描,左右指针相遇时停留的位置是大的子数组的第一个数,所以右指针要左移一位才能到小的子数组最后一个,比基准数小,和基准数交换后才符合快排规则
先从右边扫描,左右指针相遇时停留的位置是小的子数组的最后一个数,左右指针不用移动,直接和基准数交换符合快排规则
//不能用nums[j--] > base是因为nums[j--]是先比较数然后j再减1,这里无论大于还是小于base都会减一,只要当前数比base小j就应该不变,而不是减1
//这个nums[j]大于等于base j才减一,
while (j > left && nums[j] >= base) {
j--; }
//这个是j先减一然后再和base比较,不会出现多减的情况,这种是当前数比base小然后j就不变
while (nums[--j] > base);
while(num[j--] > base)等于
while(num[j] > base) {
j--;
}
j--; //找到比基准数小的数j就不用减1,减1的话就会到达比基准数小的子数组的倒数第二位这位和基准数交换后基准数右边存在比基准数小的数不符合快排规则
while (nums[--j] > base);等于
while (nums[j] > base) {
j--;
}
//这里找到比基准数小的数j没有减1,刚好是比基准数小的子数组的最后一位
数组6 1 2 5 4 3 9 7 10 8
//
while (temp <= arr[j] && i < j) {
j--;
}
举个例子:
数组 5 3 2 6 0 1 ,j一开始为5,base为5,nums[5]为1比base小,然后j减1为4,这时i为3
nums[4]和nums[3]交换不正确,用nums[5]和nums[3]交换才正确,即6应该是和1交换不应该是和0交换
正确做法是比base大j才减1,所以nums[j--] > base这种大于小于base都会减1是不正确的,nums[j]比base小j就不用减1要保持不变