200 likes | 334 Views
書名 Java 於資料結構與演算法之實習應用 書號 PG20098 原著 河西朝雄 著 譯者 周明憲 / 徐堯譯. 台北總公司/台北縣汐止市新台五路一段 112 號 10 樓 A 棟 Building A, 10F, 112, Shin-Tai-Wu Road Sec. 1, Shijr, Taipei, Taiwan. 電話/ 02-26962869 傳真/ 02-26962867 台中辦事處/台中市文心路一段 540 號 4 樓 -1 4-1F, 540, Wen Shing Road, Sec. 1, Taichung, Taiwan.
E N D
書名 Java於資料結構與演算法之實習應用 書號 PG20098 原著 河西朝雄著 譯者 周明憲/徐堯譯 台北總公司/台北縣汐止市新台五路一段112號10樓A棟 Building A, 10F, 112, Shin-Tai-Wu Road Sec. 1, Shijr, Taipei, Taiwan. 電話/02-26962869 傳真/02-26962867 台中辦事處/台中市文心路一段540號4樓-1 4-1F, 540, Wen Shing Road, Sec. 1, Taichung, Taiwan. 電話/04-23287870 傳真/04-23108168 博碩網址:http://www.drmaster.com.tw
第七章 學習重點 • 簡單來說,幾個相連起來的樹的節點就稱為圖形。 • 以公路網為例,我們可將各地點(東京、橫濱、...)想像成節點,將各地點之間的距離或來往所需時間想像成分支(邊),如此即構成1個圖形。向公路網這樣各邊都有深厚定義的圖形稱為網路(network),很適合用來顯示日常的社會現象。舉例來說,如果我們知道如何找出公路網的最短路徑,就能從東京至大阪的公路網中找出最快抵達目的地的路線,這就解決了問題。
7-0 何謂圖形 • 圖形(graph)是指以邊(edge,分支:branch)將節點(node,頂點:vertex)連接起來的物件,其形式如下。這個圖形的意義為有通往1→2的路與通往2→1的路,但沒有通往1→3的路。 • 如要將這種圖形以資料形式表示出來,就要使用下列的相鄰矩陣(adjacency matrix)。 • 這個相鄰矩陣的各元素的值在指向節點i→節點j的邊存在的時候為1,否則為0。也有些方法在i=j時,將相鄰矩陣的各元素的值設為1,而不是設為0。本書則將此值設為0。 • 此外由於Java的陣列元素是由0開始,因此本書將0的元素設為未使用,將1的元素視為開始使用的元素。 • 邊若有方向則稱為有方向圖形(directed graph),以箭頭表示。 • 圖7.1所示的圖形的邊並無方向,這種圖形稱為無方向圖形(undirected graph)。 • 有方向圖形的相鄰陣列如下所示。在有方向圖形中,1→2是可通行的,但2→1則是無法通行的。
7-1 圖形的搜尋(深度優先) • 深度優先搜尋(depth first search)演算法的大致內容如下: ‧從起點出發,查出按編號由小到大的順序前進的位置,然後前進到無法前進(以邊連接之處或還未掃瞄)為止。 ‧若已無前路,則回到有前路的位置,然後再度前進值至無法前進為止。 ‧所有的前路都已走完後,便結束程式(回到來時路)
private TextArea ta; private final int N=8; // 節點的數量 // 相鄰矩陣 private int[][] a={{0,0,0,0,0,0,0,0,0}, {0,0,1,0,0,0,0,0,0}, {0,1,0,1,1,0,0,0,0}, {0,0,1,0,0,0,0,1,0}, {0,0,1,0,0,1,0,0,0}, {0,0,0,0,1,0,1,0,0}, {0,0,0,0,0,1,0,1,1}, {0,0,0,1,0,0,1,0,1}, {0,0,0,0,0,0,1,1,0}}; private int[] v=new int[N+1]; // 查訪旗標 void visit(int i) { int j; v[i]=1; for (j=1;j<=N;j++) { if (a[i][j]==1 && v[j]==0) { ta.setText(ta.getText()+i+"->"+j+" "); visit(j); } } } ………
public Rei51Frame() { setSize(300,100); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); Button bt; add(bt=new Button("圖形搜尋"),"North"); add(ta=new TextArea(5,40),"Center"); bt.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int i; for (i=1;i<=N;i++) v[i]=0; visit(1); } }); }
7-2 圖形的搜尋(廣度優先) 廣度優先演算法的大致內容如下: • 將節點1存至佇列 • 取出節點1,將與節點相連接的未訪查節點(即2)存至佇列 • 取出節點2,以同樣方式將節點3、4、5按小至大順序存至佇列 • 取出節點7,將節點8存至佇列 • 節點6雖已取出,但由於沒有欲掃瞄的節點,因此佇列是空的。節點8也是一樣,當head=tail時,程式便結束。 節點6已拜訪過了,所以不置入
private final int N=8; // 節點數量 private int[][] a={{0,0,0,0,0,0,0,0,0}, // 相鄰矩陣 {0,0,1,0,0,0,0,0,0}, {0,1,0,1,1,1,0,0,0}, {0,0,1,0,0,0,0,1,0}, {0,0,1,0,0,0,0,0,0}, {0,0,1,0,0,0,1,0,0}, {0,0,0,0,0,1,0,1,1}, {0,0,0,1,0,0,1,0,1}, {0,0,0,0,0,0,1,1,0}}; private int[] v=new int[N+1]; // 查訪旗標 private int[] queue=new int[100]; // 佇列 private int head=0, // 開頭資料的索引 tail=0; // 尾端資料的索引 public Rei52Frame() { setSize(300,150); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } });
Button bt; add(bt=new Button("圖形的搜尋"),"North"); add(ta=new TextArea(5,40),"Center"); bt.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int i,j; for (i=1;i<=N;i++) v[i]=0; queue[tail++]=1;v[1]=1; do { i=queue[head++]; // 從佇列中取出 for (j=1;j<=N;j++) { if (a[i][j]==1 && v[j]==0) { ta.setText(ta.getText()+i+"->"+j+" "); queue[tail++]=j; // 存至佇列 v[j]=1; } } } while (head!=tail); } ……………………
7-3 拓樸排序 • 假設有1~8件工作。要做第1件工作時,須先做好第2件工作。要做第3件工作時,須先做好第1件工作。要做第7件工作時,須先做好第3、6、8件工作。這種關係能以有方向圖形表示,其圖形如下: • 從上圖中我們可以瞭解,第1~8件工作可分為第2、1、3、4、7件工作與第2、5、4、6、7、8件工作等2組。第1件工作與第5件工作由於不同組,因此先執行哪個都無妨。像這種不一定能夠比較順序的情形稱為局部順序(partial order)。對於在局部順序中產生的資料,將第1件工作至最後1件工作排成1列的情形則稱為拓樸排序(topological sort)。由於這是順序的資料,因此不一定只有1個解答。
7-3 拓樸排序 • 拓樸排序針對有方向圖形進行深度優先搜尋,從目的地返回搜尋行經路線時,將節點取回。 先將節點1設為起點,再將與其連接的未掃瞄的節點以 1 3 (4) 3 (7) 3 (1) 的順序前進。打圈的節點為被取回的節點。然後將節點2設為起點,以 2 5 6 (8) (6) (5) (2) 的順序前進。 節點3、4、5、6、7、8也是以同樣的步驟操作,但因這些節點皆已掃瞄過,因此就直接結束。將取得的節點逆向排列後, 2、5、6、8、1、3、7、4 的順序即為應執行事項的順序。若改變起點的設定方式,則可得到其他的解。
add(bt=new Button("拓樸排序"),"North"); add(ta=new TextArea(5,40),"Center"); bt.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int i; for (i=1;i<=N;i++) v[i]=0; for (i=1;i<=N;i++) if (v[i]==0) visit(i); } });
7-4 Euler的一筆畫 • 歐拉(Euler)的一筆畫通過圖形的所有的邊後再回到原來位置,這條路線的追蹤就是Euler一筆畫。這觀念與一般的一筆畫問題是相同的。 將相連的邊的數量存入相鄰矩陣的各元素內。 使用深度優先搜尋作業便可製作出求一筆畫行經路線的演算法。 ‧一面將通過的路徑消去,一面走到無路可走。 ‧最後到達的地方即為起點,而且所有的邊若全部消去(這表示已經通過所有的邊),則到達此點的路線有1個解法。 ‧若已無去路,則將路徑復原(連接),依序返回,在搜尋下一個前進的位置。
從起點1出發在回到1的時候的具體範例如圖所示。從起點1出發在回到1的時候的具體範例如圖所示。
private int success, n; // 通過路線的數目 private int[] v=new int[Root+1]; // 存有行經路線的堆疊 void visit(int i) { int j; v[n]=i; if (n==0 && i==Start) { // 通過邊並返回原位置 ta.setText(ta.getText()+"解"+(++success)+":"); for (i=0;i<=Root;i++) ta.setText(ta.getText()+v[i]+" "); ta.setText(ta.getText()+"\n"); } else { for (j=1;j<=Node;j++) { if (a[i][j]!=0) { a[i][j]--;a[j][i]--; // 將通過的路線切掉 n--; visit(j); a[i][j]++;a[j][i]++; // 將路線復原 n++; } } } }
7-5 最短路徑問題 • 例題55 戴克斯特拉法以戴克斯特拉法求出某起點至圖形上各點的最短距離 • 右側的圖形稱為網路(network)。這個圖形是指若將邊的長度視為道路的長度,則a→h的最短距離為a→d→g→h,其值為9。網路能以相鄰矩陣表示出來。矩陣中,需盡可能的將較大值M存至未相連的節點間的元素。
戴克斯特拉(Dijkstra)法 • 戴克斯特拉(Dijkstra)法為求某起點至圖形上各點的最短距離的方法之一,這個方法先從起點開始一一的確定至周邊節點的最短路徑,然後慢慢將範圍擴大,最後便求出至所有節點的最短路徑。其演算法大致內容如下: ‧求出起點至與其相連的節點的距離,然後在值最小的節點上標上記號。 ‧求出標上記號的節點至與其相連的節點的距離,再在其中找出距離最短的節點,然號標上記號。 ‧反覆做上述步驟直至所有節點都標上記號為止,最後得節點所得出的值就是從起點開始的最短距離。
戴克斯特拉法的具體範例如下: • 現在假設q點已標有記號,與q相連的點有r及p(假設p為已確定的點)。由於p為已確定的點,因此p≦q。正因為如此,通過q前往p點的距離便不可能比目前至p的距離還短。換句話說,通往已確定節點的距離不會受到更改。
public void actionPerformed(ActionEvent e) { int j,k,p=0,start,min; int[] leng=new int[N+1], // 至節點的距離 v=new int[N+1]; // 確定旗標 ta.setText(""); start=Integer.parseInt(tf.getText()); for (k=1;k<=N;k++) { leng[k]=M;v[k]=0; } leng[start]=0; for (j=1;j<=N;j++) { min=M; // 搜尋最小的節點 for (k=1;k<=N;k++) { if (v[k]==0 && leng[k]<min) { p=k;min=leng[k]; } } v[p]=1; // 確定最小的節點 if (min==M) { ta.setText("圖形沒有連接\n"); return; } // 經由p至k的距離若比當時最短的距離小的話,便會執行更新作業 for (k=1;k<=N;k++) { if ((leng[p]+a[p][k])<leng[k]) leng[k]=leng[p]+a[p][k]; } } for (j=1;j<=N;j++) ta.setText(ta.getText()+start+" -> "+j+" : "+leng[j]+"\n"); } });