算法设计——极大极小搜索
转载 http://blog.csdn.net/urecvbnkuhbh_54245df/article/details/5811060
极大极小搜索策略一般都是使用在一些博弈类的游戏之中:
这样策略本质上使用的是深度搜索策略,所以一般可以使用递归的方法来实现。在搜索过程中,对本方有利的搜索点上应该取极大值,而对本方不利的搜索点上应该取极小值。
极小值和极大值都是相对而言的。
在搜索过程中需要合理的控制搜索深度,搜索的深度越深,效率越低,但是一般来说,走法越好。
极大极小搜索可以分开写,也可以放在一起写。
主要的算法步骤如下:
输入:搜索的深度
输出:节点的最佳走法,及其对应的最佳估值
函数形式:int minMaxSearch(int depth ) 这里也可以添加int side参数表示当前谁是走棋方
如果轮到红方走棋,则
初始化最优值best = 负无穷大 //极大点 ,这里认为红方先走棋
否则
初始化最优值best = 正无穷大 //极小点
如果depth<= 0
调用评估函数值
否则
生成当前所有合理的走法
对每一步走法
执行走法
调用minMaxSearch(depth -1 ) , 并把值赋给value
撤销走法
如果轮到红方走棋,则
如果value > best
best = value
如果depth == MAX_DEPTH
bestMove = mv
否则
如果value < best
best = value
如果depth == MAX_DEPTH
bestMove = mv
返回best
在局面评估函数中一般返回双方优势的差值,以此作为评估值。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
这里总结一下负极大值搜索策略:
一个局面对红方的优势为X,那么对于黑方的优势就是-X;一个局面对红方的优势为-X,对黑方的优势就是X。在负极大值搜索算法中,没有了极小点,只有极大点。需要注意的是,局面对一方的优势转化为另一方的优势时需要加负号。局面估计区间是一个关于0点对称的区间:
[-MaxValue,MaxValue].需要注意的是,为了能使负极大值搜索算法得到正确的评价,必须修改局面评估函数的返回值,原来在极大极小搜索算法中始终返回的是红方的优势,现在要改为当前走棋方的优势。
负极大值搜索算法:
输入:搜索深度
输出:节点的最佳走法,及对应的最佳估值
函数形式:int negaMaxSearch(int depth)
初始化最优值best=负无穷大 //都是极大点
如果depth小于等于0
调用评估函数,并将结果赋给value
返回value值
否则
生成当前所有合法的走法
对于每一步走法
执行走法
value= -negaMaxSearch(depth-1) //注意函数之前有负号
撤销走法
如果 value> best
best=value
如果 depth == Max_Depth
bestMove=mv
返回best //返回某个搜索分支的最优评估值
评估函数的算法:
输入:棋局
输出:局面对当前走方的优势
rValue:红方的优势总和
bValue:黑方的优势总和
分别进行评估,具体问题具体设计,获得rValue和bValue的值
如果当前局面是红方走棋
return rValue-bValue;
否则
return bValue-rValue;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
极大极小搜索策略与负极大值搜索策略的改进——alpha-beta剪枝搜索策略
一般来说,为了在搜索过程中引起剪枝,在递归过程中要向下传递两个参数。第1个参数是alpha,它表示当前搜索节点走棋的一方搜索到的最好值,任何比它小的值都没有意义。第2个值是beta,表示对手目前的劣势,这是对手所能承受的最还的结果,比它大的值都会被舍弃。
因为是负极大值搜索,beta值越大,表示对方的劣势越明显,beta值越小,表示劣势也越小。
对于alpha-beta搜索剪枝算法,由于初始状态时还没有alpha-beta值,可以使用-MaxValue~MaxValue对应。
Alpha-Beta搜索剪枝算法如下:
输入:搜索深度、alpha、beta
输出:节点的最佳走法、及对应的最佳估值
函数形式:int alphaBetaSearch(int depth ,int alpha,int beta)
如果depth小于等于0
调用评估函数,并将结果赋给value
返回value值
否则
生成当前所有合理的走法
对每一个走法
执行走法
value= - alphaBetaSearch( depth-1 , -beta , -alpha);
//注意函数前面有负值,且参数alpha,beta分别取负值并进行交换
撤销走法
如果 value>=beta //使得可能引起的走法先被搜索,因为搜索的效率很大程度上取决于剪枝的效果
返回beta
如果 value>alpha
alpha=value
如果 depth == Max_Depth
bestmove=mv
返回alpha
调用形式:alphaBetaSearch(MaxDepth,-MaxValue,MaxValue)
习惯上把传递给一个待搜索节点的alpha和beta值组成的这个区间称为搜索窗口。
搜索窗口越窄,引发剪枝的可能性就越大,,搜索效率就越高。
传递给一个待搜索节点的alpha值会大于beta值吗?(绝对不会,连等于都不可能)。
下面是POJ3317 Stake your Claim的题目,分别使用负极大值搜索和alpha-beta剪枝优化的代码,从这个实验发现alpha-beta剪枝对效率的提高很有帮助。
下面是定义的一个局面评估的类:
- #include<iostream>
- #include<cstring>
- using namespace std;
- /*
- * 对局面进行评价的类
- */
- class Evaluater{
- public:
- Evaluater(char map[][8],int s ):size(s){
- //memcpy(square , map ,sizeof(map));
- for(int i=0;i<8 ;i++)
- for(int j=0 ; j<8 ; j++)
- square[i][j] = map[i][j];
- }
- /*
- * 局面的评估函数
- */
- int getValuated(int side){
- int p0Value=0,p1Value=0;
- for(int i=0; i<size ; i++){
- for(int j=0 ; j<size ; j++){
- if( '0' == square[i][j]){
- getMostSquares('0',i,j);
- if( count > p0Value)
- p0Value=count;
- count=1;
- }
- if( '1' == square[i][j] ){
- getMostSquares('1',i,j);
- if(count > p1Value)
- p1Value= count ;
- count=1;
- }
- }
- }
- if( 1 == side )
- return p0Value - p1Value ;
- else
- return p1Value - p0Value ;
- }
- private:
- /*
- * 搜索最大region中的己方棋子数量
- */
- void getMostSquares( char flag ,int x,int y){
- static int dir[4][2]={{0,-1},{-1,0},{0,1},{1,0}};
- int i,j;
- square[x][y]='x'; //试探过的空格标记为'x'
- for(int k=0 ;k<4 ; k++ ){
- i= x+ dir[k][0];
- j= y+ dir[k][1];
- if( i<0 || i>=size || j<0 || j>=size || square[i][j]!=flag )
- continue;
- else{
- count++;
- getMostSquares( flag , i , j );
- }
- }
- }
- enum TableSize{MAX_SIZE=8}; //棋盘最大的大小
- int size; //棋盘的实际大小
- char square[MAX_SIZE][MAX_SIZE]; //棋盘
- static int count;
- };
- int Evaluater::count=1; //静态变量一定要在类外初始化
下面是负极大值搜索的实现:一开始使用极大极小总是得不到正确的结果,后来直接使用负极大值搜索策略,这里需要注意的是,负极大值搜索对应的评估函数需要针对不同的当前下棋方来来返回评估值,另外在搜索函数中,所有的点都是极大值的点,所以best的值就可以直接赋值为INT_MIN.在递归函数返回时,也不必进行side的判断了。
- /*
- * POJ 3317 Stake Your Claim
- * 这是一道博弈题
- * 搜索策略:极大极小
- */
- #include<iostream>
- #include<fstream>
- #include<ctime>
- #define __DEBUG 0
- #include "MapEvaluate.h"
- using namespace std;
- #define MAX 8
- char map[MAX][MAX];
- int num; //棋盘的实际大小
- //1 :player0 , -1:player1
- int currSide;
- //最大的搜索深度
- int MAX_DEPTH;
- typedef struct move{
- int x;
- int y;
- move(){}
- move(int x1,int y1):x(x1),y(y1){}
- friend ostream& operator<<(ostream& out,const move& m){
- return out<<"("<<m.x<<","<<m.y<<")";
- }
- }Move;
- Move bestMove; //最好的移动
- void print(){
- for(int i=0 ; i<num ; i++){
- for(int j=0 ; j< num ; j++){
- cout<<map[i][j]<<" ";
- }
- cout<<endl;
- }
- }
- /*
- * 产生所有可能的移动方案
- */
- int genAllMove(Move* array){
- int count=0;
- for(int i=0 ; i<num ; i++)
- for(int j=0 ; j< num ; j++){
- if( '.' == map[i][j] ){
- array[count]= Move(i,j);
- count++;
- }
- }
- return count;
- }
- /*
- * 走棋
- */
- inline void makeMove(Move mv,int side){
- map[mv.x][mv.y] = (1 == side)? '0': '1' ;
- }
- /*
- * 还原棋盘
- */
- inline void unMakeMove(Move mv) {
- map[mv.x][mv.y] = '.' ;
- }
- /*
- * 极大极小搜索函数的核心算法
- */
- int negMaxSearch(int depth , int side) {
- int best,value;
- Move moveArray[12]; //最多可能有1~10个空格
- Move tmpMv;
- //使用负极大值搜索时,best的值始终为INT_MIN
- best = INT_MIN;
- if( 0 == depth ){
- Evaluater v(map,num);
- return v.getValuated(side);
- }
- int num=genAllMove(moveArray) ;
- for(int i =0 ; i< num ; i++){
- tmpMv = moveArray[i];
- makeMove(tmpMv,side) ;
- if(__DEBUG){
- print();
- cout<<endl; }
- //注意:这里不能用"~side ",否则side值一直为true
- //这里side递归返回之后,side恢复原值,因为"!side"没有改变side的原值
- // value = minMaxSearch(depth-1 , !side); //每次变换走棋方
- value = -minMaxSearch(depth -1 ,-1*side );
- unMakeMove(tmpMv);
- if(__DEBUG){
- print();
- cout<<endl; }
- //负极大值搜索
- if( value > best ){
- best = value ;
- if(depth == MAX_DEPTH)
- bestMove = tmpMv;
- }
- }
- //返回最佳的极值
- if( depth == MAX_DEPTH)
- cout<<bestMove;
- return best;
- }
- int main(){
- ifstream in("test.txt");
- int count0,count1;
- while(1){
- in>>num;
- if(0 == num)
- break;
- //每局开始都初始化
- count1=0;
- count0=0;
- for(int i=0; i<num ; i++){
- for(int j=0 ; j<num ;j++){
- in>>map[i][j];
- if( '1' == map[i][j]){
- count1 ++;
- }
- if( '0' == map[i][j]){
- count0 ++ ;
- }
- }
- }
- currSide= (count0 >count1)? -1 : 1 ;
- //对所有的空位进行搜索
- MAX_DEPTH = num*num - count1 - count0;
- if(__DEBUG){
- cout<<currSide<<endl;
- cout<<MAX_DEPTH<<endl;
- }
- clock_t time=clock();
- cout<<" "<<negMaxSearch(MAX_DEPTH,currSide)<<endl;
- cout<<"计算用时:"<<clock()-time<<"MS"<<endl;
- }
- }
下面是增加alpha-beta剪枝后的代码,注意的是剪枝的方法:
- /*
- * POJ 3317 Stake Your Claim
- * 这是一道博弈题
- * 搜索策略:极大极小
- */
- #include<iostream>
- #include<fstream>
- #include<ctime>
- #define __DEBUG 0
- #include "MapEvaluate.h"
- using namespace std;
- #define MAX 8
- char map[MAX][MAX];
- int num; //棋盘的实际大小
- //1 :player0 , -1:player1
- int currSide;
- //最大的搜索深度
- int MAX_DEPTH;
- typedef struct move{
- int x;
- int y;
- move(){}
- move(int x1,int y1):x(x1),y(y1){}
- friend ostream& operator<<(ostream& out,const move& m){
- return out<<"("<<m.x<<","<<m.y<<")";
- }
- }Move;
- Move bestMove; //最好的移动
- void print(){
- for(int i=0 ; i<num ; i++){
- for(int j=0 ; j< num ; j++){
- cout<<map[i][j]<<" ";
- }
- cout<<endl;
- }
- }
- /*
- * 产生所有可能的移动方案
- */
- int genAllMove(Move* array){
- int count=0;
- for(int i=0 ; i<num ; i++)
- for(int j=0 ; j< num ; j++){
- if( '.' == map[i][j] ){
- array[count]= Move(i,j);
- count++;
- }
- }
- return count;
- }
- /*
- * 走棋
- */
- inline void makeMove(Move mv,int side){
- map[mv.x][mv.y] = (1 == side)? '0': '1' ;
- }
- /*
- * 还原棋盘
- */
- inline void unMakeMove(Move mv) {
- map[mv.x][mv.y] = '.' ;
- }
- /*
- * 极大极小搜索函数的核心算法
- */
- int alphaBetaSearch(int depth , int side ,int alpha ,int beta) {
- int best,value;
- Move moveArray[12]; //最多可能有1~10个空格
- Move tmpMv;
- if( 0 == depth ){
- Evaluater v(map,num);
- return v.getValuated(side);
- }
- int num=genAllMove(moveArray) ;
- for(int i =0 ; i< num ; i++){
- tmpMv = moveArray[i];
- makeMove(tmpMv,side) ;
- if(__DEBUG){
- print();
- cout<<endl; }
- //注意:这里不能用"~side ",否则side值一直为true
- //这里side递归返回之后,side恢复原值,因为"!side"没有改变side的原值
- // value = minMaxSearch(depth-1 , !side); //每次变换走棋方
- value = -minMaxSearch(depth -1 ,-1*side, -beta , -alpha );
- unMakeMove(tmpMv);
- if(__DEBUG){
- print();
- cout<<endl; }
- //alpha-beta剪枝搜索
- if( value >= beta )
- return beta ;
- if( value > alpha ){
- alpha = value ;
- if(depth == MAX_DEPTH )
- bestMove = tmpMv ;
- }
- }
- //返回最佳的极值
- if( depth == MAX_DEPTH)
- cout<<bestMove;
- return alpha ;
- }
- int main(){
- ifstream in("test.txt");
- int count0,count1;
- while(1){
- in>>num;
- if(0 == num)
- break;
- //每局开始都初始化
- count1=0;
- count0=0;
- for(int i=0; i<num ; i++){
- for(int j=0 ; j<num ;j++){
- in>>map[i][j];
- if( '1' == map[i][j]){
- count1 ++;
- }
- if( '0' == map[i][j]){
- count0 ++ ;
- }
- }
- }
- currSide= (count0 >count1)? -1 : 1 ;
- //对所有的空位进行搜索
- MAX_DEPTH = num*num - count1 - count0;
- if(__DEBUG){
- cout<<currSide<<endl;
- cout<<MAX_DEPTH<<endl;
- }
- clock_t time=clock();
- cout<<" "<<alphaBetaSearch(MAX_DEPTH,currSide,-100,100)<<endl;
- cout<<"计算用时:"<<clock()-time<<"MS"<<endl;
- }
- }
算法设计——极大极小搜索相关推荐
- 一字棋游戏设计-极大极小搜索
1. 问题定义 一字棋游戏,包括两个选手.用户可以在一个3*3的棋盘上任意的选择空闲的位置拜访棋子,最早在水平方向上,或者垂直方向上或者对角线方向上形成三子一线者获胜.棋盘如图1所示.这里我们 ...
- 算法笔记--极大极小搜索及alpha-beta剪枝
参考1:https://www.zhihu.com/question/27221568 参考2:https://blog.csdn.net/hzk_cpp/article/details/792757 ...
- 五子棋ai:极大极小搜索和α-β剪枝算法的思想和实现(qt和c++)(一)引言和界面设计
源代码 GitHub上:Github livingsu/Gobang-ai:极大极小搜索和α-β剪枝 引言 alphaGo击败围棋冠军李世石的新闻让我对棋类博弈产生了浓厚的兴趣,无奈本人不会围棋,但算 ...
- (只此一篇便绝b能懂的)五子棋AI算法原理,博弈树、极大极小搜索、αβ剪枝
我在最近撰写五子棋AI程序设计报告时,翻阅了很多的资料博客,但却发现大佬们的博客,没有一篇是能让我只看它就能理解全部的AI算法.在看了众多博客后,我终于对博弈树.极大极小搜索.αβ剪枝恍然大悟,其实这 ...
- 【算法】深度优先搜索遍历的应用 设计算法以求解无向图G的连通分量的个数和无向图G的边数
应用一 设计算法以求解无向图G的连通分量的个数 图示: 深度遍历基本算法dfs(v0)如下 : void dfs(int v0) { visite(v0); visited[v0]=TRUE;w=fi ...
- 五子棋ai:极大极小搜索和α-β剪枝算法的思想和实现(qt和c++)(四)算杀模块的简单实现
一.什么是算杀?为什么要算杀? 算杀就是只算杀棋. 我用五子棋ai跟别人下了一阵子之后发现,用博弈树看6层深度(模拟ai走4步,模拟人走3步)其实根本不够,因为真正的高手看到的远比6层要多.高手进行谋 ...
- 基本搜索技术--人机博弈算法(极大极小,深度优先,负极大值)
极大极小值算法( Minimax algorithm) 在上文的博弈树中,如果我们令甲胜的局面值为1,乙胜的局面值为-1,而和局的值为0.当轮到甲走时,甲定会选择子节点值最大的走法:而轮到乙时,乙则会 ...
- 【人工智能】基于蒙特卡洛树搜索和策略价值网络的AI五子棋算法设计
基于蒙特卡洛树搜索和策略价值网络的AI五子棋算法设计 摘要 蒙特卡洛树搜索算法 五子棋博弈的状态价值函数 附1:详细论文说明下载: 附2:实现代码下载(2022.10更新后代码): 摘要 随着人工智能 ...
- 算法设计与分析第5章 回溯法(二)【回溯法应用】
第5章 回溯法 5.2 应用范例 1.0-1背包问题 有n件物品和一个容量为c的背包.第i件物品的重量是w[i],价值是p[i].求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和 ...
- 算法设计与分析之循环与递归
前言: 循环与递归可以说是算法设计中最基本但却也是最重要的工具方法.循环和递归对于学习过高级程序设计语言的人来说都并不陌生,但还是有必要仔细的探究一下循环和递归之间的相似和区别.循环与递归最大的相似之 ...
最新文章
- Nginx版本无缝升级
- java 序列化 protobuf_java序列化机制之protobuf(快速高效跨语言)
- 在NVIDIA Jetson TX2上安装TensorFlow
- 大型互联网架构演变历程-《淘宝技术这10年》
- 国内最受欢迎手游国际版折戟:腾讯也很无奈
- 大一计算机理论总结,大一计算机理论基础总结论文.doc
- fileupload控件的属性_FileUpload控件
- Android App开机自启动
- 人生一世,草木一秋,再伟大的人在历史长河中也只是一个匆匆过客
- C#读取srt字幕格式文件显示字幕
- 英语口语学习推荐的21部电影
- POCO中的异常处理和调试
- Python实现消息发送
- PbootCMS微信小程序API的封装使用教程
- 小程序输入框字数统计
- ojbk的sas proc 过程之proc format
- 管理日常工作、生活琐事的待办事项提醒工具便签
- 一本通 1086:角谷猜想
- 安吉尔净水器“落户”长沙街头,炎炎夏日带来一丝凉意
- EOS版truffle测试框架js4eos上线了,合约一键自动化测试