递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用...

35
递 递 递 递 (recursion) (recursion) 递递 : 递递递递递递递递递递递 , 递递递递递递递递递 递, 递递递递递递递递递递递递递递递递递 递递递递递递递 , 递递递递递递递递递递递递 递递递递递递 递递递递递递 递递递递递递递递 递递递递递递递递递

Post on 20-Dec-2015

461 views

Category:

Documents


13 download

TRANSCRIPT

Page 1: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

递 归 递 归 (recursion)(recursion) 定义 : 若一个对象部分地包含它自己 , 或用它自己给自己定义 , 则称这个对象是递归的; 若一个过程直接地或间接地调用自己 , 则称这个过程是递归的过程。

三种递归情况 定义是递归的 数据结构是递归的 问题的解法是递归的

Page 2: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

定义是递归的定义是递归的

求解阶乘函数的递归算法

long Factorial ( long n ) { if ( n == 0 ) return 1; else return n * Factorial (n-1);}

例如,阶乘函数

时当时当 1 ,)!1(

0 ,1!

n

n

nnn

Page 3: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

求解阶乘 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

参数传递

结果返回

递递归归调调用用

回回归归求求值值

Page 4: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

数据结构是递归的数据结构是递归的

有若干结点的单链表

例如,单链表结构

f

f

只有一个结点的单链表

Page 5: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

例 1.搜索链表最后一个结点并打印其数值

void Search ( ListNode *f ) { if ( f ->next == NULL ) printf (“%d\n”, f ->data ); else Search ( f ->next );}

f

f f f f

a0 a1 a2 a3 a4

递归找链尾

Page 6: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

例 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

f f f

x

Page 7: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

例如,汉诺塔 (Tower of Hanoi) 问题的解法: 如果 n = 1 ,则将这一个盘子直接从 X 柱移到 Z 柱上。否则,执行以下三步: ① 用 Z 柱做过渡,将 X 柱上的 (n-1) 个盘子移到 Y 柱上: ② 将 X 柱上最后一个盘子直接移到 Z 柱上; ③ 用 X 柱做过渡,将 Y 柱上的 (n-1) 个盘子移到 Z 柱上。

问题的解法是递归的问题的解法是递归的

Page 8: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,
Page 9: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

算法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 ); }}

Page 10: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

递归方法是设计非数值计算程序的重要方法,它使得程序的结构清晰,形式简洁,易于阅读,正确性容易证明。

一般地讲,一个问题采用递归算法求解时,须具备 3个条件。

(1) 能将一个问题转变成一个新问题,而新问题与原问题的解法相同或类同,所不同的仅是所处理的对象,且这些处理对象的变化是有规律的。

(2) 可以通过上述转化使问题逐步简单化。 (3) 必须有一个明确的递归出口 (递归的边界 )。

Page 11: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

递归过程及其实现递归过程及其实现 递归过程在实现时,需要自己调用自己。 层层向下递归,退出时的次序正好相反: 递归调用 n! (n-1)! (n-2)! 1! 0!=1 返回次序

递归函数运行的“层次”

Page 12: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

递归工作栈递归工作栈 函数嵌套调用时的“后调用先返回”原则 递归函数调用也是一种嵌套调用;每一次递归调用时,也需要分配存储空间(工作区)来存储参数、局部变量、返回地址等信息。

每层递归调用需分配的空间形成递归工作记录,按后进先出 (LIFO) 的栈组织。

局部变量返回地址参 数

活动记录框架

递归工作记录

Page 13: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

函数递归时的活动记录函数递归时的活动记录

……………….< 下一条指令 >

Function(< 参数表 >) ……………….<return>

调用块

函数块

返回地址返回地址 (( 下一条指令下一条指令 ) ) 局部变量 参数局部变量 参数

Page 14: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

递归过程改为非递归过程递归过程改为非递归过程 递归过程简洁、易编、易懂 递归过程效率低,重复计算多 改为非递归过程的目的是提高效率 单向递归和尾递归可直接用迭代实现其非递归过程

其他情形必须借助栈实现非递归过程

Page 15: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

计算斐波那契数列的函数 Fib(n) 的定义

求解斐波那契数列的递归算法 long Fib ( long n ) { if ( n <= 1 ) return n; else return Fib (n-1) + Fib (n-2); }

1n2),Fib(n1)Fib(n

0,1nn,)Fib(n

如 F0 = 0, F1 = 1, F2 = 1, F3 = 2, F4 = 3, F5 = 5

Page 16: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

斐波那契数列的递归调用树斐波那契数列的递归调用树

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)

Page 17: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

Fib(1) Fib(0)

Fib(2) Fib(1) Fib(0)

Fib(2)

Fib(1)

Fib(3)

Fib(4)

栈结点栈结点 n tag

tag = 1, 向左递归; tag = 2, 向右递归

Page 18: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

计算斐波那契数列的递推算法计算斐波那契数列的递推算法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)

Page 19: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

队列 (Queue) 定义 : 只允许在表的一端进行插入,而在另一端删除元素的线性表。

在队列中,允许插入的一端叫队尾( rear ),允许删除的一端称为队头 (front) 。

特点:先进先出 (FIFO)

a1 , a2 , a3 ,…, an

出队列 入队列

队头

队尾

Page 20: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

链队列:队列的链式表示

链队列中,有两个分别指示队头和队尾的指针。 链式队列在进队时无队满问题,但有队空问题。

data next

front

rear

Page 21: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

frontrear ^

空队列

frontrear

x ^ 元素 x 入队

frontrear

x ^ y ^ 元素 y 入队

frontrear

x ^^ y 元素 x 出队

^

Page 22: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

typedef int QElemType;

typedef struct Qnode { QElemType data; // 队列结点数据 struct Qnode *next; // 结点链指针} Qnode,*QueuePtr;

typedef struct { QueuePtr rear, front;} LinkQueue;

链式队列的定义

Page 23: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

链队列的主要操作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())

Page 24: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

入队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;}

Page 25: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

出队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;}

Page 26: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

循环队列 (Circular Queue)

顺序队列—队列的顺序存储表示 插入新的队尾元素,尾指针增 1 , rear = rear + 1 ,

删除队头元素,头指针增 1 , front = front + 1 , 因此,在非空队列中,头指针始终指向队列头元素,而尾指针始终指向队列尾元素的下一个位置。

队满时再进队将溢出 假溢出(图 3.12) 解决办法:将顺序队列臆造为一个环状的空间,形成循环 ( 环形 ) 队列

Page 27: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

队列的进队和出队

front rear 空队列 front rearA,B,C, D 进队

A B C D

front rearA,B 出队

C D

front rearE,F,G 进队

C D E F G

C D E F G

front rear

H 进队 , 溢出

Page 28: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

循环队列 (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

6 7

循环队列front

rear

Maxsize-1

Page 29: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

0

1

23

4

5

6 7front

BCD

rear

一般情况

A0

1

23

4

5

6 7 rear

空队列

front

队满条件: (rear+1) % maxsize == front

C0

1

23

4

5

6 7front

rear

DE

FG

A

BC

队满

C0

1

23

4

5

6 7front

rear

DE

FG

A

BC

H

Page 30: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

#define MAXSIZE 100Typedef struct{

QElemType *base;int front;int rear;

} SqQueue;

循环队列的类型定义

Page 31: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

循环队列操作的实现循环队列操作的实现

初始化队列Status InitQueue ( SqQueue &Q ) {// 构造空队列 Q.base=(QElemType *) malloc (MAXSIZE*sizeof(QElemType)); if (! Q.base) exit(OVERFLOW); Q.rear = Q.front = 0; return OK;}

Page 32: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

入队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;}

Page 33: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

出队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;

}

Page 34: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

队列的应用队列的应用 ------ 离散事件模拟离散事件模拟 银行业务模拟程序 模拟银行业务活动并计算一天中客户在银行平均逗留时间 事件驱动模拟—按事件(客户到达或客户离开)发生的先后

顺序进行处理 。

主要的数据结构: 一个事件表(有序链表) -------- 用来记录待处理的事件 n 个队列 ------ 用来对应 N 个窗口的客户队列

Page 35: 递 归 (recursion) 定义 : 若一个对象部分地包含它自己, 或用 它自己给自己定义, 则称这个对象是递归的; 若一个过程直接地或间接地调用自己,

typedef struct{ int OccurTime; int Ntype;} Event,ElemType; // 事件类型

typedef LinkList EventList // 事件链表类型

Type struct{ int ArrivalTime; int Duration;} QElemType; // 队列元素类型