880 likes | 1.14k Views
第 15 章 資料結構理論與實務. 演算法 陣列 鏈結串列 堆疊與佇列 樹狀結構 搜尋. 資料與資訊. 資料可以看成是一種沒有評估價值的基本項目或原素 (Atom) ,包括了文字、數字、圖等。. 15-1 演算法. 在尚未正式進入本書資料結構的正式說明之前,先來談談「資料」 (Data) 與「資訊」 (Information) 的相關定義。 「資料」 (Data) 的定義可以這樣形容: 例如報紙上的文字、學校的成績單、郵寄信件地址等,都是一種資料。. 15-1 演算法.
E N D
第15章 資料結構理論與實務 演算法 陣列 鏈結串列 堆疊與佇列 樹狀結構 搜尋
資料與資訊 資料可以看成是一種沒有評估價值的基本項目或原素(Atom),包括了文字、數字、圖等。 15-1 演算法 • 在尚未正式進入本書資料結構的正式說明之前,先來談談「資料」(Data)與「資訊」(Information)的相關定義。 • 「資料」(Data)的定義可以這樣形容: • 例如報紙上的文字、學校的成績單、郵寄信件地址等,都是一種資料。
15-1 演算法 • 關於資訊的處理和電腦的應用是息息相關的,尤其在近代的「資訊革命」洪潮中,如何掌握資訊、利用資訊,可以說是個人或事業體發展成功的重要原因。 • 因為電腦的充分配合,更能使資訊的價值發揮到淋漓盡致的境界。如下圖所示:
演算法的定義(1) 演算法最簡單的說法,就是一種「計算方法或法則」。或者可以將演算法看成是解決某一個工作或問題,所需要的一些有限個數的指令或步驟。 15-1 演算法 • 演算法定義: • 對於任何一種演算法而言,還必須滿足以下5種原則: • 有限性(Finiteness) • 必須在有限的步驟內解決問題,而且不可造成無窮迴路。 • 演算法和程序(procedure)是有所區別,因為程式不一定要滿足有限性的要求,如作業系統或機器上的運作程式。
演算法的定義(2) 15-1 演算法 • 有效性(Effectiveness) • 每個步驟或運算若交給人們用筆或紙計算,也能在有限時間內達成同樣效果。 • 明確性(Definiteness) • 每一個步驟或指令必須要敘述的十分清楚,不可以模糊不清。 • 輸入資料(Input) • 演算法的輸入資料可有可無,零或一個以上都可以。 • 輸出資料(Output) • 演算法的結果一定要有一或一個以上的輸出資料。
演算法的定義(3) 15-1 演算法 • 演算法可以看成一種表達問題的觀念流程,在演算法中,必須以適當的資料結構來描述問題中抽象或具體的事物,有時還得定義資料結構本身有那些操作。 • 另外電腦理論中,演算法和「程序」(Procedure)是不同的,因為程序不要求必須具有限性,反而是可以容許無窮迴路(無限性)。例如一般作業系統中的「工作排程器」(Job Scheduler)就是一種程序(Procedure)。
演算法的工具 流程圖與圖形演算法 15-1 演算法 • 只要能夠清楚、明白、符合演算法的五項基本原則,即使一般文字,虛擬語言(Pseudo-language),表格或圖形、流程圖,甚至於任何一種程式語言都可以作為表達演算法的工具:
15-1 演算法 常見程式流程圖符號
演算法績效評估 15-1 演算法 • 對於一個程式或演算法的效能分析,應該是著重在以執行時間為主。 • 例如在程式設計中,決定程式區段的「步驟計數」的精確次數絕對是一件困難的工作。 • 所可利用途一種「概量」精神來做為衡量準則,這樣的概念稱為「時間複雜度」(Time Complexity),這也是一種「漸近式表示法」(Asymptotic Notation)的應用。
15-1 演算法 • 分析演算法在所有可能的輸入組合下,最多所需要的時間,稱為Big-Oh( 唸成“big-o”)。 • 例如可定義一個T(n),表示在一個完全理想狀態的計算機中,程式執行所花費的時間,由於一個程式的執行時間並不完全和輸入量有關,演算法的好壞也有影響,所以可以把它當作輸入量n的一種函數。
15-1 演算法 • 若某演算法的執行時間T(n)的時間複雜度是O(f(n)),也就是T(n) =O(f(n)) ,意謂存在兩個常數c與n0,若n≧n0,則T(n)≦cf(n)。 • 例如執行時間T(n)=2n3+3n2+n+7,它的時間複雜度如下: • 這個問題很簡單,首先必須找出c和n0。因此可得當n0=0,c=6時,2n3+3n2+n≦6n3,所以時間複雜度為O(n3)。
SPARKS語言 15-1 演算法 • 1.SPARKS的語法及規則: • 1.演算法的名稱及相關傳遞的參數。 • 2.指令與流程。 • 3.相關註解。 • 2.指令介紹(Assignment Statement): • 變數←表示式或數值 • 例如:A←15(A=15) A←B-C (A=B-C) • 3.運算子(Operators) • 1. 布林運算子(Boolean Operator):true、false。 • 2. 邏輯運算子(Logical Operator):and、or、not。
15-1 演算法 • 3. 關係運算子(Relational Operator):≧、>、=、≠、<、≦。 • 4. 數學運算子(arithmetic operator):如+、-、**、*、/、% (mod符號)等。 • 4.迴圈: • 1.loop指令與for指令 • (1) loop(2) for <variable> <start> to <finish> by <increment> • Forever 例:for i←1 to 10 by 2
15-1 演算法 • 2.while和repeat指令 • (1) while (2) repeat • while<condition>do repeat • end until<condition> • 5.if指令與case指令 • (1) if<condition>then (2)case • :<condition 1> :Statement1 • else :<condition 2> :Statement 2 • : : • : : • :<condition n> :Statementn • :else :Statementn+1 • end
15-1 演算法 例如 : case :K<Km:U←1 :K=Km:U←2 :K>Km:U←3 end • 6.離開指令(exit statement): • 1.exit:跳出迴圈。 • 2.return:返回呼叫程式或傳回值。
15-1 演算法 • 以下範例就是利用SPARKS語言來描述串列反轉的演算法,相較於其它演算法工具,是不是簡潔許多! Procedure Invert(x) Px;QNil; WHILE P≠NIL do rq;qp; pLINK(p); LINK(q)r; END xq; END
演算法的應用(1) 15-1 演算法 • 氣泡排序法 • 又稱為交換排序法,是由觀察水中氣泡變化構思而成,逐次比較2個相鄰的記錄,若大小順序有誤,則立即對調,掃描一遍後一定有一個記錄被置於正確位置,就彷彿氣泡逐漸由水底逐漸冒升到水面上一樣。
15-1 演算法 • 以下把五筆資料26、5、37、1、61用氣泡法表示步驟及氣泡的演算法實作如下:
15-1 演算法 • 由此可知n個元素的氣泡排序法必須執行n-1次掃瞄,以上例而言,共經過4次掃瞄。 • 第一次掃瞄需要作4次的比較;第二次掃瞄需要作3次的比較,第三次掃瞄需要作2次的比較,第四次掃瞄需要作1次比較,因此,其總比較次數為4+3+2+1=10(次)。 • 主要在避免重覆比較已完成排序的資料。
演算法的應用(2) ①先假設K的值為第一個鍵值。 ②由左向右找出鍵值Ki,使得Ki>K。 ③由右向左找出鍵值Kj使得Kj<K。 ④如果i<j,那麼Ki與Kj互換,並回到步驟②。 ⑤若i≧j則將K與Kj交換,並以j為基準點分割成左右部份。然後再針對左右兩邊進行步驟①至⑤,直到左半邊鍵值=右半邊鍵值為止。 15-1 演算法 • 快速排序法 • 快速排序法的排序原理如下: • 假設有n筆R1、R2、R3…Rn記錄,其鍵值為k1、k2、k3…kn:
15-1 演算法 • 以快速排序法將下列的資料排序過程表示如下: • 利用以上過程,將左半部與右半部分別排序,形成遞迴,至於排序過程如下:
一維陣列 int score[6]={169,271,188,174,160,183 }; 15-2 陣列 • 假設A是一維陣列(One-dimension Array)名稱,它含有n個元素,亦即A是n個連續記憶體(各個元素為A[0],A[1]…A[n-1])的集合,並且每個元素的內容為a0,a1…an-1。 • 例如在C/C++中宣告一個整數的一維陣列score:
15-2 陣列 • 這表示宣告了一個一維整數陣列,名稱是score,陣列中可以放入6個整數元素設定值,而陣列元素分別是score[0]、score[1]、score[2]、…score[5] 。 • 請注意!C/C++陣列的第一個元素索引值為0,而不是1,依序排列下去。如下圖所示:
二維陣列 資料型態 二維陣列名稱[列大小][行大小]; 15-2 陣列 • 可視為一維陣列的延伸,只不過須將二維轉換為一維陣列。 • 例如一個含有m*n個元素的二維陣列A,m代表列數,n代表行數。 • 在C/C++中宣告一個整數的二維陣列,宣告方式如下:
15-2 陣列 • 以arr[3][5]說明,arr為一個3列5行的二維陣列,也可以視為3*5的矩陣。 • 在存取二維陣列中的資料時,使用的索引值仍然是由0開始計算。 • 下圖以矩陣圖形來說明二維陣列中每個元素的索引值與儲存關係:
多維陣列 float arr[2][3][4]; 15-2 陣列 • 多維陣列表示法同樣可視為一維陣列的延伸,例如在C/C++中宣告一個單精度浮點數的三維陣列: • 以下則是將arr[2][3][4]三維陣列想像成空間上的立方體圖形:
單向鏈結串列 15-3 鏈結串列 • 串列中的每一個節點均不需儲存於連續的記憶體位置,且是一個指向節點的指標。 • 這個結構中至少要有兩個或以上的欄位,分別存放資料及鏈結欄位,而這種串列就稱為「單向鏈結串列」。 • 在「單向鏈結串列」中第一個節點是「串列指標首」,指向最後一個節點的鏈結欄位設為Null表示它是「串列指標尾」,不指向任何地方。
15-3 鏈結串列 • 例如串列A={a,b,c,d,x},其單向串列資料結構如下:
環狀串列 建立的過程與單向鏈結串列相似,唯一的不同點是必須要將最後一個節點指向第一個節點。 15-3 鏈結串列 • 環狀串列的特點是在串列的任何一個節點,都可以達到此串列內的各節點,通常可做為記憶體工作區與輸出入緩衝區的處理及應用。 • 如果我們將串列的最後一個節點的指標指向串列結構開始的第一個節點,就稱此串列為環狀串列。如下圖所示:
雙向鏈結串列 ptr=RLINK(LLINK(ptr))=LLINK(RLINK(ptr)) 15-3 鏈結串列 • 雙向鏈結串列的資料結構,可以定義如下: • 1.每個節點具有三個欄位,中間為資料欄位。其中RLINK指向下一個節點,LLINK指向上一個節點。 • 2.通常加上一個串列首,此串列不存任何資料,其左邊鏈結欄指向串列最後一個節點,而右邊鏈結指向第一個節點。 • 3.假設ptr為一指向此串列上任一節點的指標,則有:
堆疊簡介 堆疊是一種具有後進先出(LIFO)特性的資料型態,所有的加入與刪除動作,皆在頂端完成。 • 遞迴程式的呼叫返迴:在遞迴之前,先將下一個指令的位址及變數的值保存到堆疊。當以後遞迴回來(Return)時,再循序從堆疊頂端取出這些相關值,並回到原來執行遞迴前的狀況再往下執行。 15-4 堆疊與佇列 • 何謂堆疊,最簡單的定義如下: • 在計算機的領域中,堆疊可以應用在以下方面:
2.二元樹的中序追蹤(Inorder)、前序追蹤(Preorder)和圖形的深入追蹤(DFS)。2.二元樹的中序追蹤(Inorder)、前序追蹤(Preorder)和圖形的深入追蹤(DFS)。 3.CPU的中斷處理(Interrupt Handling)也需要堆疊的支援。 4.將算術式的中序法轉換成後序法。 5.副程式間的呼叫及返回(Subroutine Call and Return)。 6.某些所謂堆疊計算機(Stack Computer),大部分透過彈出及壓入兩個指令來處理程式。 15-4 堆疊與佇列
遞迴(1) 假如一個函數或副程式,是由自身所定義或呼叫的,就稱為遞迴 (Recursion) ,它至少要定義2種條件,包括一個可以反覆執行或呼叫的過程,與一個跳出執行過程的出口。 15-4 堆疊與佇列 • 「遞迴」(Recursion)在程式設計上是相當好用而且特殊的演算法,也是屬於一種堆疊的應用。 • 對程式設計師而言,「函數」(或稱副程式)不單純只是能夠被其它函數呼叫(或引用)的程式單元,在某些語言還提供了自身引用的功能,這種功用就是所謂的「遞迴」。更明確的定義如下:
15-4 堆疊與佇列 • 遞迴因為呼叫對象的不同,可以區分為以下兩種: • 直接遞迴(Direct Recursion) • 指遞迴函式中,允許直接呼叫該函式本身,稱為直接遞迴(Direct Recursion)。 • 間接遞迴 • 指遞迴函式中,如果呼叫其他遞迴函式,再從其他遞迴函式呼叫回原來的遞迴函式,我們就稱做間接遞迴(Indirect Recursion)。
遞迴(2) n!=n×(n-1)×(n-2)……×1 15-4 堆疊與佇列 • 例如我們知道階乘函數是數學上很有名的函數,對遞迴式而言,也可以看成是很典型的範例。 • 3階乘等於3×2×1=6,而0階乘則定義為1。 • 我們一般以符號“!”來代表階乘。如4階乘可寫為4!。 • 任何問題想以遞迴式來表示,一般需要符合兩個條件:一個反覆的過程,以及一個跳出執行的缺口。秉持這兩個原則,n!可以寫成:
15-4 堆疊與佇列 • C的演算法程式碼如下: 01int recursive(int i) 02{ 03int sum; 04if(i == 0) /* 遞迴終止的條件 */ 05return(1); 06else 07sum = i * recursive(i-1); /* sum=n*(n-1)!所以直接呼叫本身 */ 08return sum; 09 }
佇列 15-4 堆疊與佇列 • 佇列(Queue)和堆疊一樣都是一種有序串列的抽象資料型態,不過佇列卻具備先進先出(FIFO:FIRST IN,FIRST OUT)的特性,而且所有的刪除或加入動作必須在兩端進行。如下圖所示:
運用在圖形的走訪的先廣後後深搜尋法(BFS),就是利用佇列。運用在圖形的走訪的先廣後後深搜尋法(BFS),就是利用佇列。 • 可用於計算機的模擬(simulation);在模擬過程中,由於各種事件(event)的輸入時間不一定,可以利用佇列來反應真實狀況。 • 可作為CPU的工作排程(Job Scheduling)。利用佇列來處理,可達到先到先做的要求。 • 可應用於「線上同時週邊作業系統」,就是讓輸出入的資料先在高速磁碟機中完成,也就是把磁碟當成一個大型的工作緩衝區(buffer),如此可讓輸出入動作快速完成,也縮短了反應的時間,接下來將磁碟資料輸出到列表機是由系統軟體主動負責,這也是應用了佇列的工作原理。 15-4 堆疊與佇列 • 佇列在電腦領域的應用也相當廣泛,例如:
環狀佇列(1) rear(rear+1) mod n front(front+1) mod n 15-4 堆疊與佇列 • 它是以一種Q(0:n-1)的一維陣列,同時Q(0)為Q(n-1)的下一個元素。 • 指標front永遠以逆時鐘方向指向佇列中第一個元素的前一個位置,rear則指向佇列目前的最後位置。 • 當front=(rear+1)%MAX_SIZE,佇列是滿的,而front=rear時,佇列是空的。 • 在製造環形佇列的環形運動,可以使用「模數(mod)」來達成,另外有以下的運算關係:
15-4 堆疊與佇列 • 把佇列看成如下圖的環狀結構,不過front和rear的初始值設定為0或-1。 • front永遠以逆時鐘方向指著佇列中第一個元素的前一個位置,rear則指向佇列目前的最後位置:
環狀佇列(2) 15-4 堆疊與佇列 • 以下圖例則是有關環狀佇列變動的說明: • 這種環狀陣列確實是比原先的佇列有更好的管理,而且已經避免空間無謂的浪費。
優先佇列 15-4 堆疊與佇列 • 特色是其中的元素並不以優先順序進出。 • 以每個元素的優先權進出,例如大型電腦的處理作業程序中,會對排隊中的工作程序作優先等級的分類,優先等級越高的工作程序可以搶在等級較低的程序前得到服務。 • 對於其他等級相同的工作則仍然依照佇列先進先出的原則處理。 • 每一個佇列中的元素,我們都賦予一個代表優先權的數字,最大的數字就有最高優先權。
以陣列結構實作的優先佇列 15-4 堆疊與佇列 • 利用陣列來製作優先佇列的缺點是經常需要移動大量資料,也無法預先知道需要保留多少尾部(Rear)空間給插入的工作使用。 • 佇列具有先進先出的特性,經常被電腦的作業系統用來安排電腦執行工作(job)的優先順序。 • 尤其是多人使用之多工(Multitask)電腦必須安排每一位使用者都有相等的電腦使用權。
樹 (1) • 有一個特殊的節點稱「樹根」(Root)。 • 其餘節點分為n≧0個互斥的集合,T1,T2,T3…Tn,則每一個子集合本身也是一種樹狀結構及此根節點的子樹。 15-5 樹狀結構 • 樹(tree)是另外一種典型的資料結構,可用來描述有分支的結構,樹中的元素以節點(node)來表示。 • 每個節點可代表一些資料和指標組合而成的記錄,節點之間可用樹枝相連,代表支幹(branch)的延伸。並具備以下特點:
15-5 樹狀結構 • 所謂一棵合法的樹,基本上就是符合樹是由一個或一個以上的節點所組成,而且節點間可以互相連結,但不能形成無出口的迴圈。例如下圖就是一棵合法的樹:
樹 (2) 15-5 樹狀結構 • 以下則是與樹相關的基本名詞介紹: • 分支度(Degree):每個節點所有的子樹個數。例如像節點B的分支度為2,D的分支度為3,F、K、I、J等為0。 • 樹的分支度(Degree of a Tree):樹中所有節點的最大分支度。以此圖為例,節點中最大分支度為3,所以樹的分支度也為3。 • 樹葉或稱終端節點(Terminal Nodes):分支度為零的節點,如此圖中的K、L、F、G、M、I、J。 • 非終端節點(Nonterminal Nodes):樹葉以外的節點,如A、B、C、D、E、H等。
15-5 樹狀結構 • 子點(Children):某節點所有子樹的樹根。例如:A的子點為B、C、D,B的子點為E、F。 • 父點(Parent):若X節點為Y節點的子點,則Y節點為X節點的父點。例如:B的父點為A,G的父點為C。 • 兄弟(Siblings):同一個父點的所有子點互稱兄弟。例如:B、C、D為兄弟,H、I、J也為兄弟。 • 階度(Level):令樹根的階度為1,若某節點的階度為L,則其子點的階度為L+1。例如:K位於階度4,E、F、G、H、I、J位於階度3。 • 高度(Height):樹的最大階度。例如:此圖的樹高度為4。
15-5 樹狀結構 • 樹林:是由n個互斥樹所組合成的,移去樹根即為樹林,例如此圖中移去節點A,則為包含三棵樹,即樹根為B、C、D的樹林,如下圖所示: • 祖先(Ancestor):所謂祖先是指從樹根到該節點路徑上所有包含的節點。例如:K的祖先為A、B、E節點,H的祖先為A、D節點。 • 子孫(Descendant):為該節點的子樹中所包含任一節點。例如:節點B的子孫為E、F、K、L。
二元樹 15-5 樹狀結構 • 二元樹(又稱為Kunth樹,或有序樹(Order Tree))是由節點所組成的有限集合,這個集合若不是空集合,就是由左子樹(Left Subtree)和右子樹(Right Subtree)所組成。其資料結構如下: • left及right代表指向左邊子樹和右邊子樹的指標,而data這個欄位乃是存放該節點(Node)的基本資料。 • 以上述宣告而言,此節點所存放的資料型態為整數。
15-5 樹狀結構 • 以下我們就針對樹與二元樹之間的比較作一完整分析: • 1.樹不可以為空集合,但二元樹可以是空集合。 • 2.二元樹中的每一個節點分支度(Degree) d,必定0≤d≤2,但樹中的分支度d,只要d>=0即可。 • 3.二元樹中,子樹間有次序關係,但在樹中,子樹間則沒有次序關係。例如下圖(a)與(b):