我正在扫描此代码,并且我想了解它是如何工作的。

有两种可能的树:用于空集的树,以及由整数和两个子树组成的树。不变式:对于每个节点,右侧的节点包含的整数值均高于父节点,而左侧节点的整数值则低于父节点。

这是代码:

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

我们有两种类型的树集:空和非空,每个都有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!

    10-01 14:30