我试图在所有函数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
来存储这些被丢弃的元素(例如范围中的第一个元素),因为它们可能是域中某些其他元素的图像。
使用该解决方案可以看到两个问题。第一个是列表ignored
和range
函数recur-on-first
很大,append
对它们进行查找是一项昂贵的操作。第二个问题是解决方案的递归深度取决于范围的大小。
因此,我提出了以下解决方案,该解决方案使用双向链表将元素存储在范围内。函数start
,next
并end
提供用于遍历双链表的功能。
(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
样式代码。所以我的问题是这个。
是否有一种解决方案与第二种解决方案一样有效,但不使用setf
s和可变数据结构?换句话说,是否有一个有效的函数编程解决方案来解决这个问题?