《算法导论》第9章-中位数和顺序统计量 9.1-最小值和最大值;9.2-期望为线性时间的选择算法;9.3最坏情况为线性时间的选择算法

单元基本知识引入

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;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KeepCoding♪Toby♪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值