320 likes | 460 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
第一章 學習重點 • 多多學習各種視野的演算法(Algorithms),有助於提昇程式設計功力。 • 本書從第2章起將分別說明各個領域的典型演算法。 • 本章將說明與這些領域較為不同的簡單之演算法,以奠定演算法的基礎。
1-0 何謂演算法 • 演算法(algorithms)是指解決問題的邏輯或步驟。解決問題的演算法有很多種,但是適合人類使用的演算法卻不見得(說一定也不為過)不會成為電腦適用的演算法。
1-0 何謂演算法 • 演算法的評價 • 1.信賴度高 • 2.處理效率佳 • 3.具普遍性 • 4.具擴充性 • 5.一目了然 • 6.可移植性(Portability)高
1-0 何謂演算法 • 演算法與資料結構 • 電算處理通常會處理到大量的資料,而資料結構(data structure)如有不同,則其演算法便會不同。 • 正如『Algorithms +Data Structure=Program(演算法+資料結構=程式)』(N.Wirth著)這個書名,資料結構與演算法具有緊密的關係,選取好的資料結構,就能製作出好的程式。
1-1 漸進式-排列組合 • 開始練習演算法的邏輯推演… • 例題1 求解 • 求由n個之中選出r個數字排列組合的數值
1-1 漸進式-排列組合 • 變成Java程式 • class Rei01Panel extends Panel { • long combi(int n,int r) { • int i; • long p=1; • for (i=1;i<=r;i++) • p=p*(n-i+1)/i; • return p; • } • public void paint(Graphics g) { • int n,r; • for (n=0;n<=5;n++){ • for (r=0;r<=n;r++) • g.drawString(""+n+"C"+r+"="+combi(n,r),r*60,n*20+20); • } • } • } • …
1-2 映射 • 例題2 直方圖(histogram) • 將0~100的得分按每10分分成一組(0~9,10~19,...90~99,100共11組),求各組的分數分佈(histogram) histo[0]~histo[10]為求分數的陣列,histo[0]存放0~9的分數histo[1]存放10~19的分數,依次類推。
1-2 映射 • 以35分為例,將此分數除以10(分數分佈的幅度)的商數的3之histo[3]的內容+1,分數的分佈即可計算出來。 • 由此我們就可將下列的0~100分的資料範圍映射成0~10的範圍。
1-2 映射 • class Rei02Panel extends Panel { • public void paint(Graphics g) { • final int Num=20; • int[] a={35,25,56,78,43,66,71,73,80,90, • 0,73,35,65,100,78,80,85,35,50}; • int[] histo=new int[11]; • int i,rank; • for (i=0;i<=10;i++) • histo[i]=0; • for (i=0;i<Num;i++) { • rank=a[i]/10; // 映射 • if (0<=rank && rank<=10) • histo[rank]++; • } • for (i=0;i<=10;i++) { • g.drawString(""+i*10+" - :",10,i*20+20); • g.drawString(""+histo[i],50,i*20+20); • } • } • }
1-3 排名 • 例題3 單純的方法假設有成績測驗分數的資料,現欲求其排名順序 • 假設有下列的得分資料:
1-3 排名 • class Rei03Panel extends Panel { • public void paint(Graphics g) { • final int Num=10; • int[] a={56,25,67,88,100,61,55,67,76,56}; • int[] juni=new int[Num]; • int i,j; • for (i=0;i<Num;i++) { • juni[i]=1; • for (j=0;j<Num;j++) { • if (a[j]>a[i]) • juni[i]++; • } • } • g.drawString("得分 排名",10,20); • for (i=0;i<Num;i++) { • g.drawString(""+a[i],10,i*20+40); • g.drawString(""+juni[i],50,i*20+40); • } • } • }
1-3 排名—改良 • 在例題3的排名順序演算法中,資料如為n個,反覆計算次數便為n2,因此資料如有增加,資料的處理時間變化增加。為此我們要想出減低反覆計算次數的排名順序演算法來 • 當我們將分數範圍設定為0~100時,需將此分數範圍增加。我們要設定juni[0]~juni[100]的陣列,並額外設定1個juni[101]的陣列,此2陣列的內容設定為0。
1-3 排名—改良 • 將下來將101的元素的初始值設定為1(表示排名順序為1),再將1各右邊的元素內容加在juni[100]→juni[0]的各陣列內。 • 如此就可求出〈得分+1〉的陣列順序了。以上例而言,100分的排名是在101的位置上,88分的排名是在89的位置上。 • 這個方法的資料如為n,資料範圍如為m,則排名順序反覆計算次數為n+m次。
1-3 排名—改良 • public void paint(Graphics g) { • final int Num=10,Max=100,Min=0; • int[] a={56,25,67,88,100,61,55,67,76,56}; • int[] juni=new int[Max+2]; • int i; • for (i=Min;i<=Max;i++) • juni[i]=0; • for (i=0;i<Num;i++) • juni[a[i]]++; • juni[Max+1]=1; • for (i=Max;i>=Min;i--) • juni[i]=juni[i]+juni[i+1]; • g.drawString("得分 排名",10,20); • for (i=0;i<Num;i++) { • g.drawString(""+a[i],10,i*20+40); • g.drawString(""+juni[a[i]+1],50,i*20+40); • } • }
1-4 亂數排列 • 例題4 亂數排列(效率較差的方法)以1~N的值產生亂數排列 • 以1~6為例,其亂數排列為3,2,5,1,6,4。 • 下列的演算法在做差的情況下是以的次序反覆進行計算,因此是效率差的方法。 • (1)求得1個1~的亂數。將此亂數設為排列的第1個資料。 • (2)將其下以-1次反覆計算 • (3)求得1個~1的亂數 • (4)(3)所求得的亂數如以代入目前所產生的排列中,則返回(3)。
1-4 亂數排列 • public void paint(Graphics g) { • final int N=20; • int[] a=new int[N+1]; • int i,j,flag; • a[1]=irnd(N); • for (i=2;i<=N;i++) { • do { • a[i]=irnd(N); • flag=0; • for (j=1;j<i;j++) • if (a[i]==a[j]) { • flag=1;break; • } • } while (flag==1); • } • String s=""; • for (i=1;i<=N;i++) • s=s+a[i]+","; • g.drawString(s,10,20); • }
1-4 亂數排列—改良 • public void paint(Graphics g) { • final int N=20; • int[] a=new int[N+1]; • int i,j,d; • for (i=1;i<=N;i++) • a[i]=i; • for (i=N;i>1;i--) { • j=irnd(i-1); • d=a[i];a[i]=a[j];a[j]=d; • } • String s=""; • for (i=1;i<=N;i++) • s=s+a[i]+","; • g.drawString(s,10,20); • }
1-5 蒙地卡羅法 • 例題5 求π以蒙地卡羅法求π的值
1-5 蒙地卡羅法 • …. • add(p=new Panel(),"Center"); • add(tf=new TextField("",10),"North"); • add(bt=new Button("圓周率的計算"),"South"); • bt.addActionListener(new ActionListener() { • public void actionPerformed(ActionEvent e) { • Graphics g=p.getGraphics(); • final int N=50000; • double x,y,pai; • int i,sum=0,px,py; • g.clearRect(50,10,100,100); • g.drawRect(50,10,100,100); • for (i=1;i<N;i++) { • x=Math.random(); • y=Math.random(); • if (x*x+y*y<1) { • px=50+(int)(100*x);py=110-(int)(100*y); • g.drawLine(px,py,px,py); • sum++; • } • } • pai=(double)4*sum/N; • tf.setText("圓周率="+pai); • g.dispose(); • …
1-5 蒙地卡羅法—求面積 • 練習問題5 求面積以蒙地卡羅法求出橢圓的面積 將0~2的亂數對應至,0~1的亂數對應至,並將其均勻的分佈在的長方形內。若將分佈在1/4橢圓(圖的斜線部分)內的亂數數量設為,分佈在亂數的總數設為,1/4橢圓的面積設為,則, 橢圓的面積則為:
1-5 蒙地卡羅法—求面積 • public void actionPerformed(ActionEvent e) { • Graphics g=p.getGraphics(); • final int N=50000; • double x,y,s; • int i,sum=0,px,py; • g.clearRect(50,10,100,100); • g.drawRect(50,10,100,100); • for (i=1;i<N;i++) { • x=2*Math.random(); • y=Math.random(); • if (x*x/4+y*y<=1) { • px=50+(int)(50*x);py=110-(int)(50*y); • g.drawLine(px,py,px,py); • sum++; • } • } • s=(double)4*(2.0*sum/N); • tf.setText("面積="+s); • g.dispose(); • }
1-6 歐基理德輾轉相除法 • 例題6 歐基理德輾轉相除法(之一)以歐基理德輾轉相除法求出m, n這2個整數的最大公約數 24與18的最大公約數是以下列的方式求出: 2)24 18 3)12 9 4 3
1-6 歐基理德輾轉相除法(1) • 這個方法是根據「設有整數,的最大公約數可轉換成求的最大公約數的方法」的原理而來的。 • 也就是說,m與n的大問題可轉換成的m-n與n小問題,而也可以對m-n用同樣方式反覆處理下去,當m=n時,m(或n)就為所要求出的最大公約數。 • 此方法可整理成如下的演算法:(1)當m不等於n時,反覆進行下列的運算(2)若m>n則m=m-n 否則 n=n-m(3) m(或n)為所要求出的最大公約數。 • m=24, n=18時,將m與n的值描繪出的圖形如下所示:
1-6 歐基理德輾轉相除法(1) • public void actionPerformed(ActionEvent e) { • int m,n; • m=Integer.parseInt(tf1.getText()); • n=Integer.parseInt(tf2.getText()); • while (m!=n) { • if (m>n) • m=m-n; • else • n=n-m; • } • tf3.setText("最大公約數="+m); • }
1-6 歐基理德輾轉相除法(2) • m與n的差如相差太大時,可用(m % n)來取代(m-n),這樣的處理效率較高。以下便以此方法求出最大公約數。 • m%n歐基理德輾轉相除演算法解說如下:(1)設k為m除n以後的餘數(2)將n代入m,k代入n(3)k如不等於0則返回(1)(4)m即為所要求得的最大公約數。
1-6 歐基理德輾轉相除法(2) 假設m=32, n=14時: m n k 32 14 4 (32%14=4) 14 4 2 (14%4=2) 4 2 0 ←結束條件 2 0 ↑ 答案
1-6 歐基理德輾轉相除法(2) • public void actionPerformed(ActionEvent e) { • int m,n,k; • m=Integer.parseInt(tf1.getText()); • n=Integer.parseInt(tf2.getText()); • do { • k=m % n; • m=n;n=k; • } while(k!=0); • tf3.setText("最大公約數="+m); • }
1-7 厄拉多塞篩的求質數法 • 例題7 質數的判定,判斷n是否為質數 • 質數是指除了1及其本身以外沒有其他約數的數,2,3,5,7,11...等都是質數。1並不是質數。在判斷n是否為質數時,將n除n以以下的整數直至2為止,以觀察能否除盡,這時如有被除盡的數,便被視為質數而脫離出迴圈。到了最後如無除盡的數,則此數即為質數。此外,即使以n/2以上的整數除以n,n也沒有被除盡的道理,因此程式開始時的值並不是從n開始,從n/2開始也沒關係,這點從直覺上即可判斷出來。 • 在數學計算上也可從 開始。
1-7 厄拉多塞篩的求質數法 • public void actionPerformed(ActionEvent e) { • int i,n,Limit; • n=Integer.parseInt(tf.getText()); • if (n>=2) { • Limit=(int)Math.sqrt(n); • for (i=Limit;i>1;i--) { • if (n%i ==0) • break; • } • if (i==1) • ta.setText(ta.getText()+n+"是質數\n"); • else • ta.setText(ta.getText()+n+"不是質數\n"); • } • }
1-7 厄拉多塞篩的求質數法 • 厄拉多塞篩演算法解說如下:1.將2~n的所有數存放至「篩子」。2.將「篩子」中最小的數設為質數。3.從「篩子」中取出質數的所有倍數4.在達到n之前,反覆進行(2)~(3),留在「篩子」(沒有劃斜線的部分)數即為質數。