如何在功能编程语言中实现分支定界?


26

我试图在所有函数f的集合上写一个分支和边界搜索:D-> R,其中域大小很小(| D |〜20),范围更大(| R |〜2 ^ 20 )。最初,我想出了以下解决方案。

(builder (domain range condlist partial-map)
            (let ((passed? (check condlist partial-map)))
              (cond
               ((not passed?) nil)
               (domain (recur-on-first domain range condlist partial-map '()))
               (t partial-map))))
(recur-on-first (domain range condlist partial-map ignored)
                   (cond
                    ((null range) nil)
                    (t (let ((first-to-first
                              (builder (cdr domain)
                                       (append ignored (cdr range))
                                       condlist
                                       (cons (cons (car domain) (car range)) partial-map))))
                         (or first-to-first
                             (recur-on-first domain
                                             (cdr range)
                                             condlist
                                             partial-map
                                             (cons (car range) ignored))))))))

在此,condlist函数的参数builder是解决方案应满足的条件列表。check如果条件列表中的任何元素违反了,该函数返回nil partial-map。该函数recur-on-first将域中的第一个元素分配给范围中的第一个元素,并尝试从那里构建解决方案。失败会recur-on-first调用自身尝试构建一个解决方案,该解决方案将域中的第一个元素分配给范围中第一个元素以外的其他元素。但是,它必须维护一个列表ignored来存储这些被丢弃的元素(例如范围中的第一个元素),因为它们可能是域中某些其他元素的图像。

使用该解决方案可以看到两个问题。第一个是列表ignoredrange函数recur-on-first很大,append对它们进行查找是一项昂贵的操作。第二个问题是解决方案的递归深度取决于范围的大小。

因此,我提出了以下解决方案,该解决方案使用双向链表将元素存储在范围内。函数startnextend提供用于遍历双链表的功能。

(builder (domain range condlist &optional (partial-map nil))
            (block builder
                   (let ((passed? (check condlist partial-map)))
                     (cond
                       ((not passed?) nil)
                       (domain (let* ((cur (start range))
                                      (prev (dbl-node-prev cur)))
                                 (loop
                                   (if (not (end cur))
                                     (progn
                                       (splice-out range cur)
                                       (let ((sol (builder (cdr domain)
                                                           range
                                                           condlist
                                                           (cons (cons (car domain) (data cur)) partial-map))))
                                         (splice-in range prev cur)
                                         (if sol (return-from builder sol)))
                                       (setq prev cur)
                                       (setq cur (next cur)))
                                     (return-from builder nil)))))
                       (t partial-map))))))

第二个解决方案的运行时比第一个解决方案的运行时好得多。第append一个解决方案中的操作被替换为在双链表中和从中进行拼接的元素(这些操作是恒定时间),并且递归深度仅取决于域的大小。但是这个解决方案的我的问题是它使用C样式代码。所以我的问题是这个。

是否有一种解决方案与第二种解决方案一样有效,但不使用setfs和可变数据结构?换句话说,是否有一个有效的函数编程解决方案来解决这个问题?

Answers:


1

我想到的第一个想法是:使用相同的通用方法,但是将循环替换为尾递归调用,其参数是下一个计算阶段的拼接列表?您无需修改​​拼接列表,只需在每个阶段生成一个新列表即可。不可否认,这不是固定时间,但是无论如何您都需要遍历列表以查找拼接位置。它甚至可以重用大多数节点,特别是如果您可以使用单链接列表。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.