您的位置:首页 > 其它

实用算法实现-第 15 篇 对抗搜索

2011-10-23 23:39 567 查看
《人工智能,一种现代方法》对于对抗搜索的介绍非常好。

15.1 极小值极大值策略

极大值极小值策略是一种最优策略。当对手不犯错误时,最优策略能够导致至少不比其它任何其它策略差的结果。需要注意的是,最优策略针对的是最优化对手:如果使用极大值极小值策略对付非最优化对手,可能没有使用其它策略好,但是使用那些策略对付最优化对手必定要比极大值极小值策略差。

极小值极大值策略是指存在两个最优化的博弈对手MIN和MAX,MIN的目的是使得value最小,而MAX则是使得value最大。由于两个对手都不会犯错误,即采用最优策略,故此有如下公式:



不难发现使用极小值极大值策略实际完成的是对策略空间的一种深度优先搜索。

15.2 α-β剪枝

对于两个使用最优策略的博弈者,一个博弈的value的最终结果总是确定的。而极大值极小值搜索则是通过搜索博弈树,来找出博弈的value的最终结果。极大值极小值搜索进行DFS搜索,一直深入搜索到叶子。如果结点的所有子树已搜索完毕,则该结点回溯到父结点。该搜索从初始状态,经过搜索过程P,达到当前状态S,搜索至当前结点N。可以知道,搜索过程P唯一地决定了当前状态S,而S状态正在考察结点N。

为了减少不必要的搜索,对博弈树的搜索进行α-β剪枝。每个结点N都维持两个变量α和β。α和β表示通过搜索过程P,在搜索进行到当前状态S时,MAX和MIN探索清楚的value值的一些情况。α的意义是:如果博弈过程为P,那么博弈到结点N时,MAX所能保证的value的最小值为α。β的意义是:博弈过程为P,那么博弈到结点N时,MIN所能保证的value的最大值为β。

假设在达到当前状态S时,由N的父结点F发起的搜索刚刚搜索到结点N。结点N将由父结点F得到自己的α和β的初值(博弈树的根结点的α取为-∞,β取为+∞),因为结点的α和β的初值总是由状态的S的先序状态决定的,而在状态的S的上一个状态中,F正在发起到结点N的搜索。在搜索到N结点之后,对N的子树的搜索继续进行。当N的某棵子树搜索完成之后,需要根据最优选择的结果来更新自己的α和β:如果结点N是MAX,那么只有有可能(存在一个子树,返回的vlaue的值v比α大),它会不断地抬高α;如果结点N是MIN,那么只有有可能(存在一个子树,返回的vlaue的值v比β小),它会不断地压低β。

在维护α和β的之后,可以发现有一些情况可以不必再搜索子树而直接回溯到父结点。当搜索到结点N时,双方已经博弈到第n步。

1. 如果结点N是MAX,且发现N的某子树返回的vlaue的值v比β还大,它可以断定在第n-1步MIN做决定的时候不会让MAX有这种机会。因为在第n-1步,MIN就知道存在着一个策略D,使得value值为β;如果v比β大,MIN会毫不犹豫地采用D策略。

2. 如果结点N是MIN,且发现N的某子树返回的vlaue的值v比α还小,它可以断定在第n-1步MAX做决定的时候不会让MIN有这种机会。因为在第n-1步,MAX就知道存在着一个策略D,使得value值为α;如果v比α小,MAX会毫不犹豫地采用D策略。

上述结论对于第n = 1步也是成立的,因为此时α取为取为-∞,β取为+∞,α-β剪枝的条件不可能满足。

15.2.1 实例

PKU JudgeOnline, 1085, Triangle War.

15.2.2 问题描述



如上图,A、B进行博弈。如果某人连接两点之后完成一个三角形,则得一分,且再需走一步。得分多的人获胜。

先给定一些已经走好的步骤,求最后谁胜。

15.2.3 输入

4
6
24
45
59
36
25
35
7
24
45
59
36
25
35
78
6
12
23
13
24
25
45
10
12
25
36
58
47
610
24
45
48
7 8

15.2.4 输出

Game1: B wins.
Game2: A wins.
Game3: A wins.
Game 4: B wins.

15.2.5 分析

标准的极大值极小值策略问题。考虑到时间复杂度,对博弈树进行α-β剪枝。

为了存储和表示的方便,对每个连接编号为1~18。每个连接又对应到9个三角形的三边。使用link这个数组来存储这些数据。

例如连接(1,2)为第1个连接,这个连接是第1个三角形的左边。

(1,3)为第2个连接,这个连接是第1个三角形的右边。

(2,3)为第3个连接,这个连接是第1个三角形的中边,也是第3个三角形的中边。

依次表示为如下数据:

1 1 l

1 1 r

2 1 m 3 m

1 2 l

2 2 r 3 l

2 3 r 4 l

1 4 r

2 2 m 6 m

2 4 m 8 m

1 5 l

2 5 r 6 l

2 6 r 7 l

2 7 r 8 l

2 8 r 9 l

1 9 r

1 5 m

1 7 m

1 9 m

其中,第1个数表示该边是几个三角形的边,第2个(或者第4个)数表示该边是哪个三角形的边。第3个(或者第5个)数表示该边是该三角形的那条边(左l、右r、中m)。

可一直到一条连接最少是一条三角形的边,最多是两条三角形的边。表示是三角形的那条边(边的编号)需要3 bits,表示是哪个三角形(共9个三角形)需要4 bits。故此每个连接只要14 bits就可以表示这些信息,用一个int就可以了。用calLink()函数计算成int,int的表示形式是:

18 bits 0 3bits 4 bits 3bits 4bits

边的编号 三角形编号 边的编号 三角形编号

本题目不仅可以对博弈树进行α-β剪枝,而且由于得分的具体数目不重要,重要的只是输赢,所以可以同时进行另一个剪枝:

如果MAX发现有一个策略可以达到必胜的目的时,不必继续搜索子结点,而只需回溯到父结点。同样的,如果MIN发现有一个策略可以达到必胜的目的时,不必继续搜索子结点,而只需回溯到父结点。

需要注意这种情况:给的初始状态已经表明A或者B已经获胜。

2^18种可能,每种可能需要2 bit。一个bit为是否搜索过,另一个bit为搜索结果(赢1或者输0)。用char变量表示。每个char保存四个可能。故此指针位置为:table[key >> 2]& (3 << ((key & 3) * 2))

15.2.6 程序

#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
#define MAX 0x7FFFFFF
const int link[19] =
{0,
17,33, 8387, 18, 4371, 4500,
36,8518, 8776, 21, 4758, 4887,
5016,5145, 41, 69, 71, 73};
int tri[10];
#define TRI_MASK 15
#define DIRECTION_MASK 7
#define TRI_BIT 4
#define DIRECTION_BIT 3
bool used[19];
int addStick(int pos)
{
int score;
int l;
int p;
int d;
score = 0;
used[pos] = 1;

l = link[pos];
p = l & TRI_MASK;
l = l >> TRI_BIT;
d = l & DIRECTION_MASK;
tri[p] |= d;
if(tri[p]== 7)
{
score++;
}

l = l >> DIRECTION_BIT;
p = l & TRI_MASK;
if(p != 0)
{
l = l >> TRI_BIT;
d = l & DIRECTION_MASK;
tri[p] |= d;
if(tri[p]== 7)
{
score++;
}
}
returnscore;
}
void removeStick(int pos)
{
int l;
int p;
int d;
used[pos] = 0;

l = link[pos];
p = l & TRI_MASK;
l = l >> TRI_BIT;
d = l & DIRECTION_MASK;
tri[p] &= ~d;

l = l >> DIRECTION_BIT;
p = l & TRI_MASK;
if(p != 0)
{
l = l >> TRI_BIT;
d = l & DIRECTION_MASK;
tri[p] &= ~d;
}
}
int originstep;
#define LOWEST 5
#define HIGHEST 4
int MinValue(int step, int baseValue, intalpha, int beta);
int MaxValue(int step, int baseValue, intalpha, int beta)
{
int i;
int max;
int n, t;
if(step ==18)
return0;
/*if(step ==17)//加上这个WA,难道是避开了alpha、beta的缘故?
return 1;*/
max = -MAX;
for(i = 1;i <= 18 ; i ++)
{
if(used[i]== 1)
{
continue;
}
n = addStick(i);
if(n> 0){
t = n + MaxValue(step + 1, n +baseValue, alpha, beta);
}else{
t = MinValue(step + 1, baseValue,alpha, beta);
}
removeStick(i);
if (max< t )
max = t;
if(max+ baseValue >= beta)
{
returnmax;
}
if(alpha< max + baseValue)
{
alpha = max + baseValue;
}
if(alpha> HIGHEST){
returnmax;
}
}
return max;
}
int MinValue(int step, int baseValue, intalpha, int beta)
{
int i;
int min;
int n, t;
if(step ==18) //|| step == 17)
return0;
min = MAX;
for(i = 1;i <= 18 ; i ++)
{
if(used[i]== 1)
{
continue;
}
n = addStick(i);
if(n> 0){
t = MinValue(step + 1, baseValue,alpha, beta);
}else{
t = MaxValue(step + 1, baseValue,alpha, beta);
}
removeStick(i);
if (min> t)
min = t;
if(min+ baseValue <= alpha)
{
returnmin;
}
if(beta> min + baseValue)
{
beta = min + baseValue;
}
if(beta< LOWEST){
returnmin;
}
}
return min;
}
#define LEFT 0
#define RIGHT 1
#define MIDDLE 2
const int dot[19][2] ={
{0,0},
{1, 2}, {1, 3},
{2,3},
{2,4}, {2, 5}, {3, 5}, {3, 6},
{4,5}, {5, 6},
{4,7}, {4, 8}, {5, 8}, {5, 9}, {6, 9}, {6, 10},
{7,8}, {8, 9}, {9, 10}};
int calLink()
{
freopen( "t.in","w", stdout );
int i;
int j,index;
char c;
int result;
int pos;
while(1)
{
cin >> j;
result = 0;
for(i =0; i < j; i++){
cin>> index >> c;
if(c== 'l'){
pos = LEFT;
}elseif(c == 'r'){
pos = RIGHT;
}else{
pos = MIDDLE;
}
pos = 1 << pos;
result = result << 7;
result |= (pos << 4) |index;
}
cout << result << ", ";
}
return 1;
}

int main()
{
int cases;
boolmaxTurn;
intoriginValue;
int i, j,k;
int from,to;
int n;
int losed;;
scanf("%d",&cases);
for(k = 1;k <= cases; k++){
memset(tri, 0, sizeof(tri));
memset(used, 0, sizeof(used));
maxTurn = 1;
originValue = 0;
losed = 0;
scanf("%d",&originstep);
for(i =1; i <= originstep; i++){
scanf("%d%d",&from, &to);
for(j= 1; j <= 18; j++){
if(dot[j][0]== from && dot[j][1] == to)
{
break;
}
}
n = addStick(j);
if(n== 0){
maxTurn = !maxTurn;
}else{
if(maxTurn){
originValue += n;
}else{
losed += n;
}
}
}
printf("Game%d: ", k);
if(losed<= 4 && originValue <= 4){
if(maxTurn){
originValue +=MaxValue(originstep, originValue, -MAX, MAX);
}else{
originValue +=MinValue(originstep, originValue, -MAX, MAX);
}
if(originValue> HIGHEST){
printf("A");
}elseif(originValue < LOWEST){
printf("B");
}else{
int*a = (int *)0;
*a = 0;
}
}else if(originValue > 4){
printf("A");
}else{
printf("B");
}
printf("wins.\n");
}
return 1;
}


本文章欢迎转载,请保留原始博客链接http://blog.csdn.net/fsdev/article
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: