线性时间选择(C++):求第k小的数

方法一:

    思想:首先对整个数组进行划分,利用Partition函数,以数组中某个数为基准(这里以首项为标准)将数组划分为两部分——左边部分的所有数都小于基准,右边部分都大于基准,并返回基准数的下标值。

               然后,如果要找到第k小个数,就将k的大小与数组左半边元素的个数(若为 j,包括基准)进行比较,如果k小于j,则对左边部分进行递归,找第k小个数;若k大于j,则对右边部分进行递归,找第k减去j个小数。

          

#include<iostream>
using namespace std;
template<typename T>
int Partition(T a[], int l, int r)
{
	int i = l, j = r + 1;
	T x = a[l];
	while (true)
	{
		while (a[++i] < x && i <= r);
		while (a[--j] > x);
		if (i >= j)
			break;
		T m;
		m = a[i];
		a[i] = a[j];
		a[j] = m;
	}
	a[l] = a[j];
	a[j] = x;
	return j;
}
template<typename T>
T select(T a[], int left, int right, int k)
{
	if (left == right)
		return a[left];
	int i = Partition(a, left, right);
	int j = i - left + 1;
	if (k <= j)
		return select(a, left, i, k);
	else
		return select(a, i + 1, right, k - j);

}
int main()
{
	int m, k;
	cin>> m >> k;
	int* p = new int[m];
	for (int i = 0; i < m; i++)
		cin >> p[i];
	int h = select(p, 0, m - 1, k);
	cout << h;
	return 0;

}

分析一下Partition函数:

         以数组 a = {3 2 5 1 4 6 9}为例,首先以3为基准,令x=a[0](一般写为x=a[left], left为数组左边的最小下标值),i= 0,

j=6+1(最大下标值加一),接着进循环。i自增1,判断a[i]是否小于x,若小于则继续往下判断,否则退出循环。然后,j自减1,判断a[j]是否大于x,若大于继续循环,否则退出循环。可知第一次退出时,i=2,j=3。然后交换a[i]与a[j]的值,数组变为{3 2 1 5 4 6 9},继续循环,直到i>=j时退出循环,可知退出循环时i=3,j=2。将a[0]与a[j]的值交换,返回下标j旳值。数组变为{1 2 3 5 4 6 9}

左半部分(下标值小于2的部分)都小于基准x,即3,右边都大于3。

方法二:

如果能在线性时间内找到一个划分基准,使得按这个基准所划分出的2个子数组的长度都至少为原数组长度的ε倍(0<ε<1是某个正常数),那么就可以在最坏情况下用O(n)时间完成选择任务。

例如,若ε=9/10,算法递归调用所产生的子数组的长度至少缩短1/10。所以,在最坏情况下,算法所需的计算时间T(n)满足递归式T(n)≤T(9n/10)+O(n) 。由此可得T(n)=O(n)。

步骤:

(1)将n个输入元素划分成n/5(向上取整)个组,每组5个元素,最多只可能有一个组不是5个元素。用任意一种排序算法,将每组中的元素排好序,并取出每组的中位数,共n/5(向上取整)个。

(2)递归调用select来找出这n/5(向上取整)个元素的中位数。如果n/5(向上取整)是偶数,就找它的2个中位数中较大的一个。以这个元素作为划分基准。

例子:

按递增顺序,找出下面29个元素的第18个元素:8,31,60,33,17,4,51,57,49,35,11,43,37,3,13,52,6,19,25,32,
    54,16,5,41,7,23,22,46,29.
(1) 把前面25个元素分为5(=floor(29/5))组:  (8,31,60,33,17),(4,51,57,49,35),(11,43,37,3,13),(52,6,19,25,32),(54,16,5,41,7);
(2) 提取每一组的中值元素,构成集合{31,49,13,25,16};
(3) 递归地使用算法求取该集合的中值,得到m=25;
(4) 根据m=25, 把29个元素划分为3个子数组(按原有顺序)
P={8,17,4,11, 3,13,6,19,16,5,7,23,22}
Q={25}

R={31,60,33,51,57,49,35,43,37,52,32,54,41,46,29}

(5) 由于|P|=13,|Q|=1,k=18,所以放弃P,Q,使k=18-13-1=4,对R递归地执行本算法;
(6) 将R划分成3(floor(15/5))组:{31,60,33,51,57},{49,35,43,37,52},{32,54,41,46,29}
(7) 求取这3组元素的中值元素分别为:{51,43,41},这个集合的中值元素是43;
(8) 根据43将R划分成3组: {31, 33, 35,37,32, 41, 29},{43},{60, 51,57, 49, 52,54, 46}
(9)将左半边元素的个数与18作比较,若小于18,则对右边进行递归,否则对左边进行递归。

参考文章:https://blog.csdn.net/m0_37579232/article/details/80178000

#include<iostream>
using namespace std;

template<typename T>
void sort(T a[], int m, int n)
{
	for (int i = m; i <= n; i++)
	{
		int x = i;
		for (int j = i + 1; j <= n; j++)
			if (a[x] > a[j])
				x = j;
		if (x != i)
		{
			T num = a[x];
			a[x] = a[i];
			a[i] = num;
		}
	}
}
template<typename T>
int Partition(T a[], int l, int r,T midnum)
{
	int point=0;
	for(int q=l;q<=r;q++)
		if (a[q] == midnum)
		{
			point = q;
			break;
		}
	T df = a[l];
	a[l] = a[point];
	a[point] = df;
	int i = l, j = r + 1;
	T x = a[l];
	while (true)
	{
		while (a[++i] < x && i <=r);        
		while (a[--j] > x);
		if (i >= j)
			break;
		T m;
		m = a[i];
		a[i] = a[j];
		a[j] = m;
	}
	a[l] = a[j];
	a[j] = x;
	return j;
}

template<typename T>
T select(T a[], int left, int right, int k)
{
	if(right-left<75)
	{
		sort(a, left, right);
		return a[left + k - 1];
	}
	for (int i = 0; i <= (right - left - 4) / 5; i++)
	{
		sort(a, left + 5 * i, left + 5 * i + 4);
		T jk = a[left + 5 * i + 2];
		a[left + 5 * i + 2] = a[left + i];
		a[left + i] = jk;
	}
		T x = select(a, left, left + (right - left - 4) / 5, (right - left - 4) / 10);
		int local = Partition(a, left, right, x), j = local - left + 1;
		if (k <= j)
			return select(a, left, local, k);
		else
			return select(a, local + 1, right, k - j);
	
}

int main()
{
	int m,k;
	cin >> m>>k;
	int* p = new int[m];
	for (int i = 0; i < m; i++)
		cin >> p[i];
	int n = select(p, 0, m - 1, k);
	cout << n;
	return 0;
}

 

  • 8
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值