2011-12-07 25 views
16

tôi muốn đại diện cho một "cây" của hình dạng sau trong Haskell:Làm thế nào để đại diện cho cây với chia sẻ trong Haskell

/\        
    /\/\ 
/\/\/\ 
/\/\/\/\ 
` ` ` ` ` 

/và \ là những ngành, 'lá. Bạn có thể thấy rằng bắt đầu từ bất kỳ nút nào, theo con đường bên trái, sau đó bên phải sẽ đưa bạn đến cùng một nút như sau đường dẫn bên phải rồi sang trái. Bạn có thể gắn nhãn các lá, áp dụng một chức năng của hai phần tử ở mỗi nút và truyền thông tin này đến gốc trong thời gian O (n^2). Những nỗ lực ngây thơ của tôi đang cho tôi một thời gian chạy theo cấp số mũ. Bất kỳ gợi ý nào?

+0

Tôi hoàn toàn không có được mục đích của cây. Có thể sử dụng danh sách không? Nếu lá của bạn được dán nhãn từ trái sang phải với giá trị v1 đến v5, bạn có thể đại diện cho cây của bạn theo danh sách [v1, ..., v5] không? Ví dụ: để tra cứu giá trị, bạn chỉ phải đếm số bước phù hợp trong đường dẫn của mình để xác định giá trị chính xác trong danh sách. Nói cách khác, nếu bạn gắn nhãn một chiếc lá, bạn có muốn giữ cấu trúc chia sẻ không? Đó là, nếu chúng ta dán nhãn lá ở bên trái, trái, trái, phải, là lá ở bên trái, trái, phải, trái phải thay đổi là tốt? –

+1

Jan, tôi cũng muốn gắn nhãn các nút bên trong, dựa trên các giá trị ở các lá, và sau đó tìm kiếm thông tin này một cách hiệu quả tại một điểm tương lai trong chương trình. –

Trả lời

20

Nó chắc chắn có thể xây dựng một cây có nút được chia sẻ. Ví dụ, chúng ta chỉ có thể định nghĩa:

data Tree a = Leaf a | Node (Tree a) (Tree a) 

và sau đó cẩn thận xây dựng một giá trị thuộc loại này như trong

tree :: Tree Int 
tree = Node t1 t2 
    where 
    t1 = Node t3 t4 
    t2 = Node t4 t5 
    t3 = Leaf 2 
    t4 = Leaf 3 
    t5 = Leaf 5 

để đạt được chia sẻ subtrees (trong trường hợp này t4).

Tuy nhiên, như hình thức chia sẻ này không thể quan sát được trong Haskell, nó là rất khó để duy trì: ví dụ nếu bạn đi qua một cây để relabel lá

relabel :: (a -> b) -> Tree a -> Tree b 
relabel f (Leaf x) = Leaf (f x) 
relabel f (Node l r) = Node (relabel f l) (relabel f r) 

bạn lỏng lẻo chia sẻ. Ngoài ra, khi thực hiện tính toán từ dưới lên, chẳng hạn như

sum :: Num a => Tree a -> a 
sum (Leaf n) = n 
sum (Node l r) = sum l + sum r 

bạn sẽ không tận dụng lợi thế của việc chia sẻ và có thể trùng lặp.

Để khắc phục những vấn đề này, bạn có thể làm cho việc chia sẻ rõ ràng (và do đó quan sát được) bằng cách mã hóa cây của bạn trong một cách đồ thị như:

type Ptr = Int 
data Tree' a = Leaf a | Node Ptr Ptr 
data Tree a = Tree {root :: Ptr, env :: Map Ptr (Tree' a)} 

Cây từ ví dụ trên có thể được viết như

tree :: Tree Int 
tree = Tree {root = 0, env = fromList ts} 
    where 
    ts = [(0, Node 1 2), (1, Node 3 4), (2, Node 4 5), 
      (3, Leaf 2), (4, Leaf 3), (5, Leaf 5)] 

giá phải trả là chức năng mà đi qua những cấu trúc có phần rườm rà để viết, nhưng bây giờ chúng ta có thể xác định ví dụ như một chức năng ghi nhãn lại rằng bảo tồn chia sẻ

relabel :: (a -> b) -> Tree a -> Tree b 
relabel f (Tree root env) = Tree root (fmap g env) 
    where 
    g (Leaf x) = Leaf (f x) 
    g (Node l r) = Node l r 

sum chức năng không trùng lặp với công việc khi cây đã chia sẻ nút:

sum :: Num a => Tree a -> a 
sum (Tree root env) = fromJust (lookup root env') 
    where 
    env' = fmap f env 
    f (Leaf n) = n 
    f (Node l r) = fromJust (lookup l env') + fromJust (lookup r env') 
+3

Cảm ơn Stefan! Hình thức đầu tiên của bạn là những gì tôi đã có ban đầu, và khi bạn nói rằng thật khó để duy trì việc chia sẻ. Tôi đã hy vọng có một phiên bản mà không yêu cầu nhãn rõ ràng (Ints trong trường hợp của bạn) nhưng có lẽ đó là không thể. –

+6

Tại Dutch FP Day của năm nay, tôi đã nói về cách tận dụng tối đa cả hai thế giới: vẫn viết các chức năng của bạn như thể bạn đang đi ngang qua cây (sử dụng hình ảnh và tương tự), trong khi có lợi ích của việc chia sẻ quan sát. Bạn có thể tìm thấy các trang trình bày cho cuộc nói chuyện đó trên trang web của tôi: http://www.holdermans.nl/talks/assets/nlfp11.pdf. Một bài báo về chủ đề này đang chuẩn bị. –

+0

@dblhelix - một vài năm sau đó, giấy đó đã từng hiện thực hóa chưa? – ajp

2

lẽ bạn có thể đại diện cho nó đơn giản là một danh sách các lá và áp dụng các mức chức năng theo trình độ cho đến khi bạn xuống đến một giá trị, tức là một cái gì đó như thế này:

type Tree a = [a] 

propagate :: (a -> a -> a) -> Tree a -> a 
propagate f xs = 
    case zipWith f xs (tail xs) of 
    [x] -> x 
    xs' -> propagate f xs' 
+0

Chắc chắn, nhưng tôi cũng muốn giữ các nút trung gian có sẵn để kiểm tra và điều hướng hiệu quả từ một nút đến con cháu của nó. –

Các vấn đề liên quan