第三章 栈和队列.

Slides:



Advertisements
Similar presentations
親愛的老師您好 感謝您選用本書作為授課教材,博碩文化準備本書精選簡報檔,特別摘錄重點提供給您授課專用。 說明: 博碩文化:
Advertisements

動畫與遊戲設計 Data structure and artificial intelligent
数据结构概论 第2章 线性表 董黎刚 浙江工商大学信电学院
数据结构——树和二叉树 1/96.
第六章 树和二叉树.
第五章 树 东南大学计算机学院 方效林 本课件借鉴了清华大学殷人昆老师 和哈尔滨工业大学张岩老师的课件.
第二章 线性表 1 线性表的逻辑结构及其基本操作 2 线性表的顺序存储结构 3 线性表的链式存储结构 4 静态链表 5 应用实例.
資料結構與C++程式設計進階 資料結構概論 講師:林業峻 CSIE, NTU 6/ 7, 2010.
第三章 堆疊與佇列的基本應用 3-1 簡介堆疊(Stack) 3-2 迷宮問題研究 3-3 佇列(queue)的介紹
Linked List Operations
第3章 栈和队列 3.1 栈 3.2 队列 3.3 应用.
第6章 佇列(Queues) 6-1 佇列的基礎 6-2 佇列的表示法 6-3 環狀佇列 6-4 雙佇列.
主讲教师:吴琼 微信群:C语言2016 QQ群: 密码scu2016 昵称:“真名+学号”
佇列 (Queue).
資料結構 第5章 佇列.
Chap 3 堆疊與佇列 Stack and Queue.
·线性表的定义及ADT ·线性表的顺序存储结构 ·线性表的链接存储结构 · 单向循环链表 · 双链表、双向循环链表 · 一元多项式的加法
第2章 线性表(三) 1/.
第十五章 Linked List, Stack and Queue
Ch.3 栈和队列 黄刘生 中国科学技术大学计算机系 国家高性能计算中心(合肥)
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
程序设计期末复习 黎金宁
第12章 從C到C++語言 12-1 C++語言的基礎 12-2 C++語言的輸出與輸入 12-3 C++語言的動態記憶體配置
线性表小结 元素之间的线性关系 顺序表 顺序表:元素相邻存储 单链表:后继指针链接 一维数组 给定下标随机存取
cn/~dongeliu/dsa.html 刘 东 信息学院6系 中国科学技术大学
第3章 栈和队列(二) 1/.
第十章 C高级程序应用—链表* 10.1链表的基本概念 10.2单向链表 10.3双向链表 10.4应用举例.
第5章 堆疊(Stacks) 5-1 堆疊的基礎 5-2 堆疊的表示法 5-3 堆疊的應用 - 運算式的計算與轉換
第5章 堆疊(Stacks) 5-1 堆疊的基礎 5-2 堆疊的表示法 5-3 堆疊的應用 - 運算式的計算與轉換
计算机网络讲义 第5章 批量数据处理—数组 一维数组 排序和查找 二维数组 字符串.
第3章 堆栈和队列 堆栈 堆栈应用 队列 队列应用 优先级队列 主要知识点.
第 3 讲 线性表(一).
第三章 栈与队列 £3.1 栈 £3.3 队列 £3.2 栈的应用举例 £3.1.1 栈的定义 £3.1.2 栈的顺序存储结构
第三章 栈和队列 3.1 栈 3.2 栈的应用举例 3.3 栈与递归的实现 3.4 队列.
第3章 栈和队列(一).
第三章 栈和队列.
数据结构 Data Structure 中南大学 主讲人:王国军,郑瑾 中南大学信息院计科系
第二章 线性表.
資料結構與C++程式設計進階 堆疊與佇列(Stack & Queue) 講師:林業峻 CSIE, NTU 6/ 21, 2010.
第一章 绪论.
陈海明 副教授 信息学院 计算机系 电子信息类非计算机专业选修课 程序设计实践 陈海明 副教授 信息学院 计算机系
第9章 内部排序 9.1 概述 9.2 插入排序 9.3 快速排序 9.4 选择排序 9.5 归并排序 9.6 基数排序
6.3 遍历二叉树和线索二叉树(知识点二) 遍历二叉树 一、问题的提出
#define STACK_INIT_SIZE 10
第四章 串.
資料結構 第4章 堆疊.
严蔚敏、吴伟民编著 清华大学出版社 学习网站:
陈海明 副教授 信息学院 计算机系 电子信息类非计算机专业选修课 程序设计实践 陈海明 副教授 信息学院 计算机系
王玲 第 2 章 线性表 王玲 2019/2/25.
自我參考結構 (self-reference – 1)
第三章 链表 单链表 循环链表 多项式及其相加 双向链表 稀疏矩阵.
常宝宝 北京大学计算机科学与技术系 数据结构(三) 常宝宝 北京大学计算机科学与技术系
第五章 递归与广义表 递归的概念 递归过程与递归工作栈 递归与回溯 广义表.
程式結構&語法.
4 條件選擇 4.1 程式基本結構 循序式結構 選擇式結構 重複式結構 4-3
第三章 C++的语句和简单的程序设计 主要内容:
Linked Lists Prof. Michael Tsai 2013/3/12.
第四章 栈和队列 栈 ( Stack ) 队列 ( Queue ) 优先队列 (Priority Queue) 小结.
程式的時間與空間 Time and Space in Programming
C++语言程序设计教程 第2章 数据类型与表达式 第2章 数据类型与表达式 制作人:杨进才 沈显君.
<编程达人入门课程> 本节内容 为什么要使用变量? 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ:
第二章 Java语法基础.
第 六 讲 栈和队列(一).
1. 志明打算就客戶服務開發一個語音互動(IVR)系統, 顧客可透過電話鍵盤與系統進行互動。
#include <iostream.h>
第二章 Java基本语法 讲师:复凡.
第1章 数据结构基础概论 本章主要介绍以下内容 数据结构研究的主要内容 数据结构中涉及的基本概念 算法的概念、描述方法以及评价标准.
第三章 线性表 3.1 线性表的类型定义 3.2 顺序存储的线性表 3.3 链式存储的线性表 3.4 有序表 3.5 顺序表和链表的综合比较.
Chapter 2 Entity-Relationship Model
第三章 流程控制 程序的运行流程 选择结构语句 循环结构语句 主讲:李祥 时间:2015年10月.
Presentation transcript:

第三章 栈和队列

插 入 删 除 线性表 Insert(L, i, x) Delete(L, i) (1≤ i≤ n+1) (1≤ i ≤n) Insert(L, n+1, x) Delete(L, n) Insert(L, n+1, x) Delete(L, 1) 栈 队 列 栈和队列可以看成是“插入” 和“删除” 受限定的线性表。

3.1 栈的类型定义 3.2 栈的应用举例 3.3 栈类型的实现 3.4 队列的类型定义 3.5 队列类型的实现 3.6 队列的应用举例

D={ ai | ai ∈ElemSet, i=1,2,...,n, n≥0 } 栈是限定只能在表尾进行插入和删除的线性表。 ADT Stack { }ADT Stack 数据对象: D={ ai | ai ∈ElemSet, i=1,2,...,n, n≥0 } 数据关系: R1={ <ai-1 ,ai >|ai-1 ,ai∈D, i=2,...,n } 约定an端为栈顶,a1端为栈底 基本操作:

StackTraverse (S, visit( )) InitStack (&S) (构造空栈 ) DestroyStack(&S) (销毁栈结构) ClearStack (&S) (栈清空) StackEmpty (S) (判空) StackLength(S) (求栈长) GetTop (S, &e) (求栈顶元素) Push (&S, e) (入栈) Pop (&S, &e) (出栈) StackTraverse (S, visit( )) (遍历栈)

GetTop(S, &e) 初始条件:栈 S 已存在且非空。 操作结果:用 e 返回 S 的栈顶元素。 a1 a2 … … an

Push(&S, e) 初始条件:栈 S 已存在。 操作结果:插入 e 为 栈 S 中新的 栈顶元素。 a1 a2 … … an e

Pop(&S, &e) a1 a2 … … an-1 an 初始条件:栈 S 已存在且非空。 操作结果:用 e 返回 S 的栈顶元素, 并将它从栈中删除。 a1 a2 … … an-1 an

例一、 数制转换 例二、 括号匹配的检验 例三、 迷宫求解 例四、 表达式求值 例五、 实现递归

数制转换的原理为: N = (N div d)×d + N mod d 例如:(1348)10 转换成 (2504)8 的运算过程如下: N N div 8 N mod 8 计算顺序 输出顺序 1348 168 4 168 21 0 21 2 5 2 0 2

void conversion ( ) { // 显示和输入的十进制数对应的八进制数值 InitStack(S); scanf ("%d",N); while (N) { Push(S, N % 8); N = N/8; } while (!StackEmpty(S)) { Pop(S,e); printf ( "%d", e ); } // conversion

检验含两种括弧的表达式中括弧匹配的正确性。 如表达式中 ([]())或[([ ][ ])] 等为正确的匹配, [(])或([()] 或 [ ( ) ] ) 均为不正确的匹配。 检验括弧匹配的方法可用“按期待的急迫程度进行处理”描述之 。

[ ( [ ] [ ( ) ] ) ] 例如 考虑下列括号序列: 可见,括弧匹配也遵循“后进先出”的规律。 如何表示下列“不匹配” 的情况? 到来的右括弧非是所“期待”的; 来了一个“不速之客”; 直到结束,也没有等到“期待”的匹配;

算法的设计思想: 1)凡出现左括弧,则进栈; 2)凡出现右括弧,首先检查栈是否空? 若栈空,则表明该“右括弧”多余 否则和栈顶元素比较, 若相匹配,则“左括弧出栈” 否则表明不匹配 3)表达式检验结束时, 若栈空,则表明表达式中匹配正确 否则表明“左括弧”有余。

bool matching(char exp[], int n) { // 检测长度为 n 的字符序列 exp 中的括弧是否匹配 int i = 0; mat = true; InitStack(S); while ( i<n && mat ) { switch of exp[i] { case 左括弧:{ Push(S,exp[i]); i++; break; } case  )  : { if ( !StackEmpty(S) && GetTop(S) == ( ) { Pop(S, e); i++; } else {mat = false;} break; } //case ) … …

计算机解迷宫时,通常用的是“穷举求解”的方法,即从入口出发,顺某一方向向前探索,若能走通,则继续往前走;否则沿原路退回,换一个方向再继续探索,直至所有可能的通路都探索到为止,如果所有可能的通路都试探过,还是不能走到终点,那就说明该迷宫不存在从起点到终点的通道。

# Q

# Q

结论: 1.从入口进入迷宫之后,不管在迷宫的哪一个位置上,都是先往东走,如果走得通就继续往东走,如果在某个位置上往东走不通的话,就依次试探往南、往西和往北的方向,依着某个走得通的方向继续往前直到出口为止; 2.如果在某个位置上四个方向都走不通的话,就退回到前一个位置,换一个方向再试,如果这个位置已经没有方向可试了就再退一步,如果所有已经走过的位置的四个方向都试探过了,一直退到起始点都没有走通,那就说明这个迷宫根本不通;

求迷宫路径算法的基本思想是: 若当前位置“可通”,则纳入路径,继续前进; 若当前位置“不可通”,则后退,换方向继续探索; 若四周“均无通路”,则将当前位置从路径中删除出去。

求迷宫中一条从入口到出口的路径的算法: 设定当前位置的初值为入口位置; do{ 若当前位置可通, 则{将当前位置插入栈顶; 若该位置是出口位置,则算法结束; 否则切换当前位置的东邻方块为 新的当前位置; } 否则 { }while (栈不空); … …

若栈不空且栈顶位置尚有其他方向未被探索, 则设定新的当前位置为: 沿顺时针方向旋转 找到的栈顶位置的下一相邻块; 若栈不空但栈顶位置的四周均不可通, 则{删去栈顶位置;// 从路径中删去该通道块 若栈不空,则重新测试新的栈顶位置, 直至找到一个可通的相邻块或出栈至栈空; } 若栈空,则表明迷宫没有通路。

假设限于二元运算符的表达式的定义为: 表达式 ::= (操作数) + (运算符) + (操作数) 操作数 ::= 简单变量 | 表达式 简单变量 :: = 标识符 | 无符号整数 算术运算的规则为: 先乘除后加减; 先左后右; 先括弧内后括弧外; 4+68/(24+54/6) = 8  33

设 Exp = S1 + OP + S2 则称 OP + S1 + S2 为前缀表示法 S1 + OP + S2 为中缀表示法 表达式的三种标识方法: 设 Exp = S1 + OP + S2 则称 OP + S1 + S2 为前缀表示法 S1 + OP + S2 为中缀表示法 S1 + S2 + OP 为后缀表示法

例如: Exp = a  b + (c  d / e)  f 前缀式: +  a b   c / d e f 结论: 5) 后缀式的运算规则为: 运算符在式中出现的顺序恰为表达式的运算顺序;每个运算符和在它之前出现 且紧靠它的两个操作数构成一个最小表达式; 4) 前缀式的运算规则为: 连续出现的两个操作数和在它们之前且紧靠它们的运算符构成一个最小表达式; 1)操作数之间的相对次序不变; 3)中缀式丢失了括弧信息,致使运算的次序被改变; 2)运算符的相对次序不同;

如何从后缀式求值? 先找运算符, 再找操作数 例如: a b  c d e /  f  + c-d/e ab d/e

如何从原表达式求得后缀式? 原表达式: a + b  c  d / e  f 后缀式: a b c  + d e / f   分析 “原表达式” 和 “后缀式”中的运算符: 原表达式: a + b  c  d / e  f 后缀式: a b c  + d e / f   在后缀式中,优先权高的运算符领先于优先数权的运算符出现。 每个运算符的运算次序要由它之后的一个运算符来定。

从原表达式求得后缀式的规律为: 1) 设立暂存运算符的栈; 2) 设表达式的结束符为“#”, 予设运算符栈的栈底为“#” 3) 若当前字符是操作数, 则直接发送给后缀式;

从原表达式求得后缀式的规律为: 4) 若当前运算符的优先数高于栈顶运算符,则进栈; 5) 否则,退出栈顶运算符发送给后缀式; 6) “(” 对它之前后的运算符起隔离作用,“)”可视为自相应左括弧开始的表达式的结束符。

a  ( b  ( c + d / e ) - f ) # a b c d e / +  f -  # / / + + (   - (  #

… … void transform(char suffix[ ], char exp[ ] ) { InitStack(S); Push(S, #); p = exp; ch = *p; while (!StackEmpty(S)) { if (!IN(ch, OP)) Pass( Suffix, ch); else { } if ( ch!= # ) { p++; ch = *p; } } // while } // CrtExptree … …

switch (ch) { case ( : Push(S, ch); break; case ) : Pop(S, c); while (c!= ( ) { Pass( Suffix, c); Pop(S, c) } break; defult : while(Gettop(S, c) && ( precede(c,ch))) { Pass( Suffix, c); Pop(S, c); } if ( ch!= # ) Push( S, ch); } // switch

当在一个函数的运行期间调用另一个函数时,在运行该被调用函数之前, 需先完成三项任务: 将所有的实在参数、返回地址等信息传递给被调用函数保存; 为被调用函数的局部变量分配存储区; 将控制转移到被调用函数的入口。

从被调用函数返回调用函数之前,应该完成下列三项任务: 保存被调函数的计算结果; 释放被调函数的数据区; 依照被调函数保存的返回地址将控制转移到调用函数。

多个函数嵌套调用的规则是: 后调用先返回 ! 此时的内存管理实行“栈式管理” 例如: void main( ){ void a( ){ void b( ){ … … … a( ); b( ); c( ); … … }//main }// a }// b 函数b的数据区 函数c的数据区 函数a的数据区 main的数据区

递归函数执行的过程可视为同一函数进行嵌套调用,例如: 递归工作栈:递归过程执行过程中占用的 数据区。 递归工作记录:每一层的递归参数合成 一个记录。 当前活动记录:栈顶记录指示当前层的 执行情况。 当前环境指针:递归工作栈的栈顶指针。

void hanoi (int n, char x, char y, char z) { // 将塔座x上按直径由小到大且至上而下编号为1至n // 的n个圆盘按规则搬到塔座z上,y可用作辅助塔座。 1 if (n==1) 2 move(x, 1, z); // 将编号为1的圆盘从x移到z 3 else { 4 hanoi(n-1, x, z, y); // 将x上编号为1至n-1的 //圆盘移到y, z 作辅助塔 5 move(x, n, z); // 将编号为n的圆盘从x移到z 6 hanoi(n-1, y, x, z); // 将y上编号为1至n-1的 //圆盘移到z, x作辅助塔 7 }//else 8}

void hanoi (int n, char x, char y, char z) { 1 if (n==1) 2 move(x, 1, z); 3 else { 4 hanoi(n-1, x, z, y); 5 move(x, n, z); 6 hanoi(n-1, y, x, z); 7 } 8 } 7 1 c a b 5 1 a b c 5 2 a c b 8 3 a b c 返址 n x y z

一、顺序栈 // 结构定义 typedef struct { ElemType *base; int top; int stacksize; S.base S.top S.stacksize

二、链栈 S.top // 结构定义 typedef struct { LNode *top; // 栈顶指针 int length; an an-1 a1  // 结构定义 typedef struct { LNode *top; // 栈顶指针 int length; // 栈的长度 }Stack

D={ ai | ai ∈ElemSet, i=1,2,...,n, n≥0 } 队列是限定只能在表尾进行插入和在表头进行删除的线性表。 ADT Queue { }ADT Queue 数据对象: D={ ai | ai ∈ElemSet, i=1,2,...,n, n≥0 } 数据关系: R1={ <ai-1 ,ai >|ai-1 ,ai∈D, i=2,...,n } 约定an端为队尾,a1端为队头 基本操作:

QueueTraverse (Q, visit( )) InitQueue (&Q) (构造空队列 ) DestroyQueue(&Q) (销毁队列结构) ClearQueue (&Q) (队列清空) QueueEmpty (Q) (判队空) QueueLength(Q) (求队列长度) GetHead (Q, &e) (求队头元素) EnQueue (&Q, e) (入队列) DeQueue (&Q, &e) (出队列) QueueTraverse (Q, visit( )) (遍历队列)

一、链队列 … 空队列 结构定义 typedef SLink QueuePtr; Q.front Q.rear Q.front Q.rear ∧ … a1 an Q.front Q.rear 空队列 Q.front ∧ a1 Q.rear 结构定义 typedef SLink QueuePtr; typedef struct { QueuePtr front; // 队列的头指针 QueuePtr rear; // 队列的尾指针 int length; }Queue; // 链队列

bool InitQueue (Queue &Q) { // 构造一个空队列 Q, 若空间分配失败, 基本操作接口(函数声明) bool InitQueue (Queue &Q) { // 构造一个空队列 Q, 若空间分配失败, // 则返回 FALSE, 否则返回TRUE; } Q.front = Q.rear = new Lnode; if (!Q.front) return FALSE; Q.front->next = NULL; Q.length = 0; return TRUE;

入队列操作 ∧ e s a1 ∧ an … Q.front Q.rear

bool EnQueue (Queue &Q, QElemType e) { //若存储分配成功,则在当前队列的尾元素之 //后插入 e为新的队列尾元素,并返回 TRUE, // 否则返回 FALSE } s = new LNode; if ( !s ) return FALSE; // 存储分配失败 s->data = e; s->next = NULL; Q.rear->next = s; // 修改尾结点的指针 Q.rear = s; // 移动队尾指针 ++Q.length; // 队列长度增 1 return TRUE;

a1 a2 an … Q.front Q.rear ∧ 出队列操作 p an Q.front Q.rear ∧ Q.rear p ∧

bool DeQueue (Queue &Q, QElemType &e) { // 若队列不空,则删除当前队列Q中的头元素, //用e返回其值,并返回TRUE;否则返回FALSE }//DeQueue if ( Q.front == Q.rear ) // 队列为空 return FALSE; p = Q.front->next; e = p->data; // 返回被删元素 Q.front->next = p->next; // 修改头结点指针 delete p; // 释放被删结点 return TRUE; if (Q.rear == p) Q.rear = Q.front;

二、循环队列 ——队列的顺序映象 // 结构定义 typedef struct { QElemType *elem; // 存储空间基址 int rear; // 队尾指针 int front; // 队头指针 int queuesize; // 允许的最大存储空间 } Queue; // 基本操作接口(函数声明)

Q.elem e f g Q.rear Q.front Q.rear Q.rear Q.elem Q.front Q.front 1 2 3 4 5 6 7 8 a b c d Q.elem e f g Q.rear Q.front Q.rear Q.rear 1 2 3 4 5 6 7 8 a b c d Q.elem Q.front Q.front Q.front Q.front Q.front Q.rear 1 2 3 4 5 6 7 8 a b c d e f g Q.elem h j Q.rear Q.rear Q.rear Q.front

bool InitQueue (SqQueue &Q,int maxsize ){ //构造一个最大存储空间为maxsize的空队列Q }//InitQueue if (maxsize == 0) maxsize = MAXLISTSIZE; Q.elem = new QElemType[maxsize]; // 为循环队列分配存储空间 if (!Q.elem) return FALSE; // 存储分配失败 Q.queuesize = maxsize; Q.front = Q.rear = 0; return TRUE;

bool DeQueue (Queue &Q, QElemType &e) { //用e返回其值并返回TRUE;否则返回FALSE } if (Q.front == Q.rear) return FALSE; e = Q.elem[Q.front]; Q.front = (Q.front+1) % Q.queuesize; return TRUE;

bool EnQueue (Queue &Q, QElemType e) { // 若当前队列不满,这在当前队列的尾元素 //之后插入元素 e 为新的队列尾元素, // 并返回TRUE,否则返回FALSE Q.elem[Q.rear] = e; Q.rear = (Q.rear+1) % Q.queuesize; return TRUE; } if ((Q.rear + 1) % Q.queuesize == Q.front ) return FALSE;

—循环队列应用举例 编写“逐行输出前 n 行二项式系数”的算法。 二项式系数表 第 1 行 1 1 第 2 行 1 2 1 第 1 行 1 1 第 2 行 1 2 1 第 3 行 1 3 3 1 第 4 行 1 4 6 4 1 … … … … … … 设第 i-1 行的值: (a[0]=0) a[1], …a[i], (a[i+1]=0) 则第 i 行的系数: b[j] = a[j-1]+a[j], j=1,2,…,i

s + e = 利用“循环队列”计算二项式系数的过程 输出: 1 1 2 1 1 2 1 1 1 1 1 3 1 2 1 3 1 do { 2 1 1 1 1 1 3 1 2 1 3 1 do { DeQueue(Q, s); GetHead(Q, e); if (e!=0) cout << e; EnQueue(Q, s+e); } while (e!=0); 1 2 3 4 5 1 1 3 3 1 1 2 Q.rear Q.rear Q.front Q.rear Q.front Q.rear Q.front Q.rear Q.front Q.front Q.rear Q.rear Q.rear Q.front Q.rear Q.rear Q.front Q.front 输出: 1 1 1 2 1

void yanghui ( int n ) { // 打印输出杨辉三角的前 n ( n > 0 )行 Queue Q; InitQueue(Q, n+2); EnQueue(Q, 0 ); // 添加行界值 EnQueue( Q, 1); EnQueue_Sq ( Q, 1 ); k = 1; while ( k < n ) { EnQueue ( Q, 0 ); // 行界值“0”入队列 do { // 输出第 k 行,计算第 k+1 行} while (e!=0); k++; }//while } // yanghui … … …

DeQueue ( Q, e ); // 行界值“0”出队列 while (!QueueEmpty (Q) ) { // 单独处理第 n 行的值的输出 DeQueue_Sq ( Q, e ); cout<<e<<‘ ’; }//while

 1. 掌握栈和队列类型的特点,并能在相应的应用问题中正确选用它们。  2. 熟练掌握栈类型的两种实现方法,特别应注意栈满和栈空的条件以及它们的描述方法。  3. 熟练掌握循环队列和链队列的基本操作实现算法,特别注意队满和队空的描述方法。