首页 > 代码库 > 《数据结构与算法分析:C语言描述》复习——第十章“算法设计技巧”——Alpha-Beta剪枝
《数据结构与算法分析:C语言描述》复习——第十章“算法设计技巧”——Alpha-Beta剪枝
2014.07.08 22:43
简介:
“搜索”与“剪枝”几乎是如影随形的。此处的“搜索”指的是带有回溯算法的深度优先搜索。
在之前的“Minimax策略”中我们给出了一个三连棋的程序,运行后你就知道计算一步棋要花多少时间。
为了计算最优的一步棋,我们可能需要递归9万多次。如果毫无疑问这种阶乘式的穷举过程必须通过剪枝来加速。
本篇介绍一种用于Minimax策略的剪枝思路——α-β剪枝。
剪枝的英语是pruning,所以不要想当然说成trimming。
图示:
在上一篇讲解Minimax策略的博文中我们给出了一棵博弈树:
如果按照这棵树的形状来看,延伸下去应该会造成指数级的时间增长。
那么如果我们想做到“全知全能”,就得在下第一步棋的时候穷举所有可能的结局。因此你走一步棋就得推理9万多次。
按照这种脑力消耗,就是诸葛亮也吃不消,于是我们有两种办法可以偷懒:
1. 截断:我的脑容量没那么大,所以我只考虑三步棋以内的结果。三步棋之外的结果,姑且不管了。
2. 剪枝:有些结局实际上根本不需要分析,因为我们有充分的理由提前放弃那条路径。
本次不考虑截断的问题,只谈剪枝。
我们将刚才那棵博弈树中的两个结点标记为红色:
此处我们强调两条性质:
1. 对于Min层的每个节点,它的任何子节点的值都不小于它自身。
2. 对于Max层的每个节点,它的任何子节点的值都不大于它自身。
这棵树中78和36两个结点下方的节点不会被访问到,因为没有必要。
注意78所处的是Min层,对手为了使我的利益最小化,会尽力找出最小值。而68比78小,因此在78的下方不可能找到小于68的节点。所以没有必要继续深入78中。
36的道理相同,只不过处于Max层,所以解释自然是相反的。
在每一层都可能会有这样的节点,因此随时留意这样的剪枝机会,能极大地提高效率。
如果我没记错,经过这种剪枝的程序最多只需要递归一百多次,和9万多次相比确实快了不少。
因为Min和Max层交替出现,因此这种剪枝也需要两套代码逻辑,分别称为α剪枝与β剪枝。二者合称α-β剪枝。
教材中只给出了其中一个剪枝的伪代码,如果你没想明白,肯定写不出另一个的。
接下来请运行代码吧,看看效率上的差异。不过可能的话,我还是建议你在不看代码的情况下,自己修改三连棋的程序来实现α-β剪枝。
别忘了确保程序的正确性:如果你赢了电脑,那程序肯定是错的。
(提问:对于一个没有必胜策略的游戏,完美的AI是无法打败的,你只能打平手或者输给它,是这样吗?)
实现:
1 // Optimization for Minimax game strategy, using Alpha-Beta Pruning. 2 // You can watch over the ‘function_call_count‘ variable. 3 #include <iostream> 4 #include <vector> 5 using namespace std; 6 7 int function_call_count; 8 9 bool computerWin(const vector<int> &board) 10 { 11 int i, j; 12 13 for (i = 0; i < 3; ++i) { 14 for (j = 0; j < 3; ++j) { 15 if (board[i * 3 + j] != -1) { 16 break; 17 } 18 } 19 if (j == 3) { 20 return true; 21 } 22 } 23 24 for (i = 0; i < 3; ++i) { 25 for (j = 0; j < 3; ++j) { 26 if (board[j * 3 + i] != -1) { 27 break; 28 } 29 } 30 if (j == 3) { 31 return true; 32 } 33 } 34 35 if (board[0] == board[4] && board[4] == board[8] && board[8] == -1) { 36 return true; 37 } 38 39 if (board[2] == board[4] && board[4] == board[6] && board[6] == -1) { 40 return true; 41 } 42 43 return false; 44 } 45 46 bool humanWin(const vector<int> &board) 47 { 48 int i, j; 49 50 for (i = 0; i < 3; ++i) { 51 for (j = 0; j < 3; ++j) { 52 if (board[i * 3 + j] != 1) { 53 break; 54 } 55 } 56 if (j == 3) { 57 return true; 58 } 59 } 60 61 for (i = 0; i < 3; ++i) { 62 for (j = 0; j < 3; ++j) { 63 if (board[j * 3 + i] != 1) { 64 break; 65 } 66 } 67 if (j == 3) { 68 return true; 69 } 70 } 71 72 if (board[0] == board[4] && board[4] == board[8] && board[8] == 1) { 73 return true; 74 } 75 76 if (board[2] == board[4] && board[4] == board[6] && board[6] == 1) { 77 return true; 78 } 79 80 return false; 81 } 82 83 bool fullBoard(const vector<int> &board) 84 { 85 for (int i = 0; i < 9; ++i) { 86 if (board[i] == 0) { 87 return false; 88 } 89 } 90 91 return true; 92 } 93 94 void findComputerMove(vector<int> &board, int &best_move, int &result, 95 int alpha, int beta) 96 { 97 void findHumanMove(vector<int> &, int &, int &, int, int); 98 int dc, i, response; 99 100 ++function_call_count;101 best_move = -1;102 103 if (fullBoard(board)) {104 result = 0;105 return;106 }107 108 if (computerWin(board)) {109 result = -1;110 return;111 }112 113 result = alpha;114 for (i = 0; i < 9 && result > beta; ++i) {115 if (board[i] != 0) {116 continue;117 }118 board[i] = -1;119 findHumanMove(board, dc, response, result, beta);120 board[i] = 0;121 122 if (best_move == -1 || response < result) {123 result = response;124 best_move = i;125 }126 }127 }128 129 void findHumanMove(vector<int> &board, int &best_move, int &result, int alpha, 130 int beta)131 {132 void findComputerMove(vector<int> &, int &, int &, int, int);133 int dc, i, response;134 135 ++function_call_count;136 best_move = -1;137 138 if (fullBoard(board)) {139 result = 0;140 return;141 }142 143 if (humanWin(board)) {144 result = 1;145 return;146 }147 148 result = beta;149 for (i = 0; i < 9 && result < alpha; ++i) {150 if (board[i] != 0) {151 continue;152 }153 board[i] = 1;154 findComputerMove(board, dc, response, alpha, result);155 board[i] = 0;156 157 if (best_move == -1 || response > result) {158 result = response;159 best_move = i;160 }161 }162 }163 164 void printBoard(const vector<int> &board)165 {166 cout << " 1 2 3" << endl;167 int i, j;168 169 for (i = 0; i < 3; ++i) {170 cout << i + 1;171 for (j = 0; j < 3; ++j) {172 cout << ‘ ‘;173 switch(board[i * 3 + j]) {174 case -1:175 cout << ‘X‘;176 break;177 case 0:178 cout << ‘.‘;179 break;180 case 1:181 cout << ‘O‘;182 break;183 }184 }185 cout << endl;186 }187 }188 189 int main()190 {191 vector<int> board;192 int n;193 int result;194 195 board.resize(9, 0);196 while (cin >> n) {197 if (n < 0 || n >= 9 || board[n]) {198 cout << "Invalid move" << endl;199 continue;200 }201 202 board[n] = 1;203 printBoard(board);204 if (humanWin(board)) {205 cout << "You win." << endl;206 break;207 }208 209 if (fullBoard(board)) {210 cout << "Draw." << endl;211 break;212 }213 214 result = 1;215 function_call_count = 0;216 findComputerMove(board, n, result, 1, -1);217 cout << "Number of function calls: " << function_call_count << endl;218 board[n] = -1;219 printBoard(board);220 if (computerWin(board)) {221 cout << "Computer win." << endl;222 break;223 }224 225 if (fullBoard(board)) {226 cout << "Draw." << endl;227 break;228 }229 }230 231 return 0;232 }