我正在扫描此代码,我想了解它是如何工作的。
有两种可能的树:一个用于空集的树,以及一个由整数和两个子树组成的树。不变量:对于每个节点,右侧的节点包含高于父节点的整数值,而左侧节点包含低于父节点的整数值。
以下是代码:
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 :(得分: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
且空左右子树。”< / em>的
非空集有一个构造函数参数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
添加到 root 元素小于 x
的非空集
为您提供另一个集,其中:
x
已添加“right incl x
x
添加到 root 元素大于 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!
答案 1 :(得分:4)
让我们从incl
开始吧。这是一个树上的方法,它接受一个元素并创建一个等于当前树但添加了元素的新树。它在不修改原始树的情况下执行此操作。这是将这些树作为不可变数据结构处理的所有部分,并且是“持久”数据结构概念的核心。基本上,对于我们希望对树进行的任何更改,我们希望创建一个新树并保留树的先前状态。我们还希望有效地创建新树,尽可能少地创建新节点,并链接到现有节点,而这不会影响原始节点。
以下面的树为例:
4
/ \
2 6
/ \ \
1 3 7
如果我们想要将元素5添加到此,我们希望最终得到:
4
/ \
2 6
/ \ / \
1 3 5 7
我们可以通过创建一个包含元素4的新根节点来实现这一点,该元素指向包含2的现有节点(和附加子树),以及一个包含6的新节点,该节点反过来(注意调用的递归性质) to new NonEmpty(elem, left, right **incl** x)
)指向一个包含5的新节点和包含7的现有节点。因此只创建了三个节点,并重用了四个现有节点。请注意,这不会影响原始树,它可以继续引用包含1,2,3和7的叶节点。