【问题标题】:Most efficient way to count a range of numbers without bias?无偏见地计算一系列数字的最有效方法?
【发布时间】:2011-10-31 16:40:48
【问题描述】:

我想将一系列数字打印到 STDOUT。但我不想从 0, 1 ,2 ... N-1, N 开始计数,而是想使用广度优先搜索进行迭代。我想用尽可能少/最不密集的指令来做到这一点(即没有分支)。

例如,假设范围是 [1, 128]。我想这样数:

64
32
96
16
87
...
128
1

坦率地说,我不在乎首先是广度还是深度,或者其他什么。我只想要一个均匀覆盖数轴的计数算法,这样如果数轴是一个跷跷板,它会从开始到算法结束都是平衡的。

不,这不是家庭作业:-P

编辑:寻找 O(n) 且不依赖于存储整个列表的东西。

【问题讨论】:

  • 不清楚您要完成什么。广度优先/深度优先是基于树的搜索算法,但您提到只是打印一个范围。您是否正在考虑在给定范围内选择随机值?
  • @vishakvkt 我不是在搜索,我只是在数数。但不是随机的。如果有意义的话,我想给数字线的每个部分一个公平的机会来计算每一步。
  • @Justin:我不明白......“每个部分”是什么?以及如何通过固定订单获得公平的机会?
  • @vishakvkt:我认为他试图获得相同的输出,就好像 0...N 被放入平衡二叉树中,然后输出广度优先。
  • 正是@derobert 所说的。

标签: c algorithm bit-manipulation


【解决方案1】:

使用反转的位作为键对数字进行排序。

这段 Python 代码演示了这个概念:

>>> sorted(range(1,128), key=lambda x: ('{:08b}'.format(x))[::-1])
[64, 32, 96, 16, 80, 48, 112, 8, 72, 40, 104, 24, 88, 56, 120, 4, 68, 36, 100, 20, 84, 52, 116, 12, 76, 44, 108, 28, 92, 60, 124, 2, 66, 34, 98, 18, 82, 50, 114, 10, 74, 42, 106, 26, 90, 58, 122, 6, 70, 38, 102, 22, 86, 54, 118, 14, 78, 46, 110, 30, 94, 62, 126, 1, 65, 33, 97, 17, 81, 49, 113, 9, 73, 41, 105, 25, 89, 57, 121, 5, 69, 37, 101, 21, 85, 53, 117, 13, 77, 45, 109, 29, 93, 61, 125, 3, 67, 35, 99, 19, 83, 51, 115, 11, 75, 43, 107, 27, 91, 59, 123, 7, 71, 39, 103, 23, 87, 55, 119, 15, 79, 47, 111, 31, 95, 63, 127]

查看每个数字的位模式可以了解其工作方式/原因:

>>> '{:08b}'.format(64)
'01000000'
>>> '{:08b}'.format(32)
'00100000'
>>> '{:08b}'.format(96)
'01100000'

注意,该过程也可以即时完成,不需要排序:

>>> [int('{:07b}'.format(i)[::-1], 2) for i in range(1, 128)]
[64, 32, 96, 16, 80, 48, 112, 8, 72, 40, 104, 24, 88, 56, 120, 4, 68, 36, 100, 20, 84, 52, 116, 12, 76, 44, 108, 28, 92, 60, 124, 2, 66, 34, 98, 18, 82, 50, 114, 10, 74, 42, 106, 26, 90, 58, 122, 6, 70, 38, 102, 22, 86, 54, 118, 14, 78, 46, 110, 30, 94, 62, 126, 1, 65, 33, 97, 17, 81, 49, 113, 9, 73, 41, 105, 25, 89, 57, 121, 5, 69, 37, 101, 21, 85, 53, 117, 13, 77, 45, 109, 29, 93, 61, 125, 3, 67, 35, 99, 19, 83, 51, 115, 11, 75, 43, 107, 27, 91, 59, 123, 7, 71, 39, 103, 23, 87, 55, 119, 15, 79, 47, 111, 31, 95, 63, 127]

在 C 中,反转位是一个简单的练习:

long reverse(long x) {
    long result = 0:
    int i;

    for (i=0 ; i<32 ; i++) {
        result <<= 1;
        result |= x & 1;
        x >>= 1;
    }
    return result;
}

【讨论】:

  • 谢谢。自从我在大学做这件事以来已经有几年了。我知道这行得通,但我需要查看更多位模式才能理解原因。
  • 我想知道是否有更快的方法——因为这不是特别快。您正在花费大量时间进行位反转。那么排序可能是 O(n·log n)。您可以使用 O(n) 排序,但常数项可能相当高,我们在这里谈论的是小的 n...
  • 在飞行中没问题:for (i=1 ; i
  • @Raymond Hettinger 也许我做错了什么,但是当我这样做时,我只会得到偶数。 limit = 64,并且循环设置为 8 位
  • @Justin:如果限制为 64,那么您的计数器应该只使用 6 位。 (从 0 到 63 的 8 位数字设置了高两位,所以当你反转它们时,它们都可以被 4 整除。)
【解决方案2】:

好的,我认为这可以通过最少的数据存储来实现。这仅适用于完全平衡的填充树。将其扩展到另一个元素很容易(只需打印最后一个...),但除此之外还需要更多思考。此外,如果您的范围不是 1…N,而是 M…N,那么只需将 M-1 添加到所有内容即可。

  1. 树的最下面一行是 1、1+2、1+4、1+6 等。
  2. 树的倒数第二行是 2、2+4、2+8 等。
  3. 树的倒数第三行是 4、4+8、4+16 等。

因此,对于 1..(N-1) 树,其中 N 是 2 的幂,我们可以首先使用 log₂(N) 计算树的高度。为方便起见,设 n = N-1。示例树为 N=16:

树的第一行(根)是((n-1)/2)+1。将此行称为 0。因此您可以继续打印它。下一行(第 1 行)的第一个元素是前一行第一个元素的一半。方便的是,增量是前一行的第一个值。因此,对于 N=16,第 2 行的第一个元素 = 4。您可以打印它。下一个元素是 4+8=12,可以打印。由于一行有 2*rownum 元素,您现在完成了第 0 行 (2**1 = 2) 下一行从前一行的一半开始,例如2/2 = 2。它有 2**2 = 4 个元素,增量为 4。所以,2、6、10、14。

               8

      4                12

  2       6       10         14
1   3   5   7   9   11    13    15

现在,如果你想要 1…16,你可以在右下角添加额外的元素,这样你就可以最后输出了。

当然,不是一直调用 pow(),您只需乘以 2(如果合适,编译器将转换为位移位)来确定该级别有多少节点。当然,仍然会有分支来测试循环。

我应该警告你,我今天还没有喝足够的茶,所以这显然很愚蠢。但它似乎工作。至少在我的脑海里:-D

【讨论】:

    【解决方案3】:

    也许你想要Gray code

    或一个简单的完整周期pseudo random number generator 限制在您的范围内

    【讨论】:

    • 这也是我的第一个想法,但他的示例模式与它的属性不匹配..
    • 那么 PNRG 肯定是最不“有偏见”的计数
    猜你喜欢
    • 2012-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-12
    • 1970-01-01
    • 1970-01-01
    • 2011-03-22
    • 2011-01-23
    相关资源
    最近更新 更多