【问题标题】:Oriented forest TAoCP - Algorithm in python定向森林 TAoCP - python 中的算法
【发布时间】:2009-10-27 21:40:34
【问题描述】:

我尝试实现 Donald E. Knuth 的 Algorithm O (Oriented forests):“计算机编程的艺术 - 第 4 卷,Fascile 4,生成所有树”(第 24 页)。

我的 Python 解决方案是:

def generate_oriented_forest(n):
    """Algorithm O from Knuth TAoCP, Fascicle 4, p. 25. """
    p = range(-1, n)
    while True:
       yield p[1:]
       if p[n] > 0: p[n] = p[p[n]]
       else:
           k_largest =  0
           for k in range(1,n): 
               if p[k] != 0: k_largest = k
           k = k_largest
           if k == 0: return
           j =  p[k]
           d = k-j
           if p[k-d] == p[j]: p[k] = p[j]
           else: p[k] = p[k-d] + d
           while k != n:
               #print k, p
               k = k+1
               if p[k-d] == p[j]: p[k] = p[j]
               else: p[k] = p[k-d] + d

if __name__ == "__main__":
    for el in generate_oriented_forest(4):
        print el

    # According to page 23 and also Table 1 p.4 I would expect the sequence:
    # 0123, 0122, 0121, 0120, 0111, 0110, 0101, 0100, 0000

我的实现给了我:

[0, 1, 2, 3], [0, 1, 2, 2], [0, 1, 2, 1], [0, 1, 2, 0], [0, 1, 1, 1], [0, 1, 1, 0],

[0, 1, 0, 3],

[0, 1, 0, 0], [0, 0, 0, 0]。

我已经在寻找一个错误太久了。希望有人能指出我修复代码的正确方向。我对算法的理解正确吗?对我的 python 风格的任何改进也值得赞赏。感谢您的帮助。

【问题讨论】:

  • 首先 - 尝试将所有名称为 "j","d","k" ... 的变量重命名为更易读的名称。它有助于解决您的问题。
  • @Oduvan,我相信这些是文中使用的变量名
  • @gnibbler,如果您无法阅读代码,那么您将难以理解它,甚至无法修复它。 ;P
  • @The共产鸭,有意义的名字很重要,但是当从论文中实现算法时,使用与算法中使用的相同的变量名通常是个好主意,所以你的争吵是与 Knuth 的不使用更有意义的名称

标签: python algorithm taocp


【解决方案1】:

恭喜!

看来您刚刚在 TAOCP 中发现了一个错误。毫无疑问,您肯定知道,第一个发现此类错误的人将获得 1 美元的奖励(取自圣塞里费银行)。我有一个,我可以告诉你,把它贴在你的墙上是一件很糟糕的事情。

在我看来,步骤O5中的“+d”确实是错误的;至少,我找不到一种方法来将它与算法之前的文本描述中的“克隆”步骤的描述相协调。我检查了 V4f4 的最新勘误表,但其中没有这个,所以看起来你是第一个注意到这一点的人。

为了验证,我建议您计算带有和不带有“+d”的 n=5 的值,看看哪个与预期结果匹配。如果它按照我怀疑的方式进行,请将其写下来并通过电子邮件(TAOCP 错误的地址在他的网站上)连同您的邮政地址一起发送给 Knuth,您应该会在 6 个月内收到回复(通过纸质邮件) .

【讨论】:

  • 只需给他发一封电子邮件。希望我真的是第一个。获得他的证书会是 übergeekig...
  • @sdcwc: 不需要 :( -- 如果您阅读本文,您会发现 Knuth 的政策没有任何变化,除了他发出的支票不再可以兑现银行——它们取材于他的(虚构的)“圣塞里费银行”。正如他所指出的,无论如何,很少有人兑现它们,所以绝大多数人完全不受影响。我有一个他的“San Serife”支票,我可以告诉你,无论如何我都不会兑现它。
【解决方案2】:

我不了解算法,也不知道我建议的算法更改(如下)是否正确。我所知道的是它会产生您为 n=4 引用的预期输出:

def generate_oriented_forest(n):
    """Algorithm O from Knuth TAoCP, Fascicle 4, p. 25. """
    p = range(-1,n)
    while True:
        yield p[1:]
        if p[n] > 0:
            p[n] = p[p[n]]
            continue
        for k in range(n-1,0,-1):
            if p[k] != 0: break
        else:
            break
        j = p[k]
        d = k-j
        while True:
            if p[k-d] == p[j]:
                p[k] = p[j]
            else:
                p[k] = p[k-d]
            if k==n:
                break
            k+=1

我使用 gnibbler 的代码作为起点。我使用traceit() 和打印语句来跟踪代码,因为它从 [0, 1, 1, 0] --> [0, 1, 0, 3] 转换。

我发现这是变量的状态:

[0, 1, 1, 0]  # the last yield
k: 4
d: 2
j: 1
p: [-1, 0, 1, 0, 0]
[0, 1, 0, 3]  # the problem yield

这是该代码唯一一次执行:

__main__:19:             if p[k-d] == p[j]:
__main__:22:                 p[k] = p[k-d] + d

由于 p[k-d]=p[2]=1,并且您希望 p[k] 等于 1,我“猜测”正确的公式应该是 p[k]=p[k-d]。

我也变了

for k in range(n-1,-1,-1):

for k in range(n-1,0,-1):

阻止代码在末尾产生额外的 [0, 0, 0, 0]。

【讨论】:

  • 好的。谢谢你的帮助。我会尽快用其他 n 测试它。
  • 虽然在 Knuth's Book 中发现错误似乎有点奇怪。
  • 另一种可能是p[k]=-p[k-d]+d,但这种打印错误的可能性很小
  • 我认为这两个答案都是错误的。在 Knuth 的书中第 21 页,有一个表格,其中包含不同 n 的定向树的数量。等价于每个面向 n 节点的森林都是面向 (n+1) 的树。我们应该得到以下数量的结果: n = 4 (-> A_(n+1)) 应该给出 9 个序列 n = 5 应该给出 20 个序列 n = 6 应该给出 48 个序列
【解决方案3】:

我稍微重构了您的代码,主要是为了消除第 5 步中的重复。
但是输出仍然与您得到的相同

def generate_oriented_forest(n):
    """Algorithm O from Knuth TAoCP, Fascicle 4, p. 25. """
    p = range(-1,n)
    while True:
        yield p[1:]
        if p[n] > 0:
            p[n] = p[p[n]]
            continue
        for k in range(n-1,0,-1):
            if p[k] != 0: break
        else:
            break
        j = p[k]
        d = k-j
        while True:
            if p[k-d] == p[j]:
                p[k] = p[j]
            else:
                p[k] = p[k-d] + d
            if k==n:
                break
            k+=1

if __name__ == "__main__":
    for el in generate_oriented_forest(4):
        print el

【讨论】:

  • 正如我所提到的,这是因为这个算法产生的是parent代码,而不是level代码。对于一个级别的代码生成程序,上面的 foobar 有一个忠实的 B&H 翻译。
【解决方案4】:

我挖掘了原始文章:SIAM​​ Journal of Computing,T. Beyer 和 S.M. Hedetniemi,“有根树的恒定时间生成”。该论文很好地解释了该算法。这是我的实现:

def generate_oriented_forest_2(n): 
    """SIAM J. COMPUT. Vol. 9, No. 4, November 1980
    T. Beyer and S.M. Hedetniemi: constant time generation of rooted trees.""" 

    L = range(-1, n) 
    while True: 
        yield L[1:] 
        if L[n] > 0: L[n] = L[L[n]] 
        else:
            for p in range(n-1, 0, -1): 
                if L[p] != 0: break
            else: break
            for q in range(p-1, 0, -1):
                if L[q] == L[p] - 1: break 
            i = p
            while i <= n:
                L[i] = L[i-(p-q)]
                i += 1 

它给了我预期的结果。但我仍然想知道为什么 Knuth 的算法不起作用。发现会很酷。

【讨论】:

    【解决方案5】:

    答案是:每个人都是对的!

    Knuth 的算法计算父代码,而不是级别代码。似乎“THE Donald”仍在考虑父代码,即使同时指出 B & H 算法使用级别代码。

    但是,如果您查看算法 O 的文本,您应该注意到 Knuth 提到“每个规范森林都直接由其父指针序列 p 1...p n 在节点的前序中。”

    因此,该算法应该使用父代码,而不是级别代码。 那个Knuth,他是个狡猾的人…… 所以,我公然复制了 unutbu 的代码,想出一个看起来更像你写的代码生成版本:

    def generate_oriented_forest(n):
         """Algorithm O from Knuth TAoCP, Fascicle 4, p. 25. """
         p = range(-1,n)
         while True:
             yield p[1:]
             if p[n] > 0:
                 p[n] = p[p[n]]
                 continue
             for k in range(n-1,0,-1):
                 if p[k] != 0: break
             else:
                 break
             j = p[k]
             for q in range(1,k):
                 if p[k-q] == p[j]: break
             while True:
                 p[k] = p[k-q]
                 if k==n:
                     break
                 k+=1
    
     if __name__ == "__main__":
         for el in generate_oriented_forest(4):
             print el 
    

    希望这回答了您的问题。 :)

    【讨论】:

      猜你喜欢
      • 2015-07-16
      • 2015-02-12
      • 1970-01-01
      • 2021-07-23
      • 1970-01-01
      • 1970-01-01
      • 2020-03-14
      • 2021-06-01
      • 2022-01-20
      相关资源
      最近更新 更多