Algorithm 递归方法:如何生成大括号上的所有可能性?

Algorithm 递归方法:如何生成大括号上的所有可能性?,algorithm,recursion,data-structures,catalan,Algorithm,Recursion,Data Structures,Catalan,我们如何生成大括号上的所有可能性 N值已经给了我们,我们必须产生所有的可能性 示例: 1) 如果N==1,则只有一种可能性() 2) 如果N==2,则可能性为(()),()() 3) 如果N==3,那么可能性是((()),(()),()()(),()(),()(()) 注意:左大括号和右大括号应该匹配。我的意思是)(对于N==1是无效的 我们可以用递归方法解决这个问题吗?< /P> < P>对于给定的 n>代码>,我们必须从一个开放的括号开始。现在考虑它对应的闭合括号在哪里。它可以在中间,如()

我们如何生成大括号上的所有可能性

N值已经给了我们,我们必须产生所有的可能性

示例:

1) 如果N==1,则只有一种可能性()

2) 如果N==2,则可能性为(()),()()

3) 如果N==3,那么可能性是((()),(()),()()(),()(),()(())

注意:左大括号和右大括号应该匹配。我的意思是)(对于N==1是无效的


我们可以用递归方法解决这个问题吗?< /P> < P>对于给定的<代码> n>代码>,我们必须从一个开放的括号开始。现在考虑它对应的闭合括号在哪里。它可以在中间,如<代码>())/代码>或在结尾,如<代码>(())< /代码> <代码> n=2 < /C> >

现在考虑<代码> n=3 < /代码>:

它可以在末尾:
(())
(())

或者在中间:
()(())
()
位置2。然后它也可以在位置4:
(())()

现在,我们可以将这两种情况结合起来,实现闭合支撑位于末端的情况与位于中间的情况相同,但在末端添加了N=0的所有可能性

现在,为了解决这个问题,您可以计算出开始和结束支撑之间
n
的所有可能性,同样,您可以计算出结束支撑之后
m
的所有可能性。(注意
m+n+1=n
)然后,您可以组合所有可能的组合,将它们附加到可能的列表中,然后继续移动到端部支撑的下一个可能位置

请注意,对于这些类型的问题,容易犯的一个错误是找到
i
N-i
的所有可能性,然后将它们组合起来,但是对于
N=3
来说,这将加倍计算
()
,或者至少打印两次

下面是一些解决此问题的Python 2.x代码:

memoise = {}
memoise[0] = [""]
memoise[1] = ["()"]

def solve(N, doprint=True):
    if N in memoise:
        return memoise[N]

    memoise[N] = []

    for i in xrange(1,N+1):
        between = solve(i-1, False)
        after   = solve(N-i, False)
        for b in between:
           for a in after:
               memoise[N].append("("+b+")"+a)

    if doprint:
        for res in memoise[N]:
            print res

    return memoise[N]
来自维基百科-

Dyck字是由nx和ny组成的字符串,因此该字符串的初始段的Y数不超过X(另请参见Dyck语言)。例如,以下是长度为6的Dyck字:

将符号X重新解释为开括号,Y重新解释为闭括号,Cn统计包含n对正确匹配的括号的表达式数:

另见

摘要:提出了一种生成所有Dyck字的新算法, 用于对Dyck单词进行排序和取消排序。我们强调 在编码加泰罗尼亚语相关对象时使用Dyck词的重要性 数字。作为排名算法中使用的公式的结果 我们可以得到第n个加泰罗尼亚数的递归公式

递归解决方案:

import java.util.Scanner;

public class Parentheses
{

    static void ParCheck(int left,int right,String str)
    {
            if (left == 0 && right == 0)
            {
                    System.out.println(str);
            }

            if (left > 0)
            {
                    ParCheck(left-1, right+1 , str + "(");
            }
            if (right > 0)
            {
                    ParCheck(left, right-1, str + ")");
            }

    }
    public static void main(String[] args)
    {
            Scanner input=new Scanner(System.in);
            System.out.println("Enter the  number");
            int num=input.nextInt();

            String str="";
            ParCheck(num,0,str);
    }
} 

这里的一些代码本质上是JPvdMerwe代码的压缩版本,只是它返回解决方案列表而不是打印它们

来自itertools导入产品的

def solve(num,cache={0:['']}):
如果num不在缓存中:
缓存[num]=['(%s)%s'%t用于范围(1,num+1)中的i
对于乘积中的t(solve(i-1),solve(num-i))]
返回缓存[num]
#试验
对于范围(1,5)中的num:
打印(个)
对于求解中的s(num):
印刷品
输出

1
()
2
()()
(())
3
()()()
()(())
(())()
(()())
((()))
4
()()()()
()()(())
()(())()
()(()())
()((()))
(())()()
(())(())
(()())()
((()))()
(()()())
(()(()))
((())())
((()()))
(((())))
num: 4, Catalan number: 14
14
 0 [1, 3, 5, 7] [1, 3, 5, 7] 01010101 True
 1 [1, 3, 6, 7] [1, 3, 6, 7] 01010011 True
 2 [1, 4, 5, 7] [1, 4, 5, 7] 01001101 True
 3 [1, 4, 6, 7] [1, 4, 6, 7] 01001011 True
 4 [1, 5, 6, 7] [1, 5, 6, 7] 01000111 True
 5 [2, 3, 5, 7] [2, 3, 5, 7] 00110101 True
 6 [2, 3, 6, 7] [2, 3, 6, 7] 00110011 True
 7 [2, 4, 5, 7] [2, 4, 5, 7] 00101101 True
 8 [2, 4, 6, 7] [2, 4, 6, 7] 00101011 True
 9 [2, 5, 6, 7] [2, 5, 6, 7] 00100111 True
10 [3, 4, 5, 7] [3, 4, 5, 7] 00011101 True
11 [3, 4, 6, 7] [3, 4, 6, 7] 00011011 True
12 [3, 5, 6, 7] [3, 5, 6, 7] 00010111 True
13 [4, 5, 6, 7] [4, 5, 6, 7] 00001111 True

num: 10, Catalan number: 16796
ok

这里还有几个函数,它们来自Ed Guinness链接的文章中给出的伪代码:。那篇文章使用基于1的索引,但我已将它们转换为符合Python基于0的索引

这些函数比上面的
solve
函数慢,但它们可能仍然有用。
pos\u dyck\u words
的优点是它完全是迭代的。
unrank
是迭代的,但它调用递归助手函数
f
;OTOH,
f
使用缓存,因此它不会像可能的那样慢,而且它是s仅缓存整数,与
solve
的字符串缓存相比,它使用更少的RAM。
unrank
的主要优点是它可以从索引号返回单个解决方案,而不必生成给定大小的所有解决方案

此代码仅适用于Python3。将其转换为Python2使用非常简单,您只需实现自己的缓存方案,而不是
lru\u cache
。您确实需要缓存,否则
f
对于除最小Dyck字长以外的所有字长来说都非常慢

来自itertools导入产品的

从functools导入lru\U缓存
#递归生成长度为2*num的所有Dyck字
#最快,但不是按字典顺序排列的
def solve(num,cache={0:['']}):
如果num不在缓存中:
缓存[num]=['0%s1%s'%t,用于范围(1,num+1)中的i
对于乘积中的t(solve(i-1),solve(num-i))]
返回缓存[num]
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#“unrank”的辅助函数`
#f(i,j)给出了(0,0)和(i,j)之间不相交的路径数
#网格的对角线x==y。路径由水平和垂直两部分组成
#仅限线段,不允许使用对角线
@lru_缓存(无)
定义f(i,j):
如果j==0:
返回1
如果j==1:
返回i
#如果i#1
如中所述,
N
对正确匹配的括号字符串是Dyck单词的表示形式。在另一种有用的表示形式中,括号
分别替换为
1
0
。因此,
()()
变成
101010
。后者也可以看作是(十进制)数
42
的二进制表示。总之,一些整数可以表示str
1
()
2
()()
(())
3
()()()
()(())
(())()
(()())
((()))
4
()()()()
()()(())
()(())()
()(()())
()((()))
(())()()
(())(())
(()())()
((()))()
(()()())
(()(()))
((())())
((()()))
(((())))
num: 4, Catalan number: 14
14
 0 [1, 3, 5, 7] [1, 3, 5, 7] 01010101 True
 1 [1, 3, 6, 7] [1, 3, 6, 7] 01010011 True
 2 [1, 4, 5, 7] [1, 4, 5, 7] 01001101 True
 3 [1, 4, 6, 7] [1, 4, 6, 7] 01001011 True
 4 [1, 5, 6, 7] [1, 5, 6, 7] 01000111 True
 5 [2, 3, 5, 7] [2, 3, 5, 7] 00110101 True
 6 [2, 3, 6, 7] [2, 3, 6, 7] 00110011 True
 7 [2, 4, 5, 7] [2, 4, 5, 7] 00101101 True
 8 [2, 4, 6, 7] [2, 4, 6, 7] 00101011 True
 9 [2, 5, 6, 7] [2, 5, 6, 7] 00100111 True
10 [3, 4, 5, 7] [3, 4, 5, 7] 00011101 True
11 [3, 4, 6, 7] [3, 4, 6, 7] 00011011 True
12 [3, 5, 6, 7] [3, 5, 6, 7] 00010111 True
13 [4, 5, 6, 7] [4, 5, 6, 7] 00001111 True

num: 10, Catalan number: 16796
ok
integer next_dyck_word(integer w) {
    integer const a = w & -w;
    integer const b = w + a;
    integer c = w ^ b;
    c = (c / a >> 2) + 1;
    c = ((c * c - 1) & 0xaaaaaaaaaaaaaaaa) | b;
    return c;
}