第1章資料結構導論 - 回到富國的知識共享空間

Download Report

Transcript 第1章資料結構導論 - 回到富國的知識共享空間

資料結構
陳富國
課本:輕鬆學會圖解資料結構
-使用Java
文魁行銷出版發行
第一章 資料結構導論
課前指引
資料結構科學可以稱得是近十幾年來蓬勃興起的一門新興科
學﹔它的研究重點是在電腦的程式設計領域中,如何將電腦中
相關資料的組合,以某種方式組識而成,然後在這樣的定義下,
就可以探討各種有意義的操作與關係,藉以提升程式設計上的
執行績效。
章節大綱
1-1 資料結構簡介
1-5 Java的物件導向程式設計
1-2 資料抽象化
1-6 遞迴演算法
1-3 演算法與程式設計
1-7 程式效能分析
1-4 程式設計的風格
備註:可依進度點選小節
1-1 資料結構簡介
資料結構的應用
樹狀結構
• 一種相當重要的非線性資料結構,廣泛運用在如人類社
會的族譜或是機關組織、計算機上的MS-DOS和Unix作業
系統、平面繪圖應用、遊戲設計等。
最短路徑
• 功用是在眾多不同的路徑中,找尋行經距離最短、或者
所花費成本最少的路徑,如都市運輸系統、鐵道運輸系
統、通信網路系統等。
搜尋理論
• 是一種自動從網際網路的眾多網站中蒐集資訊,經過一
定整理後,提供給使用者進行查詢的系統,例如Yahoo、
Google、蕃薯藤等。
4
1-1 資料結構簡介
演算法(1/2)
輸入(Input)
• 在演算法的處理過程中,通常所輸入資料可有可無,零
或一個以上都可以。
有效性(Effectiveness)
• 每個步驟都可正確執行,即使交給不同的人用手動來計
算,也能達成相同效果。
明確性(Definiteness)
• 每一個步驟或指令必須要敘述的十分明確清楚,不可以
模糊不清來造成混淆。
5
1-1 資料結構簡介
演算法(2/2)
有限性(Finiteness)
• 演算法一定在有限步驟後會結束,不會產生無窮迴路,
這是相當重要的基本原則。
輸出(Output)
• 至少會有一個輸出結果,不可以沒有輸出結果。
6
1-1 資料結構簡介
演算法描述工具(1/5)
流程圖
• 流程圖的繪製方向應由上至下,由左至右。
7
1-1 資料結構簡介
演算法描述工具(2/5)
虛擬語言
• 是一種接近高階程式語言寫法的語言,不過不能直接放
進電腦中執行。
• 以下是利用輾轉相除法求整數m與n的SPARKS遞迴演算法
Procedure GCD(m,n)
if n=0 then return (m) else
return (GCD(n,m mod n))
endif
end
8
1-1 資料結構簡介
演算法描述工具(3/5)
圖形
• 如陣列、樹狀圖、矩陣圖等,以下是是井字遊戲的某個
決策區域,利用決策樹圖形來表示其演算法:
9
1-1 資料結構簡介
演算法描述工具(4/5)
敘述表示法
• 演算法如下:
1.I = 1,2,…5 循 序 輸 入 Student-Name(I) 、 Chinese(I) 、 Math(I) 、
English(I)。
2.計算Average(I)=(Chinese(I)+Math(I)+English(I))/3
3.印出Average(I)及Student-Name(I)
4.如果Average(I)>60,則加印“這位同學的平均分數是及格”。
5.結束
10
1-1 資料結構簡介
演算法描述工具(5/5)
程式語言
• 以下演算法是以C/C++語言來計算所輸入兩數x、y的xy
值函數Pow():
float Pow( float x, int y )
{
float p = 1;
int i;
for( i = 1; i <= y; i++ )
p *= x;
return p;
}
11
1-2 資料抽象化
基本資料型態
如果基本資料型態更高一層,是指一個資料型態
包含其他的資料型態,稱為延伸型資料型態
(Derived Data Type),或組合資料型態。
例如
Java的類別
C中的結構(structure)型態
C++中的字串(string)或類別(class)型態。
12
1-2 資料抽象化
抽象資料型態
如果是一種自訂資料型態,可簡化一個資料型態
的表現方式及操作運算,並提供使用者以預定的
方式來使用這個資料型態。
堆疊(Stack)或佇列(Queue),就是很典型的ADT
堆疊的運作圖示
13
1-2 資料抽象化
以下的鏈結串列(Linked List)也可想像成現
實生活中的多節火車,我們只需考慮到它的
運作功用,而不須考慮每個節點的詳細資料
內容。
有5個節點的單向鏈結串列
14
1-3 演算法與程式設計
認識程式設計(1/2)
需求與目的
• 了解程式所要解決的問題,並搜集相關的輸入資訊與期
望得到的輸出結果。
設計與規劃
• 根據撰寫此程式的目的、程式的使用者、滿足需求的軟
硬體環境等,來著手設計這個程式演算法的描述。
分析與討論
• 思考其他可能適合的演算法及資料結構,最後再選出最
適當的標的,還必須考慮到可讀性、穩定性及可維護性
等因素。
15
1-3 演算法與程式設計
認識程式設計(2/2)
撰寫與編輯
• 事先必須選擇所需的程式語言,再依據演算法來繪製流
程圖,最後進行程式碼的撰寫。
測試與偵錯
• 最後必需確認程式的輸出是否符合需求,並進行包括所
謂「語意錯誤」 (Semantic Error)、「語法錯誤」
(Syntax Error)與「邏輯錯誤」(Logical Error)等相
關測試與除錯工作。
16
1-4 程式設計的風格
由上而下與模組化設計
將問題由大到小、由上而下逐步分解成較小的單元,這些
單元稱為模組,模組又可再細分為子模組…,一直細分到
不可分割為止。
分割出來的模組,問題小到容易解決,方便程式撰寫,可
讓程式設計師分工。
物件導向設計
模組化設計要不是以功能為考量,就是以資料為考量,功
能與資料模組在發展過程中容易失去內聚力,功能與資料
模組喪失緊密結合的關係。
物件導向設計將資料與功能聚合在一起成為單一個類別,
類別中有資料與行為(功能)。
17
1-4 程式設計的風格
可讀性設計
適時使用「註解」就是提高程式可讀性的第一步。
程式可讀性和可偵錯性的因素,最好將程式碼中的
識別字名稱定義為較具體且有意義。
一個程式的程式碼就像一篇文章一樣,是由一個或
數個程式區塊(Block)所構成,而程式區塊就像文
章中的段落。
程式區塊,就是由{}左右兩個大括弧所組成,包含
了多行或單行的指令。
18
1-4 程式設計的風格
控制結構設計(1/3)
循序結構
• 是一個程式敘述由上而下接著一個程式敘述的執行指令
,如下圖所示:
19
1-4 程式設計的風格
控制結構設計(2/3)
選擇結構
• 是一種條件控制敘述,包含有一個條件判斷式,如果條
件為真,則執行某些程式,一旦條件為假,則執行另一
些程式。如下圖所示:
20
1-4 程式設計的風格
控制結構設計(3/3)
重覆結構
• 主要是迴圈控制的功能。迴圈(Loop)會重複執行一個程
式區塊的程式碼,直到符合特定的結束條件為止。依照
結束條件的位置不同分為兩種:
前測試型迴圈
後測試型迴圈
21
1-4 程式設計的風格
物件導向程式設計(1/3)
封裝(Encapsulation)
• 封裝利用「類別(Class)」來實作抽象化資料型態(ADT)
• 每個類別有其資料成員與函成員,我們可將其資料成員
定義為私有的(Private),而將用來運算或操作資料的
函式成員定義為公有的(Public)或受保護的(Protected)
來實現資訊隱藏,這就是「封裝」(Encapsulation)。
• 封裝的目的在於隱藏實作的細節,降低系統的複雜性。
• 封裝最經典的例子莫過於機車的製造,一般機車的使用
者不必了解機車的製造細節,透過機車的公開方法(油
門、剎車、起動)即可以使用機車。而且,不同的製造
商只要實現標準的機車公開介面(使用方法),使用者可
以方便地自由地選擇不同製造商所製造的機車。
22
1-4 程式設計的風格
物件導向程式設計(2/3)
繼承(Inheritance)
• 繼承允許我們去定義一個新的類別來繼承既存的類別,
進而使用或修改繼承而來的方法,並可在子類別中加入
新的資料成員與函數成員。此外,透過類別的繼承行為
,讓程式開發人員能重複利用已宣告類別的成員方法;
並可經由過載(override)的動作,來重新定義及強化新
類別所繼承的各項行為。
• 要站在巨人的肩膀上…,透過繼承的方式吾人可以繼承
基礎類別後,再行擴充繼承下來的類別,一代比一代更
好!
23
1-4 程式設計的風格
物件導向程式設計(3/3)
多形(Polymorphism)
• 按照英文字面解釋,就是一樣東西同時具有多種不同的
型態。這是物件導向設計的重要特性,它展現了動態繫
結的功能。
• 多形的功能可該軟體在發展和維護時,達到充分的延伸
性。
• 多形最直接的定義就是該具有繼承關係的不同類別物件
,可以對相同名稱的成員函數呼叫,並產生不同的反應
結果。
24
1-5
Java的物件導向程式設計
類別與物件(1/2)
在原型規劃階段,或稱之為類別的設計階段,首
先必須考慮到將來產生的物件,所包含的資料。
當物件的原型規劃完成後,就可以實際地產生出
一個可用的物件,這個過程叫做具現化
(instantiation)。在JAVA中,物件的具現化方式
如下:
類別名稱 物件(變數)名稱 = new 物件建構子();
25
1-5
Java的物件導向程式設計
類別與物件(2/2)
new:依照類別建構子所代表的參考型別,配置記
憶體空間,以建立該類別的實體物件。
建構子(constructor):用來建立該類別的物件,
並在建立的同時設定初始值。
通常Java宣告基本資料型態的變數(例如整數)時
,會自動分配記憶體空間,但是類別型態的變數
在宣告時,則必須以new指令來配置記憶體空間,
但是這個配置動作並不會為所建立的物件給定初
值,如果想要在建立物件的同時給定初值,就必
須藉助建構子,這也正是建構子主要的功用。
26
1-5
Java的物件導向程式設計
範例 1.5.1
請設計一程式,來說明Java語言中類別的應用與
物件的宣告。
27
1-5
Java的物件導向程式設計
資料封裝
將靜態屬性數值與動態行為方法,包覆於此物件
所「參考」(reference)到的類別中。主要的目
的是避免物件範圍以外的程式,有任何更動或破
壞內部資料的可能。
在JAVA中是利用三種內建關鍵字private、
protected與public來設定。分別說明如下:
28
1-5
Java的物件導向程式設計
定義存取權限的語法,必須在宣告成員資料、方
法或類別之前,加入關鍵字,例如下面的程式宣
告片段:
private int usePassword
//宣告整數型態變數userPassword為
私有的資料成員
protected getPassword() //宣告類別方法getPassword為受保
護的成員方法
public class checkPassword //宣告類別checkPassword為
公開的類別
29
1-5
Java的物件導向程式設計
類別繼承(1/2)
繼承就類似遺傳的觀念,當物件導向技術以這種
生活實例去定義其功能時,則稱為繼承
(inheritance)。這種繼承模式在JAVA平台下的宣
告語法如下:
存取修飾子 class 衍生類別名稱 extends 基礎類別名稱
30
1-5
Java的物件導向程式設計
類別繼承(2/2)
存取修飾子:當修飾子省略時,表示使用預設的
存取方式。它代表的意義為:相同「套件」(
package)中的所有類別都能存取此類別。存取修
飾子的關鍵字如下表:
31
1-5
Java的物件導向程式設計
範例 1.5.2
請設計一程式,來說明有關衍生類別的存取動作
,必須依據基礎類別成員的存取修飾子來作判斷
。
32
1-5
Java的物件導向程式設計
物件多形
「多形」(polymorphism)是利用類別繼承架構的
基礎,先建立一個內容為「null」的基本類別(父
類別)物件參考,讓使用者可以透過這個物件參考
指向衍生類別物件,進而加以控制所有衍生類別(
子類別)的「同名異式」方法。
通常一個大型的應用程式會由許多的物件共同組
成,我們不能奢望在沒有任何媒介情形之下,物
件與物件之間會自動地達成協調處理。在物件導
向的觀念中,可以利用「訊息」(Message)傳遞的
手法,來達到兩個或多個物件間的互動與溝通。
33
1-5
Java的物件導向程式設計
範例 1.5.3
請設計一程式,利用Java的多形功能,來控制各
種可能的衍生物件。
34
1-6 遞迴演算法
遞迴的定義(1/3)
談到遞迴的定義,我們可以正式這樣形容,假如
一個函數或副程式,是由自身所定義或呼叫的,
就稱為遞迴 (Recursion) ,它至少要定義2種條
件,包括一個可以反覆執行的遞迴過程,與一個
跳出執行過程的出口。
例如我們知道階乘函數是數學上很有名的函數,
對遞迴式而言,也可以看成是很典型的範例,我
們一般以符號"!"來代表階乘。如4階乘可寫為4!
,n!可以寫成:
n!=n×(n-1)*(n-2)……*1
35
1-6 遞迴演算法
遞迴的定義(2/3)
可以一步分解它的運算過程,觀察出一定的規律
性:
5! = (5 * 4!)
= 5 * (4 * 3!)
= 5 * 4 * (3 * 2!)
= 5 * 4 * 3 * (2 * 1)
= 5 * 4 * (3 * 2)
= 5 * (4 * 6)
= (5 * 24)
= 120
36
1-6 遞迴演算法
遞迴的定義(3/3)
Java的n!遞迴函數演算法可以寫成如下:
public static int fac(int n)
{
if(n==0) //遞迴終止的條件
return 1;
else
return n*fac(n-1); //遞迴呼叫
}
37
1-6 遞迴演算法
範例 1.6.1
請以Java語言,設計一個n!遞迴式演算法。
38
1-6 遞迴演算法
遞迴因為呼叫對象的不同,可以區分為以下
兩種:
直接遞迴(Direct Recursion):指遞迴函數中,
允許直接呼叫該函數本身,稱為直接遞迴(Direct
Recursion)。如下例:
int Fun(...)
{
.
.
if(...)
Fun(...)
.
.
}
39
1-6 遞迴演算法
間接遞迴:指遞迴函數中,如果呼叫其他遞迴函
數,再從其他遞迴函數呼叫回原來的遞迴函數,
我們就稱做間接遞迴(Indirect Recursion)。
int Fun1(...)
int Fun2(...)
{
{
.
.
.
.
if(...)
if(...)
Fun2(...)
Fun1(...)
.
.
.
.
}
}
40
1-6 遞迴演算法
費伯那序列
費伯那序列的基本定義:
public static int Fibonacci(int n)
{
if (n==0)
// 第0項為 0
return (0) ;
else if (n==1) // 第1項為 1
return (1) ;
else
return( Fibonacci(n-1)+Fibonacci(n-2));
// 遞迴呼叫函數 第n項為n-1 跟 n-2項之和
}
41
1-6 遞迴演算法
範例 1.6.2
請以Java來設計一個計算第n項費伯那序列的遞迴
程式。
42
1-6 遞迴演算法
河內塔問題(1/9)
先玩遊戲!
http://www.novelgames.com/flashgames/game.php?id=31&l=c
河內塔問題可以這樣形容:假設有A、B、C三個木樁和n個大小
均不相同的套環(Disc),由小到大編號為1,2,3…n,編號越大
直徑越大。
開始的時候,n個套環境套在A木樁上,現在希望能找到將A木樁
上的套環藉著B木樁當中間橋樑,全部移到C木樁上最少次數的
方法。不過在搬動時還必須遵守下列規則:
1.直徑較小的套環永遠置於直徑較大的套環上。
2.套環可任意地由任何一個木樁移到其他的木樁上。
3.每一次僅能移動一個套環,而且只能從最上面的套
環開始移動。
43
1-6 遞迴演算法
河內塔問題(2/9)
現在我們考慮n=1~3的狀況,以圖示方式示範處理
河內塔問題的步驟:
• n=1個套環
(當然是直接把盤子從1號木樁移動到3號木樁。)
n=2個套環
1.將套環從1號木樁移動到2號木樁
44
1-6 遞迴演算法
河內塔問題(3/9)
n=2個套環
2.將套環從1號木樁移動到3號木樁
3.將套環從2號木樁移動到3號木樁,就完成了
45
1-6 遞迴演算法
河內塔問題(4/9)
• n=2個套環
完成
• 結論:移動了2x2-1=3次,盤子移動的次序為1,2,1(此
處為盤子次序)
步驟為:1→2,1→3,2→3(此處為木樁次序)
46
1-6 遞迴演算法
河內塔問題(5/9)
n=3個套環
1.將套環從1號木樁移動到3號木樁
2.將套環從1號木樁移動到2號木樁
47
1-6 遞迴演算法
河內塔問題(6/9)
n=3個套環
3.將套環從3號木樁移動到2號木樁
4.將套環從1號木樁移動到3號木樁
48
1-6 遞迴演算法
河內塔問題(7/9)
n=3個套環
5.將套環從2號木樁移動到1號木樁
6.將套環從2號木樁移動到3號木樁
49
1-6 遞迴演算法
河內塔問題(8/9)
n=3個套環
7.將套環從1號木樁移動到3號木樁,就完成了
完成
結論:移動了2*3-1=7次,盤子移動的次序為
1,2,1,3,1,2,1(盤子次序)
步驟為1→3,1→2,3→2,1→3,2→1,2→3,1→3(木樁次序)
50
1-6 遞迴演算法
51
1-6 遞迴演算法
河內塔問題(9/9)
以下以遞迴式來表示河內塔遞迴函數演算法:
void hanoi(int n, int p1, int p2, int p3)
{
if (n==1) //遞迴出口
printf("套環從 %d 移到 %d\n", p1, p3);
else
{
hanoi(n-1, p1, p3, p2);
printf("套環從 %d 移到 %d\n", p1, p3);
hanoi(n-1, p2, p1, p3);
}
}
52
河內塔另種較漂亮的程式寫法
void hanoi (int num, char start, char temp, char end ){
if( num > 0 ){
hanoi ( num-1 , start , end , temp );
printf("%d %c -> %c \n" , num, start , end ); /* 顯示移動狀況 */
hanoi ( num-1 , temp , start , end);
}
}
int main(){
hanoi ( 4 , 'A' , 'B' ,'C' ); /*四個盤子,以A為起始柱,以C為目標柱,以
B為暫存柱*/
return 0;
}
53
改成Java…
class hanoi{
static void hanoi (int num, char source, char temp, char dest){
if( num > 0 ){
hanoi ( num-1 , source, dest , temp );
System.out.println("移動第" + num + "盤子 從" + source+ "到"+ dest); //
顯示移動狀況
hanoi ( num-1 , temp , source, dest);
}
}
public static void main(String[] args){
hanoi( 4 , 'A' , 'B' , 'C' );
//四個盤子,以A為起始柱,以C為目標柱,以B為暫存柱
}
}
54
1-6 遞迴演算法
範例 1.6.3
請利用Java語言,並以遞迴式來實作河內塔演算
法的求解。
55
1-6 遞迴演算法
範例 1.6. 4
請問河內塔問題中,移動n個盤子需的最小移動次
數?試說明之。
解答
課文中曾經提過當有n 個盤子時,可將河內塔問題歸納
成三個步驟,其中an為移動n個盤子所需要的最少移動次
數,an-1為移動n-1個盤子所需要的最少移動次數,a1=1
為只剩一個盤子時的次數,因此可得如下式子:
an= an-1+1+ an-1
=2an-1+1
=2(2an-2+1)+1
=4an-2+2+1
=4(2an-3+1)+2+1
56
1-6 遞迴演算法
=8an-3+4+2+1
=8(2an-4+1)+4+2+1
=16an-4+8+4+2+1
=...
=...
=2n-1a1+
因此,an=2n-1*1+
=2n-1 + 2n-1-1=2n-1,得知要移動n個盤子所需的最小移動次
數為2n-1次
57
1-7 程式效能分析
程式效能分析(1/3)
範例 1.7.1
• 考慮下列x←x+1的執行次數。
(3)
(1)
:
x←x+1
:
(2)
for i←1 to n do
:
x←x+1
:
end
for i←1 to n do
:
for j←1 to m do
:
x←x+1
:
end
:
end
解答
(1)1次(2)n次(3)n*m次
58
1-7 程式效能分析
程式效能分析(2/3)
範例 1.7.2
• 考慮下列x←x+1的執行次數。
for i←1 to n do
j←i
for k←j+1 to n do
x←x+1
end
end
解答
因為j←i,且k←j+1所以可用以下數學式表示,所以其執行次數為
59
1-7 程式效能分析
程式效能分析(3/3)
範例 1.7.3
• 請決定以下片斷程式的執行時間。
k=100000
while k<>5 do
k=k DIV 10
end
解答
因為k=k DIV 10,所以一直到k=0時,都不會出現k=5的
情況,整個迴路為無窮迴路,執行時間為無限長。
60
1-7 程式效能分析
Big-oh(1/3)
範例 1.7.4
• 請證明
=O(n2)
解答
61
1-7 程式效能分析
Big-oh(2/3)
常見的Big-oh有下列幾種:
62
1-7 程式效能分析
63
1-7 程式效能分析
Big-oh(3/3)
對於n≥16時,時間複雜度的優劣比較關係如下:
O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<O(2n)
範例 1.7.5
請問河內塔遞迴氏的時間複雜度為何?
解答:由範例1.5.1中,可以得到以下結論:
an== an-1+1+ an-1
=2n-1 → O(2n)
64
1-7 程式效能分析
Ω(omega)
以下是Ω的定義:
對f(n)= Ω(g(n))(讀作”big-omega of g(n)”),意思是存在常數c
和n0,對所有的n值而言,n>= n0時,f(n)≧cg(n)均成立。例如
f(n)=5n+6,存在c=5 n0=1,對所有n≧1時,5n+6≧5n,因此
f(n)= Ω(n)而言,n就是成長的最大函數。
範例1.7.6
f(n)=6n2+3n+2,請利用Ω來表示f(n)的時間複雜
度。
f(n)= 6n2+3n+2,存在c=6 ,n0≧1,對所有的n≧n0,使得
6n2+3n+2>=6n2,所以f(n)= Ω(n2)
65
1-7 程式效能分析
θ(theta)
是一種比Big-O與Ω更精確時間複雜度的漸近表示
法。定義如下:
f(n)= θ(g(n))(讀作”big-theta of g(n)”),意是存在常數c1、c2
、n0,對所有的n≧n0時,c1g(n)≦f(n)≦c2g(n)均成立。換句話
說,當f(n)=θ(g(n))時,就表示g(n)可代表f(n)的上限與下限。
例如以f(n)=n2+2n為例,當n≧0時,n2+2n≦3n2
,可得f(n)=O(n2)。同理,n≧0時, n2+2n≧n2
,可得f(n)=Ω(n2)。所以f(n)=n2+2n=θ(n2)
66
本章結束
Q&A討論時間
67