枚举二叉树


20

二叉树

二叉树是具有三种类型的节点的树:

  • 没有子节点的终端节点
  • 一元节点,每个节点有一个孩子
  • 二进制节点,每个都有两个子节点

我们可以用BNF(巴克斯-纳尔形式)中给出的以下语法来表示它们:

<e> ::= 
      <terminal>   
    | <unary>
    | <binary>

<terminal> ::= 
    "0"

<unary> ::= 
    "(1" <e> ")"

<binary> ::= 
    "(2" <e> " " <e> ")"

在此语法中,节点是按顺序排列的,每个节点都由一个数字表示,该数字表示其拥有的子代数。

莫兹金数

Motzkin数(OEIS)(Wikipedia)有多种解释,但一种解释是,nMotzkin数是具有n节点的不同二叉树的数目。莫兹金数表开始

N          Motzkin number M(N)
1          1
2          1
3          2 
4          4 
5          9 
6         21 
7         51 
8        127 
    ...

例如M(5)9,而9个具有5个节点的二叉树是

1      (1 (1 (1 (1 0))))  
2      (1 (1 (2 0 0)))  
3      (1 (2 0 (1 0)))  
4      (1 (2 (1 0) 0))  
5      (2 0 (1 (1 0)))  
6      (2 0 (2 0 0))  
7      (2 (1 0) (1 0))  
8      (2 (1 (1 0)) 0)  
9      (2 (2 0 0) 0)  

任务

以单个正整数n作为输入,并输出所有带有n节点的不同二叉树。

示例n1到5带有括号,以提高可读性

0

(1 0)

(1 (1 0))
(2 0 0)

(1 (1 (1 0)))
(1 (2 0 0))
(2 0 (1 0))
(2 (1 0) 0)

(1 (1 (1 (1 0))))
(1 (1 (2 0 0)))
(1 (2 0 (1 0)))
(1 (2 (1 0) 0))
(2 0 (1 (1 0)))
(2 0 (2 0 0))
(2 (1 0) (1 0))
(2 (1 (1 0)) 0)
(2 (2 0 0) 0)

输入值

输入将是一个正整数。

输出量

输出应该是具有那么多节点的不同二叉树的可理解表示。使用上面的BNF语法给出的确切字符串不是强制性的:使用的语法给出树的明确表示就足够了。例如,你可以使用[]的替代(),支架额外级别的[[]]代替[],外括号存在或缺失,额外的逗号或没有逗号,多余的空格,括号或没有括号,等等。

所有这些都是等效的:

(1 (2 (1 0) 0))  
[1 [2 [1 0] 0]]  
1 2 1 0 0  
12100  
(1 [2 (1 0) 0])  
.:.--  
*%*55  
(- (+ (- 1) 1))
-+-11

也是@xnor在评论中使用的变体。由于有一种方法可以将其转换为可以理解的格式,因此可以接受。

[[[]][]]  is (2 (1 0) 0)

为了使这更容易理解一些转换的[]()像现在这样

[([])()]

现在,如果您开始

[]

然后插入一个需要两个表达式的二进制文件

 [()()] which is 2

然后为第一个()插入一个需要一个表达式的一元

 [([])()] which is 21

但由于有[]()没有内括号可以表示0,因此不需要更多表达式,您可以将其解释为

 2100

请注意,答案应在理论上适用于无限内存,但对于依赖于实现的有限输入,显然会用光内存。

输出变化

BNF             xnor       Christian   Ben
b(t, b(t, t))   [{}{{}{}}] (0(00))     (1, -1, 1, -1)                         
b(t, u(u(t)))   [{}{(())}] (0((0)))    (1, -1, 0, 0)           
b(u(t), u(t))   [{()}{()}] ((0)(0))    (1, 0, -1, 0)                     
b(b(t, t), t)   [{{}{}}{}] ((00)0)     (1, 1, -1, -1)              
b(u(u(t)), t)   [{(())}{}] (((0))0)    (1, 0, 0, -1)                          
u(b(t, u(t)))   [({}{()})] ((0(0)))    (0, 1, -1, 0)                          
u(b(u(t), t))   [({()}{})] (((0)0))    (0, 1, 0, -1)                        
u(u(b(t, t)))   [(({}{}))] (((00)))    (0, 0, 1, -1)                          
u(u(u(u(t))))   [(((())))] ((((0))))   (0, 0, 0, 0)  

一个可能检查重复树的地方

一个检查重复项的地方是M(5)。
从M(4)个树中为M(5)生成了这棵树两次

(2 (1 0) (1 0))  

首先通过添加一元分支到

(2 (1 0) 0)

其次,通过添加一元分支到

(2 0 (1 0))

了解BNF

BNF由以下简单规则组成:

<symbol> ::= expression

左边是用包围的符号名称<>
右边是用于构造符号的表达式。一些规则在构造中使用其他规则,例如

<e> ::= <terminal>

e 可以是 terminal

某些规则具有用于构造符号的字符,例如

<terminal> ::= "0"

terminal 只是字符零。

一些规则具有多种构建方式,例如

<e> ::= 
      <terminal>   
    | <unary>
    | <binary>

一个e可以是一个<terminal>或一个<unary>或一个<binary>

有些规则是一系列的部分,例如

<unary> ::= "(1" <e> ")"

一个unary是人物(1,然后什么可以构造e之后)

您总是从起始规则开始,为此<e>

一些简单的例子:

最简单的序列是just 0。因此,我们从起始规则开始<e>,看看有三个选择:

  <terminal>   
| <unary>
| <binary>

所以拿第一个<terminal>。现在一个终端别无选择,是0。因此<terminal>0<e>规则中替换为,您就完成了。

然后下一个是(1 0)。首先<e>使用<unary>具有

"(1" <e> ")"

现在,这需要一个<e>,所以我们回去<e>,使三者之一的选择,这次选择,<terminal>这给0。更换0(1 <e> )(1 0),这被替换成<unary>这样<e>(1 0)


那么,二叉树?“二叉树是一个树数据结构,其中每个节点最多具有两个孩子”
fəˈnɛtɪk

3
您的描述是二叉树的描述。二叉树不需要有两个孩子。这只是意味着他们最多有两个孩子。我猜一元二进制只是一个更具体的术语,实际上并没有什么不同。
fəˈnɛtɪk

考虑澄清什么是“ BNF”对我们这些不是计算机科学家的人而言
Luis Mendo

1
@GuyCoder我的意思是,如果有人看到“ BNF”并且不知道那意味着他们可能会被推迟并停止阅读。也许使用名称而不是首字母缩写词并在Wikipedia上添加链接就足够了
Luis Mendo

4
@ mbomb007名称已更改。为此,我应该获得同仁压力奖。:)
Guy Coder

Answers:


12

Haskell,68个字节

t 0=[""]
t 1=["0"]
t n=['(':x++y++")"|k<-[1..n-1],x<-t k,y<-t$n-k-1]

终端节点用表示0,一元和二进制节点(e)分别用表示。(ee),因此将两个三节点树分别指定为(00)((0))

例子:

*Main> t 5
["(0(00))","(0((0)))","((0)(0))","((00)0)","(((0))0)","((0(0)))","(((0)0))","(((00)))","((((0))))"]
*Main> length $ t 8
127
*Main> length $ t 15
113634 

5

CJam(37字节)

0aa{_2m*2\f+1Y$f+++:e__&}qi:A*{,A=},p

在线演示。请注意,这不是很有效,并且您可能不想尝试5在线计算输入。

解剖要遵循。


5

Pyth(24 21 19字节)

这基于我的Python 3解决方案

f!|sTf<sY0._T^}1_1t

这是我第一次使用Pyth,因此仍然可以打高尔夫球。

例如,输入为时的输出4

[[1, 0, -1], [1, -1, 0], [0, 1, -1], [0, 0, 0]]

1表示二进制节点,0表示一元节点,-1表示终端节点。每棵树的末尾都有一个隐含的终端节点。

说明

f!|sTf<sY0._T^}1_1t
f                    filter
             ^    t  length n-1 lists of elements
              }1_1   from [1, 0, -1]
 !|                  for when both
   sT                sum of list is 0, and
     f    ._T        for each prefix of list,
      <sY0           sum of prefix is non-negative.

感兴趣的是:Pyth源代码
Guy Coder

4

Brainfuck,107个字节

,>++>-[-[<-[<-[>>[[>+<-]<]>+>[[<+>>>>>+<<<<-]>]>>++>,++++>]>[<+>-[+>>]>[<->[.<<<
<<]+[->+]+>>>]]]]<[[,<]<]<]

格式:

,>++>-
[
  -
  [
    <-
    [
      <-
      [
        >>
        [[>+<-]<]
        >+>
        [[<+> >>>>+<<<<-]>]
        >>++>,++++>
      ]
      >
      [
        <+>-
        [
          +>>
        ]
        >
        [
          <->[.<<<<<]
          +[->+]
          +>>>
        ]
      ]
    ]
  ]
  <
  [
    [,<]
    <
  ]
  <
]

在线尝试

输入被视为一个字节,树12100被表示为\x01\x02\x03\x02:转换回,转换tr/\x01\x02\x03/012/,反转字符串并添加final 0。树木之间用隔开\xfe。(输出可以更容易通过例如改变第一读--36.+47.-47,其中-36装置36个的字符串-字符等)

这种方法利用了该属性(本·弗兰克尔也使用了该属性):将可能的节点视为-1, 0, 1最终-1节点,而忽略最终节点,则当且仅当(1)列表的所有前缀的和为非负数时,列表才表示有效的树;和(2)整个列表的总和等于0。第一个条件在生成中间节点时得以维护,因此最后只需要检查第二个条件。

磁带分为5节点单元,

i d x 0 0

其中i是索引(从左到右降序),d是部分和,x是元素。

控制流程示意图:

take n and push initial node
while stack is non-empty:
    if rightmost node can be decremented:
        decrement rightmost node
        if there are less than n nodes:
            push new node
        else if valid tree:
            print
    else:
        backtrack (pop)

请注意,有时会将值存储或初始化为比实际(概念)值大一到两个,并根据需要进行调整。


3

Python 3中(138 134 128 121 119字节)

from itertools import*
lambda n:[any(sum(t[:k])<0for k in range(n))|sum(t)or print(t)for t in product(*[[-1,0,1]]*~-n)]

输出示例n=5

(0, 0, 0, 0)
(0, 0, 1, -1)
(0, 1, -1, 0)
(0, 1, 0, -1)
(1, -1, 0, 0)
(1, -1, 1, -1)
(1, 0, -1, 0)
(1, 0, 0, -1)
(1, 1, -1, -1)

1表示二进制节点,0表示一元节点,-1表示终端节点。每棵树的末尾都有一个隐含的终端节点。

该程序开始耗时太久n=17


3

JavaScript(Firefox 30-57),79个字节

f=(m,l=0)=>m?[for(n of[1,0,-1])if(l>n&l<=m+n)for(a of f(m-1,l-n))[...a,n]]:[[]]

其中,-1代表终端,0一元节点和1二元节点。m=14在我的PC上开始变慢。从树的末端递归地工作。

  • 由于l末尾可能只剩下1个节点,因此无法解释的节点数受到限制。
  • 下一个节点的类型n受限于需要有足够数量的未说明节点作为其子节点的需求。

2

序言,149个 144 138 137 131 107字节

e(L,L)-->[0].

e([_|A],L)--> 
    [1],
    e(A,L).

e([_,_|A],L)--> 
    [2],
    e(A,B), 
    e(B,L).

e(M,E):-                   
    length([_|L],M),        
    e(L,[],E,[]).           

?- e(5,S).
S = [1, 1, 1, 1, 0] ;
S = [1, 1, 2, 0, 0] ;
S = [1, 2, 0, 1, 0] ;
S = [1, 2, 1, 0, 0] ;
S = [2, 0, 1, 1, 0] ;
S = [2, 0, 2, 0, 0] ;
S = [2, 1, 0, 1, 0] ;
S = [2, 1, 1, 0, 0] ;
S = [2, 2, 0, 0, 0].

并计算解决方案

e_count(N,Count) :-
    length([_|Ls], N),
    findall(., phrase(e(Ls,[]),E), Sols),
    length(Sols, Count).

?- e_count(N,Count).
N = Count, Count = 1 ;
N = 2, Count = 1 ;
N = 3, Count = 2 ;
N = Count, Count = 4 ;
N = 5, Count = 9 ;
N = 6, Count = 21 ;
N = 7, Count = 51 ;
N = 8, Count = 127 ;
N = 9, Count = 323 ;
N = 10, Count = 835 ;
N = 11, Count = 2188 ;
N = 12, Count = 5798 ;
N = 13, Count = 15511 ;
N = 14, Count = 41835 ;
N = 15, Count = 113634 

1

Python,71个字节

f=lambda n:{(a+b,)for k in range(n)for a in f(k)for b in f(n+~k)}or[()]

这将树表示为诸如的嵌套元组((((),), ()),),可以((())())通过除去逗号,空格和最外面的来将其转换为树()

早期的76字节版本:

f=lambda n:{'('+a+b+')'for k in range(n)for a in f(k)for b in f(n+~k)}or['']

1

CJam,38个字节

使用Peter Taylor的CJam回答的另一种方法。

3rim*{:(1\+[{1$+}*])\:(_:z#|!},

输出将类似于1110120020102100。每棵树都是一组n数字(此处n是输入数字)。

其基本思想是,我们产生的数字每个可能的字符串012,然后筛选只有那些形成井树的人。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.