《算法导论》——最坏时间为线性时间的选择算法
该算法与期望时间为线性时间的选择算法的最大区别就是该算法的partition中不是随机的以某一个数作为基准,而是将中位数的中位数传入作为基准,返回中位数的中位数在序列中的位置。
步骤如下
1: 将输入数组的n个元素划分为 n/5 组,每组5个元素,且至多只有一组由剩下的 n%5 个元素组成。
2: 寻找 n/5 组中每一组的中位数:首先对每组元素(至多为5个)进行插入排序,然后确定每组有序元素的中位数。
3: 对第2步中找出的 n/5 个中位数,递归调用 select 以找出其中位数 num(如果有偶数个中位数,为了方便,约定 num 是较小的中位数)
4: 利用修改过的partition版本,按中位数的中位数 num 对输入数组进行划分。让 count 比划分的低区中的元素数目多1,因此 num 是第 count 小的元素,并且有 n - count 个元素在划分的高区。
5: 如果 k == count,则返回 num。如果 k < count,则在低区递归调用 select 以找出第 k 小的元素。如果 k > count,则在高区递归查找第 k - count 小的元素。
#include "iostream"
#include "algorithm"
using namespace std;
void insert_sort(int* a, int l, int h)//插入排序
{
for (int i = l + 1; i <= h; i++)
{
int key = a[i];
int j = i - 1;
while (j >= l && a[j] > key)
{
a[j + 1] = a[j];
j--;
}
a[j + 1] = key;
}
}
int partition(int* a, int l, int h, int q)//q为中位数的中位数
{
int j = 0;
for (int i = l; i <= h; i++)//找到中位数的中位数在数组中的位置
{
if (a[i] == q)
{
j = i;
break;
}
}
swap(a[j], a[h]);//放大索引最大处
int n = l - 1;
for (int m = l; m < h; m++)
{
if (a[m] < a[h])
{
n++;
swap(a[m], a[n]);
}
}
swap(a[n + 1], a[h]);
return n + 1; //返回中位数的中位数的基准(左边小于它,右边大于它)
}
int select(int* a, int l, int h, int k)//l,h为数组的边界,k为输入
{
if (h - l+1 <= 5 )
{
insert_sort(a, l, h);
return a[l + k - 1];
}
int group = (h - l + 5) / 5; //每5个数为一组,group为组数
for (int i = 0; i < group; i++)
{
int left = l + 5 * i;
int right = l + 5 * i + 4 > h ? h : l + 5 * i + 4;//如果边界大于了数组上边界,则用数组上边界作为边界
insert_sort(a, left, right);
int mid = (left+right) / 2;
swap(a[mid], a[l + i]);//把中位数放在前面,方便后面的计算中位数的中位数
}
int q = select(a, l, l + group - 1, (1 + group)/2);//递归找到中位数的中位数
int m = partition(a, l, h, q);//返回中位数的中位数在当前划分数组里的位置
int s = m - l + 1;//中位数的中位数是第几大数
if (s == k)
{
//return a[l+k-1];//一定记得要加上L(在执行select(a, m + 1, h, k - s);时,传的k值是减过的)
return a[m];
}
if (s > k)
{
select(a, l, m - 1, k);
}
else
{
select(a, m + 1, h, k - s);
}
}
int main(void)
{
int a[10] = { 25,31,89,12,67,53,44,59,71,19 };
for(int i =1;i<11;i++)
cout<<"第"<<i<<"大数为:"<< select(a, 0, 9, i)<<endl;
system("pause");
}