强大的随机算法-简洁的O(n)时间复杂度解决查找第k大数问题优化算法

本文介绍了一种使用随机法查找数组中第K大的数的方法,并提供了完整的C++实现代码。该方法相比传统的分治法更为简洁高效,平均时间复杂度达到O(n)。

之前用了个分治法用O(n)时间复杂度简洁查找diK大数的问题。可以参考下面博客:

http://blog.youkuaiyun.com/kenden23/article/details/14645619

但是由于过于复杂,估计很多人都不想使用。

下面我用随机法来解决这个问题吧,据复杂的数学分析随机法的时间是少于4n的,而分治法反而是4n,所以其实随机法更加优化了,而且更加重要的是更加简单了。

我刚看到这个算法也是震惊了,只有分治法的不到三分之二的长度。细读一下你会发现真的是很好很强大!

下面看完整程序是如何的:

原创出处:靖空间http://blog.youkuaiyun.com/kenden23/article/details/14646479

#include<iostream>
#include<vector>
#include<algorithm>
#include<cstdlib>
#include<time.h>

using namespace std;

//low and up have to be according indices in C++; 
//k mean the kth number, according C++ stlye index is k-1
int selectKthNumRand(vector<int> &vi, int low, int up, int k)
{
	int mIndex;

	//注意这里会有几率是up=0, low=0,所以要作特殊处理
	if(up-low != 0)
		mIndex = rand()%(up-low) + low;
	else mIndex = 0;
	int mNum = vi[mIndex];

	vector<int> vec1, vec2, vec3;
	for (int i = low; i <= up; i++)
	{
		if(vi[i] > mNum) vec3.push_back(vi[i]);
		if(vi[i] == mNum) vec2.push_back(vi[i]);
		if(vi[i] < mNum) vec1.push_back(vi[i]);
	}

	if(vec1.size()>=k) 
		return selectKthNumRand(vec1, 0, vec1.size()-1, k);
	else if(vec1.size()+vec2.size()>=k) 
		return mNum;
	else if(vec1.size()+vec2.size()<k) 
		return selectKthNumRand(vec3, 0, vec3.size()-1, k-vec1.size()-vec2.size());
}

int main()
{
	int a[] = {3,5,7,9,2,12,1,0,8,14,4,6,10,11,13,5,8,12};
	vector<int> va(a, a+18);
	int b[] = {31,25,37,49,52,63,71,20,87,95,34};
	vector<int> vb(b, b+11);
	for(auto x: va)
		cout<<x<<" ";
	cout<<endl;

	srand(time(NULL));

	int mid = selectKthNumRand(va, 0, va.size()-1, 10);
	
	cout<<"The Kth number is: \n";
	cout<<mid<<endl;

	cout<<endl;
	system("pause");
	return 0;
}


这个程序的强大之处是它随机地选择分列的下标,而不用前面那种复杂的分治法。

时间复杂度最坏情况还是O(n*n),但是平均时间复杂度已经达到了O(n)了,非常好。

运行结果:

<think>嗯,用户想要优化查找完数的算法,把时间复杂度降到O(n log n)。首先,我得先回忆一下什么是完数。完数就是一个数等于它的真因子之和,比如6的真因子是1、2、3,加起来正好是6,所以6是完数。 原来的算法可能是怎么做的呢?通常,找完数的方法可能是对每个数n,遍历从1到n/2的所有数,找出所有能整除n的数,然后求和。这样的话,对于每个数n,时间复杂度是O(n),所以整个算法时间复杂度可能是O(),如果查找的范围到N的话,这显然不够高效,用户想优化到O(n log n)。 那如何优化呢?想到可能需要减少每个数的因子检查次数。例如,预先计算每个数的因子和,而不是每次都重新计算。这里可以用类似埃拉托斯特尼筛法的方法,预先为每个数累加它的因子。比如,对于每个i从1到N/2,将i的倍数加上i作为它们的因子和。这样,每个数i的倍数都会累积i的值,最终每个数的因子和就被计算出来了。这种方法的时间复杂度大概是O(n log n),因为对于每个i,要处理n/i次,总的次数是n/1 + n/2 + ... + n/n ≈ n log n。 然后,遍历所有数,检查是否等于其因子和减去自身(因为筛法过程中可能包含自己,比如当i是n的因子时,比如n=6,i=3的话,3会被加到6的因子和中,但6的真因子不包括自己,所以可能需要调整)。或者,在筛法中,从i=1开始,处理到i<=n/2,这样每个数的因子和不会包含自己。例如,i从1到n/2,每个i的倍数j = 2i, 3i,...,然后将i加到j的因子和中。这样,当处理完所有i之后,每个数的因子和已经被正确计算,不需要再减去自身。这时候,只需要检查数m是否等于它的因子和即可。 举个例子,对于m=6,i=1时,会加到2,3,4,5,6等的因子和里。i=2时,会加到4,6,8等。i=3时,加到6,9等。所以,当i遍历到3时,6的因子和已经包含了1+2+3=6,因此m=6等于其因子和,所以是完数。 这样,整个算法时间复杂度就降到了O(n log n),因为每个数的处理次数是调和级数级别的,而空间复杂度是O(n),需要存储每个数的因子和。 然后需要考虑具体的实现步骤。例如,初始化一个数组sums,长度为n+1,初始化为0。然后,对于i从1到n/2,遍历i的倍数j=2i, 3i,...直到n,将i加到sums[j]。最后,遍历每个数m,检查sums[m]是否等于m。如果是,就记录下来。 这样,预处理阶段的时间复杂度是O(n log n),而检查阶段是O(n),所以总的时间复杂度是O(n log n),满足用户的需求。 可能还需要注意一些边界情况,比如i的范围是否正确,是否覆盖了所有可能的真因子。例如,真因子不包括自身,所以在筛法中,i最多到n/2,因为超过n/2的除数只能是n本身,而真因子不包含自己。因此,i的上限设为n//2即可。 另外,对于大数的情况,这种方法的效率会比原来的方法好很多,尤其是当n很大时,O(n log n)比O()快得多。 总结下来,优化方法就是通过类似筛法的预处理,预先计算每个数的因子和,从而将时间复杂度降低到O(n log n)。</think>### 如何将查找完数算法时间复杂度优化至$O(n \log n)$? #### 1. 原算法分析 传统方法逐个检查每个数$n$的真因子和,需遍历$1$到$n/2$的所有数,时间复杂度为$O(n)$。若查找范围为$[2, N]$,总时间复杂度为$O(N^2)$[^4]。 #### 2. 优化思路:预处理因子和 使用**筛法思想**预处理每个数的因子和,具体步骤如下: 1. **初始化数组**:创建数组`factor_sum`,大小为$N+1$,初始值为$0$。 2. **筛法累加因子**:遍历每个数$i$($1 \leq i \leq N/2$),将其倍数$j = 2i, 3i, \dots$($j \leq N$)的`factor_sum[j]`加上$i$。此步骤时间复杂度为$O(n \log n)$,因每个数的处理次数为调和级数级。 3. **筛选完数**:遍历每个数$m$,若`factor_sum[m] == m`,则$m$为完数。此步骤时间复杂度为$O(n)$。 #### 3. 实现代码示例(Python) ```python def find_perfect_numbers(n): factor_sum = [0] * (n + 1) for i in range(1, n // 2 + 1): for j in range(2 * i, n + 1, i): factor_sum[j] += i return [m for m in range(2, n + 1) if factor_sum[m] == m] # 示例:查找10000以内的完数 print(find_perfect_numbers(10000)) # 输出: [6, 28, 496, 8128] ``` #### 4. 复杂度验证 - **时间复杂度**:筛法阶段为$O(n \log n)$,筛选阶段为$O(n)$,总时间复杂度$O(n \log n)$[^3]。 - **空间复杂度**:$O(n)$,用于存储因子和数组。 #### 5. 对比传统方法 | 方法 | 时间复杂度 | 适用场景 | |---------------|------------|----------------| | 传统遍历法 | $O(n^2)$ | 小规模数据 | | 筛法预处理法 | $O(n \log n)$ | 大规模数据 | #### 6. 关键优化点 - **避免重复计算**:通过预计算因子和,减少对每个数的重复因子遍历。 - **数学性质利用**:真因子不超过$n/2$,筛法可高效覆盖所有可能因子。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值