数据结构C++语言描述专题系列 (五) 递归
2015-12-16 10:34
1011 查看
1、递归的概念
递归在计算机科学和数学中是一个很重要的工具,计算机工作者用来定义句法、解决表和树形结构的搜索等问题。数学家在研究组合问题时也经常要用到递归。在数学上,一个正整数的阶乘可以用以下公式进行定义:
n! = n×(n-1)× ••• ×1,
幂函数运算可以用如下公式进行定义:
xn=x×x×∙∙∙×xx^n = x × x × ••• × x,
这种方法作为阶乘函数和幂函数运算的定义不是很严密。
正规的阶乘定义应为
1 if n = 0 n! = n ×(n-1)! if n >0
幂函数运算的定义
1 if n = 0 xn = x × xn-1 if n >0
递归的定义
若一个对象部分地包含它自己,或用它自己给自己下定义,称这
个对象是递归的;若一个过程直接地或间接地调用自己,则称这
个过程是递归的过程。
使用递归方法的情况
⑴定义是递归的;
阶乘函数的计算,由于是递归函数,用递归过程来求解。 4! = 4 ×3! = 4 ×(3 ×2!) = 4 ×(3 ×(2 ×1!)) = 4 ×(3 ×(2×(1×0!))) = 4 ×(3 ×(2×(1×1))) = 4 ×(3 ×(2×1)) = 4 ×(3 ×2) = 4 ×6 = 24
⑵数据结构是递归的;
单链表:
①一个结点,其指针域为NULL,是一个单链表;
②一个结点,其指针域指向一个单链表,仍是一个单链表。
广义表等。
⑶问题的解法是递归的。
如 Hanoi 塔、 game tree问题等。
2、递归函数的设计
每一个递归过程(recursive process)由两部分组成:⑴一个最小的基础情况,它不再需用递归来处理;
⑵一个通用的方法(又称递归步骤),它根据某种规律把问题简化成一个或几个较小的问题。这一步骤应该导致过程的最终终止的方向发展。
使用 if– else 语句,if 语句块判断递归结束的条件,处理这个最小的基础情况,else语句块处理递归的情况,或这个通用的方法。
幂函数计算
1 if n = 0 n! = n ×(n-1)! if n >0
int fractorial (int n) { if (n = = 0) return 1 else return n *fractorial(n-1) }
递归函数的设计
Hanoi塔问题
结束条件:只有一块盘子,将这一盘子直接送到C柱
递归过程:将A柱上上面的 n – 1 个盘子送到B柱,
直接把A柱上最后一个盘子移到C柱,
将B柱上 n – 1 个盘子移到C柱。
void move (int n, int A, int C, int B) { if (n = = 1) cout<<“move”<<A<<“to”<<C<<endl else{ move( n – 1, A, B, C); cout<<“move”<<A<<“to”<<C<<endl; move( n – 1, B, C, A); } }
注意 if – else 这种形式的变形。
Hanoi塔的算法
Const int disks = 64; Void move ( int count, int start, int finish, int temp); /* Pre: None. Post: The simulation of the Towers of Hanoi has terminated. */ Main ( ) { move( disks, 1, 3, 2); } Void move( int count, int start, int finish, int temp) { if (count>0) { move( count – 1, start, temp, finish); cout <<“Move disk”<< count << “from” << start << “to” << finish << “.” <<endl; move( count – 1, temp, finish, start,); } }
一般过程调用与递归过程
通常,当一个过程在运行期间调用另一个过程时,在运行被调用过程之前,系统需先完成三件事:
⑴将所有的实在参数,返回地址等信息传递给被调用过程保存;
⑵为被调用过程的局部变量分配存储区;
⑶将控制转移到被调用过程的入口。
在从被调用过程返回调用过程之前,系统也应完成三件事:
⑴保存被调用过程的计算结果;
⑵释放被调用过程数据存储区;
⑶按照被调用过程保存的返回地址将控制转移到调用过程。
一般主程序调用一个过程是外部调用,其他调用都属于内部调用,外部调用结束后,将返回调用该过程的主程序,而内部调用结束后,将返回到本次调用语句的后继语句处。
递归过程的运行过程类似于多个过程的嵌套调用,只是调用过程和被调用是同一个过程,这样,“层次”概念就显得十分重要。
3、递归过程与递归工作栈
活动记录( activation record )为确保递归过程的每次调用和返回的正确执行,在每次递归过程调用前,必须做参数保存、参数传递等工作。这一切是通过一个递归工作栈来进行处理的,每一层递归调用所需保存的信息构成一个工作记录,称之为活动记录,包括如下内容:
⑴返回地址:即上一层中本次调用自己的语句的后继语句处;
⑵在本次过程调用时,与形参结合的实参值;
⑶本层的局部变量值。
2个盘的Hanoi塔的调用示意图
3、递归过程与递归工作栈(例)
下图反映了阶乘函数计算函数调用过程
4! = 4 ×3! = 4 ×(3 ×2!) = 4 ×(3 ×(2 ×1!)) = 4 ×(3 ×(2×(1×0!))) = 4 ×(3 ×(2×(1×1))) = 4 ×(3 ×(2×1)) = 4 ×(3 ×2) = 4 ×6 = 24
递归工作栈的例
2个盘的Hanoi塔的递归工作栈示意图
分析递归过程的工具
递归树
递归算法设计的原则
为了设计一个递归算法,通常,先考虑几个较为简单的例子,待对问题解法有了比较正确的理解后,应规划一个更有通用性的解法,以下几点是应该在设计算法时予以注意的:
⑴确定关键步骤( Find the key step. )
⑵找到终止条件( Find a stopping rule. )
⑶拟订算法大纲( Outline your algorithm. )
⑷确认终止条件(Check termination. )
⑸构画递归树( Draw a recursion tree. )
4、递归过程的非递归化
用单纯的循环方式非递归化阶乘的非递归算法
int factorial (int n) /* factorial: iterative version Pre: n is a nonnegative integer. Post: Return the value of the factorial of n */ { int count, product= 1; for (count = 1; count< = n; count+ +) product﹡=count; return product; }
Fibonacci数列
Fibonacci 数列的定义
0 若n = 0 F(n) = 1 若n = 1 F(n –1)+F(n – 2) 若n≧2
终止条件:F(0) =0 或 F(1)=1
递归步骤: F(n) = F(n –1)+F(n – 2)
int fibonacci(int n) /* fibonacci: recursive version Pre: The parameter n is a nonnegative integer. Post: The function returns the nth Fibonacci number. */ { if (n <= 0) return 0; else if (n = = 1) return 1; else return fibonacci(n-1)+fibonacci(n – 2) }
递归过程的非递归化
Fibonacci数的递归树
迭代法计算Fibonacci数列
int fibonacci(int n) /* fibonacci: iterative version Pre: The parameter n is a nonnegative integer. Post: The function returns the nth Fibonacci number. */ { int last_but_one; // second previous Fibonacci number, F i-2 int last_value; // previous Fibonacci number, F i-1 int current; // current Fibonacci number F, if (n <= 0) return 0; else if (n == 1) return 1; else { last_but_one = 0; last_value = 1; for (int i = 2; i <= n; i++) { current = last_but_one + last_value; last_but_one = last_value; last_value = current; } return current; } }
尾递归
先分析一般的调用的情况
尾递归及其图示意
函数最后一句执行的语句是递归调用,称为尾递归。
消除尾递归的 Hanoi 塔算法
void move (int count, int start, int finish, int temp) /* move: iterative version. Pre: Disk count is a valid disk to be moved. Post: Moves count disks from start to finish using temp for temporary storage. */ { int swap; // temporary storage to swap towers while (count > 0 ) { // Replace the if statement with a loop. move (count-1, start, temp, finish); // first recursive call cout <<“Move disk”<< count << “from”<< start << <<“to”<< finish <<“.”<< endl; count - -; // Change parameters to mimic the swap = start; // second recursive call. start = temp; temp = swap; } }
回溯法(Backtracking)
回溯法又称为“通用的解题法”。用它可以系统地搜索一个问题的所有解:构造局部解(partial solutions),并在满足问题要求的前提下,扩大这个局部解,直至问题得到解决;如果在扩大过程中,局部解与问题要求一致性得不到保证,就应该返回,称之为回溯( backtracks ),并要删除所有得最近构造的局部解,然后寻找另外的可能解。如果把每一个解(包括局部解)视作结点,按照形成的过程,组成一个树状的解空间,称为解空间树。回溯法就是以这棵解空间树为基础,根结点为开始结点,用深度优先的方式搜索的过程。搜索开始时,这个开始结点成为一个活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点成为一个新的活结点,并成为当前扩展结点。如果在当前扩展结点处不能再向纵深方向移动,则当前的扩展结点就成为死结点。此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。回溯法就是以这种工作方式递归地再解空间树中搜索,直至找到所要求的解或解空间中已无活结点时为止。
回溯法的算法框架
由于回溯法是对解空间的深度优先搜索,一般情况可用递归函数来实现回溯法。
void Backtrack( int t) { /* 本算法是对递归回溯方法框架式的描述(或抽象算法) if ( t > n ) Output(x); else for (int i = f( n, t); i <= g( n, t ); i + +) { x[t] = h (i); if (Constraint(t) && Bound(t)) Backtrack(t + 1); } }
形式参数 t 表示递归深度,即当前扩展结点在解空间树中的深度;
n 是解空间树的高度;x 表示得到的一个可行解;
f( n, t) 和 g( n, t ) 分别表示在当前扩展结点处未搜索过的子树的起始编号和终止编号。 h (i)表示在当前扩展结点处x[t]的第i个可选值。
Constraint(t) 和 Bound(t) 分别表示在当前扩展结点处的约束函数和限界函数。
八皇后问题的算法框架
solve_from (Queens configuration) if Queens configuration already contains eight queens print configuration else for every chessboard square p that is unguarded by configuration { add a queen on square p to configuration; solve_from(configuration); remove the queen from square p of configuration; };
四皇后的解法示意
Queens 类 二个数据成员:board_size,count 前者表示问题的规模,后者不仅表示已有的皇后数,而且表示第一个未被盘上皇后占用的行的行号。主要的方法有unguarded、insert、remove、is_solved。
变量configuration 表示了问题的部分解,初始化时为空。
八皇后问题的总体算法
int main( ) /* Pre: The user enters a valid board size. Post: All solutions to the n-queens puzzle for the selected board size are printed. Uses:The class Queens and the recursive function solve_from.*/ { int board_size; print_information( ); cout <<“What is the size of the board?”<< flush; cin >> board_size; if (board_size<0 ‖board_size > max_board) cout<< “The number must be between 0 and “<<max_board<<endl; else{ Queens configuration(board_size); // 初始化configuration solve_from(configuration); // 从configuration 扩展所有解 } }
八皇后问题的抽象算法
void solve_from(Queens &configuration) /* Pre: The Queens configuration represents a partially completed arrangement of nonattacking queens on a chessboard. Post: All n-queens solutions that extend the given configuration are printed. The configuration is restored to its initial state. Uses: The class Queens and the function solve_from, recursively. */ { if (configuration.is_solved( )) configuration.print( ); else for (int col = 0; col < configuration. board_size; col+ +) if (configuration.unguarded(col)) { Configuration.insert(col); solve_from(configuration); // 递归调用增加新的皇后 configuration. remove(col); } }
八皇后问题(以二维数组为存储结构)
const int max_board = 30; ! class Queens { public: Queens(int size); bool is_solved( ) const; void print( ) const; bool unguarded(int col) const; void insert(int col); void remove(int col); int board_size; // dimension of board = maximum number of queens. private: int count; // current number of queens = first unoccupied row. bool queen_square[max_board] [max_board]; };
八皇后的构造函数与插入运算
初始化一个configuration 和 插入运算
Queens:: Queens(int size) /* Post: The Queens object is set up as an empty configuration on a chessboard with size squares in each row and column. “ */ { board_size = size; count = 0; for ( int row = 0; row < board_size; row+ +) for (int col = 0; col < board_size; col+ +) queen_square[row] [col] = false; } void Queens:: insert(int col) /* Pre: The square in the first unoccupied row (row count) and column col is not guarded by any queen. Post: A queen has been inserted into the square at row count and column col; count has been incremented by 1. */ { queen_square [count + +] [col] = true; }
互相攻击的判断
按行、列、对角线判断
互相攻击的判断(算法)
boil Queens:: unguarded(int col) const /* Post: Returns true or false according as the square in the first unoccupied row(row count,) and column col is not guarded by any queen. */ { int i; bool ok = true; // 若在列或对角线上发现皇后返回 false for (i = 0; ok && i < count; i + +) ok = ! queen_square [i] [col]; // 检查列的上部 // queen_square的初值为false,对ok赋值要求反 for(i = 1; ok && count - i >=0&&col - i >= 0; i+ +) ok = !queen_square [count - i] [col - i]; // 检查左上部分对角线 for (i = 1; ok && count - i >= 0&&col + i < board_size; i+ +) ok = !queen_square [count - i] [col + i]; // 检查右上部分对角线 return ok; }
修改后的八皇后问题
class Queens { public: Queens(int size); bool is_solved( ) const; void print( ) const; bool unguarded(int col) const; void insert(int col); void remove(int col); int board_size; private: ' int count; bool col_free[max_board]; ; bool upward_free [2 * max_board - 1]; bool downward_free[2 * max_board - 1]; int queen_in_row[max_board]; // 每行中皇后的列号 };
修改后的八皇后构造函数
Queens:: Queens(int size) /* Post: The Queens object is set up as an empty configuration on a chessboard with size squares in each row and column. */ { board_size = size; count = 0; for (int i = 0; i < board_size; i+ +) col_free[i] = true; for (int j = 0; j < (2 * board_size - 1); j+ +) upward_free[j] = true; for (int k = 0; k < (2 * board_size - 1); k+ +) downward_free[k] = true; }
修改后的八皇后部分算法
void Queens:: insert(int col) /* Pre: The square in the first unoccupied row (row count) and column col is not guarded by any queen. Post: A queen has been inserted into the square at row count and column col; count has been incremented by 1. */ { queen_in_row [count] = col; col_f ree [col] = false; upward_free [count + col] = false; downward_free [count - col + board_size - 1] = false; count + +; }
修改后的八皇后部分算法
相互攻击的判断
bool Queens:: unguarded(int col) const { return col_free(col) && upward_free[count + col] && downward_free[count - col + board_size - 1]; }
八皇后的递归树
竞赛树
竞赛树
竞赛树
class Board { public: Board( ); // constructor for initialization int done( ) const; // Test whether the game is over. void play(Move try_it); int evaluate( ) const; int legal_moves(Stack &moves) const; int worst_case( ) const; int better(int value, int old_value) const; // Which parameter does the mover pre void print( ) const; void instructions( ) const; /* Additional methods, functions, and data will depend on the game l sideration. */ }; int look_ahead(const Board &game, int depth,Move &recommended) /* Pre: Board game represents a legal game position. Post: An evaluation of the game, based on looking ahead depth moves, is returned. The best move that can be found for the mover is recorded as Move recommended. Uses: The classes Stack, Board, and Move, together with function look_ahead recursively. */ { If (game.done( ) || depth == 0) return game.evaluated( ); else{ Stack moves; game.legal_moves(moves); int value, best_value = game.worst_case( ); while (!moves.empty( )) { Move try_it, reply; moves.top(try_it); Board new_game = game; new_game.play(try_it); value = look_ahead(new_game, depth - 1, reply); if (game.better (value, best_value)) { // try_it is the best move yet best_value = value; // found recommended = try_it; } moves. pop( ); } return best_value; } }
持续更新中。。。
数据结构C++语言描述专题系列 (一) 绪论
数据结构C++语言描述专题系列 (二) 栈
数据结构C++语言描述专题系列 (三) 队列
数据结构C++语言描述专题系列 (四) 链式栈和队列
数据结构C++语言描述专题系列 (五) 递归
数据结构C++语言描述专题系列 (六) 表与串
数据结构C++语言描述专题系列 (七) 查找
数据结构C++语言描述专题系列 (八) 排序
数据结构C++语言描述专题系列 (九) 表与信息检索
数据结构C++语言描述专题系列 (十) 二叉树
数据结构C++语言描述专题系列 (十一) 多路数
数据结构C++语言描述专题系列 (十二) 集合及其表示
数据结构C++语言描述专题系列 (十三) 图
数据结构C++语言描述专题系列 (十四) 波兰表达式
相关文章推荐
- 直接插入排序,快速排序,选择排序给学生成绩排名
- 数据结构 上机测试题 汇总
- <LeetCode OJ> (1 / 15 / 16 / 18) NSum问题集合
- MySQL索引背后的数据结构及算法原理
- MySQL索引背后的数据结构及算法原理
- [ 1002 ] 考试笔记 数据结构 简代码与小小说
- 数据结构之堆排序
- 浅谈算法和数据结构: 十一 哈希表
- 浅谈算法和数据结构: 十二 无向图相关算法基础
- 浅谈算法和数据结构: 十 平衡查找树之B树
- 浅谈算法和数据结构: 九 平衡查找树之红黑树
- 浅谈算法和数据结构: 八 平衡查找树之2-3树
- 浅谈算法和数据结构: 七 二叉查找树
- 浅谈算法和数据结构: 六 符号表及其基本实现
- 浅谈算法和数据结构: 五 优先级队列与堆排序
- 浅谈算法和数据结构: 四 快速排序
- 浅谈算法和数据结构: 三 合并排序
- 浅谈算法和数据结构: 一 栈和队列
- 浅谈算法和数据结构: 二 基本排序算法
- 数据结构-栈