1. 函数调用栈的回顾
(1)用于保存函数中的实参、局部变量、临时变量等。
(2)从起始地址开始往一个方向增长(如:高地址→低地址)
(3)有一个专用“指针”标识当前已使用内存的“顶部”
(4)当函数调用结束时,栈会恢复到被调用前的状态。可以利用这个时机进行一些的回溯算法的设计。
【实例分析】函数调用栈分析:逆序打印单链表中的偶数结点(void r_print_even(Node* list)),见本课后面的源码
2. 回溯求解八皇后问题
(1)回溯算法:实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。八皇后问题就是回溯算法的典型,第一步按照顺序放一个皇后,然后第二步符合要求放第2个皇后,如果没有位置符合要求,那么就要改变第一个皇后的位置,再重新放第2个皇后的位置,直到找到符合条件的位置,以此方法放其他皇后…
(2)八皇后问题
①在一个8×8的国际象棋棋盘上,有8个皇后,每个皇后占一格;
②要求皇后间不会出现相互“攻击”的现象(不能有两个皇后处在同一行、 同一列或同一对角线上)
(3)关键数据结构定义
①棋盘:二维数组(8 × 8):0表示位置为空,1表示皇后。
②位置:struct Pos; 其中的x字段表示行,y表示列。
③方向:
水平:向左(-1,0),向右(1,0)
垂直:向上(0,1),向下(0,-1)
对角线:左上(-1,1),左下(-1,-1),右上(1,1),右下(1,-1)
(3)算法思路:
①从第一行开始,为皇后找到安全位置,然后跳到下一行,通过递归进行深度搜索。
②如果在第n行出现死胡同,如果该行为第一行,棋局失败,否则后退到上一行,进行回溯,并从该行的下一列位置继续查找安全位置。
③如果在第8行上找到了安全位置,则棋局成功。
【编程实验】八皇后问题的递归解法
#include <iostream> #include "LinkList.h" using namespace std; using namespace DTLib; struct Node { int value; Node* next; }; //创建无表头结点的单链表(己填充数据):v结点的值,len链表长度 Node* create_list(int v, int len) { Node* ret = NULL; Node* slider = NULL; for(int i=0; i<len; i++){ Node* node = new Node(); node->value = v++; node->next = NULL; //每创建好一个节点,slider指向这个节点 if( slider == NULL ){ slider = node; ret = node; }else{ slider->next = node; slider = node; } } return ret; } void destroy_list(Node* list) { while(list){ Node* del = list; list = list->next; delete del; } } //打印链表的内容 void print_list(Node* list) { while(list){ cout << list->value << "->"; list = list->next; } cout << "NULL" << endl; } //逆序打印单链表中的偶数结点 void r_print_even(Node* list) { if(list != NULL){ //深度搜索,直到最后一个节点 r_print_even(list->next); //回溯算法设计:上一行递归结束,开始回溯并判断节点的值是否为偶数 if(list->value % 2 == 0){ cout << list->value << " "; } } } template<int N> class QueenSolution : public Object { protected: struct Pos : public Object { int x; int y; Pos(int px = 0, int py = 0):x(px),y(py){} }; int m_chessboard[N][N]; //棋盘: 原点0,0在左下角,x轴正方向向右,y轴正方向向上 Pos m_direction[3]; //三个方向向量 LinkList<Pos> m_solution; int m_count; void init() { m_count = 0; //初始化棋盘 for(int i=0; i<N; i++){ for (int j=0; j<N; j++){ m_chessboard[i][j] = 0; } } //初始化方向矢量 //左下角 m_direction[0].x =-1; m_direction[0].y =-1; //向下 m_direction[1].x =0; m_direction[1].y =-1; //右下角 m_direction[2].x =1; m_direction[2].y =-1; } //打印棋盘内容 void print() { typename LinkList<Pos>::iterator iter = m_solution.begin(); while(iter != m_solution.end()){ cout << "(" << (*iter).x <<"," <<(*iter).y <<") "; iter++; } cout << endl; for(int y=0; y<N; y++){ for (int x=0; x<N; x++){ switch (m_chessboard[x][y]) { case 0:cout << " ."; break; case 1:cout << " #"; break; } } cout << endl; } cout << endl; } bool isInner(int x, int y) { return (0<=x) && (x<N) && (0<=y) && (y<N); } //检测在棋盘的(x,y)位置是否可以放置皇后 bool check(int x, int y, int d) { bool ret = true; while(ret && isInner(x, y)) { ret = ret && (m_chessboard[x][y] == 0); x += m_direction[d].x; y += m_direction[d].y; } return ret; } //在第row行尝试放置皇后 void place(int row) { int y = row; if(row < N){ for(int x=0; x<N; x++){ if(check(x, y, 0) && check(x, y, 1) && check(x, y, 2)){ m_chessboard[x][y] = 1; //放置皇后 m_solution.insert(Pos(x,y)); //将该位置加入链表 place(row + 1); //在下一行放置皇后。 //当place(row+1)函数返回时,表示第row+1行无法找到放置皇后的地方(或找到解决方案后正常返回) m_chessboard[x][y] = 0; //回溯,将当前位置的皇后移除。 m_solution.remove(m_solution.length() - 1); } } }else{ //由于采用深度优先搜索,所以放置到第SIZE行时,表示己经找到解决方案 m_count++; //每找到一行方案,m_count加1。 print(); //打印解决方案 } } public: QueenSolution(){init();} void solve() { place(0); //从第0行开始放置 cout << "total : " << m_count << endl; } }; int main() { //测试:逆序打印单链表中的偶数结点 Node* list = create_list(1, 10); print_list(list); r_print_even(list); destroy_list(list); cout << endl; //测试:八皇后解决方案 QueenSolution<8> qs; qs.solve(); return 0; } /*测试结果: 1->2->3->4->5->6->7->8->9->10->NULL 10 8 6 4 2 (0,0) (4,1) (7,2) (5,3) (2,4) (6,5) (1,6) (3,7) //显示时,使用的坐标系(原点在左上角,x向右为正,y向下为正) # . . . . . . . . . . . # . . . . . . . . . . # . . . . . # . . . . # . . . . . . . . . . . # . . # . . . . . . . . . # . . . . (0,0) (5,1) (7,2) (2,3) (6,4) (3,5) (1,6) (4,7) # . . . . . . . . . . . . # . . . . . . . . . # . . # . . . . . . . . . . . # . . . . # . . . . . # . . . . . . . . . . # . . . //省略其余解决方案... total : 92 */
3. 小结
(1)程序运行后的栈存储区专供函数调用使用
(2)栈存储区用于保存实参、局部变量和临时变量等。
(3)利用栈存储区能够方便的实现回溯算法
(4)八皇后问题是栈回溯的经典应用。