Liệt kê cây nhị phân


20

Cây nhị phân

Cây nhị phân là cây có các nút gồm ba loại:

  • các nút thiết bị đầu cuối, không có con
  • các nút unary, mỗi nút có một con
  • các nút nhị phân, có hai con mỗi

Chúng ta có thể biểu diễn chúng bằng ngữ pháp sau, được đưa ra trong BNF (dạng BackusIP Naur):

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

<terminal> ::= 
    "0"

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

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

Trong ngữ pháp này, các nút được đưa ra theo thứ tự và mỗi nút được biểu thị bằng một chữ số là số con mà nó có.

Số Motzkin

Số Motzkin ( OEIS ) ( Wikipedia ) có nhiều cách hiểu, nhưng một cách giải thích là nsố Motzkin là số cây nhị phân riêng biệt có nnút. Một bảng số Motzkin bắt đầu

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

ví dụ M(5)là 9 và chín cây nhị phân riêng biệt có 5 nút là

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)  

Bài tập

Lấy một số nguyên dương duy nhất nlàm đầu vào và đầu ra tất cả các cây nhị phân riêng biệt có ncác nút.

Ví dụ ntừ 1 đến 5 có dấu ngoặc đơn để dễ đọc

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)

Đầu vào

Đầu vào sẽ là một số nguyên dương.

Đầu ra

Đầu ra phải là một đại diện dễ hiểu của các cây nhị phân riêng biệt với nhiều nút đó. Không bắt buộc phải sử dụng chuỗi chính xác được cung cấp bởi ngữ pháp BNF ở trên: cú pháp được sử dụng cung cấp một biểu diễn rõ ràng của các cây. Ví dụ: bạn có thể sử dụng []thay vì (), một mức dấu ngoặc bổ sung [[]]thay vì []dấu ngoặc đơn bên ngoài có hoặc thiếu, dấu phẩy thừa hoặc không có dấu phẩy, dấu cách thêm, dấu ngoặc đơn hoặc không dấu ngoặc đơn, v.v.

Tất cả những thứ này là tương đương:

(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

Cũng là một biến thể được xác định bởi @xnor trong một bình luận. Vì có một cách để dịch cái này sang một định dạng có thể hiểu được nên nó được chấp nhận.

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

Để làm điều này dễ hiểu chuyển đổi một số []để ()thích quá

[([])()]

Bây giờ nếu bạn bắt đầu với

[]

sau đó chèn một nhị phân cần hai biểu thức bạn nhận được

 [()()] which is 2

và sau đó cho lần đầu tiên () chèn một unary cần một biểu thức bạn nhận được

 [([])()] which is 21

nhưng vì []hoặc ()không có dấu ngoặc trong có thể biểu thị 0 mà không cần thêm biểu thức nào bạn có thể hiểu nó là

 2100

Lưu ý rằng các câu trả lời sẽ hoạt động trên lý thuyết với bộ nhớ vô hạn, nhưng rõ ràng sẽ hết bộ nhớ cho đầu vào hữu hạn phụ thuộc vào việc thực hiện.

Biến thể đầu ra

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ột nơi có thể để kiểm tra các cây trùng lặp

Một nơi để kiểm tra trùng lặp là với M (5).
Cây này được tạo hai lần cho M (5) từ M (4) cây

(2 (1 0) (1 0))  

đầu tiên bằng cách thêm một nhánh unary vào

(2 (1 0) 0)

và thứ hai bằng cách thêm một nhánh unary vào

(2 0 (1 0))

Hiểu về BNF

BNF bao gồm các quy tắc đơn giản:

<symbol> ::= expression

trong đó bên trái là một tên biểu tượng được bao quanh bởi <>.
Bên phải là biểu thức để xây dựng biểu tượng. Một số quy tắc sử dụng các quy tắc khác trong xây dựng, ví dụ

<e> ::= <terminal>

e có thể là một terminal

và một số quy tắc có các ký tự được sử dụng để xây dựng biểu tượng, ví dụ:

<terminal> ::= "0"

terminal chỉ là số không.

Một số quy tắc có nhiều cách để xây dựng chúng, ví dụ:

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

An ecó thể là a <terminal>hoặc a <unary>hoặc a <binary>.

Và một số quy tắc là một chuỗi các bộ phận, ví dụ

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

A unarylà các ký tự (1theo sau bởi những gì có thể được xây dựng để etheo sau ).

Bạn luôn bắt đầu với quy tắc bắt đầu, mà cho việc này <e>.

Một số ví dụ đơn giản:

Trình tự đơn giản nhất là chỉ 0. Vì vậy, chúng tôi bắt đầu với quy tắc bắt đầu <e>và thấy rằng có ba lựa chọn:

  <terminal>   
| <unary>
| <binary>

vì vậy hãy là người đầu tiên <terminal>. Bây giờ một thiết bị đầu cuối không có sự lựa chọn và là 0. Vì vậy, thay thế <terminal>bằng 0trong <e>quy tắc và bạn đã hoàn tất.

Sau đó, tiếp theo là (1 0). Bắt đầu với <e>và sử dụng quy tắc <unary>

"(1" <e> ")"

Bây giờ điều này cần một <e>vì vậy chúng tôi quay trở lại <e>và đưa ra lựa chọn của một trong ba, lần này là lựa chọn, <terminal>mang lại 0. Thay thế 0vào (1 <e> )cho (1 0), và điều này được thay thế vào <unary>nên <e>(1 0).


Vì vậy, một cây nhị phân? "Cây nhị phân là cấu trúc dữ liệu cây, trong đó mỗi nút có nhiều nhất hai con"
fəˈnɛtɪk

3
Mô tả của bạn là của cây nhị phân. Cây nhị phân không cần có 2 con. Nó chỉ có nghĩa là họ có nhiều nhất là 2 con. Tôi đoán unary-binary chỉ là một thuật ngữ cụ thể hơn mà thực sự không có nghĩa gì khác.
fnɛtɪk

Hãy xem xét làm rõ "BNF" là gì đối với những người trong chúng ta không phải là nhà khoa học máy tính
Luis Mendo

1
@GuyCoder Quan điểm của tôi là, nếu ai đó nhìn thấy "BNF" và không biết điều đó có nghĩa là họ có thể bị tắt và ngừng đọc. Có lẽ sử dụng tên thay vì từ viết tắt và thêm một liên kết đến Wikipedia sẽ đủ
Luis Mendo

4
@ mbomb007 Tên đã thay đổi. Tôi sẽ nhận được một giải thưởng áp lực cho điều đó. :)
Guy Coder

Câu trả lời:


12

Haskell, 68 byte

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

Các nút đầu cuối được đại diện bởi 0, các nút đơn và nhị phân theo (e)resp. (ee), vì vậy hai cây ba nút được cho là (00)((0)).

Ví dụ:

*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 byte)

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

Bản demo trực tuyến . Lưu ý rằng điều này không hiệu quả lắm và có lẽ bạn không muốn thử tính toán đầu vào 5trực tuyến.

Mổ xẻ để làm theo.


5

Pyth ( 24 21 19 byte)

Điều này dựa trên giải pháp Python 3 của tôi .

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

Đây là lần đầu tiên tôi sử dụng Pyth nên có khả năng nó vẫn có thể chơi được.

Ví dụ , đầu ra khi đầu vào là 4:

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

1 đại diện cho nút nhị phân, 0 đại diện cho nút đơn và -1 đại diện cho nút cuối. Có một nút thiết bị đầu cuối ngụ ý ở cuối mỗi cây.

Giải thích :

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

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

Định dạng:

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

Dùng thử trực tuyến

Đầu vào được lấy dưới dạng byte và cây 12100được biểu diễn dưới dạng \x01\x02\x03\x02: để chuyển đổi lại, dịch tr/\x01\x02\x03/012/, đảo ngược chuỗi và thêm một kết thúc 0. Cây được ngăn cách bởi \xfe. (Đầu ra có thể được đọc dễ dàng hơn bằng cách thay đổi đầu tiên -thành -36.vào +47.-47, trong đó -36có nghĩa là một chuỗi gồm 36 -ký tự, v.v.)

Cách tiếp cận này sử dụng thuộc tính (mà Ben Frankel cũng đã sử dụng): xem xét các nút có thể -1, 0, 1và bỏ qua -1nút cuối cùng , một danh sách đại diện cho một cây hợp lệ khi và chỉ khi (1) tất cả các tiền tố của danh sách có tổng không âm, và (2) tổng của toàn bộ danh sách bằng 0. Điều kiện đầu tiên được duy trì trong khi tạo các nút trung gian, do đó chỉ cần kiểm tra điều kiện thứ hai ở cuối.

Băng được chia thành các ô 5 nút,

i d x 0 0

trong đó ichỉ số (giảm dần từ trái sang phải), dlà tổng một phần và xlà phần tử.

Phác thảo dòng điều khiển:

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)

Lưu ý rằng đôi khi một giá trị được lưu trữ hoặc khởi tạo là một hoặc hai lớn hơn giá trị thực tế (khái niệm) và được điều chỉnh khi cần thiết.


3

Python 3 ( 138 134 128 121 119 byte)

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

Ví dụ đầu ra, cho 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 đại diện cho nút nhị phân, 0 đại diện cho nút đơn và -1 đại diện cho nút cuối. Có một nút thiết bị đầu cuối ngụ ý ở cuối mỗi cây.

Chương trình bắt đầu mất quá nhiều thời gian xung quanh n=17.


3

JavaScript (Firefox 30-57), 79 byte

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

Trong đó -1đại diện cho một thiết bị đầu cuối, 0một nút đơn và 1một nút nhị phân. Bắt đầu chậm m=14trên PC của tôi. Đệ quy hoạt động trở lại từ cuối cây.

  • Số lượng các nút không xác định lđược giới hạn bởi thực tế là chỉ có thể có 1 nút còn lại ở cuối.
  • Loại nút tiếp theo nbị giới hạn bởi sự cần thiết phải có đủ các nút không được đếm cho các nút của nó.

2

Prolog, 149 144 138 137 131 107 byte

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].

Và để đếm các giải pháp

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 byte

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

Điều này đại diện cho cây như các bộ dữ liệu lồng nhau như ((((),), ()),), có thể được chuyển đổi ((())())bằng cách loại bỏ dấu phẩy, dấu cách và ngoài cùng ().

Phiên bản 76 byte trước đó:

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 byte

Sử dụng một cách tiếp cận khác nhau mà câu trả lời CJam của Peter Taylor.

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

Đầu ra sẽ là một cái gì đó như 1110120020102100. Mỗi cây là một nhóm các nchữ số (trong đó nlà số đầu vào).

Ý tưởng cơ bản là chúng ta tạo ra mỗi chuỗi có thể có của chữ số 0, 12, và sau đó lọc chỉ những người mà là cây well-formed.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.