2013-10-08 108 views
5

我正在掃描此代碼,並且我想了解它是如何工作的。瞭解純功能持久二叉樹

有兩種可能的樹:空樹的樹和由一個整數和兩個子樹組成的樹。不變量:對於每個節點,右側的節點包含的整數值高於父節點,而左側的節點包含低於父節點的整數值。

下面是代碼:

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 

} 

如何使用這個數據結構?它如何實現持久性?它是如何工作的?

+0

請注意,這不是一個純功能樹。它是一棵不變的樹,每個樹操作都會在內存中創建一個可以保存的類實例。 – 0kcats

回答

7

代碼直接映射到您提供的描述。

讓我們舉一個簡單的例子來演示用法:首先有一個空集,比如說e 然後給它添加一個元素來獲得另一個集合,比如s1。然後,您將有 2套,es1

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 

我猜你所熟悉的斯卡拉速記,可以讓你鍵入的 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)。

我們有兩種類型的樹集;空和非空,並且每個具有用於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到非空集,其元件比01少 x 給你另一個集,其中:
    • 根元素是一樣的原來設定
    • 子樹是不變的 - 一樣的,在原設定
    • 新權子樹變成原來的右子樹樹 x添加到它「:right incl x
  • 添加元素x到非空集,其元件比x 給你另一個集,其中更大
    • 根元素是相同的原始集合
    • right子樹不變 - 與原始集相同
    • 新左子樹成爲原始左子樹 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從其它套派生集(樹),在不改變原設定。這是因爲在非常階段,我們要麼 -

  1. 回報新基於離開的人,而不是修改 現有結構,
  2. 回報現有的結構,因爲它們的數據結構。

contains的工作方式相同:Empty有任何輸入返回false的實現。 NonEmpty如果給定元素與它的根相同,或者它的左邊或右邊的子樹都包含它,它會很快返回true!

+0

我們可以在永久樹上實現刪除操作嗎? – pannu

4

讓我們從incl開始。這是一個樹上的方法,它接受一個元素並創建一個等於當前樹但添加了元素的新樹。它在不修改原始樹的情況下執行此操作。這是處理這些樹作爲不可變數據結構的一部分,也是「持久」數據結構思想的核心。本質上,對於我們希望對樹進行的任何更改,我們希望創建一棵新樹並保留樹的以前狀態。我們也希望有效地創建新樹,創建儘可能少的新節點,並鏈接到現有節點,而不會影響原始節點。

以一個例子下面的樹:

4 
/\ 
    2 6 
/\ \ 
1 3 7 

如果我們想元素5添加到這一點,我們希望與落得:

 4 
/ \ 
    2  6 
/\ /\ 
1 3 5 7 

我們可以通過創建做到這一點包含指向包含2的現有節點(和附屬子樹)的元素4的新根節點以及包含6的新節點(其注意到調用的遞歸性質爲new NonEmpty(elem, left, right **incl** x))指向包含6的新節點,該新節點包含5和現有節點包含7.因此只有三個節點被創建,並且四個現有節點被重用。請注意,這不會影響可繼續引用包含1,2,3和7的葉節點的原始樹。