【问题标题】:Generate permutation using a single stack使用单个堆栈生成排列
【发布时间】:2011-06-25 18:39:37
【问题描述】:

任何人都可以解释算法以在仅使用单个堆栈时生成可能的排列,并且推送和弹出是唯一允许的操作。 搜索了很多,但没有明确的答案。 这种排列的总数也由加泰罗尼亚数字给出。但我没有得到证明。请尽可能解释一下。

谢谢!!

【问题讨论】:

  • 我不明白“单栈”的要求。如果我使用一个额外的数据结构,它不是一个“堆栈”,而是一个“数组”或“队列”或“列表”。会这样吗?我猜这是不允许的。但在那种情况下,我不明白这怎么可能。为了检索堆栈中深度n 的元素,您必须弹出并存储位于其顶部的n 元素,从而需要额外的O(n) 内存。我错过了什么吗?
  • 另外,可以使用语言级递归来实现它,这意味着第二个堆栈将隐式作为嵌套函数调用的本地上下文的“堆栈”存在.这是否允许?
  • 我猜这是关于递归的一些奇怪的作业? 编辑哈哈奥黛丽,你打出来的,而我正在做同样的事情。
  • 排列应该如何表示?它是在运行时固定的还是动态指定的?应该如何处理输出?是一一发送到只读输出队列中,还是简单地存储在堆栈中?另外如何指定输入?它是否再次预加载到堆栈中?允许多少暂存内存?
  • 单栈是指只允许推送和便便操作。输入采用队列的形式,如 1 2 3 4 5,一旦元素被弹出,它就会被发送到输出队列并且不能被推回。

标签: algorithm stack permutation catalan


【解决方案1】:

这个问题使用输入队列和输出队列以及堆栈。

操作是“将一个项目从输入队列推入堆栈”和“将一个项目从堆栈弹出到输出队列”。

                  1 2 3
output  ______   ______  input  
              \ /
        <--+   |   +---
       pop |   |   | push
           |   |   v

             stack

例如,输入1 2 3,可以得到输出2 1 3,顺序如下:

  • 将 1 从输入推入堆栈
  • 将 2 从输入推到堆栈
  • 从堆栈弹出 2 到输出
  • 从栈中弹出 1 到输出
  • 将 3 从输入推送到堆栈
  • 从堆栈弹出 3 到输出

但是如果你尝试生成3 1 2,你会遇到困难。


如何通过这些操作生成所有可能的排列?好吧,递归执行很简单:在任何给定状态下(“状态”由输入队列、堆栈和输出队列的内容组成),您最多可以执行两种可能的操作(您可以推送如果输入队列中至少有一项;如果堆栈上至少有一项,则可以弹出),这将为您提供最多两种可能的新状态供您探索。

有关此问题的更多详细信息以及与加泰罗尼亚数字的关系,请查找 Knuth 的“计算机编程的艺术”第 1 卷(第 3 版)的副本 - 在 §2.2.1 中进行了讨论;请参阅第 242-243 页上的练习 2 - 5(以及第 240 页上我的图表的更好版本!)。

【讨论】:

    【解决方案2】:

    首先,在以下假设下,不可能为任意排列编写算法:

    1. 您只能按顺序从输入中读取。

    2. 类似的写入输出顺序发生,写入输出的数据一旦写入就无法读取。

    3. 除了一个堆栈之外,您只允许使用恒定数量的内存。 (这意味着没有额外的递归或数据结构)。

    这是上下文无关语言引理的结果:

    维基:http://en.wikipedia.org/wiki/Pumping_lemma_for_context-free_languages

    (或者也可以查看:Michael Sipser (1997)。计算理论导论。我相信这是第 4 章中的练习之一。)

    现在您可以轻松地实现一种算法,通过打破其中任何一个假设来解决此问题。例如,如果您可以从输入中任意读取,则不需要堆栈:

    def permute(seq, permutation):
        result = []
        for i in permutation:
            result.push(seq[i])
        return result
    

    或者如果你修复了一个排列,问题就变得有限,你同样不需要堆栈。您只需将常用算法展开为所有输入的特殊情况(即就像在编译器中进行部分评估一样)。这太可怕了,所以我不会费心写出所有细节,但它仍然有效,因为可能输入的总数是一个固定(但很大!)常数。

    【讨论】:

      【解决方案3】:

      我正在考虑同样的问题,最后编写了一个小的 Prolog 程序来生成排列,并“发现”了与加泰罗尼亚数字的关系,然后找到了你的问题。所以这并不是你问题的真正答案,但这里是 Prolog 程序:

      % Generate permutation counts
      count_pushpop(N-K) :-
          length(_, N),
          findall(Seq, pushpop(N, Seq), Seqs),
          length(Seqs, K).
      
      
      % Create an integer sequence from 1 to N
      % and permutate it using all possible push-pop
      % operations starting with an empty stack.
      pushpop(N, Seq) :-
          numlist(1, N, List),
          pushpop(List, [], Seq).
      
      
      % Generate all the possible ways a list
      % of items can be pushed into a stack
      % and poped out of it.
      pushpop([], [], []).
      
      pushpop([H | List], Stack, Seq) :-
          pushpop(List, [H | Stack], Seq).
      
      pushpop(List, [H | Stack], [H | Seq]) :-
          pushpop(List, Stack, Seq).
      

      证明并非所有n! 排列都是可能的:

      ?- findall(Seq, pushpop(3, Seq), Seqs).
      Seqs = [[3, 2, 1], [2, 3, 1], [2, 1, 3], [1, 3, 2], [1, 2, 3]].
      

      证明它生成了加泰罗尼亚数字(或者如果不是堆栈溢出,这将作为证明;)):

      ?- count_pushpop(N-K).
      N = K, K = 0 ;
      N = K, K = 1 ;
      N = K, K = 2 ;
      N = 3,
      K = 5 ;
      N = 4,
      K = 14 ;
      N = 5,
      K = 42 ;
      N = 6,
      K = 132 ;
      N = 7,
      K = 429 ;
      N = 8,
      K = 1430 ;
      N = 9,
      K = 4862 ;
      N = 10,
      K = 16796 ;
      N = 11,
      K = 58786 ;
      N = 12,
      K = 208012 ;
      ERROR: Out of global stack
      

      【讨论】:

        猜你喜欢
        • 2012-01-18
        • 1970-01-01
        • 1970-01-01
        • 2015-01-14
        • 2016-02-26
        • 2014-06-08
        • 2020-06-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多