資料結構與演算法 第三章 堆疊和佇列 徐熊健.

Slides:



Advertisements
Similar presentations
資料結構與演算法 課程教學投影片.
Advertisements

第一單元 建立java 程式.
4-1 認識堆疊 4-2 堆疊的應用 4-3 算術運算式的求值 4-4 中序法轉換為前序法 4-5 前序與後序式轉換成中序式
動畫與遊戲設計 Data structure and artificial intelligent
1.6 中国人口迁移.
动物激素的调节及其在农业生产中的应用(B级)
主題五 CPU Learning Lab.
第三章 堆疊與佇列的基本應用 3-1 簡介堆疊(Stack) 3-2 迷宮問題研究 3-3 佇列(queue)的介紹
第6章 佇列(Queues) 6-1 佇列的基礎 6-2 佇列的表示法 6-3 環狀佇列 6-4 雙佇列.
第 3 章 堆疊與佇列.
簡易C++除錯技巧 長庚大學機械系
佇列 (Queue).
佇列與推疊 (Queue and Stack)
資料結構設計與C++程式應用 Fundamentals of Data Structures and Their Applications Using C++ 第3章 佇列 資料結構設計與C++程式應用.
資料結構簡介.
Chapter 3 堆疊與佇列 3.1 堆疊和佇列基本觀念 3.2 堆疊的加入與刪除 3.3 佇列的加入與刪除 3.4 其他型式的佇列
第三章 堆疊與佇列 Stacks & Queues
Chap 3 堆疊與佇列 Stack and Queue.
Ch.3 栈和队列 黄刘生 中国科学技术大学计算机系 国家高性能计算中心(合肥)
4 堆疊與佇列 4.1 前言 四種基本的資料結構 (可儲存資料的容器) 陣列 (Array)、串列(List): 最基本
cn/~dongeliu/dsa.html 刘 东 信息学院6系 中国科学技术大学
(Circular Linked Lists)
堆疊簡介 遞迴 佇列 算術運算式的表示法 中序轉換為前序或後序 前序與後序轉換成中序
堆疊 Stack chapter 4 德明科技大學資訊科技系.
第5章 堆疊(Stacks) 5-1 堆疊的基礎 5-2 堆疊的表示法 5-3 堆疊的應用 - 運算式的計算與轉換
第5章 堆疊(Stacks) 5-1 堆疊的基礎 5-2 堆疊的表示法 5-3 堆疊的應用 - 運算式的計算與轉換
第三章 栈与队列 £3.1 栈 £3.3 队列 £3.2 栈的应用举例 £3.1.1 栈的定义 £3.1.2 栈的顺序存储结构
第3章 栈和队列(一).
第三章 栈和队列.
数据结构 Data Structure 中南大学 主讲人:王国军,郑瑾 中南大学信息院计科系
Chapter 3 堆疊與佇列 ( Stacks and Queues ).
資料結構與C++程式設計進階 堆疊與佇列(Stack & Queue) 講師:林業峻 CSIE, NTU 6/ 21, 2010.
Chapter 4 堆疊和佇列 資料結構導論 - C語言實作.
JAVA 程式設計與資料結構 第四章 陣列、字串與數學物件.
Chap3 Linked List 鏈結串列.
第三章 栈和队列.
資料結構 第4章 堆疊.
严蔚敏、吴伟民编著 清华大学出版社 学习网站:
第四章 堆疊與佇列 課前指引 資料結構學科中,堆疊(Stack)與佇列是兩種相當典型的抽象資料型態,也是一種有特定進出規則的線性串列應用,主要特性是限制了資料插入與刪除的位置和方法。本章中將會介紹堆疊與佇列的概念與特性,及在計算機領域的各種相關應用。
樹 2 Michael Tsai 2013/3/26.
第 四 章 堆疊(Stack) 課程名稱:資料結構 授課老師:________ 2019/2/23.
程式設計 博碩文化出版發行.
第一單元 建立java 程式.
Chapter6 队列(Queues) The Abstract Data Type
Chapter 4 資料結構.
陈海明 副教授 信息学院 计算机系 电子信息类非计算机专业选修课 程序设计实践 陈海明 副教授 信息学院 计算机系
資料結構 優點 缺點 1 陣列 (Array) 沒有額外變量 (例head, next,...) 運作/操作較簡單 更新資料時,若要保持順序,需要移動較大量資料 靜態結構Static (宣告時已決定了陣列元素多少,不能在程式執行期間增減元素) 2 隊列Queue (FIFO) 容易更新 加入enqueue:
Chapter 3 堆疊與佇列 3.1 堆疊和佇列基本觀念 3.2 堆疊的機入與刪除 3.3 佇列的加入與刪除 3.4 環狀佇列
BC430 ABAP Dictionary Views、 Search Help 報告者:林聖期、程汎汝.
top b top a a top 空栈 a 进栈 b 进栈 top top e e e top d d d c c c b b b a a
CH05. 選擇敘述.
挑戰C++程式語言 ──第8章 進一步談字元與字串
Chap2 Stack & Queue.
第四章 栈和队列 栈 ( Stack ) 队列 ( Queue ) 优先队列 (Priority Queue) 小结.
Chap2 Stack & Queue.
基础会计.
第 六 讲 栈和队列(一).
陣列與結構.
資料結構使用Java 第7章 堆疊(Stack).
第4章 堆疊和佇列 資料結構設計與C++程式應用
第八节 算术运算符和算术表达式.
選擇性結構 if-else… switch-case 重複性結構 while… do-while… for…
適用於多選一 可減少if 與 else配對混淆的錯誤.
計算機程式設計 老師:謝孟諺 助教:楊斯竣.
本教學投影片係屬教科書著作之延伸,亦受著作權法之保護。
第13章 電腦解題與演算法 13-4 資料結構.
ABAP Basic Concept (2) 運算子 控制式與迴圈 Subroutines Event Block
10303: How Many Trees? ★★☆☆☆ 題組:Contest Archive with Online Judge
13-1 電腦可以協助解決哪些問題 13-2 電腦解題簡介 13-3 電腦解題規劃-演算法 13-4 認識資料結構
ABAP Basic Concept (2) 運算子 控制式與迴圈 Subroutines Event Block
Presentation transcript:

資料結構與演算法 第三章 堆疊和佇列 徐熊健

大 綱 3.1 堆疊 3.2 堆疊的基本運算 3.3 佇列 3.4 佇列的基本運算 3.5 環狀佇列 3.6 老鼠走迷宮 大  綱 3.1  堆疊 3.2  堆疊的基本運算 3.3  佇列 3.4  佇列的基本運算 3.5  環狀佇列 3.6  老鼠走迷宮 3.7  運算式的轉換和求值

堆疊和佇列 堆疊擁有的共同特性就是—先行進入者將較後離去(first in last out, 或簡稱FILO)、或後來進入者可先行離去(last in first out, 或簡稱LIFO)。 佇列共同特性在於—先行進入者將率先離去(first in first out, 或簡稱FIFO)。

3.1  堆疊 (a)描繪了依循增加(push)甲,乙,丙,丁,至堆疊S的邏 輯圖示。 (b)描繪了依循取出(pop)堆疊S內元素的邏輯圖示。

程序呼叫之範例一

堆疊解決堆疊程序呼叫時流程轉移的過程 堆疊保證了程序呼叫時流程轉移的正確性 在A程序中呼叫 (call)程序B,遂將返回位址#push入堆疊中,流程轉至B。 在程序B中呼叫程序C,將返回位址△push入堆疊中,流程轉至C。 空堆疊 程序C執行結束,pop出堆疊頂端元素△,做為流程轉移的目的地(返回B)。 程序B執行結束,pop出堆疊頂端元素#做為流程轉移目的地(返回A)。 堆疊保證了程序呼叫時流程轉移的正確性

程序呼叫之範例二

堆疊的操作過程 空堆疊 push#, 呼叫B push△呼叫C push*呼叫D pop出* 流程跳至* pop出△ 流程跳至△ pop出⊙ 流程跳至⊙ pop出# 流程跳至# push ⊙ 呼叫D push ● 呼叫D pop出● 流程跳至●

3.2 堆疊的基本運算 程式3-1 堆疊的宣告 1 #define maxsize 5; 2 int stack[maxsize]; 3.2 堆疊的基本運算 程式3-1 堆疊的宣告 1 #define maxsize 5; 2 int stack[maxsize]; 3 int top= -1; 第2行中宣告了大小為5個整數的陣列,將之做為堆疊使用,命名成stack; 第3行宣告的top整數,將當成堆疊stack頂端元素在陣列中的註標,宣告成-1表示目前為一空堆疊,沒有任何頂端元素。

Push 運算 程式3-2 1 void push(int element) 2 { if (IsFull( )) StackFull(); 3 else Stack[++top]=element; 4 }

Pop 運算 程式3-3 1 int pop() 2 { if (IsEmpty()) 3 { StackEmpty(); 4 return -1; 5 } 6 return Stack[top--]; 7 }

IsFull運算及IsEmpty運算 程式3-4 IsFull運算 1 int IsFull( ) 2 { if(top == maxsize-1) return 1; 3 else return 0; 4 } 陣列Stack中[0]~[maxsize-1]的位置皆放滿元素),來決定是否堆疊已滿?若滿了傳回1,否則傳回0。 程式3-5 IsEmpty運算 1 int IsEmpty() 2 { if ( top == -1) return 1; 3 else return 0; 4 }

3.3 佇 列 佇列 (queue) 是一個有序串列,為達成先進先出FIFO的效果,所有加入的動作皆在固定的一端進行,而所有刪除的動作則在另一端進行。 若Q = (ai, ai+1, … , aj) 為一佇列,我們稱ai為前端 (front) 元素,aj為後端 (rear) 元素 資料的加入乃在Q的後端進行,資料的刪除則由Q的前端進行,如此即可形成先進先出的資料結構。

佇列的邏輯圖示及其基本運算 依序增加元素進入佇列中 刪除佇列中的元素

3.4 佇列的基本運算 程式3-7 佇列的宣告 程式3-8 addQ運算 1 #define maxsize 10; 3.4 佇列的基本運算 程式3-7 佇列的宣告 1 #define maxsize 10; 2 int Queue[maxsize]; 3 int front = -1; 4 int rear = -1; 程式3-8 addQ運算 1 void addQ(int element) 2 { if (IsQFull()) QueueFull (); 3 else Queue [++rear]= element; 4 }

deleteQ 運算 程式3-9 1 int deleteQ() 2 { if (IsQEmpty()) 3 { QueueEmpty(); 4 return 0; 5 } 6 else return Queue[++front]; 7 } 第6行則當Queue 不是空的時候,傳回Queue[++front],正是自該Queue中刪去的元素值,而且front註標也調整至正確的位置

IsQEmpty運算及IsQFull運算 1 int IsQEmpty() 2 { if (rear == front) return 1; 3 return 0 ; 4 } 程式3-11 IsQFull運算 1 int IsQFull() 2 { if (rear == maxsize-1) return 1; 3 return 0; 4 }

… 3.5 環狀佇列 線性的陣列因陣列元素位置與註標之對應關係,使其不方便模擬佇列的增加、刪除的消長現象,而用過的空位無法再行利用; 3.5 環狀佇列 線性的陣列因陣列元素位置與註標之對應關係,使其不方便模擬佇列的增加、刪除的消長現象,而用過的空位無法再行利用; 不過若將線性的陣列折繞成環狀 (circular) 陣列,就可再次使用到空出的空間。 假設n = 5,利用mod(i) 將循序的數列i轉變為呈環狀計數的數列: 於是原程式的++rear或++front,須分別改為++rear%n和++front%n,其中n為佇列的大小即可有環狀計的註標。 i 1 2 3 4 5 6 7 8 9 10 11 12 … mod(i)

範例3-5 rear front (a)空佇列 10 (b) addQ(10) rear front (c) addQ(20) rear 30 10 (d) addQ(30) rear front 20 30 10 (e) addQ(40) rear front 40 20 30 10 (f) addQ(50),但佇列已滿 rear front 40 (h) deleteQ() 30 40 front (i) addQ(60) rear 30 40 front 60 (j) addQ(70) rear 30 40 front 60 70 (g) deleteQ() 20 30 40 front

程式3-12 環狀佇列 1 #define maxsize 10; 2 int CQueue[maxsize]; 程式3-12  環狀佇列 1 #define maxsize 10; 2 int CQueue[maxsize]; 3 int front = 0; 4 int rear = 0; 5 6 void addCQ(int element) 7 { if (IsCQFull()) CQueueFull (); 8 else CQueue [++rear%maxsize] = element; 9 } 10 front所指的位置為特意保留的空位;Queue[(front+1)% maxsize]為環狀佇列Queue的前端元素,Queue[rear % maxsize]為環狀佇列Queue的後端元素

程式3-12 環狀佇列(續) 11 int deleteCQ() 12 { if (IsCQEmpty()) 程式3-12  環狀佇列(續) 11 int deleteCQ() 12 { if (IsCQEmpty()) 13 { CQueueEmpty(); 14 return 0; 15 } 16 else return CQueue[++front%maxsize]; 17 } 18 19 int IsCQEmpty() 20 { if (rear == front) return 1; 21 return 0 ; 22 } 23 24 int IsQFull() 25 { if ((rear+1)%n == front) return 1; 26 return 0; 27 }

3.6 老鼠走迷宮 老鼠走迷宮是個有趣的心理實驗,把老鼠放入迷宮中,老鼠可靠單純的嚐試錯誤 (trial and error) 法,即找到出口。這種嚐試錯誤的策略並不考慮運算時間是否經濟,但對走迷宮這類毫無線索的問題,依然是個可行的策略,我們或稱之為「窮舉」 (enumeration) 的策略。 我們可用電腦程式模擬老鼠走迷宮的過程,採用的策略即為嚐試錯誤法,但是曾經走錯的路我們不應再次嚐試,雖然實際上老鼠不見得記得住曾走錯的路,但電腦在記憶方面可高明得多。事實上用來「記住窮舉過程中已走過的路徑」最好的資料結構就是堆疊 。

3.6 老鼠走迷宮(續) 首先應先設想必需的資料結構: 迷宮的表示(用二維陣列如何?隔牆、通路如何區隔?) 路徑的表示(用表示迷宮二維陣列的註標如何?) 走過的路徑(用另一個與迷宮二維陣列一樣大小的二維陣列,記錄是否走過如何?) 遭遇錯誤後的移動(利用堆疊記得從何而來?) 各位應養成思考問題時,同時溶入資料結構考量的能力。

3.6 老鼠走迷宮(續) 演算法3-1老鼠走迷宮 輸入:迷宮,以二維陣列maze表示。 輸出:從入口至出口的路徑。 1 (i,j,dir)=(1,1,E) 2 push ((i,j,dir)) 3 while (堆疊仍有資料) do 4 { (i,j,dir) = pop() 5 while (在(i,j)處仍有路可走) do 6 { (u,v) = 自(i,j)欲嚐試的下一步座標 7 if ((u == m)&&(v == p)) 8 { 成功找到出口,輸出路徑,可以停止 } 9 if ((!maze[u][v])&&(!mark[u][v])) 10 //(u,v)可以走,且不常走過 11 { mark[u][v] = 1; 12 dir = 下個可能嚐試的方向 13 push((i,j,dir)) 14 i=u; j=v, dir=N 15 } 16 } 17 }

3.6 老鼠走迷宮(續) 程式3-13路徑資料的結構化宣告 1 #define possible_direction 8 2 struct offset 3 { int dx,dy; 4 }; 5 enum directions {N,NE,E,SE,S,SW,W,NW}; 6 struct offset move[possible_direction]; 7 struct position 8 { int x,y; 9 directions dir; 10};

3.6 老鼠走迷宮 程式3-14老鼠走迷宮(上接程式3-13) 1 int m,p,top; 2 void push (struct position data) 3 { if (top == (m*p-1)) StackFull(); 4 else Stack[++top] = data; 5 } 6 struct position pop() 7 { if (top == -1) StackEmpty(); 8 else return Stack[top--]; 9 } 10 void path(int m, int p) 11 { struct position Stack [m*p]; 12 struct position step; 13 int i,j,u,v; 14 directions d; 15 step.x=step.y=1; step.dir=E; 16 push(step); 17 while (堆疊內有元素) 18 { step=pop(); 19 i=step.x ; j=step.y ; d=step.dir ;

3.6 老鼠走迷宮(續) 20 while (d<NW) 21 { // (i,j)處仍有可移動的選擇 22 u=i+move[d].dx ; v=j+move[d].dy; 23 if ((u==m) && (v==p)) 24 { // 輸出(u,v),(i,j),再將stack內的所有元素 25 pop輸出,則構成一條由出口至入口的路徑 26 return; 27 } 28 if ((!maze[u][v]) && (!mask[u][v])) 29 { mask [u][v]=1; 30 step.x=i ; step.y=j ; step.dir=d+1; 31 push(step); 32 i=u ; j=v ; d=N 33 } 34 else d++ ; 35 } 36 } //j輸出:”此迷宮無出路”之訊息 37 }

3.7 運算式的轉換和求值 在電腦中處理算術運算式 (arithmetic expression) ,需要堆疊的協助,在本節中即介紹兩者之間的關係。

3.7.1 算術運算式 在程式語言中,算術運算式經常被引用來計算希望的結果,例如下面的指定敘述 (assignment statement) : X = A+(B-C)*D 等號右邊即為一算術運算式;一個算術運算式包括了運算子 (operator) 如:+, -, *, /, …等;和運算元 (operand) 如上式的A, B, C和D。除此之外,運算式的計算得依據運算子的優先順序 (priority) ,方得算出正確的結果。

3.7.1 算術運算式 數學上運算子的優先順序如表3-1所示 表3-1 運算子的優先順序(以C語言為例) 優先順序 運算子 1 3.7.1 算術運算式 數學上運算子的優先順序如表3-1所示 表3-1 運算子的優先順序(以C語言為例) 優先順序 運算子 1 負號、! (邏輯否定) 2 *、/、% 3 +、- 4 <、<=、>=、> 5 ==、!= 6 && 7 ||

3.7.1 算術運算式(續) 運算子依表3-1的優先順序(值愈小,優先順序愈高),將其對應的運算元加以計算;相同順序的運算子,則依由左至右的順序進行計算;若有括號內者應先計算(先內層後外層)。於是上式與下式是一樣的運算式: X=(A+((B-C)*D) 若沒有好的演算法,決定運算的先後順序,對電腦而言依然因難。在下一節中我們會介紹後序 (postfix) 運算表示式,利用之配合堆疊,即可順利地解決運算式求值 (evaluation) 的問題。

3.7.2後序運算式表示法 傳統的算術運算式將運算元放在運算子的中間,遂稱之為中序運算表示式 (infix notation) 。 而後序運算表示法 (postfix notation) 則將運算子放在對應運算元之後; 類似地,前序運算表示法 (prefix notation) 則將運算子放在對應運算元之前。 這裡的運算元指的是二元運算子 (binary operator) ;單元運算子 (unary operator) 如:負號、否定、…的並不在此討論。

3.7.2 後序運算式表示法(續) 範例3-10列出幾個運算式的不同表示法。 中序表示 後序表示 前序表示 B-C BC- -BC 3.7.2 後序運算式表示法(續) 範例3-10列出幾個運算式的不同表示法。 中序表示 後序表示 前序表示 B-C BC- -BC (B-C)*D BC-D* *-BCD A+(B-C)*D ABC-D*+ +A*-BCD 範例3-10

(A+((B-C)*D)) ABC-D*+ (A+((B-C)*D)) +A*-BCD 3.7.2 後序運算式表示法(續) 事實上只需加上適當的括號,我們可以輕易地轉換運算式的不同表示法,請見範例3-11 範例3-11 將中序運算式A+(B-C)*D加上適當的括號: (A+((B-C)*D)) 把運算元挪放到對應右括號的左邊,再去掉括號,即形成其後序運算式: (A+((B-C)*D)) ABC-D*+ 把運算元挪放到對應左括號的右邊,再去掉括號,即形成其前序運算式: (A+((B-C)*D)) +A*-BCD

第一次: A+X1*D (X1存放B-C的結果) 3.7.2 後序運算式表示法(續) 試想若編輯器中只存放中序運算式,而只會自左向右檢查式中的優先順序,則編輯器須多次解讀此中序運算式: A+(B-C)*D 第一次: A+X1*D (X1存放B-C的結果) 第二次: A+X2 (X2存放X1*D的結果) 第三次: X3 (X3存放A+X2的結果) 這種「遂次自左向右挑出順序最高者執行」方法的效率是很差的。

X1=B-C、X2=(B-C)*D、X3=A+(B-C)*D; 3.7.2 後序運算式表示法(續) 範例3-12 以A+(B-C)*D為例,令 X1=B-C、X2=(B-C)*D、X3=A+(B-C)*D; 則其後序運算式ABC-D*+的求值運算,只須運用堆疊S,並自左向右查看後序運算式一次即可完成,其中遇見運算元就直接push 入堆疊中,遇見運算子則pop堆疊兩次,取得的運算元即以該運算子做計算,結果再push 入堆疊中,直至後序運算式的每一元素皆看完為止。

3.7.2 後序運算式表示法(續) 演算法3-2後序運算式的求值運算 輸入:後序運算式e 輸出:計算出後序運算式e的值 3.7.2 後序運算式表示法(續) 演算法3-2後序運算式的求值運算 輸入:後序運算式e 輸出:計算出後序運算式e的值 1 n = passing(e, token); 2 for (i=0; i<n; i++) 3 if (token[i]為運算元) push(token[i]); 4 else 5 { pop出token[i]此運算子所需的運算元; 6 計算出token[i]此運算的值,令為x; 7 push(x); 8 }

3.7.3 中序運算式轉為後序運算式 我們先觀察下圖3-21的例子,不難發現兩者運算元出現的順序都一樣; 中序運算法:A+(B-C)*D 3.7.3 中序運算式轉為後序運算式 我們先觀察下圖3-21的例子,不難發現兩者運算元出現的順序都一樣; 中序運算法:A+(B-C)*D 後序運算式:ABC-D*+ 圖3-21 事實上在範例3-11中,我們已經知道中序轉後序、或中序轉前序的轉換過程,只須更動運算子的位置,運算元的順序並無任何更動。 至於計算順序的決定,我們可看出一些規則,如: B-C先運算乃因其在括號內; + 的位置在 * 後,乃因 + 的執行順序低於 * 由此可知在中序轉後序時,中序運算式中 * 雖比 + 後看到,但要先輸出,可見我們需要一個堆疊—存放後到但先出的運算子。原則上目前遇到的運算子x應先push入堆疊中,因為x能否輸出的決定得由x之後的運算子來決定;而x進入堆疊前,則可決定x之前的運算子是否可輸出了。

3.7.3 中序運算式轉為後序運算式(續) 演算法3-3中序運算式轉後序運算式 輸入:中序運算式e 輸出:對應輸入之後序運算法 3.7.3 中序運算式轉為後序運算式(續) 演算法3-3中序運算式轉後序運算式 輸入:中序運算式e 輸出:對應輸入之後序運算法 1 n = passing(e, token); 2 push(“#”); 3 for (i=0; i<n; i++) 4 { s = token(i); 5 if (s是一運算元) output(s); 6 else if (s == ”)”) 7 //將堆疊中第一個 ( 之前的運算子皆pop出並印出之 8 while ((x=pop())!=”(”) output(x); 9 else 10 { while (p(s)<=q(Stack[top]) 11 { x = pop(); 12 output(x); 13 } 14 push(s); 15 } 16 } 17 while (Stack[top]!=”#”) 18 { x = pop(); 19 output(x); 20 }