• 沒有找到結果。

立 政 治 大 學

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) 在計算過程中不會計算到,我們在製作詴題表格時不會在乎它,到時候這位置將以*

相關文件