改进前的快速排序
代码实现:
//快速排序
void quick(int arr[],int start,int end){
int i = start;
int j = end;
int mid = arr[start];
int tmp;
while(i < j){
//从头往后找,比基准小就继续
while(arr[i] < mid){
i++;
}
//循环结束,i的位置大于等于基准元素
//从后往前找,比基准大就继续
while(arr[j] > mid){
j--;
}
//循环结束,j的位置小于等于基准元素
//交换位置
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
if(start < j){
quick(arr,start,j-1);
}
if(end > j){
quick(arr,j+1,end);
}
}
int main(){
int num[] = {20,30,20};
quick(num,0,2);
for(int i = 0; i < 9;i++){
printf("%d ",num[i]);
}
printf("\n");
return 0;
}
对于{20,30,20}这样一个有重复数字的队列,基准元素与下标i和下标j的元素相等时就会陷入死循环,当他们交换位置后,arr[i] < mid 不成立,跳出循环。arr[j] > mid 不成立,跳出循环。继续发生交换位置。再次比较外层循环 i < j 成立,继续内层循环。程序就会一直卡在这里。
改进后的快速排序
代码实现:
void quick(int arr[], int start, int end) {
if (start >= end) {
//1.start>end,子数组没有元素
//2.start=end,子数组只有一个元素
return; // 递归终止条件
}
int i = start;
int j = end;
int pivot = arr[start]; // 选择第一个元素作为基准
while (i < j) {
// 从右向左找小于基准的元素
while (i < j && arr[j] >= pivot) {
j--;
}
// 从左向右找大于基准的元素
while (i < j && arr[i] <= pivot) {
i++;
}
// 交换i和j位置的元素
if (i < j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
//当 i = j时,arr[i]一定是小于等于基准的值
// 最后将基准放到正确的位置
arr[start] = arr[i];
arr[i] = pivot;
// 递归处理基准左右两边的数组
quick(arr, start, i - 1);
quick(arr, i + 1, end);
}
int main() {
int num[] = { 20,30,20};
int size = sizeof(num) / sizeof(num[0]);
quick(num, 0, size - 1);
for (int i = 0; i < size; i++) {
printf("%d ", num[i]);
}
printf("\n");
return 0;
}
我们先从右向左开始找小于基准的元素(注意一定是先从右向左,后从左向右寻找),在从左向右找大于基准的元素,同时在寻找过程中一直保持 i < j,二者有一个不满足就跳出当前循环。
为什么先从右向左再从左向右找,反过来不可以吗?
不可以,我们先从右向左找小于基准的元素,再从左向右找大于基准的元素,如果(i < j )之后交换此时(i 和 j)位置上的元素,之后在进入循环判断。这样当(i == j)时,arr[i] 一定是小于等于 基准元素。因为交换位置前 arr[i] > pivot, arr[j] < pivot, 交换位置后arr[i] < pivot, arr[j] > pivot,此时j先向左移动,i再根据(i 和 j)大小关系再判断是否往右移动,(因为是j先移动,所以j停下来一定是遇到比基准元素小的元素 或者 i==j 这样的情况)这样我们能够确保(i == j)时 ,arr[i] 一定是小于等于基准元素。
//当 i = j时,arr[i]一定是小于等于基准的值
// 最后将基准放到正确的位置
arr[start] = arr[i];
arr[i] = pivot;
此时因为arr[i] 是小于或者等于基准元素的,我们让arr[i]往左放,pivot放在arr[i] 的位置上,这样我们就能保证基准左边的一定是小于等于基准元素的,右边一定是大于等于基准元素的。
之后就是递归函数的使用了,难点在于理解
//当 i = j时,arr[i]一定是小于等于基准的值
// 最后将基准放到正确的位置
arr[start] = arr[i];
arr[i] = pivot;
这段代码的作用,交换i==j时的元素与基准元素,另外必须是先从右向左找再从左向右找(很重要)还有就是递归函数的终止条件(代码注释已经说的很明白了,不另做赘述)
if (start >= end) {
//1.start>end,子数组没有元素
//2.start=end,子数组只有一个元素
return; // 递归终止条件
}