簡體   English   中英

了解純功能持久二進制樹

[英]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套, 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

我猜你熟悉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從其他集合中派生集合(樹),而不更改原始集合。 這是因為在非常階段,我們要么 -

  1. 返回基於現有結構的新數據結構,而不是修改現有結構,OR,
  2. 按原樣返回現有結構。

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM