【问题标题】:dividing by 2 and ceiling until remains 1除以 2 和上限直到剩下 1
【发布时间】:2013-02-13 07:24:55
【问题描述】:

仅对自然数具有以下算法: 轮数(n)={1,如果 n=1; 1+轮(ceil(n/2)),否则} 所以用编程语言写这将是

int rounds(int n){
    if(n==1)
        return 1;
    return 1+rounds(ceil(n/2));
}

我认为这有时间复杂度 O(log n)

有更好的复杂性吗?

【问题讨论】:

  • 如果您能描述算法的意图,这将更容易回答。它显然会产生一个与log2(n) 相关的数字,并需要~log2(n) 迭代来完成它。
  • 哦,好的。例如,我有一个在 3-6 人的桌子上玩的游戏。第一轮桌数为 ceil(number_of_players/6),下一轮将从每桌最好的 3 名玩家中选出。这导致每下一轮 ceil(number_of_tables/2) 表。问题是会有多少轮?

标签: math complexity-theory logarithm


【解决方案1】:

从1向上列出结果,

rounds(1) = 1
rounds(2) = 1 + rounds(2/2) = 1 + 1 = 2

接下来,当ceil(n/2) 为 2 时,rounds(n) 将为 3。这是 n = 3n = 4

rounds(3) = rounds(4) = 3

那么,当ceil(n/2) 为3 或4 时,结果将为4。3 <= ceil(n/2) <= 4 发生当且仅当2*3-1 <= n <= 2*4,所以

round(5) = ... = rounds(8) = 4

继续,你可以看到

rounds(n) = k+2 if 2^k < n <= 2^(k+1)

通过归纳。

你可以把它改写成

rounds(n) = 2 + floor(log_2(n-1)) if n > 1 [and rounds(1) = 1]

在数学上,您还可以通过将n = 1 重写为来统一对待它

rounds(n) = 1 + floor(log_2(2*n-1))

如果您使用的是固定宽度类型,最后一个公式可能会溢出。

所以问题是

  • 你能以多快的速度将一个数字与 1 进行比较,
  • 一个数字减 1 的速度有多快,
  • 您能以多快的速度计算正整数的以 2 为底的对数(下限)?

对于固定宽度类型,即有界范围,所有这些当然都是 O(1) 操作,但是您可能仍然对使其尽可能高效感兴趣,即使计算复杂度没有进入游戏。

对于本机机器类型 - intlong 通常是 - 比较和减去整数是非常快速的机器指令,因此唯一可能有问题的是以 2 为底的对数。

许多处理器都有一个机器指令来计算机器类型值的前导 0 位,如果编译器可以访问它,您将获得以 2 为底的对数的非常快速的实现。如果没有,您可以获得比使用one of the classic bit-hacks 递归更快的版本。

例如,足够新的 gcc 和 clang 版本有一个 __builtin_clz(对应于 64 位类型的 __builtin_clzl)映射到处理器上存在的 bsr* 指令,并且可能是一个很好的如果处理器不提供,则使用一些位旋转来实现。

版本

unsigned rounds(unsigned long n) {
    if (n <= 1) return n;
    return sizeof n * CHAR_BIT + 1 - __builtin_clzl(n-1);
}

使用bsrq 指令需要(在我的盒子上)0.165 秒来计算 rounds 1 到 100,000,000,bit-hack

unsigned rounds(unsigned n) {
    if (n <= 1) return n;
    --n;
    n |= n >> 1;
    n |= n >> 2;
    n |= n >> 4;
    n |= n >> 8;
    n |= n >> 16;
    n -= (n >> 1) & 0x55555555;
    n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
    n = (n & 0x0F0F0F0F) + ((n >> 4) & 0x0F0F0F0F);
    return ((n * 0x01010101) >> 24)+1;
}

耗时 0.626 秒,简单循环

unsigned rounds(unsigned n) {
    unsigned r = 1;
    while(n > 1) {
        ++r;
        n = (n+1)/2;
    }
    return r;
}

需要 1.865 秒。

如果你不使用固定宽度的类型,而是使用任意精度的整数,情况会有所改变。天真的循环(或递归)仍然使用Θ(log n) 步骤,但这些步骤平均花费Θ(log n) 时间(或更糟),所以总的来说你有一个Θ(log² n) 算法(或更糟)。那么使用上面的公式不仅可以提供一种具有较低常数因子的实现,而且可以提供一种算法复杂度较低的实现。

  • 比较 1 可以在恒定时间内完成合适的表示,O(log n) 是合理表示的最坏情况。
  • 从一个正整数中减去 1 会得到 O(log n) 的合理表示。
  • 对于某些表示,可以在恒定时间内计算以 2 为底的对数,对于其他合理的表示,可以在 O(log n) 中完成 [如果它们使用 2 的幂,所有任意精度库我对做半熟悉;如果他们使用 10 次方基数,那就不一样了]。

【讨论】:

    【解决方案2】:

    如果您认为算法是迭代的并且数字是二进制的,那么这个函数会移出最低位,如果移出的数字是 1,则将数字加 1。因此,除了增量之外,它计算数字中的位数(即最高1的位置)。增量最终会将结果加一,除非数字的形式为 1000...。因此,您将获得位数加一,或者如果数字是 2 的幂,则获得位数。根据您的机器型号,这可能比 O(log n) 的计算速度更快。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-06-08
      • 2020-01-10
      • 2019-01-28
      • 1970-01-01
      • 2018-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多