改进前的快速排序
代码实现:
//快速排序
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; // 递归终止条件}