递归集合联合:它是如何工作的?

时间:2013-04-25 14:21:41

标签: scala recursion functional-programming theory

我目前正在下班后的空闲时间参加Coursera的Scala课程,试图最终尝试功能性编程。我目前正在进行一项任务,我们应该“计算”包含一些对象的两个集合的并集。我故意省略细节,因为这对我在这里要问的内容并不重要。然而,相关的是集合被定义为二叉树,每个节点包含一个元素和两个子树。

情况确实如此;讲座中的示例union如下:

def union(other:BTSet) :BTSet = ((left union right) union other) incl element

问题1:坦率地说,即使阅读了相关的常见问题解答和其他论坛帖子,我仍然不明白这个功能的工作原理和原因。除了在头节点添加(incl调用)元素之外,在union实现中绝对没有完成“动作”,它只是一遍又一遍地调用自身。我非常感谢一些解释......

问题2:课程论坛包含很多帖子,说明这个解决方案根本没有效率,而且还不够好。因为我不明白它是如何工作的,所以我不明白为什么它不够好。

请注意,我不以任何方式要求为分配解决方案提供扰流板。我更愿意“为年级做好工作”,但我根本不明白我应该在这里做什么。我不相信课程中提供的说明和指导足以让您了解函数式编程的怪癖,因此我欢迎任何解决如何正确思考而不是如何正确编码。

6 个答案:

答案 0 :(得分:25)

  A
 / \  union  D
B   C

((B union C) union D) incl A
  ^^^^^^^^^......................................assume it works

(  B             )
(    \   union D ) incl A
(     C          )

(((0 union C) union D) incl B) incl A
   ^^^^^^^^^.....................................just C

(((C union D) incl B) incl A
   ^^^^^^^^^.....................................expand

((((0 union 0) union D) incl C) incl B) incl A
    ^^^^^^^^^....................................just 0

(((0 union D) incl C) incl B) incl A
   ^^^^^^^^^.....................................just D

((D incl C) incl B) incl A
^^^^^^^^^^^^^^^^^^^^^^^^^^.......................all incl now

一步一步地写出来。现在你看到联合减少为应用于右手参数的一堆incl语句。

答案 1 :(得分:4)

我认为incl将元素插入到现有集合中?如果是这样,那就是所有实际工作的发生地。

union的定义是包含任一输入集中的所有内容的集合。给定两个存储为二叉树的集合,如果将第一个集合的联合与第二个集合的分支一起使用,结果中唯一可能缺少的元素是第二个树的根节点处的元素,所以如果你插入那个元素,你有两个输入集的联合。

这只是一种非常低效的方法,可以将两个集合中的每个元素插入到一个空白的新集合中。推测重复被incl丢弃,因此结果是两个输入的并集。


暂时忽略树形结构可能会有所帮助;它对于基本算法并不重要。假设我们有抽象的数学集。给定一个包含未知元素的输入集,我们可以做两件事:

  • 向其添加元素(如果元素已存在则无效)
  • 检查集合是否为非空,如果是,则将其分解为单个元素和两个不相交的子集。

为了取两组{1,2}和{2,3}的并集,我们首先将第一组分解为元素1和子集{}和{2}。我们使用相同的过程递归地获取{},{2}和{2,3}的并集,然后在结果中插入1。

在每个步骤中,问题从较小的输入上的一个联合操作减少到两个联合操作;标准的分而治之算法。当达到单例集合{x}和空集{}的并集时,联合很简单{x},然后返回到链中。

树结构仅用于允许案例分析/分解为较小的集合,并使插入更有效。使用其他数据结构也可以完成相同的操作,例如分成两半的列表用于分解,并通过穷举检查唯一性来完成插入。要有效地使用union,需要一个更聪明的算法,并利用用于存储元素的结构。

答案 2 :(得分:3)

基于上面的所有回答,我认为真正的主力是incl,而调用union的递归方式只是用于遍历集合中的所有元素。

我想出了以下的union实现,这样更好吗?

def union(other:BTSet) :BTSet = right union (left union (other incl element))

答案 3 :(得分:2)

  2
 / \  union  4
1   3

((1 union 3) union 4) incl 2
  ^^^^^^^^^......................................assume it works

(((E union E) union 3 incl 1) union 4) incl 2
   ^^^^^^^^^.....................................still E

(E union E) union 3 incl 1 = E union 3 incl 1 = 3 incl 1

以下子树应 3 包含 1

(  3             ) 
(    \   union D ) incl 2
(      1         )


(((1 union E) union 4) incl 3) incl 2
   ^^^^^^^^^.......................................expand

(((( (E union E) union E) incl 1) union 4) incl 3) incl 2
      ^^^^^^^^^^^^^^^^^^^^^^^^^^..................still 1

((1 union 4) incl 3) incl 2
   ^^^^^^^^......................................continue

((((E union E) union 4) incl 1) incl 3) incl 2
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^..........expand 1 union 4

((4 incl 1) incl 3) incl 2
  ^^^^^^^^^^^^^^^^^^^^^^^^^............Final union result 

谢谢@Rex Kerr提出的步骤。我将第二步替换为实际的运行时步骤,这可以更清楚地描述Scala union函数。

答案 4 :(得分:1)

除非您查看基本案例,否则您无法理解递归算法。事实上,理解的关键通常在于首先理解基础案例。由于未显示基本情况(可能是因为您没有注意到首先存在一个),因此无法理解。

答案 5 :(得分:-3)

我正在做相同的课程,union的上述实施确实效率极低。

我提出了以下不那么实用的解决方案来创建二叉树集合,这样会更有效:

def union(that: BTSet): BTSet = {
  var result:BTSet = this
  that.foreach(element => result = result.incl(element))
  result
}