Scheme中使用递归的置换

时间:2014-05-13 15:48:59

标签: scheme

我找到了以下代码,它在Scheme中进行了排列。我的意思是如果我输入相似的参数'(1 2 3)它会给我:

((1 2 3) (1 3 2) (2 1 3) (2 3 1) (3 1 2) (3 2 1))

代码如下:

(define (remove x lst)
  (cond
    ((null? lst) '())
    ((= x (car lst))(remove x (cdr lst)))
    (else (cons (car lst) (remove x (cdr lst))))))

(define (permute lst)
  (cond
    ((= (length lst) 1)(list lst))
    (else (apply append(map(lambda (i) (map (lambda (j)(cons i j))
                                            (permute (remove i lst))))lst)))))

第一个函数删除,似乎很简单,只有通过将它与列表的开头进行比较并以其他方式递归调用,才能除去x表示的字符,即使它是否重复。

我完全没有得到它的部分是置换功能。对于我所知道的地图,将一个函数应用于参数的每个元素(在本例中为一个列表),并且apply只将一个函数完全应用于所有参数。那么这一行到底是做什么的:

(apply append(map(lambda (i) (map (lambda (j)(cons i j))
                                                (permute (remove i lst))))lst)))))

对我而言,它似乎只是想创建一个包含两个元素的对:i和j,它们将成为列表中的元素置换(如果我们假设列表只是一堆连接对) 。但那个再次呼唤置换和删除的部分,那个部分在做什么?只是删除列表的头部以生成列表的子集,其中元素i的头部是固定的,直到元素用尽为止?

任何帮助?

由于

2 个答案:

答案 0 :(得分:5)

让我们分开,从内到外。修复lst并将内部表达式应用于其中一个元素。

> (define lst '(1 2 3))
> (define i 1)
> (permute (remove i lst))
((2 3) (3 2))

看起来很好:内部表达式删除了元素,并以递归方式生成列表其余部分的排列。现在map lambda超过这些排列:

> (map (lambda (j) (cons i j)) (permute (remove i lst)))
((1 2 3) (1 3 2))

因此,内部map会生成以i开头的所有排列,我们在此设置为1

外部map确保通过将lst的所有元素视为第一个元素来生成所有排列。

> (map (lambda (i) (map (lambda (j) (cons i j))
>                       (permute (remove i lst))))
>      lst)
(((1 2 3) (1 3 2)) ((2 1 3) (2 3 1)) ((3 1 2) (3 2 1)))

但这会生成嵌套太多的列表。应用append会使列表列表变平,

> (append '(1 2) '(3 4) '(5 6))
(1 2 3 4 5 6)
> (apply append '((1 2) (3 4) (5 6)))
(1 2 3 4 5 6)

所以我们得到一个排列的平面列表。

答案 1 :(得分:2)

我总是发现更容易理解更高的算法 在深入实施并尝试理解之前的水平 发生了什么事。所以问题是:排列是什么 列表,你会怎么找到它们?

单个元素列表的排列显然只是列表 本身。

(a b)的排列是集合[(a b) (b a)]

(a b c)的排列是集合

[(a b c) (a c b) (b c a) (b a c) (c a b) (c b a)]

一般来说有n!长度列表n的排列 - 我们有n 第一个元素的选择,一旦我们选择了(n-1)个选项 对于第二个元素,(n-2)表示第三个元素,依此类推。这个 随着我们越来越多地修复第一个,自由度会降低 列表中的元素非常具有启发性:也许我们可以代表 找到长度为n的列表的排列 长度列表的排列(n - 1),依此类推,直到达到 单元素列表的排列。

事实证明,列表的排列恰好是集合 [元素前置于list \ element的排列,为每个元素 列表中的元素]。

查看(a b c)案例证实了这一点 是的 - 我们a前面有(b c)(c b)(b c)b之前(a c)(c a)的排列等等。这个 将元素添加到子列表的操作可以定义为

(define (prepend j)
  (cons element j))

以及为所有人做的操作 子列表的排列将为(map prepend (permute sublist))。现在,为每个元素定义一个新的prepend函数是 也许是矫枉过正 - 特别是因为它们都有相同的形式。那么一个 更好的方法就是使用lambda,它捕获的值 正在考虑的因素。所需的操作是 然后(map (lambda (j) (cons element j)) (permute sublist))。现在我们 想要将此操作应用于列表的每个元素,以及方式 这是使用另一张地图,给出:

(map (lambda (element)
       (lambda (j) (cons element j) (permute sublist)))
     list)

现在,这看起来不错,但是存在一个问题:递归的每个阶段都需要单个 元素并将它们变成一个列表。这对于长度为1的列表来说很好, 但是对于更长的列表,它会针对每个递归调用重复,我们得到 非常深层次的嵌套列表。我们真正想做的就是全力以赴 这些列表位于相同的基础上,这正是(apply append ...)所关注的内容。这几乎就是所有这一切。唯一的 缺少的是如何首先生成子列表。但 这也很简单 - 我们只使用remove,因此sublist = (remove element list)。把所有东西放在一起,我们有

(apply append (map (lambda (i)
                      (lambda (j) (cons i j))
                      (permute (remove i lst)))
                    lst))

基本案例负责长度= 1的案例,所有其他案件都可以从那里找到