我需要做一些实时 DFT,当样本数量可以分解为小因素时,我使用的算法是最有效的。
假设我有一个数字n
和因素2, 3, 5
。如何找到最接近的数(与 相比n
),其素数分解不包含除 以外的数字2,3,5
?
n
几乎总是低于50,000
所以蛮力可能是一个好主意。
在 1 到 50000 的范围内,恰好有 265 个数字只能被 2,3,5 整除。所以你可以做一个小表格,然后在表格中查找答案。但是,在我的计算机上,平均需要 6.5 微秒才能找到给定数字最近的 2-3-5 可因数,所以我想说蛮力已经足够了。
int isValid( int n )
{
while ( n%2 == 0 )
n /= 2;
while ( n%3 == 0 )
n /= 3;
while ( n%5 == 0 )
n /= 5;
return n == 1;
}
int findBest( int n )
{
for ( int i = 0; i < n; i++ )
{
if ( isValid(n+i) )
return n+i;
if ( isValid(n-i) )
return n-i;
}
return 0; // should never get here
}
int main( void )
{
for ( int n = 1; n <= 50000; n++ )
printf( "%d->%d\n", n,findBest(n) );
}
这并不能完全解决上述问题——给定一些整数 x,它只会找到除 2 和 3(或任何其他给定的因子对)之外没有因子的最接近的大于或等于数字。但我认为它很可爱,因为它在 O(log x) 时间和 O(1) 空间中运行,无论如何它可能很有用!它在概念上类似于 Bresenham 线算法。在伪代码中:
为什么这行得通?请注意,对于某些 i,我们始终有 y = 2^i*3^j,j >= 0,并且随着时间的推移,i 只会减少,而 j 只会增加。我们在进入步骤 2 时维护的不变量是“已知任何具有 a > i 或 b < j 的值 z = 2^a*3^b 都是无趣的(即,无效或不比某些已经发现的解决方案更好),所以不需要考虑”。当我们第一次到达第 2 步时,这显然是正确的,因为 y 是 2 的幂并且已经 >= x,所以任何数字 z = 2^a*3^b 且 a > i 将至少为 2*y (不管 b)哪个比 y 差;并且任何整数 z 的值都不能小于 y 中 3 的 j = 0 次方。
(陈述这个不变量的另一种方式是“要么我们已经找到了最优解,要么是某个数字 z = 2^a*3^b 且 a <= i 和 b >= j。”)
如果满足第 2 步的条件“y < x”,则 y = 2^i*3^j 不是一个有效的解决方案,因为它太低了。更强烈的是,很明显对于任何 a <= i,2^a*3^j 也不能是有效的解决方案,因为任何这样的解决方案至少与 y 一样低。所以现在我们知道(从不变量)任何满足 (a > i OR b < j) 的对 (a, b) 都是无趣的,并且我们从刚刚成功的“y < x”测试中知道任何对 (a, b) 满足 (a <= i AND b = j) 也无趣。现在考虑满足稍微不同的条件 (a > i OR b < j+1) 的任何对 (a, b):如果 (a, b) 不满足第一个无趣条件(来自不变量),那么我们有 ( (a > i OR b < j+1) AND !(a > i OR b < j)),简化为 ((a > i OR b < j+1) AND (a <= i AND b > = j)) 通过德摩根规则,然后到 (b < j+1 AND a <= i AND b >= j) (因为使 (a <= i AND b >= j) 为真需要 (a <= i ) 为真,迫使 (a > i) 为假,从而允许从 OR 中消除它),这显然与 (a <= i AND b = j) 相同——但这正是我们刚刚通过“y < x”测试的成功建立了第二种无趣。因此,这确定了满足 (a > i OR b < j+1) 的任何对 (a, b) 都是无趣的——在增加 j 之后,它恰好成为我们想要保留的不变量。i) 为假,因此允许将其从 OR 中消除),这显然与 (a <= i AND b = j) 相同——但这正是我们刚刚建立第二种无趣的条件,通过“y < x”测试的成功。因此,这确定了满足 (a > i OR b < j+1) 的任何对 (a, b) 都是无趣的——在增加 j 之后,它恰好成为我们想要保留的不变量。i) 为假,因此允许将其从 OR 中排除),这显然与 (a <= i AND b = j) 相同——但这正是我们刚刚建立第二种无趣的条件,通过“y < x”测试的成功。因此,这确定了满足 (a > i OR b < j+1) 的任何对 (a, b) 都是无趣的——在增加 j 之后,它恰好成为我们想要保留的不变量。
在第 5 步中减少 i 的理由几乎相同,但相反,所以我不会详细说明。细微的差别是,如果我们进入第 5 步,而不是得到一个无效的解决方案,我们只是有一个至少与 b 中的最佳解决方案一样高的解决方案(请注意,我们在必要时更新了 b 以便这将继续保持),所以它和每一个更高的解决方案(从这一点开始)对我们来说都是无趣的。
算法生成的每个 y 值要么比任何先前生成的值少一个因子 2,要么多一个因子 3,因此很明显,所有生成的 y 值都是不同的。前面段落中的推理表明,唯一没有生成的 y 值是那些已被证明无趣的值。因此,如果算法总是在有限时间内停止,那么它是正确的。下一节将暗示情况确实如此。
第 5 步(具有将 i 减 1 的效果)永远不会执行超过 log2(x)+1 次,因为 i 从这个值或更小开始,没有其他影响 i,当 i 达到 0 时,y 将是奇数,导致步骤 4 终止函数。但是步骤 2 中将 j 增加 1 的条件可以触发多少次?
最初 y >= x,因此要达到第 2 步触发所需的 y < x 条件需要执行第 5 步。但是一旦通过第 5 步的某些执行实现了 y < x,它就会在下一次执行时立即撤消第 2 步:这是因为要完全执行第 5 步,我们必须有 y >= x,如果我们将 y 除以 2(在第 5 步)然后乘以 3(在下一步2),它必须至少和以前一样大,这意味着 y >= x 再次意味着步骤 2 将永远不会连续触发两次,而不会在两者之间执行步骤 5。因此,第 2 步将最多触发第 5 步的次数,即最多 log2(x)+1 次。这将算法的整体运行时间限制在 O(log x)。
我不确定是否有任何有效的解决方案。以下是找到最接近 n 的数字的蛮力方法。
int diff=Integer.MAX_VALUE;
int num=0;
public void closestNumber(int n,int curr)
{
if(diff < Math.abs(n -curr) && curr >= n)
return;
if(diff >= Math.abs(n -curr))
{
diff = Math.abs(n -curr);
num=curr;
}
closestNumber(n,curr*2);
closestNumber(n,curr*3);
closestNumber(n,curr*5);
}
closestNumber(n,1);
System.out.println("closest number: "+num);
编辑:
下面的代码找到最接近目标的数字,该数字可被给定因子集中的至少一个数字整除。它没有为明确的目标提供解决方案,即找到只能被一组给定因子整除的最接近的数字。
原来的:
可被 2、3 或 5 整除的数列是OEIS A080671,具有简单的递归公式 a(n+22) = a(n)+30。此外,该级数方便地只有一个整数间隙。这意味着您可以简单地确定您的数字是否位于这些间隙之一,然后选择下一个或上一个整数。
class NumberFinder
{
public:
NumberFinder()
{
for (int i = 0; i < 2 * 3 * 5; i++)
{
bool hasSmallFactors =
(i % 2 == 0) ||
(i % 3 == 0) ||
(i % 5 == 0);
series.push_back(hasSmallFactors);
}
}
int Find(int n)
{
int x = n % (2 * 3 * 5);
if (series[x]) return n; // already good
return n + 1; // guaranteed to be good
}
private:
vector<bool> series;
};
这也可以推广到任何一组所需的因素:
class NumberFinder
{
public:
NumberFinder(vector<int> factors)
{
product = 1;
for (auto factor : factors) product *= factor;
for (int i = 0; i < product; i++)
{
bool hasSmallFactors = false;
for (auto factor : factors)
{
if (i % factor == 0) hasSmallFactors = true;
}
series.push_back(hasSmallFactors);
}
}
int Find(int n)
{
int lo = n;
int hi = n;
bool found = series[n % product];
while (!found)
{
if (--lo < 0) lo = 0;
hi++;
found = series[hi % product] || series[lo % product];
}
if (series[lo % product]) return lo;
return hi;
}
private:
int product;
vector<bool> series;
};