【问题标题】:Binary selection process二进制选择过程
【发布时间】:2012-08-08 18:24:50
【问题描述】:

我一直在从事一项看似简单却让我发疯的任务。因此,如果您喜欢编程挑战……请继续阅读。

我希望能够采用数字范围,例如[1:20] 并使用类似于二进制搜索算法的机制打印值。因此,首先打印最低值(在本例中为 1),然后是中间值(例如在本例中为 10),然后将范围分成四等份并打印 1/4 和 3/4 处的值(在本例中为 5和 15) 然后分成八等份,直到打印完范围内的所有值。

这个应用程序(这里实际上没有必要理解)是用于内存页面访问机制,当首先在中间范围访问页面时,它的行为会更有效。

对于这个问题,取任何数字范围并以上述方式打印值就足够了。

对此有什么想法吗?一个伪代码解决方案就可以了。我会对此进行尝试,但到目前为止我所尝试的一切都没有减少它。谢谢。

更新:根据要求,示例 [1:20] 的所需输出将是这样的:1、10、5、15、3、7、12、17、2、4、6、8、11 , 13, 16, 18, 9, 19, 20

根据所使用的算法,此输出可以以许多类似的方式呈现。但是,这个想法是首先显示半值,然后是四分之一,然后是八分之一,然后是 16 分等,而忽略先前呈现的值。

【问题讨论】:

  • 您能否为示例案例提供所需的完整输出?
  • 但您只能使用您分配的页面,对吗?
  • 对类似问题的回答可能会有所帮助:stackoverflow.com/a/11761192/1009831
  • 请忽略该问题的内存页面分配方面,因为它不是一个熟悉的问题。
  • 在我看来,从1 开始序列是一个错误,因为所有其他输出都是中点。

标签: python c algorithm binary-search


【解决方案1】:

这里有一些 Python 代码产生与您的示例类似的输出:

def f(low, high):
    ranges = collections.deque([(low, high)])
    while ranges:
        low, high = ranges.popleft()
        mid = (low + high) // 2
        yield mid
        if low < mid:
            ranges.append((low, mid))
        if mid + 1 < high:
            ranges.append((mid + 1, high))

例子:

>>> list(f(0, 20))
[10, 5, 15, 2, 8, 13, 18, 1, 4, 7, 9, 12, 14, 17, 19, 0, 3, 6, 11, 16]

low, high 范围不包括端点,这是 Python 中的约定,因此结果包含从 0 到 19 的数字。

代码使用 FIFO 来存储仍需要处理的子范围。 FIFO 以全范围初始化。在每次迭代中,弹出下一个范围并产生中点。然后,如果当前范围的上下子范围非空,则将它们附加到 FIFO 中。

编辑:这是 C99 中完全不同的实现:

#include <stdio.h>

int main()
{
    const unsigned n = 20;
    for (unsigned i = 1; n >> (i - 1); ++i) {
        unsigned last = n;    // guaranteed to be different from all x values
        unsigned count = 1;
        for (unsigned j = 1; j < (1 << i); ++j) {
            const unsigned x = (n * j) >> i;
            if (last == x) {
                ++count;
            } else {
                if (count == 1 && !(j & 1)) {
                    printf("%u\n", last);
                }
                count = 1;
                last = x;
            }
        }
        if (count == 1)
            printf("%u\n", last);
    }
    return 0;
}

这通过使用一些技巧来确定一个整数是否已经在早期的迭代中出现,从而避免了 FIFO 的必要性。

您还可以轻松地在 C 中实现原始解决方案。由于您知道 FIFO 的最大大小(我猜它类似于 (n+1)/2,但您需要仔细检查),您可以使用用于保存排队范围的环形缓冲区。

编辑 2:这是 C99 中的另一个解决方案。它被优化为只进行一半的循环迭代,并且只使用位运算和加法,没有乘法或除法。它也更简洁,并且在结果中不包含0,因此您可以按照最初的意图在开始时进行此操作。

for (int i = 1; n >> (i - 1); ++i) {
    const int m = 1 << i;
    for (int x = n; x < (n << i); x += n << 1) {
        const int k = x & (m - 1);
        if (m - n <= k && k < n)
            printf("%u\n", x >> i);
    }
}

(这是我从一开始就打算编写的代码,但我花了一些时间来理解它。)

【讨论】:

  • 这看起来很甜蜜。明天我会尝试这种方法并报告。谢谢。
  • 哇——这个答案很完美。我必须了解 deque 对象和 yield 特性——这两者都非常值得学习。对我来说不幸的是 - 我必须在 c 代码中实现这一点。我将需要创建一个子范围结构的 FIFO 列表,这在 c 中实现起来要困难得多。这强调了python对于这类问题的强大功能。有人有任何提示可以让我在 c 中实现吗?
  • @ZincX:我用一些提示更新了我的答案。为不明显的 C 代码道歉。
  • 我只能说哇!实际上,我继续在 c 中实现了您的原始算法并使其正常工作,但当然它使用了大量代码。这个秒解决方案完美地工作并且非常优雅。非常感谢您的帮助!
  • @ZincX:我终于找到了一个令我满意的算法版本——看我的第二次编辑。
【解决方案2】:

Binary Heap 作为数组已经有这个结构。您可能希望以这种形式存储数组并按顺序打印。对于节点i,子节点是2i + 12i + 2

【讨论】:

  • 想要的结果是一种非常不寻常的二叉堆形式(如果你真的这么称呼它的话):它将是一棵存储在数组中的二叉搜索树,就像你通常存储二叉树的方式一样堆。然而,tt 不满足堆属性,这个观察也不意味着构造这个数组的简单方法。
【解决方案3】:

嗯...您基本上是在寻找某种空间填充曲线。我几乎可以肯定,你可以通过巧妙的小技巧来做到这一点。您可能想看看在某些缓存无意识模板算法中使用的 Morton Z 顺序或 Ahnentafel 索引的索引计算方式。几年前我看过这个,索引与您描述的相似,并通过位旋转完成。

【讨论】:

  • 空间填充曲线是从区间到单位平方一对一映射的连续函数。这与提出的问题有什么关系?
【解决方案4】:

1/2 很容易,对吧?

那么为什么不递归地做呢,所以 1/4 是 1/2 的 1/2,而 1/8 是 1/4 的 1/2?

【讨论】:

  • 什么对½容易?你如何终止这个算法?您如何跟踪已经消耗的数字?
  • @SvenMarnach 在递归解决方案中,您会将序列分成几部分,然后再细分,依此类推。因此,如果您正确划分,则无需跟踪任何内容。当下一个除法结果为空序列时,终止发生。
  • @HonkyTonk:直接递归的方法会以错误的顺序产生值。我不确定你到底在说什么算法,但我想不出一个简单的递归解决方案。
猜你喜欢
  • 1970-01-01
  • 2017-07-22
  • 1970-01-01
  • 2015-12-29
  • 1970-01-01
  • 1970-01-01
  • 2017-09-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多