170 likes | 297 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
第九章 學習重點 • 電腦中最有發展的應用技術之一為遊戲。可以用雙手自由的操縱畫面中的虛構世界,這種快感正是電玩的魅力。這種遊戲若要創造出更精確的虛構世界,就必須具有在高度繪圖技術及即時(real-time)情況下能夠適時反應的速度,因此這是一種沒有電腦就無法玩的遊戲。 • 一般而言,遊戲(game)一詞令人有較為世俗的感覺,而益智(puzzle)則具有強烈的智慧思考遊戲的意義。古今中外,人們總會想出各種益智遊戲。這證明了不管是在哪個時代,人類對智慧的好奇心一樣都很旺盛。 • 若將電腦應用在益智遊戲上,便會有如第4章所示的河內塔問題一般,明快的獲得解決。但一般而言,益智遊戲無法適用明快的演算法,而益智遊戲所適用且演算法也常常須一五一十的檢查各項狀況。這種演算法有逆向追蹤與動態程式,本章會探討說明這兩個演算法。
9-1 魔術方塊 • 例題64 奇數魔術方塊將1~n2的數字排列在n×n(n為奇數)的正方形格子中,且各行、各列及各對角線的數字合計皆須相同 • 數字少的時候,試著數次將格子填滿,就能求出解答,但數字多的時候,這方法就不適用了。求出n×n(n=3、5、7、9、...)的魔術方塊的演算法之大致內容如下: (1)將1放入第1行的中央 (2)以方塊大小n除以放入數目的餘數若為1,則立即前進至下1個格子,否則前進至斜上方。 (3)若超出上方,則移至同1列的最下1格 (4)若超出右方,則移至同1行的最左1格
public void paint(Graphics g) { final int N=7; // n魔術方塊(n=3,5,7,9,...) int i,j,k; int[][] hojin=new int[N+1][N+1]; j=(N+1)/2;i=0; for (k=1;k<=N*N;k++) { if ((k%N)==1) i++; else { i--;j++; } if (i==0) i=N; if (j>N) j=1; hojin[i][j]=k; } g.drawString("奇數魔術方塊(N="+N+")",140,20); for (i=1;i<=N;i++) { for (j=1;j<=N;j++) g.drawString(""+hojin[i][j],40*j,20*i+20); } }
練習問題64 4N魔術方塊 • 解4的倍數(4,8,12,16,...)的4N魔術方塊。 4×4方塊的解法如圖9.3所示。 (a) 由左上角開始從左到右的將1,2,3,4依序填入小格子內,再將5,6,7,8依序填入下一列的格子內,但不須將數字填入對角線內的格子。 (b) 由左上角開始從左到右的將16,15,14,13依序填入小格子內,再將12,11,10,9依序填入下一列的格子內,但只須將數字填入對角線的格子內。 (c) 將(a)與(b)組合起來即為解答。
public void paint(Graphics g) { final int N=8; // 4N魔術方塊(n=4,8,12,16,...) int i,j; int[][] hojin=new int[N+1][N+1]; for (j=1;j<=N;j++) { for (i=1;i<=N;i++) { if (j%4==i%4 || (j%4+i%4)%4==1) hojin[i][j]=(N+1-i)*N-j+1; else hojin[i][j]=(i-1)*N+j; } } g.drawString("4N魔術方塊 (N="+N+")",160,20); for (i=1;i<=N;i++) { for (j=1;j<=N;j++) g.drawString(""+hojin[i][j],40*j,20*i+20); } }
9-2 戰略性猜拳 • 例題65 製作可讀出猜拳對手的習慣並使自己猜拳能力大增的程式 分別以0、1、2代表石頭、剪刀、布,然後製作如表9.1所示的電腦與人的猜拳對戰表。 當computer與man內分別存有0~2的資料時,則能以 (computer-man+3)%3 的值來進行下列的判定: 0 ... 平手 1 ...電腦輸 2 ...電腦贏 假設有人喜歡在出布以後再出石頭,也就是說這類人會受到自己先前出的拳的影響而決定出下一拳。電腦若遇到這種對手,則其戰略為: 設對手之前出的拳為M,目前出的拳為man時,則表9.2的戰略表的table[m][man]的內容便要+1。猜拳時若使用這個方法,則可至做出如圖9.2所示的戰略資料。 table[m][man]
宣告 class Rei65Frame extends Frame { private Checkbox cb1,cb2,cb3; private TextArea ta; private int[][] table={{0,0,0}, // 戰略表 {0,0,0}, {0,0,0}}; private int[] hist={0,0,0}; // 勝敗次數 private String[] hand={"石頭","剪刀","布"}; private String[] msg={"平手","你贏","電腦贏"}; private int m=0; ………………………………
public void actionPerformed(ActionEvent e) { int computer,man,jg; if (cb1.getState()) man=0; else if (cb2.getState()) man=1; else man=2; if (table[m][0]>table[m][1] && table[m][0]>table[m][2]) computer=2; else if (table[m][1]>table[m][2]) computer=0; else computer=1; jg=(computer-man+3) % 3; hist[jg]++; table[m][man]++; // 學習 m=man; ta.setText("電腦出:"+hand[computer]+"\n"+ "判定:"+msg[jg]+"\n"+ "你的勝敗結果:"+hist[1]+"勝"+hist[2]+"敗"+hist[0]+"平手\n"); }
9-3 逆向追蹤 • 逆向追蹤(back tracking)並不是要徹徹底底的搜尋全部的局面,而是有效率的判定不須搜尋的局面、並降低搜尋時間的演算法。 • 練習問題66 8個皇后在8×8的棋盤上放置8個西洋棋的皇后棋子,然後求出所有皇后都互不對立的局面。西洋棋的皇后棋子可縱向、橫向與斜向前進。所謂的互不對立是指,某一皇后棋子可以前進的位置其他的皇后棋子不會進入。
private int[] column=new int[N+1], // 表示同一欄內是否有皇后棋子 rup=new int[2*N+1], // 表示是否在由右上角至左下角的對角線上 lup=new int[2*N+1], // 表示是否在由右下角至左下角的對角線上 queen=new int[N+1]; // 皇后棋子的位置 private int num; // 解答的編號 private String result; // 顯示結果
void backtrack(int i) { int j,x,y; if (i>N) { result=result+"\n解答"+(++num)+"\n"; for (y=1;y<=N;y++) { for (x=1;x<=N;x++) if (queen[y]==x) result=result+" Q"; else result=result+" ."; result=result+"\n"; } } else { for (j=1;j<=N;j++) { if (column[j]==1 && rup[i+j]==1 && lup[i-j+N]==1) { queen[i]=j; // (i,j)為皇后棋子的位置 column[j]=rup[i+j]=lup[i-j+N]=0; // 局面的變更 backtrack(i+1); column[j]=rup[i+j]=lup[i-j+N]=1; // 回復至原貌 } } } }
9-4 動態規劃 • 動態規劃(dynamic programming)是最化畫問題的有效解法。 • 求n個元素的最佳解時,可將由i個元素組成的部分集合最佳解製成表格後,求出結果。當增加1個元素時,便以此表為基礎計算出最佳解的變化,然後根據計算結果修改表格的內容。如此反覆操作直至全部集合(n個元素全部)均執行完畢,最後最佳解就可由表中求得。也就是說,動態規劃為從空集合的最佳解(初始值)開始,每當增加1個元素就將最佳解更新,而求得全部集合的最佳解之方法
背包問題與最佳解判定 將物品i放入時,大小s的背包的最佳解是否變更可由下列的式子來判定。 (1)將物品i放入至大小s的背包內後的剩餘空間設為p。 (2)將與 p同樣大小的背包之現在最佳解加上物品i的金額,並將此金額設為newvalue。 (3)若newvalue>「大小s的背包之現在最佳解(value[s])」,則 ‧以newvalue修改大小s的最佳解 ‧將物品i視為最後放入背包的物品,並記錄至item[s]。
public void paint(Graphics g) { final int Limit=8, // 背包的重量限制 N=5, // 物品的種類 Min=1; // 重量的最小值 int i,s,p; int[] item=new int[Limit+1]; long[] value=new long[Limit+1]; long newvalue; class body { String name; // 品名 int size; // 重量 long price; // 價格 public body(String n,int s,long p){ name=n; size=s; price=p; } } body[] a={new body("plum",4,4500),new body("apple",5,5700), new body("orange",2,2250),new body("strawberry",1,1100), new body("melon",6,6700)}; ………………
for (s=0;s<=Limit;s++) { value[s]=0; // 初始值 } for (i=0;i<N;i++) { // 物品的編號 for (s=a[i].size;s<=Limit;s++) { // 背包的大小 p=s-a[i].size; // 空背包的大小 newvalue=value[p]+a[i].price; if (newvalue>value[s]) { value[s]=newvalue;item[s]=i; // 最佳解的修改 } } } int y=1; g.drawString("品名",10,20); g.drawString("價格",80,20); for (s=Limit;s>=Min;s=s-a[item[s]].size) { g.drawString(""+a[item[s]].name,10,y*20+20); g.drawString(""+a[item[s]].price,80,y*20+20); y++; } g.drawString("合計",10,y*20+20); g.drawString(""+value[Limit],80,y*20+20); }