Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Capitolo 7
Capitolo 7
Algoritmi enumerativi
Come abbiamo visto, né gli algoritmi greedy né quelli basati sulla ricerca
locale sono in grado, in molti casi, di garantire l’ottimalità della soluzione
trovata. Nel caso in cui sia importante determinare una soluzione ottima
del problema è necessario quindi ricorrere ad algoritmi diversi. Esistono
più approcci possibili per la determinazione della soluzione esatta di un
problema di OC N P-arduo; tra questi, quelli di enumerazione implicita
sono certamente i più diffusi.
Questi algoritmi esplorano in modo sistematico lo spazio delle soluzioni
alla ricerca di una soluzione ottima. Le valutazioni inferiori (euristiche) e
superiori (rilassamenti) sul valore ottimo della funzione obiettivo discussi
nei capitoli precedenti, insieme ad opportune regole di dominanza, vengono
sfruttate per ottenere informazioni sul problema che permettano di esclu-
dere dalla ricerca aree dello spazio delle soluzioni in cui dimostrabilmente
non si trovi la soluzione ottima; queste aree si dicono quindi visitate impli-
citamente dall’algoritmo. Le modalità della ricerca fanno quindi sı̀ che al
termine si abbia la garanzia dell’ottimalità della soluzione determinata. Se
opportunamente implementati, utilizzando euristiche e rilassamenti efficaci
ed efficienti e molti altri importanti dettagli discussi nel seguito (regole di
visita, separazione e dominanza, pretrattamento, tecniche poliedrali . . . ),
gli algoritmi di enumerazione implicita riescono spesso a risolvere in tempi
accettabili istanze di dimensioni rilevanti di problemi N P-ardui.
Comunque, anche usando le migliori tecnologie disponibili, non si può
mai escludere l’eventualità di dover esaminare una frazione consistente dello
spazio delle soluzioni, per cui questi algoritmi hanno in generale una com-
plessità esponenziale. Per questo non esistono implementazioni generiche
in grado di risolvere istanze di dimensione arbitraria di qualsiasi problema
di OC. Anche se sono a tutt’oggi disponibili strumenti software in grado
di risolvere in tempi brevi qualsiasi problema di PLI di piccole dimensioni
(qualche decina di variabili), il successo di queste tecniche per problemi di
scala maggiore è estremamente variabile e dipende fortemente dal problema
307
308 CAPITOLO 7. ALGORITMI ENUMERATIVI
in esame e dalle istanze specifiche. Molto spesso il processo che porta alla
determinazione di soluzioni ottime di problemi di OC di interesse pratico
passa attraverso la realizzazione di approcci ad-hoc, o quantomeno attraver-
so l’implementazione, all’interno di software generici, di moduli specifici per
il problema e le istanze in esame. Questo giustifica l’interesse nella forma-
zione di esperti in grado di comprendere il funzionamento di questi algoritmi
ad un elevato livello di dettaglio ed intervenire su di essi sfruttando al meglio
tutte le informazioni disponibili sullo specifico problema da risolvere.
(P ) max{c(x) : x ∈ X} ,
(Pi ) max{c(x) : x ∈ Xi } ;
allora
X0 = {x ∈ X : xi = 0} e X1 = {x ∈ X : xi = 1} .
X
x1 = 0 x1 = 1
X0 X1
x2 = 0 x2 = 1 x2 = 0 x2 = 1
x3 = 0 x3 = 1 x3 = 0 x3 = 1 x3 = 0 x3 = 1 x3 = 0 x3 = 1
2 3
(a) 1
5 4
(b) 1
2 3 5
3 4 2 4 5 3 4
4 5 3 5 4 2 5 4 2 4 2 3
5 4 5 3 5 2 4 2 3 2
(P̄ 0 ) max{c̄(x) : x ∈ X̄ 0 }
Per dimostrare che l’algoritmo cosı̀ modificato produce una valutazione infe-
riore di z(P ) affetta da errore assoluto minore od uguale a ε, ossia z ≥ z(P )−
ε, mostreremo che l’algoritmo in effetti produce una valutazione superiore
arbitrariamente accurata su z(P ). Ciò si basa sul seguente risultato:
7.1. ALGORITMI DI ENUMERAZIONE IMPLICITA 317
ossia che l’unione delle regioni ammissibili dei problemi corrispondenti a tutti i nodi potati
e dei problemi corrispondenti a tutti i nodi attivi da l’insieme ammissibile originario X.
Infatti, questo è vero alla prima iterazione (in cui Q = {(P )} e Q00 = ∅) e resta vero
ogniqualvolta un nodo viene potato oppure viene applicata l’operazione di separazione.
Ovviamente, questo è a maggior ragione vero sostituendo Q con Q0 (alcuni nodi potati
potrebbero essere figli di nodi in Q0 ). Il risultato segue quindi dal Lemma 7.1. 3
• il rilassamento e l’euristica;
tutti quelli richiesti dalla visita dello spazio delle soluzioni. L’osservazione
fondamentale è che durante la visita vengono risolti una serie di problemi
di ottimizzazione simili, ossia che differiscono per “pochi” dati: in questi
casi, la conoscenza di una soluzione ottima per uno di questi problemi può
costituire un valido aiuto nel determinare la soluzione ottima degli altri. Si
consideri ad esempio un algoritmo Branch-and-Bound in cui ad ogni nodo
dell’albero delle decisioni venga risolto un problema di PL, ad esempio il
rilassamento continuo di una formulazione PLI del problema, e la regola di
separazione sia implementata semplicemente fissando una variabile (questo
esempio sarà discusso più in dettaglio in seguito); si possono applicare in
questo caso le tecniche di riottimizzazione per la PL discusse nel paragrafo
3.3.4. Ciò tipicamente permette di rendere estremamente più efficiente l’ap-
proccio complessivo, in quanto il tempo necesario per risolvere i problemi
di PL nei nodi interni dell’albero della visita è una frazione di quello ne-
cessario per risolvere il rilassamento continuo iniziale al nodo radice (questo
può avere un impatto anche sulla scelta delle strategie di visita dell’albero
della visita, come discusso nel seguito). Può quindi accadere che algorit-
mi per la PL che siano efficienti nel risolvere “da zero” i problemi ma non
molto efficienti nel riottimizzare a seguito di piccoli cambiamenti nei da-
ti del problema risultino complessivamente meno efficaci, quando utilizzati
all’interno di un approccio di enumerazione implicita, di algoritmi magari
meno efficienti “da zero”, ma molto efficienti in riottimizzazione (questo è
ad esempio il caso dei metodi del punto interno, un’alternativa agli algoritmi
del simplesso discussi in queste dispense). Ciò vale ovviamente anche per
altri problemi di ottimizzazione.
Possiamo concludere questa sezione dicendo semplicemente che per l’ef-
ficienza complessiva di un approccio di enumerazione esplicita è necessario
scegliere accuratamente rilassamento ed euristica, tenendo conto non solo
dell’efficienza ed efficacia di ciascuno dei due separatamente ma anche del-
le interazioni tra i due; occorre inoltre selezionare accuratamente, tra gli
algoritmi risolutivi disponibili, quelli più adatti all’uso nel contesto di un
algoritmo enumerativo.
singola variabile del problema, ossia che seleziona un arco (i, j) e definisce
X00 come l’insieme di tutti i cammini in X 0 che non contengono (i, j) e X10
come l’insieme di tutti i cammini in X 0 che contengono (i, j). Si consideri
quindi il rilassamento da risolvere ad un generico nodo dell’albero delle deci-
sioni: si tratta di determinare un cammino di costo minimo che non contiene
un certo insieme di archi, e che contiene un altro insieme di archi. Mentre la
prima richiesta non ha alcun impatto sulla struttura del problema – è suf-
ficiente eliminare gli archi dal grafo sul quale si calcola il cammino minimo
– la seconda richiesta ne cambia profondamente la difficoltà: determinare
il cammino di costo minimo tra tutti quelli che contengono un insieme di
archi dati è un problema N P-arduo 2 . Quindi, questa regola di branching
non può essere utilizzata in combinazione con questo rilassamento.
È bene notare che in moltissimi casi è possibile adattare facilmente eu-
ristiche e rilassamenti affinchè lavorino su problemi in cui sono state prese
decisioni. Ad esempio, per il problema dello zaino è immediato adattare il
rilassamento e le euristiche viste al caso in cui alcune variabili sono fissate a
0 (basta eliminare i corrispondenti oggetti dalla lista) o a 1 (basta eliminare
i corrispondenti oggetti dalla lista diminuendo opportunamente la capacità
dello zaino). Ciò corrisponde al fatto che i problemi di OC molto spesso
hanno la proprietà di auto-riducibilità, per cui il problema (P ) in cui alcune
variabili sono fissate può essere riformulato come come un’istanza diversa
(più piccola) dello stesso problema.
Infine, una regola di branching che generi “pochi” figli evita alcuni pro-
blemi. Innanzitutto, una regola che generi, ad esempio, solo due figli per
ogni nodo è più semplice da implementare. Viceversa, una regola che genera
2
Il problema può essere risolto con una complessità polinomiale nella dimensione del
grafo ma esponenziale nella cardinalità dell’insieme di archi fissati.
7.1. ALGORITMI DI ENUMERAZIONE IMPLICITA 327
“molti” figli per ogni nodo può essere complessa da implementare, e può ri-
sultare costosa sia in termini di memoria che di tempo. Infatti, se ogni nodo
genera “molti” figli l’insieme Q cresce rapidamente; quindi cresce la quan-
tità di memoria necessaria per memorizzare le descrizioni dei nodi attivi, e
le operazioni su Q possono diventare costose. Inoltre, generare molti figli ha
intrinsecamente un costo che può essere rilevante; come esempio estremo si
pensi ad una regola che genera, per ogni nodo, un numero di figli esponen-
ziale nella dimensione del problema. Questo è in un certo senso il requisito
“duale” rispetto a quello dell’equisuddivisione degli insiemi: mentre quello
garantisce che l’albero non sia troppo profondo, ossia che esista un cammino
“ragionevolmente corto” dalla radice a ciascuna soluzione, questo garantisce
che l’albero non sia troppo poco profondo, ossia che lo sforzo necessario per
esaminare tutti i figli di un dato nodo sia limitato.
7.1.2.5 Preprocessing
Le operazioni di “pretrattamento” (preprocessing) del problema sono tut-
te quelle manipolazioni sui dati del problema che consentono di ottenere
rapidamente informazione utile a rendere più efficiente il processo risolutivo.
Spesso le operazioni di preprocessing sfruttano proprietà apparentemente
banali ed ovvie del problema; alcuni semplici esempi sono:
• nel problema dello zaino, qualsiasi oggetto i con a i > b non può far
parte della soluzione ottima, ossia si può porre x i = 0;
• nel problema del (CMST), nessun arco (i, j) tale che c ij ≥ max{cri , crj }
(dove r è la radice) può far parte dell’albero ottimo;
Può apparire sorprendente che sia necessario verificare questo tipo di con-
dizioni; in prima approssimazione sembrerebbe ragionevole che, ad esempio,
non venissero forniti in input ad un problema dello zaino oggetti che da
soli non entrano nello zaino. Ciò però non sempre accade, per almeno due
motivi:
1. l’istanza del problema può essere stata generata a partire da dati com-
plessi con una procedura complessa, e può essere di dimensioni tali da
non rendere possibile una sua verifica puntuale da parte di un esperto;
328 CAPITOLO 7. ALGORITMI ENUMERATIVI
Esempio 7.1:
Si consideri ad esempio il problema
• rilassamento continuo;
• visita depth-first;
oggetti dello zaino per costo unitario non crescente all’inizio dell’algoritmo,
entrambe possono essere risolti in O(n), e riutilizzando per l’euristica parte
del lavoro svolto nel rilassamento. Il rilassamento continuo permette anche
il fissaggio basato sui costi ridotti. Poichè la soluzione del rilassamento è
intera tranne per la variabile “critica” h (la prima variabile a non entrare
completamente nello zaino) al più, ed ottima se x h è intera, l’ovvia regola di
branching è quella di fissare xh rispettivamente a 0 ed 1 nei figli del nodo.
Questa strategia partiziona lo spazio delle soluzioni, costruisce esattamente
due figli per nodo e rende inammissibile la soluzione ottima del rilassamento.
La strategia è anche “teoricamente” bilanciata, ma in pratica ciò può non
risultare del tutto vero. Infatti, in molte istanze del problema dello zaino la
soluzione ottima contiene “pochi” oggetti, per cui fissare un oggetto come
facente parte della soluzione può avere un effetto maggiore che escluderlo
dalla soluzione (può esservi un numero minore di soluzioni ammissibili nel
primo caso che nel secondo).
La visita depth-first permette di implementare l’algoritmo in modo molto
efficiente. Ogni volta che si scende (o si sale) di un livello nell’albero delle
decisioni la capacità residua b̄ può diminuire (aumentare), oppure rimanere
costante. La soluzione ottima del rilassamento può essere determinata molto
efficientemente partendo dalla soluzione ottima del rilassamento nel nodo
padre. Per via della potenziale asimmetria tra il fissare una variabile a 0 e
ad 1, può essere consigliabile generare (e quindi visitare) per primo il nodo
in cui la variabile viene fissata ad 1.
Esempio 7.2:
Si consideri la seguente istanza del problema dello zaino:
Ad ogni nodo dell’albero delle decisioni denotiamo con x̄ la soluzione ottima del rilassa-
mento continuo (si noti che gli oggetti sono già ordinati per costo unitario non crescente)
e z̄ la corrispondente valutazione superiore, con x la soluzione determinata dall’euristica
CUD e z la corrispondente valutazione inferiore, e con c∗ il vettore dei costi ridotti; si noti
che „ «
ch ci ch
c∗i = ci − y ∗ ai = ci − ai = − ai
ah ai ah
(si vedano i paragrafi 5.1.2.1 e 6.1.2.2).
Al nodo radice dell’albero delle decisioni abbiamo quindi
Il gap assoluto al nodo radice è quindi di 5.25. Essendo c∗ = (2.25, 1, 0, −1), nessun costo
ridotto è in valore assoluto maggiore del gap è non è quindi possibile fissare variabili.
Occorre quindi effettuare l’operazione di branching sulla variabile frazionaria x 3 .
Il successivo nodo estratto da Q corrisponde quindi al problema in cui x3 = 1, ossia
per cui il gap al nodo scende a 5 (la soluzione candidata non viene modificata); dato che
c∗ = (1, 0, ∗, −2) (la variabile x3 è fissata e quindi non compare nel rilassamento continuo,
pertanto non ha costo ridotto), nessuna variabile viene fissata ed è necessario effettuare
l’operazione di branching sulla variabile frazionaria x2 .
Il successivo nodo estratto da Q corrisponde quindi al problema in cui x2 = x3 = 1,
b̄ = 4. Poichè a1 = 5 > b̄ viene quindi fissata anche x1 = 0. Il rilassamento continuo e
l’euristica forniscono quindi
x̄ = x = (0, 1, 1, 1) z̄ = z = z = 21
ed il nodo viene potato per ottimalità, mentre viene aggiornata la soluzione candidata.
Si noti che non effettuare il preprocessing, ossia non fissare x1 = 0, avrebbe comportato
ottenere x̄ = (0.80, 1, 1, 0) e z̄ = 23.8 e quindi non poter potare il nodo.
Si esegue quindi un “backtrack” e si passa ad esaminare il problema in cui x2 = 0,
x3 = 1, per cui il rilassamento continuo e l’euristica forniscono
abbiamo quindi z(P ) ≤ max{21, 23.5} = 23.5, e quindi possiamo affermare che la soluzione
candidata ha un gap massimo di 2.5 rispetto alla soluzione ottima.
Poichè si ha c∗ = (2.75, 2, ∗, 0), è possibile fissare x1 = 1: infatti, 23.5 − 2.75 =
20.75 < 21. Ancora una volta si noti che 20.75 è una valutazione superiore (approssimata)
del valore del rilassamento continuo in cui x1 = 0, che ha soluzione ottima (0, 1, 0, 1)
con costo 14. Comunque questo non permette di terminare l’esplorazione, per cui si deve
eseguire il branching sulla variabile frazionaria x4 .
7.1. ALGORITMI DI ENUMERAZIONE IMPLICITA 333
x̄ = x = (1, 1, 0, 0) z̄ = z = z = 19
(P ) max{cx : Ax ≤ b , x ∈ Zn }
Esempio 7.3:
Si consideri l’istanza del problema (TSP) illustrata in figura 7.3.
5
2 4
1 3
1 6 7 6 5
4
2
3 5
3
2 4 z = 13 2 4 z = 21
(a) 1 1
3 5 3 5
2 4 z = z = 17
(b) 1
3 5
2 4 z = 15 2 4 z = 21
(c) 1 1
3 5 3 5
2 4 z = 19 > 17
(d) 1
3 5
2 4 z = z = 16 < 17
(e) 1
3 5
costruiscono tre figli: nel primo è escluso dal ciclo l’arco (1, 2), nel secondo è escluso dal
ciclo l’arco (1, 4), nel terzo è escluso dal ciclo l’arco (1, 3).
Si passa quindi ad esaminare il nodo in cui è escluso dal ciclo l’arco (1, 2): l’1-albero
corrispondente, mostrato in figura 7.4(b), è un ciclo Hamiltoniano di costo 17. Viene
quindi aggiornata la soluzione candidata, ed il nodo è potato per ottimalità.
Visitando il nodo in cui è escluso dal ciclo l’arco (1, 4) si ottengono l’1-albero ed
il ciclo Hamiltoniano mostrati in figura 7.4(c); poiché il problema non è stato risolto e
z = 15 < z = 17, occorre effettuare l’operazione di branching. Per questo si seleziona
il nodo 2, che ha tre archi incidenti nell’1-albero, e si costruiscono tre figli: nel primo è
escluso dal ciclo l’arco (1, 2), nel secondo è escluso dal ciclo l’arco (2, 5), nel terzo è escluso
dal ciclo l’arco (2, 4).
Visitando il nodo in cui sono esclusi dal ciclo sia l’arco (1, 4) che l’arco (1, 2) si ottiene
l’1-albero mostrato in figura 7.4(d), di costo 19 > z = 17, e quindi il nodo viene potato
dalla valutazione superiore.
Visitando il nodo in cui sono esclusi dal ciclo sia l’arco (1, 4) che l’arco (2, 5) si ottiene
l’1-albero mostrato in figura 7.4(e), che è un ciclo Hamiltoniano di costo 16: viene quindi
aggiornata la soluzione candidata, ed il nodo è potato per ottimalità.
Visitando il nodo in cui sono esclusi dal ciclo sia l’arco (1, 4) che l’arco (2, 4) si otten-
gono l’1-albero ed il ciclo Hamiltoniano mostrati in figura 7.5(a); poiché il problema non
7.1. ALGORITMI DI ENUMERAZIONE IMPLICITA 337
è stato risolto e z = 15 < z = 16, occorre effettuare l’operazione di branching. Per questo
si seleziona il nodo 5, che ha tre archi incidenti nell’1-albero, e si costruiscono tre figli: nel
primo è escluso dal ciclo l’arco (2, 5), nel secondo è escluso dal ciclo l’arco (3, 5), nel terzo
è escluso dal ciclo l’arco (4, 5).
2 4 z = 15 2 4 z = 19
(a) 1 1
3 5 3 5
2 4 z = 17 > 16
(b) 1
3 5
2 4 z = 20 > 16
(c) 1
3 5
2 4 z = 18 > 16
(d) 1
3 5
2 4 z = 16 ≥ 16
(e) 1
3 5
Per tutti e tre i nodi appena generati si ottengono 1-alberi, mostrati rispettivamente
in figura 7.5(b), 7.5(c) e 7.5(d), che hanno costo maggiore di z = 16; tutti e tre i nodi
vengono quindi potati dalla valutazione superiore, e si risale nell’albero delle decisioni (si
esegue un “backtrack”) di due livelli.
Si visita quindi il nodo in cui è escluso dal ciclo l’arco (1, 3), ottenendo l’1-albero
mostrato in figura 7.5(e); poiché il costo dell’1-albero è pari a z = 16, non possono esistere
in questo sottoalbero dell’albero delle decisioni soluzioni di costo inferiore a 16 (si noti che
in principio potrebbero essercene di costo uguale) ed il nodo viene potato dalla valutazione
superiore. Siccome Q = ∅ l’algoritmo termina, dimostrando che la soluzione candidata,
mostrata in figura 7.4(e), è ottima per il problema.
338 CAPITOLO 7. ALGORITMI ENUMERATIVI
non appartenente, e cosı̀ via finché non si giunge al primo arco del cammi-
no tale che il sottocammino che parte da r e contiene tutti i primi archi
di P , fino a quello, viola il vincolo di lunghezza. L’ultimo figlio è quindi
quello in cui l’arco “critico” è fissato come non appartenente al cammino
ottimo e tutti i precedenti sono fissati come appartenenti: non è necessario
generare altri figli, perchè nessun cammino ammissibile può contenere altri
sottocammini di P che comprendono r. Questa regola di branching è un
caso particolare di una regola che si può applicare per problemi con variabili
xi ∈ {0, 1}, in cui nella soluzione ottima x ∗ del rilassamento si abbia che
l’insieme S = {i : x∗i = 1} = {i1 , i2 , . . . ik } contiene “troppe” variabili. La
regola di branching crea un figlio con x i1 = 0, un figlio con xi1 = 1 e xi2 = 0,
un figlio con xi1 = xi2 = 1 e xi3 = 0 e cosı̀ via. Nel caso di (CSP) non è
necessario generare tutti i figli in quanto alcuni di essi sono certamente non
ammissibili. Si noti che questa regola non è bilanciata.
Questa regola di branching fissa archi come appartenenti al cammino, e
quindi rischierebbe di incorrere negli stessi problemi di incompatibilità col
rilassamento della regola standard. Ma in questo caso non sono fissati archi
qualsiasi: vengono sempre fissati interi sottocammini che partono da r. È
quindi possibile adattare il rilassamento semplicemente rimuovendo tutti i
nodi del cammino fissato dal grafo, tranne il nodo terminale, e rendendo il
nodo terminale del cammino la radice del cammino minimo. Contempora-
neamente occorre aggiornare la soglia di massima lunghezza L sottraendo
ad essa la lunghezza del sottocammino fissato: a questo punto si può anche
applicare un’operazione di preprocessing eliminando dal grafo tutti gli archi
che abbiano lij > L0 , dove L0 è la soglia aggiornata.
Esempio 7.4:
Applichiamo l’algoritmo Branch-and-Bound all’istanza rappresentata in figura 6.2(a). Al
nodo radice si ha la situazione rappresentata in figura 7.6(a): il cammino P (archi eviden-
ziati) ha costo 3 ma lunghezza 3 > L = 2. Poiché non si è determinata nessuna soluzione
ammissibile si ha z = −∞, e quindi un gap infinito: si applica quindi l’operazione di
branching.
2 3 2 3
(a) (b)
1 4 1 4
2 3 3
(c) (d)
4 4
Il primo figlio esaminato corrisponde ad eliminare l’arco (1, 2) dal grafo. Il corrispon-
dente cammino minimo è mostrato in figura 7.6(b): il cammino ha costo 4 ed è ammissibile,
per cui si pone z = 4 ed il nodo viene potato per ottimalità.
Il secondo figlio esaminato corrisponde a fissare l’arco (1, 2) come facente parte del
cammino, eliminando contestualmente l’arco (2, 3) ed il nodo 1 dal grafo, spostando la
radice r al nodo 2 e ponendo L = 1. Questo è illustrato in figura 7.6(c), insieme al
cammino ottimo che ha costo 5 > z = 4: il nodo viene quindi potato dalla valutazione
inferiore.
Il terzo ed ultimo figlio esaminato corrisponde a fissare entrambe gli archi (1, 2) e
(2, 3) come facenti parte del cammino, eliminando contestualmente l’arco (3, 4) ed i nodi
1 e 2 dal grafo, spostando la radice r al nodo 3 e ponendo L = 0. Questo è illustrato in
figura 7.6(d): sul grafo non esistono cammini tra 3 e 4, quindi il nodo viene potato per
inammissibilità. Essendo Q vuoto l’algoritmo termina riportando come soluzione ottima
il cammino di costo 4 mostrato in figura 7.6(b).
Esercizio 7.12 Si discuta se e come sia possibile adattare gli algoritmi per
il problema dei cammini minimi studiati nel paragrafo 2.3 affinché sfruttino
la conoscenza del cammino e delle etichette ottime corrispondenti al padre
per risolvere in modo più efficiente i rilassamenti nei nodi figli.
Riferimenti Bibliografici
F. Maffioli “Elementi di Programmazione Matematica”, Casa Editrice Ambrosiana,
2000.
A. Sassano “Modelli e Algoritmi della Ricerca Operativa”, FrancoAngeli, 1999.
L. Wolsey “Integer Programming”, Wiley-Interscience, 1998.
K. Marriott, P.J. Stuckey “Programming with Constraints: An Introduction”,
MIT Press. 1998.