Alpha_Beta 剪枝

Tic-Tac-Toe算法笔记

 

这几天在用Python写Tic-Tac-Toe小游戏,顺便接触了一些简单的人机博弈算法,其实在算法方面我完全算是个新手,所以这也算是一个反复折腾学习的过程。而Tic-Tac-Toe应该算是人机博弈里最简单的应用了,最经典的算法是miniMax算法,也叫极大极小值算法,主要方法就是通过考虑双方博弈N步后,从所有可能的走法中选一步最佳的走法来走。

先简单说说算法的基本思想:
1. 设博弈双方中一方为MAX,另一方为MIN。然后为其中的一方(计算机)找一个最佳走法。

2. 为了找到当前棋局的最优走法,需要对各个可能的走法所产生的后续棋局进行比较,同时也要考虑对方可能的走法,并对后续棋局赋予一定的权值(或者称之为分数)。也就是说,以当前棋局为根节点生成一棵博弈树,N步后的棋局作为树的叶子节点。同时从树根开始轮流给每层结点赋予Max和Min的称号

3. 用一个评估函数来分析计算各个后续棋局(即叶子节点)的权值,估算出来的分数为静态估值

4. 当端节点的估值计算出来后,再推算出父节点的得分。推算的方法是:对于处于MAX层的节点,选其子节点中一个最大的得分作为父节点的得分,这是为了使自己在可供选择的方案中选一个对自己最有利的方案;对处于MIN层的节点,选其子节点中一个最小的得分作为父节点的得分,这是为了立足于最坏的情况,这样计算出的父节点的得分为倒推值。

5.如此反推至根节点下的第一层孩子,如果其中某个孩子能获得较大的倒推值,则它就是当前棋局最好的走法。
miniMax

 

上次的MiniMax算法虽然在效果上已经达到了预期的目的,即不可战胜的棋力,但在效率上仍然不够理想。电脑每次走步都得估计往下N层棋局的所有情况并估值,层数虽然可以控制,但在大棋局(如五子棋,象棋等)游戏中如此生成的博弈树分支叶子仍然相当庞大,由此有了在此基础上进行“剪枝”的改进算法–Alpha-beta剪枝算法(Alpha-Beta algorithms)。此算法主要优点在于其在边生成博弈树时候边计算评估各节点的倒推值,并且根据评估出的倒推值范围,及时停止扩展那些已无必要再扩展的子节点,即相当于剪去了博弈树上的一些分枝,从而节约了机器开销,提高了搜索效率。

 

还是简单说说算法的基本思想(再次声明此算法是建立在MiniMax算法基础上):
1. 对于一个MIN节点,若能估计出其倒推值的上确界Beta,并且这个Beta值不大于MIN的父节点(MAX节点)的估计倒推值的下确界Alpha,即Alpha≥Beta,则就不必再扩展该MIN节点的其余子节点了,因为这些节点的估值对MIN父节点的倒推值已无任何影响了,这一过程称为Alpha剪枝。
2. 对于一个MAX节点,若能估计出其倒推值的下确界Alpha,并且这个Alpha值不小于MAX的父节点(MIN节点)的估计倒推值的上确界Beta,即Alpha≥Beta,则就不必再扩展该MAX节点的其余子节点了,因为这些节点的估值对MAX父节点的倒推值已无任何影响了。这一过程称为Beta剪枝。
3. 一个MAX节点的Alpha值等于其后继节点当前最大的最终倒推值,一个MIN节点的Beta值等于其后继节点当前最小的最终倒推值

如果觉得还是有点难以理解(比如我第一次接触就觉得不知所云),看看下面的伪代码:

alpha-beta(player,board,alpha,beta)
    if(game over in current board position)
        return winner

    children = all legal moves for player from this board
    if(max's turn)
        for each child
            score = alpha-beta(other player,child,alpha,beta)
            (we have found a better best move....)
            if score > alpha then alpha = score
            (cut off...)
            if alpha >= beta then return alpha
        return alpha (this is our best move)
    else (min's turn)
        for each child
            score = alpha-beta(other player,child,alpha,beta)
            (opponent has found a better worse move.....)
            if score < beta then beta = score
            (cut off....)
            if alpha >= beta then return beta
        return beta (this is the opponent's best move)

简单的说,在MiniMax函数中我们已经知道,对于MIN节点,我们是要找其子节点的最小估值,如上面的代码,当min’s turn时候,我们先估值,如果 score < beta then beta = score,即是把MIN节点的孩子中的估值最小值给赋给Beta,这时候在判断Alpha是否大于等于Beta,Alpha是这个MIN节点的父亲节点的下界,即MAX节点,想一想,MAX应该是要找其子节点的最大估值,而它目前的下界Alpha都大于Beta了,那么可以遇见在将来这个MIN节点肯定不会被它的父亲MAX节点选取了,那么此时这个MIN节点可以停止继续展开孩子估值了,因为注定不会被选取,继续进行只是在浪费时间和资源。

 

上面讲的这个过程就好像两个人,其中一个人有几个口袋(即是几个MIN节点),每个口袋有几种东西,你(MAX)可以选取其中一个口袋,而无疑他会把你选取口袋里价值最低的物品给你(即MIN取最小值),而你为了获得最大收益只能判断这几个口袋所有的“最低价值物品”中那件还算是最有价值(即是MAX取最大值),MiniMax算法是你把对方所有口袋翻个遍再判断要哪个口袋,无疑这样效率不高。那么Alpha-Beta算法所做的就是:首先你先把第一个口袋里所以物品依次翻出来,假如第一个口袋里是车钥匙和手表,那么假如选这个口袋你只能得到手表,这个就是Beta了,因为此时还没有Alpha值,那么你把这个第一次获得的Beta再向上赋给Alpha,再翻第二个口袋,假如第一次翻出的是咸鱼(ew~~~),先赋给Beta,那么这个咸鱼Beta怎么说都比第一个口袋的表垃圾吧?!即此时手表Alpha >= 咸鱼Beta,因为对方只会把口袋价值最低的物品给你,这个口袋要么最低是咸鱼,要么还有比咸鱼给无语的东西,意味着这个口袋已经不值得在往下翻了,就算里面还有很多东西。

如果你把上面伪代码中的加粗部分去掉,那便是miniMax的算法,所以说这个算法其实是一个通过减少不必要的分支来节约时间资源的“砍枝”算法

 

http://hi.baidu.com/highsam/blog/item/5a70011381b70f8a6438db52.html

 

以下是一个简单的Alpha_beta剪枝井字棋C代码: #include <stdio.h> #define N 3 #define human 'O' #define computer 'X' #define empty ' ' int board[N][N]; void print_board() { int r, c; printf("\n"); for (r = 0; r < N; r++) { for (c = 0; c < N; c++) { printf("%c", board[r][c]); if (c != N - 1) printf("|"); } printf("\n"); if (r != N - 1) { for (c = 0; c < N; c++) printf("--"); printf("\n"); } } } int evaluate(char player) { int r, c; int score = 0; char opponent = (player == human) ? computer : human; // check rows for (r = 0; r < N; r++) { int player_count = 0, opponent_count = 0; for (c = 0; c < N; c++) { if (board[r][c] == player) player_count++; else if (board[r][c] == opponent) opponent_count++; } if (player_count == N) return 1000; if (opponent_count == N) return -1000; score += player_count * player_count; score -= opponent_count * opponent_count; } // check columns for (c = 0; c < N; c++) { int player_count = 0, opponent_count = 0; for (r = 0; r < N; r++) { if (board[r][c] == player) player_count++; else if (board[r][c] == opponent) opponent_count++; } if (player_count == N) return 1000; if (opponent_count == N) return -1000; score += player_count * player_count; score -= opponent_count * opponent_count; } // check diagonals int player_count = 0, opponent_count = 0; for (r = 0; r < N; r++) { if (board[r][r] == player) player_count++; else if (board[r][r] == opponent) opponent_count++; } if (player_count == N) return 1000; if (opponent_count == N) return -1000; score += player_count * player_count; score -= opponent_count * opponent_count; player_count = 0, opponent_count = 0; for (r = 0; r < N; r++) { if (board[r][N - r - 1] == player) player_count++; else if (board[r][N - r - 1] == opponent) opponent_count++; } if (player_count == N) return 1000; if (opponent_count == N) return -1000; score += player_count * player_count; score -= opponent_count * opponent_count; return score; } int alphabeta(int depth, int alpha, int beta, char player) { int r, c, score; if (depth == 0) return evaluate(player); char opponent = (player == human) ? computer : human; // maximize score if it's computer's turn if (player == computer) { score = alpha; for (r = 0; r < N; r++) { for (c = 0; c < N; c++) { if (board[r][c] == empty) { board[r][c] = computer; score = alphabeta(depth - 1, alpha, beta, opponent); board[r][c] = empty; if (score > alpha) alpha = score; if (beta <= alpha) return alpha; } } } return alpha; } // minimize score if it's human's turn else { score = beta; for (r = 0; r < N; r++) { for (c = 0; c < N; c++) { if (board[r][c] == empty) { board[r][c] = human; score = alphabeta(depth - 1, alpha, beta, opponent); board[r][c] = empty; if (score < beta) beta = score; if (beta <= alpha) return beta; } } } return beta; } } void computer_move() { int r, c; int max_score = -1000; char opponent = human; for (r = 0; r < N; r++) { for (c = 0; c < N; c++) { if (board[r][c] == empty) { board[r][c] = computer; int score = alphabeta(4, -1000, 1000, opponent); board[r][c] = empty; if (score > max_score) { max_score = score; printf("Computer moves to (%d, %d) with score %d\n", r, c, score); } } } } } int main() { int r, c; for (r = 0; r < N; r++) { for (c = 0; c < N; c++) { board[r][c] = empty; } } printf("Let's play Tic Tac Toe!\n"); print_board(); while (1) { printf("\nYour move:\n"); scanf("%d%d", &r, &c); if (r < 0 || r >= N || c < 0 || c >= N || board[r][c] != empty) { printf("Invalid move. Try again.\n"); continue; } board[r][c] = human; print_board(); int score = evaluate(human); if (score == 1000) { printf("You win!\n"); break; } else if (score == -1000) { printf("Computer wins!\n"); break; } else if (score == 0) { printf("Tie game!\n"); break; } printf("\nComputer's move:\n"); computer_move(); print_board(); score = evaluate(computer); if (score == 1000) { printf("Computer wins!\n"); break; } else if (score == -1000) { printf("You win!\n"); break; } else if (score == 0) { printf("Tie game!\n"); break; } } return 0; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值