Wie implementiert man einen Branch-and-Bound in einer funktionalen Programmiersprache?

26

Ich versuche, eine verzweigte und gebundene Suche für die Menge aller Funktionen f: D -> R zu schreiben, wobei die Domänengröße klein ist (| D | ~ 20) und der Bereich viel größer ist (| R | ~ 2 ^ 20) ). Anfangs hatte ich die folgende Lösung.

(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))))))))

Hier ist der Parameter condlistder Funktion buildereine Liste von Bedingungen, die von einer Lösung erfüllt werden sollten. Die Funktion checkgibt nil zurück, wenn ein Element in der Liste der Bedingungen von der verletzt wird partial-map. Die Funktion recur-on-firstweist dem ersten Element im Bereich das erste Element in der Domäne zu und versucht, daraus eine Lösung zu erstellen. Gelingt dies recur-on-firstnicht, wird versucht, eine Lösung zu erstellen, die das erste Element in der Domäne einem anderen Element als dem ersten Element im Bereich zuweist. Es muss jedoch eine Liste geführt werden ignored, in der diese verworfenen Elemente (wie das erste Element im Bereich) gespeichert sind, da es sich möglicherweise um Bilder einiger anderer Elemente in der Domäne handelt.

Es gibt zwei Probleme, die ich mit dieser Lösung sehen kann. Der erste Grund ist, dass die Listen ignoredund rangedie Funktion recur-on-firstziemlich umfangreich sind und dass appendsie eine teure Operation sind. Das zweite Problem ist, dass die Rekursionstiefe der Lösung von der Größe des Bereichs abhängt.

Deshalb habe ich die folgende Lösung gefunden, die doppelt verknüpfte Listen verwendet, um die Elemente im Bereich zu speichern. Die Funktionen start, nextund endbieten Einrichtungen iterieren der doppelt verknüpften Liste.

(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))))))

Die Laufzeit der zweiten Lösung ist viel besser als die Laufzeit der ersten Lösung. Die appendOperation in der ersten Lösung wird durch das Verbinden von Elementen in und aus einer doppelt verknüpften Liste ersetzt (diese Operationen sind zeitlich konstant), und die Rekursionstiefe hängt nur von der Größe der Domäne ab. Aber mein Problem mit dieser Lösung ist, dass es CStyle-Code verwendet. Meine Frage lautet also:

Gibt es eine Lösung, die genauso effizient ist wie die zweite, aber keine setfs und veränderlichen Datenstrukturen verwendet? Mit anderen Worten, gibt es eine effiziente funktionale Programmierlösung für dieses Problem?

Balagopal Komarath
quelle

Antworten:

1

Die erste Idee, die mir in den Sinn kommt: Verwenden Sie denselben allgemeinen Ansatz, ersetzen Sie die Schleife jedoch durch einen rekursiven Endaufruf, dessen Parameter die gespleißte Liste für die nächste Stufe der Berechnung ist. Sie müssen die gespleißte Liste nie ändern, sondern erstellen in jeder Phase eine neue Liste. Dies ist zwar keine konstante Zeit, aber Sie müssen die Liste durchgehen, um herauszufinden, wo Sie die Verbindung herstellen können. Die meisten Knoten können möglicherweise sogar wiederverwendet werden, insbesondere, wenn Sie eine einfach verknüpfte Liste verwenden können.

Davislor
quelle