Download presentation
Presentation is loading. Please wait.
1
递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己, 则称这个过程是递归的过程。 三种递归情况 定义是递归的 数据结构是递归的 问题的解法是递归的
2
定义是递归的 求解阶乘函数的递归算法 long Factorial ( long n ) { if ( n == 0 ) return 1; else return n * Factorial (n - 1); } 例如,阶乘函数
3
求解阶乘 n! 的过程 主程序 主程序 main : fact(4) 参数 4 计算 4*fact(3) 返回 24 参数 3 计算 3*fact(2) 返回 6 参数 2 计算 2*fact(1) 返回 2 参数 1 计算 1*fact(0) 返回 1 参数 0 直接定值 = 1 返回 1 参数传递参数传递 结果返回结果返回 递归调用递归调用递归调用递归调用 回归求值回归求值回归求值回归求值
4
数据结构是递归的 有若干结点的单链表 例如,单链表结构 f f 只有一个结点的单链表
5
例 1. 搜索链表最后一个结点并打印其数值 void Search ( ListNode *f ) { if ( f -> next == NULL ) printf (“%d\n”, f -> data ); else Search ( f -> next ); } f ffff a0a0 a1a1 a2a2 a3a3 a4a4 递归找链尾
6
例 2. 在链表中寻找等于给定值的结点, 并打印其数值 void Search ( ListNode * f, ListData x ) { if ( f ! = NULL ) if ( f -> data == x ) printf ( “ %d\n ”, f -> data ); else Search ( f -> next, x ); } 递归找含 x 值的结点 f fff x
7
例如,汉诺塔 (Tower of Hanoi) 问题的解法: 如果 n = 1 ,则将这一个盘子直接从 X 柱 移到 Z 柱上。否则,执行以下三步: ① 用 Z 柱做过渡,将 X 柱上的 (n - 1) 个盘子移 到 Y 柱上: ② 将 X 柱上最后一个盘子直接移到 Z 柱上; ③ 用 X 柱做过渡,将 Y 柱上的 (n - 1) 个盘子移 到 Z 柱上。 问题的解法是递归的
9
算法 void hanoi (int n, char X, char Y, char Z) { // 解决汉诺塔问题的算法 if ( n == 1 ) printf (" move %s",X, " to %s”,Z); else { Hanoi ( n-1, X, Z, Y ); printf (" move %s",X, " to %s”,Z); Hanoi ( n-1, Y, X, Z ); }
10
递归方法是设计非数值计算程序的重要方法,它使得程 序的结构清晰,形式简洁,易于阅读,正确性容易证明。 一般地讲,一个问题采用递归算法求解时,须具备 3 个 条件。 (1) 能将一个问题转变成一个新问题,而新问题与原问 题的解法相同或类同,所不同的仅是所处理的对象,且 这些处理对象的变化是有规律的。 (2) 可以通过上述转化使问题逐步简单化。 (3) 必须有一个明确的递归出口 ( 递归的边界 ) 。
11
递归过程及其实现 递归过程在实现时,需要自己调用自己。 层层向下递归,退出时的次序正好相反: 递归调用 n! (n-1)! (n-2)! 1! 0!=1 返回次序 递归函数运行的 “ 层次 ”
12
递归工作栈 函数嵌套调用时的 “ 后调用先返回 ” 原则 递归函数调用也是一种嵌套调用;每一次递归调用时, 也需要分配存储空间(工作区)来存储参数、局部变 量、返回地址等信息。 每层递归调用需分配的空间形成递归工作记录,按后 进先出 (LIFO) 的栈组织。 局部变量 返回地址 参 数 活动 记录 框架 递归 工作记录
13
函数递归时的活动记录 ………………. Function( ) ………………. 调用块 函数块 返回地址 ( 下一条指令 ) 局部变量 参数
14
递归过程改为非递归过程 递归过程简洁、易编、易懂 递归过程效率低,重复计算多 改为非递归过程的目的是提高效率 单向递归和尾递归可直接用迭代实现其 非递归过程 其他情形必须借助栈实现非递归过程
15
计算斐波那契数列的函数 Fib(n) 的定义 求解斐波那契数列的递归算法 long Fib ( long n ) { if ( n <= 1 ) return n; else return Fib (n - 1) + Fib (n - 2); } 如 F 0 = 0, F 1 = 1, F 2 = 1, F 3 = 2, F 4 = 3, F 5 = 5
16
斐波那契数列的递归调用树 Fib(1)Fib(0) Fib(1)Fib(2) Fib(3) Fib(4) Fib(1)Fib(0) Fib(2) Fib(1)Fib(0) Fib(1)Fib(2) Fib(3) Fib(5)
17
Fib(1)Fib(0) Fib(2)Fib(1)Fib(0) Fib(2) Fib(1) Fib(3) Fib(4) 栈结点 n tag tag = 1, 向左递归; tag = 2, 向右递归
18
计算斐波那契数列的递推算法 Long FibIter(long n){ if (n <=1) return n; long twoback=0,oneback=1,current; for (int I=2;I<=n;I++){ current =twoback+oneback; twoback=oneback;oneback=current; } return current; } 事件复杂度: O(n)
19
队列 (Queue) 定义 : 只允许在表的一端进行插入,而在另 一端删除元素的线性表。 在队列中,允许插入的一端叫队尾 ( rear ), 允许删除的一端称为队头 (front) 。 特点: 先进先出 (FIFO) a 1 , a 2 , a 3 , … , a n 出队列入队列 队头队头 队尾队尾
20
链队列:队列的链式表示 链队列中,有两个分别指示队头和队尾的指针。 链式队列在进队时无队满问题,但有队空问题。 data next front rear
21
front rear ^ 空队列 front rear x^ 元素 x 入队 front rear x^y^ 元素 y 入队 front rear x^^y 元素 x 出队 ^
22
typedef int QElemType; typedef struct Qnode { QElemType data; // 队列结点数据 struct Qnode *next; // 结点链指针 } Qnode,*QueuePtr; typedef struct { QueuePtr rear, front; } LinkQueue; 链式队列的定义
23
链队列的主要操作 Status InitQueue ( LinkQueue &Q ) Status DestroyQueue(LinkQueue &Q) Status ClearQueue(LinkQueue &Q) Status QueueEmpty ( LinkQueue Q ) int QueueLength(LinkQueue Q) Status GetHead ( LinkQueue Q, QElemType &e) Status EnQueue(LinkQueue &Q,QElemType e) Status DeQueue(LinkQueue &Q,QElemType &e) Status QueueTraverse(LinkQueue Q,visit())
24
入队 Status EnQueue ( LinkQueue &Q, QElemType e ) { p = ( QueuePtr ) malloc( sizeof ( QNode ) ); ….. p->data = e; p->next = NULL; Q.rear->next = p; // 入队 Q.rear =p; return OK; }
25
出队 int DeQueue ( LinkQueue &Q, QElemType &e) { // 删去队头结点,并返回队头元素的值 if ( Q.front==Q.rear ) return ERROR;// 判队空 p = Q.front ->next; e = p->data;// 保存队头的值 Q.front->next = p->next; // 新队头 if (Q.rear == p) Q.rear = Q.front ; free (p); return OK; }
26
循环队列 (Circular Queue) 顺序队列 — 队列的顺序存储表示 插入新的队尾元素,尾指针增 1 , rear = rear + 1 , 删除队头元素,头指针增 1 , front = front + 1 , 因此,在非空队列中,头指针始终指向队列头 元素,而尾指针始终指向队列尾元素的下一个 位置。 队满时再进队将溢出 假溢出(图 3.12) 解决办法:将顺序队列臆造为一个环状的空间, 形成循环 ( 环形 ) 队列
27
队列的进队和出队 front rear 空队列 frontrear A,B,C, D 进队 A B C D frontrear A,B 出队 C D front rear E,F,G 进队 C D E F G front rear H 进队, 溢出
28
循环队列 (Circular Queue) 队头、队尾指针加 1 ,可用取模 ( 余数 ) 运算实现。 队头指针进 1: front = (front+1) %maxsize; 队尾指针进 1: rear = (rear+1) % maxsize; 队列初始化: front = rear = 0; 队空条件: front == rear; 队满条件: (rear+1) % maxsize == front; 0 1 23 4 5 67 循环队列 front rear Maxsize-1
29
0 1 2 3 4 567 front B CD rear 一般情况 A 0 1 23 4 567rear 空队列 front 队满条件: (rear+1) % maxsize == front C 0 1 23 4 5 67 front rear D E F G A B C 队满 C 0 1 23 4 5 67 front rear D E F G A B C H
30
#define MAXSIZE 100 Typedef struct{ QElemType *base; int front; int rear; } SqQueue; 循环队列的类型定义
31
循环队列操作的实现 初始化队列 Status InitQueue ( SqQueue &Q ) { // 构造空队列 Q.base=(QElemType *) malloc (MAXSIZE*sizeof(QElemType)); if (! Q.base) exit(OVERFLOW); Q.rear = Q.front = 0; return OK; }
32
入队 Status EnQueue ( SqQueue &Q, QElemType e ) { if ( (Q.rear+1) % MAXSIZE ==Q.front) return ERROR; // 队满 Q.base[Q.rear] = e; Q.rear = ( Q.rear+1) % MAXSIZE; return OK; }
33
出队 Status DeQueue ( SqQueue &Q, QElemType &e ) { if ( Q.front == Q.rear ) return ERROR; // 队空 e = Q.base[Q.front]; Q.front = ( Q.front+1) % MAXSIZE; return OK; }
34
队列的应用 --- 离散事件模拟 银行业务模拟程序 模拟银行业务活动并计算一天中客户在银行平均逗留时间 事件驱动模拟 — 按事件(客户到达或客户离开)发生的先后 顺序进行处理 。 主要的数据结构: 一个事件表(有序链表) -------- 用来记录待处理的事件 n 个队列 ------ 用来对应 N 个窗口的客户队列
35
typedef struct{ int OccurTime; int Ntype; } Event,ElemType; // 事件类型 typedef LinkList EventList // 事件链表类型 Type struct{ int ArrivalTime; int Duration; } QElemType; // 队列元素类型
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.