這邊我們要解的問題是有名的 "四色定理", 簡單來說就是使用四種顏色對某一個 Graph 進行塗色, 限制條件式每個區塊與該區塊的臨邊的區塊不能是同一個顏色, 底下是一個範例:
(使用四種顏色對大陸地圖著色)
而在某些較簡單的圖, 只需要三種顏色即可以完成. 這邊我們的問題是要對 Australia 的地圖進行著色並滿足上述條件:
Problem analysis:
在 AI 的領域, 這是個標準的 CSP (Constraint Satisfaction Problems) 問題, 而因此我們可以引用在 CSP 問題中的演算法來解這個問題. 而一個 CSP 問題會有三個元件進行問題描述 X, D 與 C:
接著分析問題, 我們將每個區塊用一個變數來表示:
因為這個圖形相對簡單, 所以使用三種顏色即可滿足條件. 接著每個變數的 Domain 則是:
最後便是這些變數間的關係或是限制條件, 白話說是每個區塊與其臨邊顏色不同, 寫成限制條件則像是:
BACKTRACKING Search Algorithm:
這邊要使用的演算法 "BACKTRACKING Search" 的 pseudo code 如下:
在深入說明該演算法前, 要來介紹幾個在 CSP 問題裡面 term 的解釋:
- consistent
- Binary constraint
- Arc consistency
- MRV (Minimum Remaining Values)
)
Choosing the variable with the fewest "legal" values - is called the minimum remaining variables (MRV) heuristic. It also has been called the "most constraint variable" or "fail-first" heuristic. the latter because it picks a variable that is most likely to cause a failure soon, thereby pruning the search tree.
- least-constraining-value
- Complete assignment
所以雖然 pseudo code 很短, 但是背後的學問可大的 orz. 首先這個演算法的概念就是一次取一個 variable 進行賦值, 如果 "consistent" 則取下一個 variable 繼續賦值並檢查 "consistent", 但如果該值不 consistent, 則從該 variable 的 Domain 取下一個值繼續上述步驟; 一旦所有的值都試過且都不 consistent, 則回到上一個賦值的 variable, 並對該 variable 重新賦值 (backtracking); 到所有可能性都試過還是不行的話呢, 就返回 failure, 或者所有的 variables 都成功賦值 (complete) 且都滿足 constraint (consistent), 則返回 assignment.
而在進行上述過程中, 有些 heuristic 方法可以提高演算法的效能, 如:
- MRV: 在挑選 variable 時, 先選擇最有影響力的. 在這個問題中便是 SA. 因為它有 5 個臨邊, 一旦 SA 決定顏色, 其他臨邊便被限制不能是該顏色.
- least-constraining-value: 在挑選 variable 的值時, 先挑選該值會造成其它臨邊受限的狀況為最小. 這與 MRV 剛好相反, 因為我們的目的是要找到 solution 而不是那些受限的路徑! 因此挑最有可能達到目的地的方向走就對了.
- forward checking: 再決定某個 variable 的值後, 該 variable 的臨邊某些值便不能被 assign 了. 因此到了對該 variable 賦值時, 便可以避開那些受限的值.
Programming:
在實作的部分, 我使用類別 Variable 來代表某個 variable:
- Variable:
- package c6.mapcolor;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.List;
- import java.util.PriorityQueue;
- import java.util.Set;
- public class Variable
{ - public String name; // Name of variable
- public T value = null; // Value of variable
- public List
domain = new ArrayList(); // Domain of variable - public HashMap
> restricts = new HashMap>(); // Restriction from other variables - public List
> neighbors = new ArrayList>(); // Connected variables - /**
- * BD: The available values fit constraints.
- * @return
- */
- public List
cstValues() - {
- List
cstList = new ArrayList (); - Set
rstValueSet = new HashSet (); - for(List
vals:restricts.values()) rstValueSet.addAll(vals); - for(T t:domain) if(!rstValueSet.contains(t)) cstList.add(t);
- return cstList;
- }
- public Variable(String n){this.name = n;}
- public void setDomain(T ...vals)
- {
- for(T val:vals) domain.add(val);
- }
- public void addNeighbor(Variable
...vars){ for(Variablevar:vars) neighbors.add(var);} - public void addMNeighbor(Variable
...vars) - {
- for(Variable
var:vars) - {
- neighbors.add(var);
- var.neighbors.add(this);
- }
- }
- @Override
- public String toString()
- {
- StringBuffer strBuf = new StringBuffer("");
- strBuf.append(String.format("%s", name));
- if(value!=null) strBuf.append(String.format("(%s)", value));
- return strBuf.toString();
- }
- @Override
- public int compareTo(Variable o) {
- Integer n1 = Integer.valueOf(neighbors.size());
- Integer n2 = Integer.valueOf(o.neighbors.size());
- return n2.compareTo(n1);
- }
- }
- package c6.mapcolor;
- public interface IConstraint
{ - public boolean isConsistent(Variable
a, Variable b); - }
- package c6.mapcolor;
- public class ArcDiff
{ - @Override
- public boolean isConsistent(Variable
a, Variable b) { - if(a.value.equals(b.value))
- {
- return false;
- }
- return true;
- }
- }
- package c6;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.PriorityQueue;
- import c6.mapcolor.IConstraint;
- import c6.mapcolor.Variable;
- public class Algorithms {
- public static boolean BACKTRACKING_SEARCH(List
variables, HashMap > cs) - {
- List
assignment = new LinkedList (); - PriorityQueue
pq = new PriorityQueue (); - for(Variable v:variables) pq.add(v);
- return BACKTRACK(pq, cs, assignment);
- }
- public static boolean BACKTRACK(PriorityQueue
pq, HashMap > cs, List assignment) - {
- if(pq.size()==0) return true;
- Variable var = pq.poll();
- for(Object v:var.cstValues())
- {
- var.value = v;
- //System.out.printf("\t[Test] Check %s...\n", var);
- boolean isConsistent = true;
- List
clist = cs.get(var); - for(IConstraint cst:clist)
- {
- for(Object av:var.neighbors)
- if(!cst.isConsistent(var, (Variable)av))
- {
- System.out.printf("\t[Info] %s is inconsistent because of %s!\n", var, av);
- isConsistent = false;
- break;
- }
- }
- if(!isConsistent)
- {
- continue;
- }
- System.out.printf("\t[Info] Assign %s...\n", var);
- if(!INFERENCE(var, cs)) continue;
- assignment.add(var);
- if(BACKTRACK(pq, cs, assignment)) return true;
- assignment.remove(var);
- pq.add(var);
- }
- for(Object nv:var.neighbors) ((Variable)nv).restricts.remove(var); // Remove restriction owing to current var.
- System.out.printf("\t[Info] %s is out of choice and backtrack...\n", var.name);
- return false;
- }
- protected static boolean INFERENCE(Variable var, HashMap
> cs) - {
- // Forward checking
- for(Object o:var.neighbors)
- {
- Variable nv = (Variable)o;
- if(nv.value!=null) continue;
- List rstValues = new ArrayList();
- nv.restricts.remove(var);
- List
clist = cs.get(var); - for(Object v:nv.cstValues())
- {
- nv.value = v;
- for(IConstraint c:clist)
- {
- if(!c.isConsistent(var, nv))
- {
- System.out.printf("\t\tRestict %s to be %s owing to %s...\n", nv.name, v, var);
- rstValues.add(v);
- break;
- }
- }
- }
- nv.value = null;
- nv.restricts.put(var, rstValues);
- if(nv.domain.size() == nv.restricts.size())
- {
- System.out.printf("\t[Info] %s is inconsistent now!\n", nv);
- return false;
- }
- }
- return true;
- }
- }
接著便可以如下使用上述類別定義我們的問題並執行 Backtracking search 演算法:
- package c6.mapcolor;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.LinkedList;
- import java.util.List;
- import c6.Algorithms;
- public class Main {
- /**
- * @param args
- */
- public static void main(String[] args) {
- // 1) Define variables X={...} and corresponding domain of them D={...}
- Variable
wa = new Variable ( "WA"); wa.setDomain("Red", "Blue", "Green"); - Variable
nt = new Variable ( "NT"); nt.setDomain("Red", "Blue", "Green"); - Variable
sa = new Variable ( "SA"); sa.setDomain("Red", "Blue", "Green"); - Variable
q = new Variable ( "Q"); q.setDomain("Red", "Blue", "Green"); - Variable
nsw = new Variable ( "NSW"); nsw.setDomain("Red", "Blue", "Green"); - Variable
v = new Variable ( "V"); v.setDomain("Red", "Blue", "Green"); - Variable
t = new Variable ( "T"); t.setDomain("Red", "Blue", "Green"); - // 2) Define constraint graph
- wa.addMNeighbor(nt, sa);
- nt.addNeighbor(sa, q);
- sa.addNeighbor(q, nsw, v);
- q.addNeighbor(nsw);
- nsw.addNeighbor(v);
- t.addNeighbor(v, nsw);
- List
variables = new ArrayList (); - variables.add(wa);
- variables.add(nt);
- variables.add(sa);
- variables.add(q);
- variables.add(nsw);
- variables.add(v);
- variables.add(t);
- // 3) Define constraints for variables C={}
- IConstraint arcDiff = new ArcDiff();
- List
clist = new LinkedList (); clist.add(arcDiff); - HashMap
> constraints = new HashMap >(); - constraints.put(wa, clist);
- constraints.put(nt, clist);
- constraints.put(sa, clist);
- constraints.put(q, clist);
- constraints.put(nsw, clist);
- constraints.put(v, clist);
- constraints.put(t, clist);
- // 4) Run the algorithm Backtracking search
- if(Algorithms.BACKTRACKING_SEARCH(variables, constraints))
- {
- for(Variable var:variables)
- {
- System.out.printf("\t[Answer] %s\n", var);
- }
- System.out.printf("\t[Test] Done!\n");
- }
- else
- {
- System.out.printf("\t[Test] Fail!\n");
- }
- }
- }
完整代碼可以在 這裡 下載.
沒有留言:
張貼留言