• 《数据结构与算法分析: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 }
  • 相关阅读:
    class 类添加属性
    Ajax请求数据data被JSON.stringify()的数据django解码过程
    Python之浏览器的前进或后退
    滚动条-智能等待-富文本
    Python之阶乘代码
    Python之用型号构成一个三角形代码
    C# Dictionary
    使用C#创建Windows服务
    sql取出每月最早的上报的数据
    mvc NPOI导入读取Excel文件
  • 原文地址:https://www.cnblogs.com/zhuli19901106/p/3832669.html
Copyright © 2020-2023  润新知