國
立 政 治 大 學
‧
N a tio na
l C h engchi U ni ve rs it y
第三章 使用 ASP 生成演算法詴題
本章節將以 Dijkstra 最短路徑演算法或氣泡排序法等等的演算法為例子,闡釋如 何用 ASP 描寫我們所希望產生的問題特徵,並講解部分如何將經由 ASP 程式執行後 所得到的原始詴題格式包裝成我們所要呈現出的詴題類型。
3.1:Dijkstra 最短路徑演算法的 ASP 規則範例
圖 3-1:網頁輸入範例
首先,我們將會有如圖 3-1 的網頁讓使用者輸入他所希望的詴題特徵,這會在我 們的檔案內添加以下的內容,而這些內容將會用來當作詴題要用的特徵。
#const nNodes = 8.
#const minnewEdge = 4.
#const maxnewEdge = 7.
#const minnUpdate = 2.
#const maxnUpdate = 4.
#const maxBranch = 3.
我們製作 Dijkstra 演算法的方式是先建立出一個只具有解答邊的解答樹,建立完 以後再插入一些不會影響解答樹的邊,最後成為我們所需要的圖形,例如圖 3-2 所示。
‧ 國
立 政 治 大 學
‧
N a tio na
l C
圖 3-2:Dijkstra 圖形建構示意圖h engchi U ni ve rs it y
我們所編寫的規則如下:
1. weight(1..10).
2. node(0..nNodes-1).
規則 1 規定圖形內每一邊之權重必頇為 1 至 10 間的ㄧ整數。規則 2 表示圖形內共 有 nNodes 個節點,編號分為為 0 至 nNodes – 1,參數 nNodes 代表節點數量,其數值 由使用者經由網頁介面輸入。
3. { parent(X,Y) } :- node(X), node(Y), X < Y.
4. :- #count { (X,Y) : parent(X,Y) } != nNodes-1.
規則 3 的 parent(X,Y)表示(X,Y)構成詴題圖中的一個邊,該邊不僅位於解答樹上,
同時 Y 的最短路徑即是由 X 的最短路徑再沿(X,Y)邊所形成。為了減少無意義編號排
‧
圖形上形成一個生成樹(spanning tree)的必要條件。5. reachable(0).
6. reachable(Y) :- reachable(X), parent(X,Y).
7. :- node(X), not reachable(X).
利用 parent 限制子圖的邊數尚不足以保證子圖足以構成一棵生成樹,我們還必頇 要求該子圖構成一個連通圖(connected graph)。我們的做法是保證節點 0 可以經由 parent 構成的子圖到達每一節點。我們利用 reachable(X) 表示節點 0 可以到達節點 X,
規則 5 與 6 定義 reachable 的語意,規則 7 表示所有節點必頇均可到達。
8. nonParent(X,Y) :- not parent(X,Y), not parent(Y,X), node(X), node(Y).
9. minnewEdge { newEdge(X,Y) : nonParent(X,Y) } maxnewEdge.
我們前面所設計 parent 為詴題的解答邊,然而我們的詴題除了解答邊以外還應該 有其他的邊才能構成我們的詴題,我們將非解答邊的邊命名為問題邊。所有的問題邊 只可能出現在兩個非 parent 關係的節點之間,所以我們在規則 8 使用 nonParent 找尋 所有非 parent 關係的節點關係。然後在規則 9 使用 newEdge(X,Y)來代表節點(X,Y)之 間有條問題邊,且方向為 X 朝向 Y,newEdge 將由在所有 nonParent 關係中抓取使用 者規定數量的兩點來構成
10. edge(X,Y) :- parent(X,Y).
11. edge(X,Y) :- newEdge(X,Y).
12. 1 { edge(X,Y,L) : weight(L) }1 :- edge(X,Y).
13. parent(X,Y,W) :- parent(X,Y), edge(X,Y,W).
14. newEdge(X,Y,W) :- newEdge(X,Y), edge(X,Y,W).
15. :- newEdge(X,Y,E), distance(X,D1), distance(Y,D2), not D1+E > D2.
不論是問題邊還是解答邊,皆為我們詴題圖形上的邊,我們命名為圖形邊,我們 使用 edge(X,Y)來表示 X,Y 兩個節點是有邊相連的。規則 10 與規則 11 表示問題邊與
‧
16. nChildren(N,C) :- node(N), C = #count{ C' : parent(N,C') }.
17. hasBranch(K) :- nChildren(_,K).
18. :- maxBranch(K1), hasBranch(K2),not K1 >= K2.
規則 16 的 nChildren(N,C)表示節點 N 有 C 個子節點,規則 17 的 hasBranch(K)表 示至少有一節點的子節點數為 K。我們在前面參數設定已經設定好了 maxBranch(K1),
規則 18 規定不能有其他節點的子節點數大於 K1
19. distance(0,0).
20. distance(Y,D1+D2) :- distance(X,D1), parent(X,Y,D2) 21. distance(Y,D1+D2) :- distance(X,D1), parent(X,Y,D2).
22. :- distance(X,D1), distance(Y,D2), X < Y, not D1 <= D2.
我們用 distance(X,Y)來表示從節點 0 到節點 X 的距離為 Y。規則 19 表示起點到 起點距離為 0,規則 20 和 21 表示,如果起點到 X 的距離為 D1,然後 X 和 Y 有解答 邊相連且邊權重為 D2,則起點到 Y 的距離為 D1+D2。規則 22 表示點的號碼越大的,
起點到他的距離會越遠。
23. distance(X,Src,D) :- distance(Src, D1), edge(Src, X, D2), D = D1+D2.
表的更新次數是一個難易度的標準,規則 23 的 Src 代表選定的節點,此行規則表 示起點經由 X 到 Src 的距離為 D,且 D 為起點到 X 的距離加上邊(X,Src)的距離。這樣 做是為了仿照每輪計算後是否要更新計算的表格。
24. nonUpdate(X,Src1,Src2) :- distance(X,Src1,D1), distance(X,Src2,D2), Src1 <
Src2, D1 <= D2.
25. update(X,Src1,Src2) :- distance(X,Src1,D1), distance(X,Src2,D2), D1 > D2, Src1
‧
< Src2, not nonUpdate(X,_,Src1).
26. nUpdate(N) :- N = #count { (X,Src2) : update(X,Src1,Src2)}.
27. :- nUpdate(N), not N >= minnUpdate.
28. :- nUpdate(N), not N <= maxnUpdate.
首先我們在規則 24 設定 nonUpdate(X,Src1,Src2),代表節點 0 經由 Src1 到 X 的距 離比經由 Src2 到 X 的距離還短,如此一來就不會發生更新現象,並且將 Src2 排除在 後面規則 25 所設定的 update 規則。規則 25 代表,起點經由 Src1 和 Src2 到 X 的距離 分別為 D1 和 D2,如果 Src2 比 Src1 大且所花的距離 D2 比 D1 小,則我們會將表的 Src1 更 改 為 Src2 。 規 則 26 的 nUpdate(N) 為 計 算 總 更 新 次 數 , 我 們 只 抓 取 update(X,Src1,Src2)中不一樣的 X 和 Src2 的數量,避免有重複更新的問題(例如 1 變成 3 再變成 5,然後又多算了一次 3 變成 5)。規則 27 和 28 則是規定我們的更新次數不 應超出使用者規定的範圍外。
29. line(X,Y,W) :- parent(X,Y,W).
30. sup(X,Y,W):- newEdge(X,Y,W).
統一 Graphviz 製圖用的名稱,規則 29 將解答邊更改為 line,規則 30 將問題邊更 改為 sup。
31. #show line/3.
32. #show sup/3.
以上兩行代表我們希望 Clingo 呈現出哪些結果,line 代表解答邊,sup 代表非解
‧
3. #const minWeight = 1.#const maxWeight = 20.
4. #const lastTreeEdge = 9.
5. #const minComponent = 2.#const maxComponent = 2.
6. #const minAlt = 2.#const maxAlt = 4.
規則 1~規則 6 為供使用者自訂的參數。規則 1 代表圖形之節點數,規則 2 代表圖
‧
14. reachable(N) :- reachable(N'), end(E,(N',N)).
15. reachable(N) :- reachable(N'), end(E,(N,N')).
16. :- node(N), not reachable(N).
為了建立連通圖,我們編寫了規則 13~16。我們使用 reachable(X)代表節點 1 可到 達節點 X(代表同一點或是可經由邊相連)。規則 13 代表節點 1 自己是可到達的。規則
19. betteredge(N,E1) :- choose(N,E), E1<E ,end(E1,(V1,V2)), g(N-1,V1,R1), g(N-1,V2,R2), R1 != R2.
20. :- betteredge(N,E).
21. :- not choose(nNodes-1,lastTreeEdge).
接下來編輯跟選擇有關的規則,我們使用 choose(N,E)表示在第 N 次的選擇中選擇 了節點 E。規則 17 的 choose(1,1)代表第一次選擇一定是第一條邊,規則 18 限制每次 選擇的邊一定要有著比前一次選擇的邊有著更高的權重。規則 19 的 betteredge(N,E1) 代表在第 N 次選擇了邊 E,但是有著號碼比 E 更小的邊 E1 可被選擇,則為 betteredge(N,E1),規則 20 限制不得產生 betteredge。規則 21 表示在最後一次所選擇的 邊要為規則 4 所設定的最終選擇邊。
22. g(0,V,V) :- node(V).
23. :- choose(N,E), end(E,(V1,V2)), g(N-1,V1,R), g(N-1,V2,R).
接下來編寫每一條邊在計算過程中要被選擇還是捨棄。首先我們的方式為將每一 個點放入只有自己的集合當中。接下來如果是選擇邊的話,我們將會將邊所相連的兩
‧
24. g(N,V,R1) :- choose(N,E), end(E,(V1,V2)), g(N-1, V1, R1), g(N-1,V2,R2), R1< R2, g(N-1,V,R1).
25. g(N,V,R1) :- choose(N,E), end(E,(V1,V2)), g(N-1, V1, R1), g(N-1,V2,R2), R1< R2, g(N-1,V,R2).
26. g(N,V,R2) :- choose(N,E), end(E,(V1,V2)), g(N-1, V1, R1), g(N-1,V2,R2), R1> R2, g(N-1,V,R1).
27. g(N,V,R2) :- choose(N,E), end(E,(V1,V2)), g(N-1, V1, R1), g(N-1,V2,R2), R1> R2, g(N-1,V,R2).
28. g(N,V,R3) :- choose(N,E), end(E,(V1,V2)), g(N-1,V1,R1), g(N-1,V2,R2), g(N-1,V,R3), R3 != R1, R3 != R2.
29. alt(K) :- K1 = #count { N1: choose(N1,E1), N1 < nNodes-1, not choose(N1+1,E1+1) }, K2 = #count { N : N >1, choose(N,E), not choose(N-1, E-1) }, K = K1+K2 .
‧
34. :- component(K), not K >= minComponent.35. :- component(K), not K <= maxComponent.
接下來編寫最大集合數的設定,此處的集合數只計算節點數量>1 的集合。規則 32 代表在第 N 次的選擇中擁有集合數 K,規則 33 33. component(K)則是在每次選擇 所得到的集合數當中,最大的集合數為 K。規則 34 與 35 規定集合數要位於我們所規 定的上下限當中。
36. include(E,yes) :- choose(N,E).
37. include(E,no) :- e(E,_), #false : choose(N,E).
規則 36 與 37 的 include(E,yes/no)代表邊 E 是否被我們所選擇,被選擇代表 yes,
被捨棄代表 no。
38. edge(X,Y,W) :- e(E,W), end(E,(X,Y)).
39. line(X,Y,W) :- choose(N,E), e(E,W), end(E,(X,Y)).
40. sup(X,Y,W) :- edge(X,Y,W), not line(X,Y,W).
前面已經完成了我們的 Kruskal 圖形製作,但是我們要在網頁上生成圖形需要有
‧
2. #const maxvalue = 15.
3. minmove(10).maxmove(20).
8. :- typenumber(N), not N > maxpos/3*2.
我們不希望一個數列出現太多組重複的數字,所以我們希望對重複數字進行一些 限制,規則 7 用來計算全部的數字 N 共有幾種,規則 8 限制 N 的數量不能比數字上限 的 66%還要少,這將會有比較多的不同數字。
9. timenumber(N,T) :- T = #count { P : p(P,N) }, num(N).
10. maxtimenumber(TIME) :- TIME = #max { T : timenumber(N,T) }.
11. :- maxtimenumber(N), not N < 2.
規則 7~8 解決了不同數字種類可能過少的問題,但是還是可能產生過多相同數字 的 問 題 , 所 以 這 要 在 規 則 9~11 解 決 。 規 則 9 用 來 計 算 每 個 數 字 出 現 幾 次 , timenumber(N,T)代表數字 N 共出現了 T 次。規則 10 用來計算在所有的數字出現次數當中 最多的出現數字次數為多少次,規則 11 為設置我們最多的數字出現次數只能為一次,這樣此 數列就不會有重複的數字了。到此為止便完成我們的數列設置,接下來將氣泡進行排序。
‧
14. beforebig(P1,N1,T) :- t(P1,N1,T), t(P2,N2,T), P1 > P2, N1 < N2.
15. big(P1,P2,T) :- t(P1,N1,T), t(P2,N2,T), P2 > P1, N1 <= N2.
16. best(P1,P3,T) :- P3 = #min{ P2 : big(P1,P2,T) }, t(P1,N1,T). 18. t(P1-1,N1,T) :- beforebig(P1,N1,T-1), time(T).
19. t(P1,N1,T) :- t(P1,N1,T-1), not beforebig(P1,N1,T-1), t(P1+1,N2,T-1), N1 <= N2, time(T).
20. t(P2-1,N1,T) :- t(P1,N1,T-1), not beforebig(P1,N1,T-1), t(P2,N2,T-1), best(P1,P2,T-1), P1 < P2, N1 <= N2, time(T).
接下來的規則 17~20 將進行交換。規則 17 代表進行交換過以後的時間中,最大
‧ 國
立 政 治 大 學
‧
N a tio na
l C h engchi U ni ve rs it y
直到遇到不小於自己的值。如此一來持續重複便會完成氣泡排序
21. move(K,T) :- K = #count {N1 : beforebig(P1,N1,T) }, time(T).
22. allmove(K1) :- K1 = #sum { K,T : move(K,T) }.
23. :- allmove(K), minmove(X), not K >= X.
24. :- allmove(K), maxmove(X), not K <= X.
25. #show t/3.
規則 21 代表計算我們在每個時間 T 的時候數字移動的次數。規則 22 則是將所有 移動次數加起來,成為我們整個氣泡排序的次數。規則 23 與 24 代表我們希望移動次 數要在設置的範圍內,否則計算太過簡單。最後的規則 24,將會秀出在每個時間點時 每個位置放置什麼數字。如此一來整個氣泡排序便完成了
3.4:霍夫曼樹的 ASP 規則範例
接下來我們將介紹生成一個霍夫曼樹的範例。我們所做的方式為先編寫建立一個 完全樹的規則,然後只在其中拿取符合我們需求的點,最後對其填入值使其變為我們 所需要的霍夫曼樹。如圖 3-2 所表示,假設我們想製作一個樹高為 4,樹葉數量為 5 的一個霍夫曼樹,首先我們會建立出一個樹高一樣為 4,但樹葉數量為 8 的完全樹,
接下來再抓取其中 5 個樹葉及其祖先組成一個完滿二元樹(Full Binary),其他不符合需 求的點便移除掉,最後所成的便為我們所需要的霍夫曼樹。
‧ 國
立 政 治 大 學
‧
N a tio na
l C h engchi U ni ve rs it y
圖 3-3:霍夫曼樹建構示意圖
1. #const nleaf = 8.
2. #const treeBits = 4.
上方為供使用者自訂的參數,規則 1 的 nleaf 代表葉子的數量,規則 2 的 treeBits 代表所產生樹的高度。
3. nleaf { leaf(1..2**treeBits ) } nleaf.
4. :- A = #max { N : htd(N)} , not A < (2**treeBits).
5. :- A = #max { N : htd(N)} , not A > (2**(treeBits-1)).
‧
7. line(X,Y,VX,VY) :- sup(X,Y), val(X,VX), val(Y,VY).
規則 3 為建立樹葉 leaf(X),X 代表的是樹葉在樹中的位置(以完全樹來看)。規則
11. inode(N) :- htd(N), htd(N*2).
12. leaf(N) :- htd(N), not htd(N*2).
13. htd(N) :- leaf(N).
14. :- leaf(N), htd(2*N).
接下來我們將生成完全樹的架構,我們使用 htd(N)代表 N 在樹內,且是由上而下
15. bits(1,0) :- htd(1).
16. bits(N,K+1) :- htd(N), bits(N/2,K).
17. sameBits(N1,N2) :- htd(N1), htd(N2), bits(N1,B1), bits(N2,B2), B1 = B2.
18. lessBits(N1,N2) :- htd(N1), htd(N2), bits(N1,B1), bits(N2,B2), B1 < B2.
樹建立好以後我們接下來建立樹的層級規則,使用 bits 來代表在樹的第幾層。規 則 15 代表樹的 root 節點為 bits(0),我們表示為 bits(1,0)。規則 16 代表,一個節點 N 在父親的下層。規則 17 的 sameBits(N1,N2)代表 N1 與 N2 位於相同的層,規則 18 的 lessBits(N1,N2)代表 N1 在 N2 的上方(但不一定剛好在上面一層)
‧
19. gt(N1,N2) :- htd(N1), htd(N2), lessBits(N1,N2).
20. gt(N1,N2) :- htd(N1), htd(N2), sameBits(N1,N2), N1 > N2.
規則 19 與 20 為建立 gt(N1,N2)的關係,用來代表 N1 節點的值會比 N2 節點的值
23. :- val(N1,V1), val(N2,V2), gt(N1,N2), not V1 > V2.
24. val(N,W1+W2) :- inode(N), val(2*N,W1), val(2*N+1,W2).
25. leafv(N,V) : leaf(N), val(N,V).
一切都設定好後,接下來我們將沿著上方做出的完全樹的架構,在其中選擇我們
26. #show line/4.
27. #show leafv/2.
最後使用 show 將所需要的結果拿出來。使用 line 表示親子與值的關係, leafv 表 示樹葉的值。
3.5:最長公共子序列的 ASP 規則範例
接下來將介紹最長公共子序列的程式範例。我們首先會先建立好兩個序列的規則,
‧
4. #const maxlcs1 = 2.#const maxlcs2 = 4.
5. minpath(10).maxpath(20).
13. lcs(L) :- lcs(xsize,ysize,L).
接下來為建立 lcs 的基礎規則,我們使用 lcs(K1,K2,L)表示在 x 序列的 1 到 K1 位 置的點以及在 y 序列中 1 到 K2 位置的點當中,最長的公共子序列長度為 L。規則 10 表示雙方位置皆為 0 的時候 lcs 長度為 0。規則 11 與規則 12 表示,不管其中一個序列 到了多後方,只要另外一個序列位置在 0,則 lcs 也為 0。規則 13 的 lcs(L)則是抓取出 有得出的 lcs(_,_L)長度。
‧
14. lcs(K1,K2,L+1) :- x(K1,C1), y(K2,C2), C1 = C2, lcs(K1-1,K2-1,L).
15. lcs(K1,K2,L2) :- x(K1,C1), y(K2,C2), C1 != C2, lcs(K1,K2-1,L1), lcs(K1-1,K2,L2), L1 <= L2.
16. lcs(K1,K2,L1) :- x(K1,C1), y(K2,C2), C1 != C2, lcs(K1,K2-1,L1), lcs(K1-1,K2,L2), L1 >= L2.
前面我們建立的是 lcs 的基礎規則,接下來我們要做的是做遞迴運算的規則,如 此一來就可以從頭慢慢計算到最後,規則 14 表示,如果計算到了(x,y)序列的(K1,K2) 位置,且兩個位置的文字一樣,則 lcs 的長度為(x,y)序列的(K1-1,K2-1)位置的 lcs 長度 +1。規則 15 與規則 16 表示,一樣是計算到(x,y)序列的(K1,K2)位置,但兩個位置的文 字不一樣,則 lcs(K1,K2,L)的 lcs 長度將會延用 lcs(K1,K2-1,L1)、lcs(K1-1,K2,L2)這兩 個當中較大的 lcs 長度。
17. path(xsize,ysize).
18. path(K1-1,K2-1) :- path(K1,K2), x(K1,C), y(K2,C), K1 > 1, K2 > 1.
19. path(K1-1,K2) :- path(K1,K2), x(K1,C1), y(K2,C2), K1 > 1, K2 > 0, not C1 = C2.
24. pathLcs(K1,K2,L) :- lcs(K1,K2,L), path(K1,K2).
25. space(K1,K2) :- lcs(K1,K2,_), not pathLcs(K1,K2,_), not K1 = 0, not K2 = 0.
26. answer(X,Y,C,1) :- pathLcs(X,Y,1), x(X,C), y(Y,C).
‧ 國
立 政 治 大 學
‧
N a tio na
l C h engchi U ni ve rs it y
27. answer(X2,Y2,C2,L) :- answer(X1,Y1,C1,L-1), X2 >= X1, Y2 >= Y1, pathLcs(X2,Y2,L), x(X2,C), y(Y2,C), C2 = (C1,C).
28. final(C) :- answer(X,Y,C,L), lcs(L).
接下來我們將找尋 LCS 字串,我們在規則 24 使用 pathLcs(K1,K2,L)來代表(K1,K2) 的 LCS 為 L,且其為會被我們計算到的子數列。規則 25 的 space(K1,K2)表示(K1,K2) 在計算過程中不會計算到,我們在製作詴題表格時不會在乎它,到時候這位置將以*
接下來我們將找尋 LCS 字串,我們在規則 24 使用 pathLcs(K1,K2,L)來代表(K1,K2) 的 LCS 為 L,且其為會被我們計算到的子數列。規則 25 的 space(K1,K2)表示(K1,K2) 在計算過程中不會計算到,我們在製作詴題表格時不會在乎它,到時候這位置將以*