极大极小搜索策略一般都是使用在一些博弈类的游戏之中:
这样策略本质上使用的是深度搜索策略,所以一般可以使用递归的方法来实现。在搜索过程中,对本方有利的搜索点上应该取极大值,而对本方不利的搜索点上应该取极小值。
极小值和极大值都是相对而言的。
在搜索过程中需要合理的控制搜索深度,搜索的深度越深,效率越低,但是一般来说,走法越好。
极大极小搜索可以分开写,也可以放在一起写。
主要的算法步骤如下:
输入:搜索的深度
输出:节点的最佳走法,及其对应的最佳估值
函数形式: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
下面是增加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 ;
//对所有的空位进行搜索
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;
}
}
posted on 2011-07-10 21:42
菜青虫 阅读(3046)
评论(0) 编辑 收藏 引用