[英]Understanding purely functional persistent binary trees
我正在掃描這段代碼,我想了解它是如何工作的。
有兩種可能的樹:一個用於空集的樹,以及一個由整數和兩個子樹組成的樹。 不變量:對於每個節點,右側的節點包含高於父節點的整數值,而左側節點包含低於父節點的整數值。
這是代碼:
abstract class IntSet{
def incl(x: Int): IntSet // include element x in the IntSet
def contains(x: Int): Boolean // is x an element of the set?
def union(other: IntSet): IntSet // union of this and other set
}
object Empty extends IntSet{
def contains(x: Int): Boolean = false
def incl(x:Int): IntSet = new NonEmpty(x, Empty, Empty)
override def toString = "."
def union(other:IntSet): IntSet = other
}
class NonEmpty(elem: Int, left: IntSet, right: IntSet) extends IntSet{
def contains(x: Int): Boolean =
if (x < elem) left contains x
else if (x > elem) right contains x
else true
def incl(x: Int): IntSet =
if (x < elem) new NonEmpty(elem, left incl x, right)
else if (x > elem) new NonEmpty(elem, left, right incl x)
else this
override def toString = "{" + left + elem + right + "}"
def union(other:IntSet): IntSet =
((left union right) union other) incl elem
}
這個數據結構是如何使用的? 它如何實現持久性? 它是如何工作的?
代碼直接映射到您提供的描述。
讓我們舉一個簡單的例子來演示用法:你必須先空集,說e
那么你給它添加一個元素來獲得另一套,說s1
。 然后你將有2套, e
和s1
:
val e = Empty
e contains 42 // will be false
// create s1 from e
val s1 = e incl 1 // e does not change; it remains a reference to the Empty set.
// Now we have s1, a set that should contain (1) and nothing else.
s1 contains 1 // will be true
s1 contains 42 // will be false
我猜你熟悉Scala簡寫,它允許你輸入s1 incl 1
而不是s1.incl(1)
請注意,只能有一個空集,所以這同樣好:
val s1 = Empty incl 1
然后讓我們說你要添加,比如2
來得到另一個集合s2
其元素必須包括{1, 2}
。
val s2 = s1 incl 2
s2 contains 1 // true
s2 contains 2 // true
s2 contains 3 // false
因此, incl
任何集合的方法接受一個元素並返回一個新集合 - 它不會更改集合(調用include
方法的原始對象ob)。
我們有兩種類型的樹集; empty和non-empty各有一個incl
的實現:
// Empty
def incl(x:Int): IntSet = new NonEmpty(x, Empty, Empty)
讀取: “向空(樹)集添加元素會產生另一個集合,該集合是非空樹,只有一個根節點值為1
且空左右子樹。”
非空集具有構造函數參數elem
,它表示樹的根,並且對於NonEmpty
所有方法都是可見的。
// Non-Empty
def incl(x: Int): IntSet =
if (x < elem) new NonEmpty(elem, left incl x, right)
else if (x > elem) new NonEmpty(elem, left, right incl x)
else this
讀取:(與上述if-else的順序相反):
x
添加到非空集合,其根元素也是x
為您提供相同的集合( this
) x
到非空集,其根元素是小於 x
為您提供了另一組,其中:
x
加上“: right incl x
x
添加到非空集,其根元素大於 x
為您提供另一個集合,其中:
x
添加到它“: left incl x
“持久性”是通過不改變任何樹或子樹的事實來實現的。 在示例中
val s1 = Empty incl 1 // s1 is a tree with only a root(1) an no branches.
val s2 = s1 incl 2 // s2 is another tree with -
// - the same root(1),
// - the same left-subtree as s1, (happens to be Empty)
// - a new subtree which in turn is a tree with -
// - the root element (2)
// - no left or right brances.
s1 contains 1 // true
s1 contains 2 // false
s2 contains 1 // true
s2 contains 2 // true
val s3 = s2 incl -3 // s2.incl(-3)
// from s2 we get s3, which does not change s2's structure
// in any way.
// s3 is the new set returned by incl, whose
// - root element remains (1)
// - left subtree is a new tree that contains
// just (-3) and has empty left, right subtrees
// - right subtree is the same as s2's right subtree!
s3.contains(-3) // true; -3 is contained by s3's left subtree
s3.contains(1) // true; 1 is s3's root.
s3.contains(2) // true; 2 is contained by s3's right subtree
s3.contains(5) // false
我們只使用incl
從其他集合中派生集合(樹),而不更改原始集合。 這是因為在非常階段,我們要么 -
contains
工作方式相同: Empty
有一個實現,對任何輸入都返回false
。 如果給定元素與它的根相同,或者左邊或右邊的子樹包含它,則NonEmpty
快速返回true!
讓我們從incl
開始吧。 這是一個樹上的方法,它接受一個元素並創建一個等於當前樹但添加了元素的新樹。 它在不修改原始樹的情況下執行此操作。 這是將這些樹作為不可變數據結構處理的所有部分,並且是“持久”數據結構概念的核心。 基本上,對於我們希望對樹進行的任何更改,我們希望創建一個新樹並保留樹的先前狀態。 我們還希望有效地創建新樹,盡可能少地創建新節點,並鏈接到現有節點,而這不會影響原始節點。
以下面的樹為例:
4
/ \
2 6
/ \ \
1 3 7
如果我們想要將元素5添加到此,我們希望最終得到:
4
/ \
2 6
/ \ / \
1 3 5 7
我們可以通過創建一個包含元素4的新根節點來實現這一點,該元素指向包含2的現有節點(和附加子樹),以及一個包含6的新節點,反過來(注意對new NonEmpty(elem, left, right **incl** x)
的調用的遞歸性質) new NonEmpty(elem, left, right **incl** x)
)指向包含5的新節點和包含7的現有節點。因此,僅創建了三個節點,並重用了四個現有節點。 請注意,這不會影響原始樹,它可以繼續引用包含1,2,3和7的葉節點。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.