随机选择算法
问题
从一个无序数组中求出第k大的数。
解法
原理与快速排序相似,在快排基础上用数组中随机元素代替第一个元素,这样可以保证不存在一组特定的数据能使该算法出现最坏情况(例如对于快速排序来说,最坏情况是序列中元素已经接近有序,这时时间复杂度会劣化到 O ( n 2 ) O(n^2) O(n2))。
利用randPartition函数重排数组,使得随机元素p左边的数均小于等于p,右边的数均大于p,此时判断p下标m即表明p为数组中的第m大数。若 m > k,则往p左侧递归,找到左侧区间中的第k大数;若 m < k,则往p右侧递归,找到右侧区间中的第 k - m 大数。
期望时间复杂度为 O ( n ) O(n) O(n)。
#include <ctime>
#include <cstdlib>
#include <cmath>
/* 以随机元素为界,划分左右区间 */
int randPartition(int a[], int left, int right)
{
int p = (int) round(1.0 * rand() / RAND_MAX * (right - left) + left); // 生成一个区间[left, right]内的随机下标
int temp = a[p];
a[p] = a[left];
a[left] = temp; // 将随机元素与区间第一个元素交换,并用temp记录其值
while (left < right)
{
while (left < right && a[right] > temp)
right--;
a[left] = a[right];
while (left < right && a[left] <= temp);
left++;
a[right] = a[left];
}
a[left] = temp; // 将随机元素放在夹出来的位置
return left; // 返回下标
}
/* 找到第k大数,并对数组进行划分 */
void randSelect(int a[], int left, int right, int k)
{
if (left == right) // 递归边界
return;
int p = randPartition(a, left, right); // 以随机元素为界划分左右区间
int m = p - left + 1; // a[p]是数组中的第m大数
if (m == k)
return;
if (m > k) // 第k大数在左侧,找到左区间中的第k大数
randSelect(a, left, p - 1, k);
else // 第k大数在右侧,找到右区间中的第 k - m 大数
randSelect(a, p + 1, right, k - m);
}
int main()
{
srand((unsigned)time(NULL)); // 初始化随机数种子
}