1、第二章 线性表的顺序存储及其运算,2.1 线性表的概念一、线性表的结构特性 二、线性表的抽象数据类型2.2 顺序表及其运算一、什么是顺序表二、顺序表的运算2.3 栈 一、栈的概念 二、栈的抽象数据类型 三、顺序栈及其操作实现 四、栈应用例,第二章 线性表的顺序存储及其运算,2.4 队列一、队列及其抽象数据类型 二、顺序队列及其操作实现 三、队列应用例四、优先队列2.5 数组与矩阵的表示一、数组的顺序分配二、规则矩阵的压缩存储三、稀疏矩阵的三元组顺序表表示,2.1 线性表的概念,一、线性表的结构特性属性相同的数据元素按某种关系排列的表 例: 农历节气表 ( 立春, 雨水, 惊蛰, 春分, 清明,
2、 , 大雪, 冬至, 小寒, 大寒 )表中元素是字符抗灾衣被捐赠登记表 按捐赠时间先后( 单位, 姓名, 棉被, 棉衣裤, 毛衣裤, 帽类 )奥运会各国家队奖牌数统计表 按金牌、银牌、铜牌数多少( 国家, 金牌数, 银牌数, 铜牌数 )表中元素为记录,2.1 线性表的概念,线性表( Linear List ) 具有相同特性数据元素的有限序列;可描述为:B=( D, R ) D= ai | i=1, 2, , n ;R= ( ai, ai+1) | i=1, 2, , n-1 ;也可以简单表示为: B=( a1, a2, , ai, , an )表中元素个数 n 表长度, n=0 时称为空表;结
3、构特性: 元素之间具有线性关系 (元素在位置上有序); 元素在表中的位置由其序号决定; 表长度可变;,2.1 线性表的概念,二、线性表的抽象数据类型数据部分:数据元素,数据元素之间的关系描述;操作部分:根据应用需要确定按照功能可以归纳为以下基本类型: 属性设置:确定类型的基本属性值; 读取属性:读取类型的属性值; 插入:在对象的指定位置加入新的数据元素; 删除:删除对象中的指定数据元素; 查找:在对象查找满足条件的数据元素; 遍历:按某种方式不重复地访问对象中所有数据元素; 关系访问:访问对象中有特定关系的元素;,2.1 线性表的概念,ADT LIST数据:线性表 L= ( a0, a1, ,
4、 an) , n0 ;操作:void InitList ( *L) ; / 初始化 L指向的线性表ElemType GetElemlist (*L, int pos ) ; / 得到表中第pos个元素int FindList ( *L, ElemType item ) ; / 查找给定关键字元素/ 修改表中指定元素int ModifyList (*L, ElemType item ) ;,2.1 线性表的概念,int InsertList (*L, ElemType item ) ; / 向表中插入元素 int DeleteList (*L, ElemType item ) ; / 删除表中元
5、素int LenthList (*L) ; / 求表的长度void SortList (*L) ; / 按关键字值对表元素排序 void TraverseList (*L) ; / 对表进行遍历并输出void ClearList (*L) ; / 清除表中所有元素,2.2 顺序表及其运算,一、什么是顺序表顺序表线性表的顺序存储(向量式存储)存储方法: 表中元素按逻辑顺序依次放在连续存储空间中; 每个元素所占存储单元长度相同;例: 利用数组实现线性表的顺序存储,要求:数组长度 表长度表中元素地址计算:ADR( ai )=ADR( a1 ) + ( i -1 )* kk为每个元素所占字节数;,12
6、34 : : n,2.2 顺序表及其运算,二、 顺序表的运算若对表示顺序表的数组空间采用动态分配,对顺序表结构作如下定义:#define MaxSize 100 typedef struct ElemType listMaxSize ; / 存储线性表的数组int len ; / 线性表长度 SeqList ; 数据结构操作的实现与具体的存储结构有关以下考虑以SeqList为类型的顺序表基本操作(运算)的实现最基本的操作(运算):(1) 在表中插入一个元素 (2) 删除表中某个元素,2.2 顺序表及其运算,1. 顺序表初始化为存储线性表动态分配数组空间,置初始线性表为空void InitLis
7、t (SeqList *L ) / 动态分配存储空间L -List =(ElemType*)malloc(MaxSize*sizeof(ElemType) ;if ( ! L-list ) printf( “ Memory allocation failure ! n“ ) ;exit (1) ;L-len = 0 ; / 置初始线性表为空,2. 顺序表的插入运算在表中第 i个位置插入一个元素 item 设表长为 len 插入前:A= ( a0, a1, , ai-1, ai, , alen-1 ) 表长为 len插入后:A= ( a 0, a 1, , a i-1, a i, a i+1,
8、, a len ) 表长为 len+1 表首元素位置不变,表向后增长ak 0 k i A与A 的关系:a k = item k = iak-1 i klen实现算法:考虑因素: 表长不能超出给定空间上溢处理 若所给插入位置不合理,要处理 正常插入操作后表长应增加1,2.2 顺序表及其运算,2.2 顺序表及其运算,算法描述:void insertlist (SeqList *L, ElemType item, int i ) int j ;if ( L-len =MaxSeze ) / 若表空间满,不能插入 printf (“表满!n“) ;exit (1) ;if ( i L-len-1 )
9、i = L-len ;for ( j=L-len-1 ; j=i ; j- ) L-list j+1 = L-list j ; L-list i = item ;L-len + + ;,2.2 顺序表及其运算,算法分析: 时间复杂度:决定本算法执行时间的主要操作:数据元素移动次数;设表长为n,表中第i个位置插入,移动元素次数为: n i 设在各位置插入数据元素的概率为Pi ,平均移动次为:设各位置插入的概率相等,即: Pi = 1/(n+1),则有:即:插入一元素的时间复杂度为:O(n) 空间复杂度:原地工作( in place )思考:在有序顺序表中插入一个数据元素, 算法?,2.2 顺序表
10、及其运算,3. 顺序表的删除运算在表中删除第pos个元素删除前:(b0,b1 ,bi ,bi+1 ,bn-1) 表长为 n ;删除后:(b0,b1,bi-1,bi+1,bn-2 ) 表长为 n-1 ;算法考虑:表空( L-len = 0)不能做删除 下溢处理;指定的删除位置不存在,要处理;正常删除操作,表长 n 减 1;算法描述:参考教材 算法分析: 与插入运算类似;平均时间复杂度: O(n);空间复杂度:原地工作思考:在有序顺序表中删除指定元素, 算法?,2.2 顺序表及其运算,4. 顺序表的查找 从线性表中查找具有给定值的第一个元素 设L为无序表,实现算法:int FindList (Se
11、qList *L , ElemType item)int i ;for ( i= 0; ilen; i+)if ( L-list i = item ) return i ; return -1 ; / -1查找失败标志思考:若 L为有序表,实现算法?,2.2 顺序表及其运算,算法复杂度: 时间复杂度:算法运行时间主要由比较元素的次数决定,当查找元素在表中第 i 个位置时,其比较次数为: i + 1 设表长为 n ,若表中各元素被查找的概率相等,元素值不等, 则查找一个元素的平均比较次数为:若没有查到所查元素(查找失败)比较次数为:n即:其时间复杂度为:O(n) 空间复杂度: 原地工作 ( in
12、 place )其他运算(操作)算法参考教材,2.2 顺序表及其运算,顺序表的主要优点 : 容易实现 ; 直观、易理解;顺序表的限制: 对存储空间有要求; 插入、删除操作的效率较低 ;,2.3 栈,一、什么是栈(Stack)问题1:要求正、逆过程保证严格的相反顺序例: 进入大沙漠考察与原路返回:,2.3 栈,问题2:多级中断的进入与返回假设某系统执行时遇有4级中断,其保护各级中断现场与恢复现场的过程为:(打印机) (磁盘) (采集卡) (电源)1级中断 2级中断 3级中断 4级中断 保存现场1 保存现场2 保存现场3a: b: c: end return return return,2.3 栈
13、,为保证中断正确执行,须依次记住每层中断的现场及返回地址;进入中断当各层中断“返回”时,则要按记入的相反次序逐个恢复现场继续执行;中断返回,2.3 栈,从上述表的特点看栈的结构特性:表中元素有序线性表元素进入与退出顺序相反特殊性 栈限制插入、删除操作只能在一端进行的特殊线性表表中允许进行插入、删除操作的一端栈顶,另一端栈底;如:( a1,a2,a3,a4,an-1 )栈底 栈顶栈结构特性:栈中元素后进先出 ( LIFO )结论:可用于记忆正、逆顺序;,2.3 栈,二、栈的抽象数据类型ADT Stack 数据:栈S 以Stack 为存储类型;操作:void Initstack ( *s) ; /
14、 初始化S指向的栈void Push ( *s, ElemType item) ; / 元素进栈 ElemType Pop ( *s) ; / 元素退栈ElemType GetTop ( *s) ; / 读取栈顶元素值int Emptype Stack ( *s) ; / 判断栈是否空int FullStack ( *s) ; / 判断栈是否满void ClearStack ( *s) ; / 清空栈,2.3 栈,三、顺序栈栈的顺序存储顺序栈最先入栈的元素栈底最后入栈的元素栈顶假定:以数组 stack MaxSize 存储栈,数组空间采用动态分配; 用 top 指示栈顶位置, 定义顺序栈 Se
15、qStack 的结构类型为:#define MaxSize 100typedef struct ElemType stackMaxSize;int top; SeqStack ;,2.3 栈,设定初始栈空 top = -1,栈满 top = MaxSize 1 ,考虑顺序栈操作实现:1. 栈初始化void InitStack (SeqStack *s ) s-stack = ( ElemTpye*)malloc(MaxSize*sizeof(ElemType) ;if (!s-stack) printf (“Memory allocation failure!n“) ;exit (1) ;s-
16、top = -1 ; / 置栈空,2.3 栈,2. 进栈操作(入栈) void Push( SeqStack *s, Elemtype item ) if ( s-top = = MaxSize-1) / 检查栈中是否还有空间 printf ( “Stack Overflow!n“) ;exit (1) ;s-top+ ; / 移动栈顶指针,使其指向新的栈顶位置s-stacks-top=item ; / 新元素入栈注意: 栈初始设定(初始化)与运算操作保持一致。stack MaxSize为栈空间,初始 top = -1 (开口“向上”),也可以设,初始 top = MaxSize (开口“向下
17、”)。 若发现栈满,也可做动态扩大栈空间处理,如:if ( s-top= =s-MaxSize -1 ) AllocatStack (s) ;,2.3 栈,3. 退栈操作 ElemType Pop (SeqStack *s) if (s-top= = -1 / 检查栈是否空printf ( “Stack is empty! n“) ;exit (1) ;s-top- ; / 栈顶指针退1,使其指向新的栈顶位置 return s-stacks-top+1 / 返回栈顶元素值进栈、退栈操作的时间复杂度为 O(1)4. 读取栈顶元素值ElemType GetTop (SeqStack *s)实现与退
18、栈操作相似,只是不删除栈顶元素(栈顶指针不移动),2.3 栈,5. 清空栈 void ClearStack ( SeqStack *s ) s-top = -1 ;思考:(1) 如果上述存储栈空间不变, 但设初始时top = MaxSize,栈满、栈空如何判断?进栈、退栈指针如何变化?(2) 如果上述存储类型不变, 初始时设 top = 0, 操作实现又有何变化?,2.3 栈,6. 双栈操作适用情况:需要同时使用两个栈方 法:两个栈共同开辟一个存储空间,栈底设于两端 如:优 点: 两栈互补余缺,充分利用存储空间;必要时可以定义其结构类型,定义和实现其操作;思考: 操作实现?,2.3 栈,四、栈
19、应用1. 用栈记忆处理层次例,程序中括弧匹配检查程序的嵌套结构通常用多重大括弧来表示,一对括弧表示一层例如: while(条件1) while (条件2) while (条件3) ,2.3 栈,序号 1 2 3 4 5 6各个括弧的出现顺序为: 编译程序检查程序嵌套正确性的方法是: 每遇一个左括弧,即“期望”有一个右括弧出现与其匹配; 当出现一右括弧,将其与在它之前距离最近的左括弧匹配;即,最后出现的左括弧与在之其后最先出现的右括弧匹配;实现:设置一个栈置为空,重复做: 若读入为“”入栈; 若读入为“”,栈不空, 且栈顶为“”出栈;否则,匹配有错; 扫描结束时,栈应为空,否则括弧不匹配;,2.
20、3 栈,top,top,初始栈空:思考: 算法描述?,top,top,遇括弧 6 ,遇括弧 1 ,遇括弧 2 ,遇括弧 3 ,遇括弧 4 ,遇括弧 5 ,top,top,2.3 栈,例, 印刷电路板布线问题判断在印刷电路板的一面上设定的布线是否有交叉 (短路) 给定印刷板的矩形分布区,引脚在外围如图(a)所示(a) (b) (c)若给定连线的引脚对: (1, 4) , (2, 3), (5, 8), (6, 7)判断能否合理布线 ?,2.3 栈,判断办法:对每条引线检查是否有其他引线的两个引脚跨越其划分的两个分区,对有其他引线划分的各层子分区做同样检查实现方法: 对所有引脚顺序编号; 设置一个
21、栈, 依次读入各引脚编号, 并做: 若栈空, 所读引脚号入栈; 若栈不空, 栈顶引脚号与读入号属于一条引线, 栈顶号退栈 若栈不空,且栈顶号与读入号不属一条引线,读入号入栈读入全部引脚号并处理完成后,若栈空则可布线,否则需调整如:读入引脚1 读入引脚2 读入引脚3 读入引脚4,top,top,top,top,2.3 栈,例, 算术表达式求值(1) 直接计算中缀表示式中缀表示式 运算符位于两个操作数之间:(a+b)*c ;确定运算符的优先数:运算符: * * / + - ( ) ;优先数: 4 3 3 2 2 1 1 0定义: 操作数栈,初始空 记忆表达式中的操作数;运算符栈,初始有结束符 “
22、; ” 记忆表达式中的运算符;从左向右扫描表达式 ,根据运算规则确定处理方法: 操作数入操作数栈; 扫描到的运算符优先级 栈顶运算符优先级,运算符入栈;否则,处理栈顶运算符; 括弧、结束符单独处理;,2.3 栈,(2) 后缀表达式求值后缀表达式 逆波兰式; 后缀表示式的特点: 运算符跟在运算对象之后; 不含括号; 中缀表示式 后缀表示式转换规则:运算符置于操作数之后例:中缀表达式:X-Y 后缀表示式为:XY-中缀表达式:X*Y-Z 后缀表示式为:XY*Z-中缀表达式:X*(Y-Z) 后缀表示式为:XYZ-*中缀表达式:X-Y*(Z+U)/V 后缀表示式为:XYZU+*V/-,2.3 栈,计算后
23、缀表示式的算法思路: 设后缀表达式以 “ ;” 结束,操作数栈 S 初始为空; 从左到右依次扫描后缀表达式的每个词 W ,重复做: 若W为操作数,W 进栈 S ,继续扫描下个词; 若W为运算符,从栈 S 中依次退栈两个操作数x、 y,设当前运算符为,则做 y x , 结果入栈 S,继续扫描;重复上述过程,直至扫描到结束符“;”,结果在栈 S 中; 如,计算: A+B*(C-D)/E; ABCD-*E/+ ;,2.3 栈,2. 栈与回溯法 例,求解简单背包问题设有总容量为T的背包和n 件体积分别为w1, w2,wn的物品,要求找出使其中的若干件物品正好装满背包的所有解。 求解思路:回溯法 逐件试
24、装 步骤: 把 n 件物品排列; 若背包能容纳物品,依次将各物品装入背包;当背包不能容纳某件物品时,放弃,继续选取下一件试; 若剩余物品中找不到合适物品,取出最后放入物品,继续选取下一件试;重复 、,至求得所有解,或无解;,1 2 3 n,2.3 栈,0 1 2 3 4 物体编号 i 设:T=8,5 件物体体积分别为: 1, 5, 3, 4, 2 体积v(i) 回溯利用栈实现 有解:(1, 5, 2),(1, 3, 4),(5, 3) 栈中状态:,栈空 取最后一件,2.3 栈,1,求得第1组解的过程:T = 8, v(i): 1, 5, 3, 4, 2 ,351,451,51,251,2.3
25、栈,参考算法:void knapsack (int w, int T, int n) / w0wn-1 n 件物品体积int i =0 ; / 从编号为0的物品开始试装InitStack(S) ; / 栈初始化 while (!EmptyStack(s) / 继续试装下一物品,2.3 栈,3. 栈与递归 (1) 递归的概念 递推思路:从初始条件出发,逐次推出结果 (如:求 n ! ) 递归思路:由算法自身到达递归边界 (递归终止条件) 递归方法用于解决: 可分解为结构自相似的问题 结构自相似的问题构成问题的部分与问题本身结构相似 求解过程:原问题 递归求子问题 递归求更小子问题 有直接解或已知
26、条件 逆过程逐步回代求出结果 递归算法的表现形式:自己调用自己 (直接或间接) 递归算法的优点:结构清晰、简练,有直接解法的特殊情况 始基,与原问题“相似”的子问题 递归,问题特点:可分解为,2.3 栈,(2) 分治法与递归分治法的求解步骤: 划分: 原问题(规模n)划分为k个 子问题(规模大致相等) ; 求解子问题:子问题解法经常与原问题相同; 合并:合并各子问题的解;伪码描述:DividConquer (P) / 求解规模为n的问题 P if (P的规模足够小) 直接求解 P ;else 分解为k个子问题 P1, P2, , Pk ;for (i=1; i=k; i+)yi= DividC
27、onquer(Pi) ; / 求解子问题return Merger(y1, y2, , yk) / 合并子问题解得到原问题解,2.3 栈,由分治法产生的子问题往往是原问题的较小模式 ,即:子问题与原问题求解方法相同,亦即:问题具有结构自相似的特征 适合采用递归算法解决; 结论:分治策略与递归方法经常同时用于算法设计中 例,求数组中最大元素的递归算法思路描述:if(待选元素个数2) 选出大者;递归返回;else 对分选择元素;递归选出前半部最大元素 j ;递归选出前半部最大元素 k ;比较 j、k; 并返回大者;,2.3 栈,算法描述:int Select_Max (int a, int low
28、, int high) / 找alow ahigh中最大者 int mid, j, k ;if (high-lowk) return j ; / 返回两部分的最大元素 else return k ;,2.3 栈,(3) 递归算法设计的基本思路 对于递归定义的问题,可根据定义得到递归算法例6,求阶乘n! =求解算法:long Fact(int n) if (n0) printf ( “n值不合理n“) ; exit(1) ;if (n=0) return 1;else return ( n* Fact (n-1) ) ;,1 n = 0,n (n-1)! n 0,2.3 栈,例, 计算斐波那契数
29、 斐波那契数列的定义:( 0, 1, 1, 2, 3, 5, 8, )Fib(n) = 算法描述:long Fib (int n) if (n0) printf (“n值不合理n“) ; exit(1) ; if (n =1| n=0) return n ;else return Fib(n-1)+Fib(n-2) ;,n n =0,1,Fib(n-1) + Fib(n-2) n 1,2.3 栈,例, 计算阿克曼函数阿克曼函数也是递归定义:x+1 n=0x n=1 且 y=00 n=2 且 y=01 n=3 且 y=02 n4 且 y=0Ack ( n-1, Ack(n, x, y-1), x
30、 ) n0且y 0,Ack(n, x, y) =,2.3 栈,计算算法:int Ack( int n, int x, int y ) int result ;if (n = 0) result = x+1;else if (y = 0) if (n = 1) result = x ;else if (n = 2) result = 0 ;else if (n = 3) result = 1 ;else Result = 2 ;else result = Ack( n-1, Ack (n, x, y-1), x ) ;return result ; ,2.3 栈, 对有递归结构的问题,根据结构的
31、特性也容易写出递归算法 如,一个顺序表可以看成有递归结构:顺序表 子顺序表 更小的子表 ;对于例5中求数组中最大元素问题,也可以从顺序表结构特点出发得到其递归算法过程:“查找最大元素” 若表长为1 元素本身;(递归边界)否则,划分为两个子表,对子表分别递归调用过程“查找最大元素”;选出大者,2.3 栈,又如,一个层次结构的网络:主节点子网主节点 子网主节点访问网络中每个节点操作:过程:“ 访问网络中每个节点 ” 若网络不空,则做:访问网络主节点;对子网 1 递归调用过程“访问网络中每个节点” ;对子网 2 递归调用过程“访问网络中每个节点” ;对子网 3 递归调用过程“访问网络中每个节点” ;
32、,2.3 栈, 对于没有明显递归定义和递归结构、但求解过程是递归的问题, 需要分析归纳,找出求解问题及子问题的线索例, Hainoi 塔问题源 X 中间 Y 目标 Z移动盘子的基本规律:当 n 1 : 源柱 中间柱源柱 目标柱 (归纳项)中间柱 目标柱 当 n =1 : 直接搬移一个盘子 (基本项),n-1个盘子,n-1个盘子,第n号盘子,X Z,1,X Y,2,Z Y,1,X Z,3,Y Z,1、2,2.3 栈,又如:求解谜宫问题1 2 3 4 1 2 3 4 ,0 1 2 3 4 5,0 1 2 3 4 5,起始,扩充为,试探方向,2.3 栈,解题思路:一步一试探,通则向前走,碰壁则回退另
33、找通路。基本规律:依次按各方向寻找通路的下一个位置; 若下个位置通且未试过,则前进一步,记录走过的位置与标志;搜寻路径: 若下个试探位置不能走,对当前位置的(归纳项) 下一个方向试探; 若当前位置上各方向都无通路,退回到上一位置,继续试探; 试探位置到达出口,已走通,结束试探; 已回退到入口处,未走通,结束试探 ;,终止搜寻: (基本项),2.3 栈,设计递归算法基本要素:递归算法 设计步骤: 分析问题,归纳出解决问题的基本规律 (基本操作) 解决问题过程中要重复调用它; 根据问题性质和已知条件确定基本项分析重复调用终止的条件 确定递归边界控制递归调用正常结束; 确定递归调用参数,写出递归调用
34、形式上层的已知条件能传递给下层,下层能把结果传递给上层;使每一层递归都向终止条件前进。,分析归纳项重复调用的基本操作,找出基本项重复调用停止的条件,2.3 栈,例, 写出能够输出n 个布尔量的所有可能组合的算法 分析:布尔量取值 1(真) 或 0 (假);n个布尔量所有组合 2n种,每种组合 n个布尔值;归纳: n个布尔量的2n种组合 22n-1种组合 ;2n-1种组合 n-1个布尔量可能的组合,每种组合 n-1个布尔值;n个布尔量可能的组合= 1( n-1个布尔量的组合) + 0 ( n-1个布尔量的组合) 结论:子问题与原问题有相似结构可以用递归求解,2.3 栈,算法思路:以布尔型数组bn
35、表示n个布尔量;归纳项: (重复操作)b0bn-1所有组合 = bn-1=1 基本项: (重复操作停止)bn-1=0递归调用参数: b表示布尔量n 布尔量个数k当前要取值的位序,b0 + b1 bn-1所有组合,b0 + b1 bn-1所有组合,1,0,2.3 栈,算法描述:/ 输出布尔量bk bn-1的所有可能组合,初始调用k为0void bool_value (int b, int n, int k) int i ;if ( k=n ) / 到达递归边界,输出一种组合 for (i=0; in; i+) printf ( “%d“, bi ) ;printf (“n“) ;else / 分
36、别把下标为k的布尔值置1和0,再递归求k+1位后的组合 bk=1 ; bool_value ( b, n, k+1) ;bk=0 ; bool_value ( b, n, k+1) ;,例,写出求从自然数1,2,d,n中任取d个数的所有组合的算法。分析:思路 以降序逐个取定组合中的数;方法 轮流取定每个数;如,可以依次取定组合中的第1个数分别为 :n, n-1, , d归纳: d位组合数=已取定数+ 其后的数中取d-1个数之组合 结论: 可以采用递归方法求解。 归纳项: 1, 2, , n 中任取d个数组合= 取定的第1个数 +其后的数中取d-1个数之组合; 基本项:所取的组合数个数= d ;
37、调用参数:n 自然数个数;d 组合数个数。,2.3 栈,算法描述:int resultMaxNum全局数组,用于保存当前求得的组合其大小 由问题而定, result0=d 。void combin(int n, int d) / n自然数个数,d组合数个数 int i, j ;for (i=n; i=d; i-) resultd= i ; / 依次选定组合中的最大数if (d1) combin(i-1, d-1) ; / 组合中的数还不够else / 已经找到一个满足要求的组合 for ( j=result0 ; j0; j-) printf ( “%d“, resultj ) ;printf
38、 ( “n“ ) ; / 输出找到的组合类似问题:写出自然数 1, 2, , n的全排列(有n!种)的算法。,2.3 栈,2.3 栈,(4) 递归执行与递归栈递归执行的第一阶段:递推,逐步向递归边界推进 递归执行的第二阶段:回归,从已知条件出发逆递推过程,逐层求值,求出结果;递推、回归过程互逆;需用栈记忆调用层次递归工作栈递推过程,需记录各层调用参数 压栈回归过程,需逐层恢复各参数 退栈例,求Ack(3, 1, 1) 的过程如下:Ack (3, 1, 1) = Ack(2,Ack( 3, 1, 0 ),1)= Ack( 2, 1, 1 )= Ack(1,Ack( 2, 1, 0 ), 1)=
39、Ack( 1, 0, 1 )= Ack(0,Ack( 1, 0, 0 ), 0)= Ack( 0, 0, 0 ) = 1,2.3 栈,阿克曼函数也是递归定义:x+1 n=0x n=1 且 y=00 n=2 且 y=01 n=3 且 y=02 n4 且 y=0Ack ( n-1, Ack(n, x, y-1), x ) n0且y 0,Ack(n, x, y) =,2.3 栈,计算过程中递归工作栈各层 n、x、y 及返回值:,2.3 栈,递归过程中递归工作栈每一层需要保存的参数包括:局部变量、实际参数、返回地址 例,Hanoi 塔算法Hanoi ( int n , int x, int y, in
40、t z ) if (n = =1) move ( x, 1, z )else Hanoi ( n-1, x, z, y )r1: move ( x, n, z )Hanoi ( n-1, y, x, z )r2 : return 每一层递归要保存参数: 返址、n值、x值、y值、z值 工作记录,2.3 栈,设:调用函数返址为r0调用 Hanoi( 3, x0 , y0 , z0 )时,递归栈中的状态变化例:进调用(第1层) 进第2层调用 进第3层调用 返回第2层进入一层递归调用时,该层参数入栈(记录);退出一层递归调用时,栈顶参数出栈;当前层递归参数总是在栈顶。,2.3 栈,在某些特殊情况下,可
41、能希望消去算法中的递归 消除算法中递归调用的思路: 设置一个保存返址、实参、局部变量的栈; 对于每一个递归调用语句用以下三个语句代替:PUSH(本层返址、实参、局部变量);GOTO 0 第0句为递归过程的开始; POP(栈顶记录); 为此: 要设必要的语句标号; 算法中的出口对应返回上一层调用的语句; 最后优化算法; 若算法本身很容易用简单方法(如:循环、设置栈)消除递 归,则不必按此思路做。,2.4 队列,一、队列及其抽象数据类型1.队列(Queue)的结构特性针对问题:按先后顺序处理(先到先服务)如:生活中的排队、打印队列、作业队列等;队列限制表中元素在一端插入、另一端删除的特殊线性表允许
42、删除的一端队头, 允许插入的一端队尾为运算需要,设队头指针front, 队尾指针rear例如,队列:( a1, a2,an )队头 队尾队列特性:队列中元素先进先出( FIFO ),2.4 队列,2. 队列的抽象数据类型ADT Queue 数据:队列Q,元素类型为 ElemType, 存储类型为 Queue操作:void InitQueue ( *q ) ; / 初始化q指向的队列 void EnQueue (*q, ElemType item ) ; /入队, item值插到尾 ElemType OutQueue ( *q ) ; / 退队, 删除队头元素并返回ElemType PeekQueue ( *q ) ; / 读出队头元素void ClearQueue ( *q ) ; / 清空队列,2.4 队列,二、顺序队列及其运算1. 顺序队列的存储类型顺序队列队列的顺序存储结构例: 0 1 2 mfront rear为方便运算,设定:队头指针 front 指向队头元素的前一个位置;元素退队,front后移队尾指针 rear 指向队尾元素位置;元素入队 rear 后移,