找到不小于N的最小常数

时间:2012-02-11 18:16:24

标签: algorithm math prime-factoring hamming-numbers smooth-numbers

  

Regular numbers是均数除以60的幂的数字。例如,60 2 = 3600 = 48×75,因此48和75都是60的幂的除数。因此,它们也是常规数字。

这是rounding up to the next power of two的扩展名。

我有一个整数值 N ,它可能包含大的素因子,我想把它四舍五入到只由小素因子组成的数字(2,3和5)

示例:

  • f(18) == 18 == 21 * 32
  • f(19) == 20 == 22 * 51
  • f(257) == 270 == 21 * 33 * 51

找到满足此要求的最小数字的有效方法是什么?

所涉及的值可能很大,所以我想避免枚举从1开始的所有常规数字或维护所有可能值的数组。

8 个答案:

答案 0 :(得分:5)

好的,希望第三次在这里有魅力。用于p的初始输入的递归分支算法,其中N是在每个线程内“构建”的数字。这里的NB 3a-c作为单独的线程启动或以异步方式完成(准)。

  1. 在p之后计算下一个最大的2的幂,称之为R. N = p。

  2. 是N> R'退出这个帖子。 p只由小素因子组成吗?你完成了。否则,请转到步骤3.

  3. 在3a-c中的任何一个之后,转到步骤4.

    a)圆形p到最接近的2的倍数。该数字可表示为m * 2 b)圆形p到最接近的3的倍数。该数字可表示为m * 3 c)圆形p到最接近的5的倍数。该数字可表示为m * 5.

  4. 转到步骤2,p = m。

  5. 我省略了关于跟踪N的记账,但我认为这很简单。

    编辑:忘了6,谢谢ypercube。

    编辑2:如果这达到30,(5,6,10,15,30)意识到这是不必要的,那就把它拿出来。

    编辑3 :(我保证的最后一个!)添加了30次幂检查,这有助于防止此算法占用所有RAM。

    编辑4:根据finnw的观察,将30的幂变为2的幂。

答案 1 :(得分:5)

通过直接枚举三元组~ n^(2/3)(i,j,k)可以在第n个成员的N = 2^i * 3^j * 5^k周围产生slice of the Hamming sequence任意细的WP says

该算法适用于log2(N) = i+j*log2(3)+k*log2(5);列举所有可能的k s,对于每个可能的j,找到顶部i,从而找到三元组(k,j,i)并将其保留在“band”中,如果在给定“高度”低于给定的高对数顶值(当width <1时,最多只有一个i),然后按对数排序。

<{> the original code n ~ (log N)^3,即运行时间~ (log N)^2。在这里,我们不关心序列中找到的三元组的确切位置,因此instantly的所有计数计算都可以丢弃:

slice hi w = sortBy (compare `on` fst) b where       -- hi>log2(N) is a top value
  lb5=logBase 2 5 ; lb3=logBase 2 3                  -- w<1 (NB!) is log2(width)
  b  = concat                                        -- the slice
      [ [ (r,(i,j,k)) | frac < w ]                   -- store it, if inside width
        | k <- [ 0 .. floor ( hi   /lb5) ],  let p = fromIntegral k*lb5,
          j <- [ 0 .. floor ((hi-p)/lb3) ],  let q = fromIntegral j*lb3 + p,
          let (i,frac)=properFraction(hi-q) ;    r = hi - frac ]   -- r = i + q
                    -- properFraction 12.7 == (12, 0.7)

-- update: in pseudocode:
def slice(hi, w):
    lb5, lb3 = logBase(2, 5), logBase(2, 3)  -- logs base 2 of 5 and 3
    for k from 0 step 1 to floor(hi/lb5) inclusive:
        p = k*lb5
        for j from 0 step 1 to floor((hi-p)/lb3) inclusive:
           q = j*lb3 + p
           i = floor(hi-q)
           frac = hi-q-i                     -- frac < 1 , always
           r = hi - frac                     -- r == i + q
           if frac < w:
              place (r,(i,j,k)) into the output array
   sort the output array's entries by their "r" component
        in ascending order, and return thus sorted array

列举了切片中的三元组,这是一个简单的排序和搜索问题,实际上O(1)时间(对于任意薄切片)来找到上面的第一个三元组{ {1}}。那么,实际上,对于恒定宽度(对数),切片中的数字量(N - (i,j,k)空间中“上地壳”的成员)再次为{{1}并且排序需要log(N)时间(因此搜索,甚至是线性的,然后需要m ~ n^2/3 ~ (log N)^2运行时间)。但是,经过一些实证观察后,对于更大的m log m s,宽度可以变小;三元组计数的常数因子远远高于随后的排序。

即使是恒定宽度(logarthmic),它也会非常快地运行,计算汉明序列the billionth中的第1,000,000个值和0.05秒内的my post on a DDJ blogs discussion

“顶级三重乐队”的最初想法归功于Louis Klauder,正如2008年GordonBGood所引用的那样。

the comments going up to a trillionth entry中提到的

更新:,不需要整个频段,而只需要目标上下的一两个值。该算法很容易修改为该效果。在继续算法之前,还应测试输入是汉明数本身,以避免双精度的舍入问题。没有任何圆整问题比较预先知道的汉明数的对数是不同的(尽管序列中的{{3}}使用对数值中的大约14位有效数字,只留下1-2位数,所以情况实际上可能会转向那里;但对于十亿分之一,我们只需要11位有效数字。)

update2:结果是对数的双精度将其限制在低于大约20,000到40,000个十进制数字的数字(即10万亿到100万亿分之一的汉明数)。如果对于如此大的数字真的需要这个,那么算法可以切换回使用Integer值本身而不是它们的对数,这将会更慢。

答案 2 :(得分:4)

这是Python中的一个解决方案,基于Will Ness answer但采用一些快捷方式并使用纯整数数学来避免遇到日志空间数值精度错误:

import math

def next_regular(target):
    """
    Find the next regular number greater than or equal to target.
    """
    # Check if it's already a power of 2 (or a non-integer)
    try:
        if not (target & (target-1)):
            return target
    except TypeError:
        # Convert floats/decimals for further processing
        target = int(math.ceil(target))

    if target <= 6:
        return target

    match = float('inf') # Anything found will be smaller
    p5 = 1
    while p5 < target:
        p35 = p5
        while p35 < target:
            # Ceiling integer division, avoiding conversion to float
            # (quotient = ceil(target / p35))
            # From https://stackoverflow.com/a/17511341/125507
            quotient = -(-target // p35)

            # Quickly find next power of 2 >= quotient
            # See https://stackoverflow.com/a/19164783/125507
            try:
                p2 = 2**((quotient - 1).bit_length())
            except AttributeError:
                # Fallback for Python <2.7
                p2 = 2**(len(bin(quotient - 1)) - 2)

            N = p2 * p35
            if N == target:
                return N
            elif N < match:
                match = N
            p35 *= 3
            if p35 == target:
                return p35
        if p35 < match:
            match = p35
        p5 *= 5
        if p5 == target:
            return p5
    if p5 < match:
        match = p5
    return match

英文:迭代5s和3s的每个组合,快速找到每对的下一个2> =目标的幂并保持最小的结果。 (如果只有其中一个是正确的,那么迭代每个可能的2的倍数是浪费时间)。如果它发现目标已经是常规数字,它也会提前返回,尽管这不是绝对必要的。

我已经对它进行了相当彻底的测试,测试了从0到51200000的每个整数,并与OEIS http://oeis.org/A051037上的列表进行了比较,以及从常规数字中得到±1的许多大数字等。它是{{ 3}},找到FFT的最佳大小(now available in SciPy as fftpack.helper.next_fast_len)。

我不确定日志方法是否更快,因为我无法让它足够可靠地运行来测试它。我认为它有相似数量的操作?我不确定,但速度相当快。花费&lt; 3秒(或gmpy为0.7秒)来计算2 142 ×3 80 ×5 444 是上面的下一个常规数字2 2 ×3 454 ×5 249 +1(100,000,000个常规数,有392位数)

答案 3 :(得分:3)

您希望找到m的最小数字m >= N和所有m = 2^i * 3^j * 5^k的{​​{1}}。

取对数,方程式可以改写为:

i,j,k >= 0

您可以将 log m >= log N log m = i*log2 + j*log3 + k*log5 log2log3log5计算为(足够高,具体取决于logN的大小)准确度。然后这个问题看起来像Integer Linear programming问题,您可以尝试使用这个NP难问题的已知算法之一来解决它。

答案 4 :(得分:1)

已编辑/更正::更正了通过scipy tests的代码:

这是一个基于endolith's answer的答案,但几乎可以通过使用float64对数表示形式进行基础比较来查找通过条件的三值,从而几乎消除了冗长的多精度整数计算,只有在存在该条件时才采用全精度比较对数值可能不够准确的机会,仅当目标非常接近上一个或下一个常规数字时才会发生:

import math

def next_regulary(target):
    """
    Find the next regular number greater than or equal to target.
    """
    if target < 2: return ( 0, 0, 0 )
    log2hi = 0
    mant = 0
    # Check if it's already a power of 2 (or a non-integer)
    try:
        mant = target & (target - 1)
        target = int(target) # take care of case where not int/float/decimal
    except TypeError:
        # Convert floats/decimals for further processing
        target = int(math.ceil(target))
        mant = target & (target - 1)

    # Quickly find next power of 2 >= target
    # See https://stackoverflow.com/a/19164783/125507
    try:
        log2hi = target.bit_length()
    except AttributeError:
        # Fallback for Python <2.7
        log2hi = len(bin(target)) - 2

    # exit if this is a power of two already...
    if not mant: return ( log2hi - 1, 0, 0 )

    # take care of trivial cases...
    if target < 9:
        if target < 4: return ( 0, 1, 0 )
        elif target < 6: return ( 0, 0, 1 )
        elif target < 7: return ( 1, 1, 0 )
        else: return ( 3, 0, 0 )

    # find log of target, which may exceed the float64 limit...
    if log2hi < 53: mant = target << (53 - log2hi)
    else: mant = target >> (log2hi - 53)
    log2target = log2hi + math.log2(float(mant) / (1 << 53))

    # log2 constants
    log2of2 = 1.0; log2of3 = math.log2(3); log2of5 = math.log2(5)

    # calculate range of log2 values close to target;
    # desired number has a logarithm of log2target <= x <= top...
    fctr = 6 * log2of3 * log2of5
    top = (log2target**3 + 2 * fctr)**(1/3) # for up to 2 numbers higher
    btm = 2 * log2target - top # or up to 2 numbers lower

    match = log2hi # Anything found will be smaller
    result = ( log2hi, 0, 0 ) # placeholder for eventual matches
    count = 0 # only used for debugging counting band
    fives = 0; fiveslmt = int(math.ceil(top / log2of5))
    while fives < fiveslmt:
        log2p = top - fives * log2of5
        threes = 0; threeslmt = int(math.ceil(log2p / log2of3))
        while threes < threeslmt:
            log2q = log2p - threes * log2of3
            twos = int(math.floor(log2q)); log2this = top - log2q + twos

            if log2this >= btm: count += 1 # only used for counting band
            if log2this >= btm and log2this < match:
                # logarithm precision may not be enough to differential between
                # the next lower regular number and the target, so do
                # a full resolution comparison to eliminate this case...
                if (2**twos * 3**threes * 5**fives) >= target:
                    match = log2this; result = ( twos, threes, fives )
            threes += 1
        fives += 1

    return result

print(next_regular(2**2 * 3**454 * 5**249 + 1)) # prints (142, 80, 444)

由于消除了大多数长时间的多精度计算,因此不需要gmpy,并且在IDEOne上,上述代码花费0.11秒而不是0.48秒来使Endolith的解决方案能够找到下一个大于100万的正数,如图所示;找到十亿分之一后的下一个正则数需要0.49秒(而不是5.48秒)(下一个是(761,572,489)之后的(1334,335,404) + 1),并且随着范围的增加,差异会变得更大。与此处的几乎没有相比,endolith版本的精度计算变得越来越长。因此,此版本可以在IDEOne上约50秒内从序列中的万亿分之一计算下一个常规数,而使用endolith版本可能需要一个多小时。

该算法的英文描述与内衬版本几乎相同,区别如下:   1)计算参数目标值的浮点对数估计(我们不能直接使用内置的log函数,因为对于表示为64位浮点数的范围可能太大了),   2)比较对数表示值,以确定仅在大约两个或三个数字(取决于四舍五入)的目标值上下的估计范围内的合格值,   3)仅在上述定义的窄带内比较多精度值,   4)输出三重索引而不是完整的长整数精度(十亿分之一的整数约为840个十进制数字,十亿分之一的十倍),然后可以轻松地将其转换为长整数精度值如果需要的话。

除了可能的非常大的多精度整数目标值,大约相同大小的中间评估比较值以及三元组的输出扩展(如果需要)之外,该算法几乎不使用任何内存。该算法是对endlith版本的一种改进,尽管它没有精确度,但它仍成功地将对数值用于大多数比较,并且将比较数的范围缩小到只有几个。

该算法将适用于参数范围超过10万亿(在IDEOne速率下为几分钟的计算时间)的参数,由于@WillNess的讨论缺乏对日志表示值的精确度,该算法将不再正确;为了解决这个问题,我们可以将对数表示更改为由固定长度整数组成的“对您自己拥有”的对数表示(124位表示约两倍的指数范围,如果超过十万位数,则适合于目标)一个愿意等待);由于较小的多精度整数运算要比float64运算要慢,所以它会稍慢一些,但是由于大小有限(可能慢三倍左右),所以不会慢很多。

现在,这些Python实现(不使用C或Cython或PyPy等)都不是特别快的,因为它们比编译语言中的实现慢约100倍。作为参考,以下是Haskell版本:

{-# OPTIONS_GHC -O3 #-}

import Data.Word
import Data.Bits

nextRegular :: Integer -> ( Word32, Word32, Word32 )
nextRegular target
  | target < 2                   = ( 0, 0, 0 )
  | target .&. (target - 1) == 0 = ( fromIntegral lg2hi - 1, 0, 0 )
  | target < 9                   = case target of
                                     3 -> ( 0, 1, 0 )
                                     5 -> ( 0, 0, 1 )
                                     6 -> ( 1, 1, 0 )
                                     _ -> ( 3, 0, 0 )
  | otherwise                    = match
 where
  lg3 = logBase 2 3 :: Double; lg5 = logBase 2 5 :: Double
  lg2hi = let cntplcs v cnt =
                let nv = v `shiftR` 31 in
                if nv <= 0 then
                  let cntbts x c =
                        if x <= 0 then c else
                        case c + 1 of
                          nc -> nc `seq` cntbts (x `shiftR` 1) nc in
                  cntbts (fromIntegral v :: Word32) cnt
                else case cnt + 31 of ncnt -> ncnt `seq` cntplcs nv ncnt
          in cntplcs target 0
  lg2tgt = let mant = if lg2hi <= 53 then target `shiftL` (53 - lg2hi)
                      else target `shiftR` (lg2hi - 53)
           in fromIntegral lg2hi +
                logBase 2 (fromIntegral mant / 2^53 :: Double)
  lg2top = (lg2tgt^3 + 2 * 6 * lg3 * lg5)**(1/3) -- for 2 numbers or so higher
  lg2btm = 2* lg2tgt - lg2top -- or two numbers or so lower
  match =
    let klmt = floor (lg2top / lg5)
        loopk k mtchlgk mtchtplk =
          if k > klmt then mtchtplk else
          let p = lg2top - fromIntegral k * lg5
              jlmt = fromIntegral $ floor (p / lg3)
              loopj j mtchlgj mtchtplj =
                if j > jlmt then loopk (k + 1) mtchlgj mtchtplj else
                let q = p - fromIntegral j * lg3
                    ( i, frac ) = properFraction q; r = lg2top - frac
                    ( nmtchlg, nmtchtpl ) =
                      if r < lg2btm || r >= mtchlgj then
                        ( mtchlgj, mtchtplj ) else
                      if 2^i * 3^j * 5^k >= target then
                        ( r, ( i, j, k ) ) else ( mtchlgj, mtchtplj )
                in nmtchlg `seq` nmtchtpl `seq` loopj (j + 1) nmtchlg nmtchtpl
          in loopj 0 mtchlgk mtchtplk
    in loopk 0 (fromIntegral lg2hi) ( fromIntegral lg2hi, 0, 0 )


trival :: ( Word32, Word32, Word32 ) -> Integer
trival (i,j,k) = 2^i * 3^j * 5^k

main = putStrLn $ show $ nextRegular $ (trival (1334,335,404)) + 1 -- (1126,16930,40)

此代码将在无法测量的时间内计算出十亿分之一的下一个正数,并在IDEOne上计算0.69秒后的十亿分之一(并且可能会运行得更快,除非IDEOne不支持LLVM)。在JIT编译“热身”之后,甚至Julia也将以类似Haskell的速度运行。

EDIT_ADD :Julia代码如下:

function nextregular(target :: BigInt) :: Tuple{ UInt32, UInt32, UInt32 }
    # trivial case of first value or anything less...
    target < 2 && return ( 0, 0, 0 )

    # Check if it's already a power of 2 (or a non-integer)
    mant = target & (target - 1)

    # Quickly find next power of 2 >= target
    log2hi :: UInt32 = 0
    test = target
    while true
        next = test & 0x7FFFFFFF
        test >>>= 31; log2hi += 31
        test <= 0 && (log2hi -= leading_zeros(UInt32(next)) - 1; break)
    end

    # exit if this is a power of two already...
    mant == 0 && return ( log2hi - 1, 0, 0 )

    # take care of trivial cases...
    if target < 9
        target < 4 && return ( 0, 1, 0 )
        target < 6 && return ( 0, 0, 1 )
        target < 7 && return ( 1, 1, 0 )
        return ( 3, 0, 0 )
    end

    # find log of target, which may exceed the Float64 limit...
    if log2hi < 53 mant = target << (53 - log2hi)
    else mant = target >>> (log2hi - 53) end
    log2target = log2hi + log(2, Float64(mant) / (1 << 53))

    # log2 constants
    log2of2 = 1.0; log2of3 = log(2, 3); log2of5 = log(2, 5)

    # calculate range of log2 values close to target;
    # desired number has a logarithm of log2target <= x <= top...
    fctr = 6 * log2of3 * log2of5
    top = (log2target^3 + 2 * fctr)^(1/3) # for 2 numbers or so higher
    btm = 2 * log2target - top # or 2 numbers or so lower

    # scan for values in the given narrow range that satisfy the criteria...
    match = log2hi # Anything found will be smaller
    result :: Tuple{UInt32,UInt32,UInt32} = ( log2hi, 0, 0 ) # placeholder for eventual matches
    fives :: UInt32 = 0; fiveslmt = UInt32(ceil(top / log2of5))
    while fives < fiveslmt
        log2p = top - fives * log2of5
        threes :: UInt32 = 0; threeslmt = UInt32(ceil(log2p / log2of3))
        while threes < threeslmt
            log2q = log2p - threes * log2of3
            twos = UInt32(floor(log2q)); log2this = top - log2q + twos

            if log2this >= btm && log2this < match
                # logarithm precision may not be enough to differential between
                # the next lower regular number and the target, so do
                # a full resolution comparison to eliminate this case...
                if (big(2)^twos * big(3)^threes * big(5)^fives) >= target
                    match = log2this; result = ( twos, threes, fives )
                end
            end
            threes += 1
        end
        fives += 1
    end
    result
end

答案 5 :(得分:0)

这是我想到的另一种可能性:

如果 N X 位,那么最小的常规数 R N 将在范围
[2X-1, 2X]

e.g。如果 N = 257(二进制100000001),那么我们知道 R 1xxxxxxxx,除非 R 完全等于下一次幂为2(512)

要生成此范围内的所有常规数,我们可以先生成奇数正则数(即3和5的幂的倍数),然后取每个值并乘以2(通过位移)多次必须将它带入这个范围。

在Python中:

from itertools import ifilter, takewhile
from Queue import PriorityQueue

def nextPowerOf2(n):
    p = max(1, n)
    while p != (p & -p):
        p += p & -p
    return p

# Generate multiples of powers of 3, 5
def oddRegulars():
    q = PriorityQueue()
    q.put(1)
    prev = None
    while not q.empty():
        n = q.get()
        if n != prev:
            prev = n
            yield n
            if n % 3 == 0:
                q.put(n // 3 * 5)
            q.put(n * 3)

# Generate regular numbers with the same number of bits as n
def regularsCloseTo(n):
    p = nextPowerOf2(n)
    numBits = len(bin(n))
    for i in takewhile(lambda x: x <= p, oddRegulars()):
        yield i << max(0, numBits - len(bin(i)))

def nextRegular(n):
    bigEnough = ifilter(lambda x: x >= n, regularsCloseTo(n))
    return min(bigEnough)

答案 6 :(得分:-1)

我写了一个小的c#程序来解决这个问题。它不是很优化,但它是一个开始。 对于大到11位的数字,此解决方案非常快。

private long GetRegularNumber(long n)
{
    long result = n - 1;
    long quotient = result;

    while (quotient > 1)
    {
        result++;
        quotient = result;

        quotient = RemoveFactor(quotient, 2);
        quotient = RemoveFactor(quotient, 3);
        quotient = RemoveFactor(quotient, 5);
    }

    return result;
}

private static long RemoveFactor(long dividend, long divisor)
{
    long remainder = 0;
    long quotient = dividend;
    while (remainder == 0)
    {
        dividend = quotient;
        quotient = Math.DivRem(dividend, divisor, out remainder);
    }
    return dividend;
}

答案 7 :(得分:-1)

你知道吗?我会把钱投入到这个命题上,实际上,'哑'算法是最快的。这是基于以下观察:下一个常规数字通常看起来不比给定的输入大得多。因此,只需开始计数,并在每次增量后重构,看看你是否找到了常规数字。但是为每个可用的核心创建一个处理线程,对于N个核心,每个线程检查每个第N个数字。当每个线程找到一个数字或越过2次幂阈值时,比较结果(保持一个正在运行的最佳数字),你就是。