试题5 快速排序
以下代码可以从数组a[]中找出第k小的元素。
它使用了类似快速排序中的分治算法,期望时间复杂度是O(N)的。
请仔细阅读分析源码,填写划线部分缺失的内容。
#include <stdio.h>
int quick_select(int a[], int l, int r, int k) {
int p = rand() % (r - l + 1) + l;
int x = a[p];
{int t = a[p]; a[p] = a[r]; a[r] = t;}
int i = l, j = r;
while(i < j) {
while(i < j && a[i] < x) i++;
if(i < j) {
a[j] = a[i];
j--;
}
while(i < j && a[j] > x) j--;
if(i < j) {
a[i] = a[j];
i++;
}
}
a[i] = x;
p = i;
if(i - l + 1 == k) return a[i];
if(i - l + 1 < k) return quick_select(________________); //填空
else return quick_select(a, l, i - 1, k);
}
int main()
{
int a[] = {1, 4, 2, 8, 5, 7, 23, 58, 16, 27, 55, 13, 26, 24, 12};
printf("%d\n", quick_select(a, 0, 14, 5));
return 0;
}
注意:只填写划线部分缺少的代码,不要抄写已经存在的代码或符号。
解题思路:
快速排序的一个算法模板,只要你懂的快速排序的原理,就只需要稍微阅读下这个代码就可以填写出来了,答案在本博客最后。
如果不懂的快速排序,你可以继续往下看:
快速排序
快速排序是一个算法复杂度为 O(N) 的一种排序算法,他处理的过程是:
- 从一堆数中,任意找一个数作为基准,将这堆数中的比他小的放到他的前面,比他大的放到后面。(如果是从大到小,反着来)
- 然后递归左半边和右半边
话不多说,来个例子。
有8个数:8 4 7 58 2 1 5 23
,从小到大排序。
我们定义一个函数 sort(int a, int b) 函数,表示从下标 a,到下标 b-1 这段区间进行快速排序,并且我们规定每次都选取这段区间的第一个数作为基准数。
那么一开始 sort(0, 8) ,用变量 x 来存放这个基准数,x = 8
那么我们要怎么样才能把大于大的数换到左边去,小于与到的数换到右边去呢?
可以这样,我们定义两个变量 i,j,分别指向这段数组的头的位置和尾的位置。
- 首先让 j 往前移动,当找到第一个比他小的数,就将这个数的值放到 i 所指的下标。(如下图)
- 接下来,轮到 i 下标开始往前移动,当找到第一个比基准值大的值,就把 i 下标下的值放到 j 下标对于的位置。
- 循环 1,2 这个过程,直到不满足 i < j 这个条件的时候,结束这个循环。
- 结束循环后,i 和 j 的位置如图:
这个个时候,将原先的X的值,放到这个位置 i 和 j 所指的位置上。
这样左边就是比8小或等于的数,右边就是比8大的数。
然后循环递归左半边,和右半边。
题目代码解析
而蓝桥的题目,不是要我们排序,而是要我们找出数组中,第K小的数。
那我们每次找的基准数,按从小到大,在基准数前面的数的个数+1,就是他要找的K。
所以关键就是懂的题目给出的这个函数的意思
// 函数参数的意思:a数组,l左端点,r右端点,k要找的第几个小的数
int quick_select(int a[], int l, int r, int k) {
int p = rand() % (r - l + 1) + l; // 随机找一个基准值
int x = a[p]; // 保存基准值
{int t = a[p]; a[p] = a[r]; a[r] = t;} // 把基准值放到最后一个位置
int i = l, j = r; // 定义下标
while(i < j) { // 开始循环
while(i < j && a[i] < x) i++; // 从左边开始,找到第一个比它大的值
if(i < j) { // 不越界的情况下,填值
a[j] = a[i];
j--;
}
while(i < j && a[j] > x) j--; // 然后从右到左,找到第一个比它小的值
if(i < j) { // 不越届的情况下,填值
a[i] = a[j];
i++;
}
}
a[i] = x; // 将基准值填入
p = i;
if(i - l + 1 == k) return a[i]; // 相等,说明基准值就是答案
// 前面的数更少,说明要找的值,在右边,递归右边
if(i - l + 1 < k) return quick_select(________________); //填空
// 前面的数更多,说明要找的值,在左边,递归左边
else return quick_select(a, l, i - 1, k);
}
答案:quick_select(a, i+1, r, k-i+l-1)