バイナリツリーを列挙する


20

二分木

二分木は、3つのタイプのノードを持つツリーです。

  • 子を持たないターミナルノード
  • それぞれが1つの子を持つ単項ノード
  • それぞれ2つの子を持つバイナリノード

BNF(Backus–Naur形式)で与えられる次の文法でそれらを表現できます。

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

<terminal> ::= 
    "0"

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

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

この文法では、ノードは事前順序で指定され、各ノードは、子の数である数字で表されます。

モッツキン数

Motzkin番号(OEIS)(Wikipedia)には多くの解釈がありますが、1つの解釈は、nth Motzkin番号はnノードを持つ別個の二分木の数であるということです。Motzkin番号の表が始まります

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

たとえばM(5)、9であり、5つのノードを持つ9つの異なる二分木は

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ノードを持つすべての異なるバイナリツリーを出力します。

n読みやすくするために括弧を含む1〜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)

入力

入力は1つの正の整数になります。

出力

出力は、その数のノードを持つ明確なバイナリツリーのわかりやすい表現である必要があります。上記の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)

これを容易に理解するようにするには、いくつかのを変換[]する()ので、好き

[([])()]

今から始めたら

[]

次に、取得する2つの式を必要とするバイナリを挿入します

 [()()] which is 2

そして、最初の()のために、あなたが得る1つの式を必要とする単項式を挿入します

 [([])()] 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)  

重複したツリーをチェックできる場所

重複をチェックする1つの場所はM(5)です。
この1つのツリーは、M(4)ツリーからM(5)に対して2回生成されました

(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> ")"

Aはunary文字である(1ために構築することができるものが続くe続きます)

常に開始ルールから開始し<e>ます。これはこのためです。

いくつかの簡単な例:

最も単純なシーケンスはjust 0です。したがって、開始ルールから始めて、<e>3つの選択肢があることを確認します。

  <terminal>   
| <unary>
| <binary>

だから最初のものを取る<terminal>。現在、端末には選択肢がなく、です0。そのため、ルール内でを置き換える<terminal>と完了です。0<e>

その後、次の1です(1 0)。で始まる<e>ルール<unary>を使用する

"(1" <e> ")"

今、これが必要な<e>ので、戻って<e>3つのうちの1つを選択し、今回は選択<terminal>します0。交換0(1 <e> )与え(1 0)、これはに置き換えられ<unary>そう<e>です(1 0)


それで、二分木?「二分木は、各ノードに最大2つの子があるツリーデータ構造です」
-fəˈnɛtɪk

3
説明はバイナリツリーの説明です。二分木には2人の子供がいる必要はありません。それは彼らがせいぜい2人の子供を持っていることを意味します。単項バイナリは、より具体的な用語であり、実際には何の違いも意味しないと思います。
fəˈnɛtɪk

「BNF」は、コンピュータ科学者ではない人たちのもののために何であるか明確に考えてみましょう
ルイスMendo

1
@GuyCoder私のポイントは、誰かが「BNF」を見て、それが何を意味するのかわからない場合、彼らは延期されて読みを停止するかもしれないということです。おそらく、頭字語の代わりに名前を使用し、Wikipediaへのリンクを追加するだけで十分でしょう
ルイスメンドー

4
@ mbomb007名前が変更されました。そのためのピアプレッシャー賞を獲得する必要があります。:)
ガイCoderの

回答:


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)、したがって、2つの3ノードツリーは(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.


4

brainfuck、107バイト

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

フォーマット済み:

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

オンラインで試す

入力はbyteとして取得され、ツリー12100は次のように表され\x01\x02\x03\x02ます。tr/\x01\x02\x03/012/文字列を逆変換、変換、逆変換し、finalを追加し0ます。木はで区切られてい\xfeます。(出力は、例えば、最初のを変更することにより、読みやすくすることができる--36.+47.-47-3636個のストリング手段-文字等)

このアプローチでは、プロパティ(ベンフランケルも使用)を使用します:可能なノードを-1, 0, 1最終-1ノードとして考慮し、リストは、(1)リストのすべてのプレフィックスに負の合計がない場合にのみ有効なツリーを表します。 (2)リスト全体の合計がに等しい0。最初の条件は中間ノードの生成中に維持されるため、最後に確認する必要があるのは2番目の条件のみです。

テープは5ノードのセルに分割され、

i d x 0 0

where 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)

値は実際の(概念的な)値よりも1つまたは2つ大きい値として格納または初期化され、必要に応じて調整される場合があることに注意してください。


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]]:[[]]

where -1は、ターミナル、0単項ノード、および1バイナリノードを表します。m=14PCで遅くなり始めます。ツリーの最後から再帰的に戻ります。

  • 未計上のノードの数は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で入力番号です)。

基本的な考え方は、我々は数字のそれぞれの可能な文字列を生成することで01、そして2、その後、整形式の木あるものだけをフィルタリングします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.