单元基本知识引入
1、在一个由n个元素组成的集合中,第i个顺序统计量是该集合中第i小的元素。例如在一个元素集合中,最小值是第1个顺序统计量(i=1),最大值是第n个顺序统计量(i=n)。当n为奇数的时候,中位数唯一,在i = (n+1)/2处;当n为偶数的时候,中位数有两个,分别在i = n/2+1和i = n/2处。因此不考虑奇偶性的时候,中位数总是出现在 i = ⌊(n+1)/2⌋处和i = ⌈(n+1)/2⌉处,分别为下中位数和上中位数。
9.1 最小值和最大值
1、如何寻找最小值?
最简单的方法就是通过n-1次比较比较出来。(非常简单)
MINIMUM(A)
1 min = A[1]
2 for i = 2 to A.length
3 if min > A[i]
4 min = A[i]
5 return min
2、同时找到最小值和最大值
我们只需要最多3⌊n/2⌋次比较就可以比较出来结果,方法就是记录当前的最小值和最大值。
①首先我们将一对输入元素相互进行比较,把较小的和当前最小值比较,较大的和当前最大值比较,因此每两个元素只需要3次比较。
②如何设置最大值和最小值?
n为奇数:将最大值和最小值都设置为第一个元素的值,然后成对处理剩下的元素。
n为偶数:将前两个元素做比较,然后决定最大值和最小值的初值,再成对处理剩下的元素。
③分析总的比较次数:
n为奇数:总共进行3⌊n/2⌋次比较。
n为偶数:先进行一次初始比较,然后进行3(n-2)/2次比较,总共3n/2-2次比较。
9.2 期望为线性时间的选择算法
一、目的
目的是寻找第i小的数字。
二、伪代码与分析
我们会给出一个RANDOMIZED-SELECT算法,且假设输入的数字都是互异的。
PARTITION(A,p,r)
1 x = A[r] //将A[r]作为主元
2 i = p - 1
//通过循环,使当A[j]<=x时,A[j]移动到最前面一部分
3 for j = p to r-1
4 if A[j] <= x
5 i = i + 1
6 exchange A[i] with A[j]
//最终将主元和A[i+1]互换(即到上面所说的A[q]上)
7 exchange A[i+1] with A[r]
8 return i+1
RANDOMIZED-PARTITION(A,p,r)
1 i = RANDOM(p,r) //随机在A[p...r]中抽取一个元素
2 exchange A[r] with A[i] //将A[r]和抽取的元素进行交换
3 return PARTITION(A,p,r) //通过之前的PARTITION方法返回结果
RANDOMIZED-SELECT(A,p,r,i)
1 if p == r //检查递归的基本情况,即数组A只包含一个元素,此种情况只需返回A[p]即可
2 return A[p]
3 q = RANDOMIZED-PARTITION(A,p,r) //将数组返回成为子数组A[p...q-1]和A[q+1...r],使前者每一个元素小于或等于A[q],后者每一个元素大于等于A[q]
4 k = q - p + 1 //计算A[p...q]的元素个数,即低区元素加上A[q]
5 if i == k //the point value is answer //检查主元是否是第i小的元素
6 return A[q]
7 else if i < k //如果主元不是第i小的元素,那么就要确认是在低区还是在高区里
8 return RANDOMIZED-SELECCT(A,p,q-1,i)
9 else return RANDOMIZED-SELECT(A,q+1,r,i-k)
三、运行时间
期望运行时间为θ(n)。
9.3 最坏情况为线性时间的选择算法
一、思路分析
算法SELECT也使用快速排序的确定性划分算法PARTITION,但区别就是将划分的主元作为输入参数。
通过执行下面的步骤,SELECT可以确定一个有n>1个不同元素的输入数组中第i小的元素。(如果n=1,则SELECT返回它的唯一输入数作为第i小的元素)
1、将输入数组的n个元素划分为⌊n/5⌋组,每组5个元素,且最多只有一组由剩下的n mod 5个元素组成。
2、寻找这⌈n/5⌉组中每一组的中位数:首先对每组进行插入排序,然后确定每组有序元素的中位数。
3、对第2部中找到的⌈n/5⌉个中位数递归调用SELECT以找出其中为数x(如果有偶数个中位数,为了方便,约定x是较小的中位数)
4、利用修改过的PARTITION版本,按中位数的中位数x对输入数组进行划分。让k比划分的地区中的元素数目多1,因此x是第k小的元素,并且有n-k个元素在划分的高区。
5、如果i=k,则返回x。如果i<k,则在低区调用SELECT来找出第i小的元素,如果i>k,则在高区递归查找第i-k小的元素。
二、代码
第一个:C语言写的版本
#include <stdio.h>
#define N 50
int Partition(int * a, int p, int r, int x)//以值x来进行分割,即x作为主元
{
int k;
int pos;
//将x与A[r]进行交换
for(k = p; k <= r; k++)//遍历数组找x的索引值
{
if(a[k] == x)
pos = k;
}
int t = a[pos];
a[pos] = a[r];
a[r] = t;
/*直接交换是行不通的,因为是数组内部元素的交换,因此必须先找到x对应的数组元素
int t = x;
x = a[r];
a[r] = t;
*/
/*
//通过循环将数组依据主元分成低区和高区(方法1,按照书中伪代码)
int i, j, temp;
i = p-1;
for(j = p; j <= r-1; j++)
{
if(a[j] <= t)
{
i+=1;
//exchange A[i] with A[j]
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
//exchange A[i+1] with A[r]
temp = a[i+1];
a[i+1] = a[r];
a[r] = temp;
return i+1;//返回划分后的x所对应的索引
*/
//通过循环将数组依据主元分成低区和高区(方法2,将主元随着循环一起移动到低区)
int i, j, temp;
i = p-1;
for(j = p; j <= r; j++)
{
if(a[j] <= t)
{
i+=1;
//exchange A[i] with A[j]
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
return i;//返回划分后的x所对应的索引
}
int Insertion_sort(int * a, int p, int r)//插入排序使之递增排列
{
int i, j;
for(i = p+1; i <= r; i++)
{
j = i;
while(j > p && a[j] < a[j-1])
{
int temp = a[j];
a[j] = a[j-1];
a[j-1] = temp;
j--;
}
}
}
int Select(int *a, int p, int r, int i, int len)//返回第i个元素的值
{
if(p==r)//仅一个元素时直接返回
{
return a[p];
}
int midval[N];
int group = len%5==0 ? len/5 : len/5+1;
if(len%5==0)//每组刚好5人
{
int i;
for(i = 0; i < group; i++)
{
Insertion_sort(a,p+5*i,p+5*i+4); //插入排序
midval[i] = a[p+i*5+2];
}
}
else//最后一组不满5人
{
int i;
for(i = 0; i < group-1; i++)
{
Insertion_sort(a,p+5*i,p+5*i+4);
midval[i] = a[p+i*5+2];
}
//单独处理最后一组
int lastgroupsize = len%5;
Insertion_sort(a,p+5*i,r);
midval[i] = a[p+i*5+lastgroupsize/2];
}
int pos2 = Select(midval,0,group-1,group/2,group);//对midval[]递归查找其中位数
int q = Partition(a,p,r,pos2);//以中位数pos2来划分元素
int k = q-p;//划分元素的相对位置
if(i == k)
return a[q];//划分元素刚好为所查元素,返回
else if(i < k)
return Select(a,p,p+k-1,i,k);//继续处理左半边
else
return Select(a,p+k+1,r,i-k-1,r-p-k);//继续处理右半边
}
int main()
{
int a[] = {34,65,21,32,555,11,4,78,64,99,25,100,24};
int len = sizeof(a)/sizeof(int);
int k;
int i;
for(i = 0; i < len; i++)
{
printf("%d ", a[i]);
}
printf("\ninput nth to search\n");
scanf("%d",&k);
int ans = Select(a,0,12,k-1,13);
printf("ans %d\n", ans);
return 0;
}