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

}

这个数据结构是如何使用的?它是如何实现持久性的?它是如何工作的?

4

2 回答 2

7

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

让我们举一个简单的例子来演示用法:你首先有一个空集合,比如说,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)。

我们有两种类型的树集;空的和非空的,每个都有一个实现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元素为的非空集合中也会 得到相同的集合 ( )xthis
  • 将元素添加到其元素小于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. 根据现有结构返回新的数据结构,而不是修改现有结构,或者,
  2. 原样返回现有结构。

contains以同样的方式工作:Empty有一个返回false任何输入的实现。NonEmpty如果给定元素与其根元素相同,或者它的左子树或右子树包含它,则快速返回 true!

于 2013-10-08T04:21:18.890 回答
4

让我们从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))指向到一个包含 5 的新节点和包含 7 的现有节点。因此只创建了三个节点,并重用了四个现有节点。请注意,这不会影响原始树,它可以继续引用包含 1、2、3 和 7 的叶节点。

于 2013-10-08T03:48:54.120 回答