參考1:https://www.zhihu.com/question/27221568
參考2:https://blog.csdn.net/hzk_cpp/article/details/79275772
參考3:https://blog.csdn.net/BIT1120172185/article/details/80963609
極小極大搜索算法即minimax搜索算法
主要應用於零和博弈(非勝即負,如圍棋,象棋,井子棋等),完全信息(玩家知道之前所有的步驟。象棋就是完全信息,因為玩家是交替着落子,且之前的步驟都能在棋盤上體現)
這個算法采用搜索算法遞歸實現,一層為先手,記為a, 一層為后手,記為b, 交替出現
對於最終局面,有一個分數(比如:先手勝分數為1, 平局分數為0,先手輸分數為-1)
先手a想要讓這個分數越大越好,后手b想要讓這個分數越小越好,於是搜索到先手這一層,取最大返回,搜索到后手這一層,取最小返回
如下圖:
於是偽代碼為:
int MaxMin(position,int d) { int bestvalue,value; if(game over) //檢查游戲是否結束 return evaluation(p);// 游戲結束,返回估值 if(depth<=0) //檢查是否是葉子節點 return evaluation(p);//葉子節點,返回估值 if(max) //極大值點 bestvalue=-INFINTY; else //極小值點 bestvalue=INFINTY; for(each possibly move m) { MakeMove(m); //走棋 value=MaxMin(p,d-1); UnMakeMove(m); //恢復當前局面 if(max) bestvalue=max(value,bestvalue);//取最大值 else bestvalue=min(value,bestvalue);//取最小值 } return bestvalue; }
關於alpha-beta剪枝:
如果當前層為取最小,如果取最小后比上一層當前最大值還小,則不需要往下搜索,因為上一層根本不會選擇當前節點往下搜,還有更好的選擇
同理,如果當前層為取最大,如果取最大后比上一層當前最小值還大,則不需要往下搜索,因為上一層根本不會選擇當前節點往下搜
具體推薦看最上面的知乎鏈接點贊最多的回答。
alpha-beta剪枝后的偽代碼:
int AlphaBeta(nPlay,nAlpha,nBeta) { if(game over) return Eveluation; //勝負已分,返回估值 if(nPly==0) return Eveluation; //葉子節點返回估值 if(Is Min Node) //判斷 節點類型 { // 極小值節點 for(each possible move m) { make move m; //生成新節點 score=AlphaBeta(nPly-1,nAlpha,nBeta)//遞歸搜索子節點 unmake move m;//撤銷搜索過的節點 if(score<nBeta) { nBeta=score;//取極小值 if(nAlpha>=nBeta) return nAlpha;//alpha剪枝,拋棄后繼節點 } } return nBeta;//返回最小值 } else {//取極大值的節點 for(each possible move m) { make move m; //生成新節點 score=AlphaBeta(nPly-1,nAlpha,nBeta)//遞歸搜索子節點 unmake move m;//撤銷搜索過的節點 if(score>nAlpha) { nAlpha=score;//取極小值 if(nAlpha>=nBeta) return nBeta;//nBeta剪枝,拋棄后繼節點 } } return nAlpha;//返回最小值 } }
例題1:POJ - 1568
思路:井子棋下的步數小於等於4必平局
代碼:

#pragma GCC optimize(2) #pragma GCC optimize(3) #pragma GCC optimize(4) #include<cstdio> #include<iostream> using namespace std; #define fi first #define se second #define pi acos(-(long double)1.0) #define LL long long //#define mp make_pair #define pb push_back #define ls rt<<1, l, m #define rs rt<<1|1, m+1, r #define ULL unsigned LL #define pll pair<LL, LL> #define pli pair<LL, int> #define pii pair<int, int> #define piii pair<pii, int> #define pdd pair<long double, long double> #define mem(a, b) memset(a, b, sizeof(a)) #define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #define fopen freopen("in.txt", "r", stdin);freopen("out.txt", "w", stout); //head char s[10], mp[10][10]; int cnt = 0, ansx, ansy; int check(char c) { for (int i = 0; i < 4; i++) { int a = 0; for (int j = 0; j < 4; j++) { if(mp[i][j] == c) a++; } if(a == 4) return 1; a = 0; for (int j = 0; j < 4; j++) { if(mp[j][i] == c) a++; } if(a == 4) return 1; } int a = 0; for (int i = 0; i < 4; i++) if(mp[i][i] == c) a++; if(a == 4) return 1; a = 0; for (int i = 0; i < 4; i++) if(mp[i][3-i] == c) a++; if(a == 4) return 1; return 0; } int dfs(int step, int a, int b) { if((cnt-step)%2 == 0) { int tmp = check('o'); if(tmp || step == 0) return -tmp; } else { int tmp = check('x'); if(tmp || step == 0) return tmp; } if((cnt-step)%2 == 0) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if(mp[i][j] == '.') { mp[i][j] = 'x'; int tmp = dfs(step-1, a, b); mp[i][j] = '.'; if(tmp >= a) { if(step == cnt) { ansx = i; ansy = j; } a = tmp; if(b <= a) return b; } } } } return a; } else { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if(mp[i][j] == '.') { mp[i][j] = 'o'; int tmp = dfs(step-1, a, b); mp[i][j] = '.'; if(tmp <= b) { b = tmp; if(a >= b) return a; } } } } return b; } } int main() { while(~scanf("%s", s)) { if(s[0] == '$') return 0; for (int i = 0; i < 4; i++) scanf("%s", mp[i]); cnt = 0; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) if(mp[i][j] == '.') cnt++; if(cnt >= 12) { printf("#####\n"); continue; } int ans = dfs(cnt, -1, 1); if(ans == 1) printf("(%d,%d)\n", ansx, ansy); else printf("#####\n"); } return 0; }
例題2:POJ - 1085
思路1:記憶化搜索dp, dp[s]表示從狀態s出發先手所能獲得的最大利益。
特點:空間大,時間短
代碼:

#pragma GCC optimize(2) #pragma GCC optimize(3) #pragma GCC optimize(4) #include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<cmath> #include<climits> using namespace std; #define fi first #define se second #define pi acos(-1.0) #define LL long long //#define mp make_pair #define pb push_back #define ls rt<<1, l, m #define rs rt<<1|1, m+1, r #define ULL unsigned LL #define pll pair<LL, LL> #define pli pair<LL, int> #define pii pair<int, int> #define piii pair<pii, int> #define pdd pair<long double, long double> #define mem(a, b) memset(a, b, sizeof(a)) #define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #define fopen freopen("in.txt", "r", stdin);freopen("out.txt", "w", stout); //head int line[18][2] = {{1, 2}, {1, 3}, {2, 3}, {2, 4}, {2, 5}, {4, 5}, {3, 5}, {3, 6}, {5, 6}, {4, 7}, {4, 8}, {7, 8}, {5, 8}, {5, 9}, {8, 9}, {6, 9}, {6, 10}, {9, 10}}; int tri[9][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}, {9, 10, 11}, {12, 13, 14}, {15, 16, 17}, {2, 4, 6}, {5, 10, 12}, {8, 13, 15}}; int num[11][11]; int dp[1<<20]; int count(int s) { int cnt = 0; for (int i = 0; i < 9; i++) if((s&(1<<tri[i][0])) && (s&(1<<tri[i][1])) && (s&(1<<tri[i][2]))) cnt++; return cnt; } int dfs(int s) { if(~dp[s]) return dp[s]; if(__builtin_popcount(s) == 18) return 0; int prenum = count(s); int ans = INT_MIN; for(int i = 0; i < 18; i++) { if(!(s&(1<<i))) { int nownum = count(s|(1<<i)); if(nownum == prenum) { ans = max(ans, -dfs(s|1<<i)); } else { ans = max(ans, nownum - prenum + dfs(s|1<<i)); } } } return dp[s] = ans; } int main() { int T, m, u, v; for (int i = 0; i < 18; i++) num[line[i][0]][line[i][1]] = num[line[i][1]][line[i][0]] = i; mem(dp, -1); scanf("%d", &T); for(int cs = 1; cs <= T; cs++) { int st = 0; scanf("%d", &m); int prenum = 0, nownum = 0; int cnt = 0, step = 0; for (int i = 1; i <= m; i++) { scanf("%d %d", &u, &v); st |= 1<<num[u][v]; nownum = count(st); if(nownum > prenum) { if(step%2 == 0) cnt += nownum - prenum; else cnt -= nownum - prenum; } else step++; prenum = nownum; } if(step%2 == 0) cnt += dfs(st); else cnt -= dfs(st); if(cnt > 0) printf("Game %d: A wins.\n", cs); else printf("Game %d: B wins.\n", cs); } return 0; }
思路2:極大極小搜索+alpha-beta剪枝
特點:空間小,時間長
代碼:

#pragma GCC optimize(2) #pragma GCC optimize(3) #pragma GCC optimize(4) #include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<cmath> #include<climits> using namespace std; #define fi first #define se second #define pi acos(-1.0) #define LL long long //#define mp make_pair #define pb push_back #define ls rt<<1, l, m #define rs rt<<1|1, m+1, r #define ULL unsigned LL #define pll pair<LL, LL> #define pli pair<LL, int> #define pii pair<int, int> #define piii pair<pii, int> #define pdd pair<long double, long double> #define mem(a, b) memset(a, b, sizeof(a)) #define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #define fopen freopen("in.txt", "r", stdin);freopen("out.txt", "w", stout); //head int line[18][2] = {{1, 2}, {1, 3}, {2, 3}, {2, 4}, {2, 5}, {4, 5}, {3, 5}, {3, 6}, {5, 6}, {4, 7}, {4, 8}, {7, 8}, {5, 8}, {5, 9}, {8, 9}, {6, 9}, {6, 10}, {9, 10}}; int tri[9][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}, {9, 10, 11}, {12, 13, 14}, {15, 16, 17}, {2, 4, 6}, {5, 10, 12}, {8, 13, 15}}; int num[11][11]; int count(int s) { int cnt = 0; for (int i = 0; i < 9; i++) if((s&(1<<tri[i][0])) && (s&(1<<tri[i][1])) && (s&(1<<tri[i][2]))) cnt++; return cnt; } int dfs(int s, int a, int b, int alpha, int beta, int step) { if(__builtin_popcount(s) == 18) { if(a > b) return 1; else return -1; } int prenum = count(s); for(int i = 0; i < 18; i++) { if(!(s&(1<<i))) { int nownum = count(s|(1<<i)); if(nownum == prenum) { if(step%2 == 0) { int tmp = dfs(s|1<<i, a, b, alpha, beta, step+1); if(tmp >= alpha) { alpha = tmp; if(alpha >= beta) return alpha; } } else { int tmp = dfs(s|1<<i, a, b, alpha, beta, step+1); if(tmp <= beta) { beta = tmp; if(alpha >= beta) return beta; } } } else { if(step%2 == 0) { int tmp = dfs(s|1<<i, a+nownum-prenum, b, alpha, beta, step); if(tmp >= alpha) { alpha = tmp; if(alpha >= beta) return alpha; } } else { int tmp = dfs(s|1<<i, a, b+nownum-prenum, alpha, beta, step); if(tmp <= beta) { beta = tmp; if(alpha >= beta) return beta; } } } } } if(step%2 == 0) return alpha; else return beta; } int main() { int T, m, u, v; for (int i = 0; i < 18; i++) num[line[i][0]][line[i][1]] = num[line[i][1]][line[i][0]] = i; scanf("%d", &T); for(int cs = 1; cs <= T; cs++) { int st = 0; scanf("%d", &m); int prenum = 0, nownum = 0; int step = 0, a = 0, b = 0; for (int i = 1; i <= m; i++) { scanf("%d %d", &u, &v); st |= 1<<num[u][v]; nownum = count(st); if(nownum > prenum) { if(step%2 == 0) a += nownum - prenum; else b += nownum - prenum; } else step++; prenum = nownum; } int cnt = dfs(st, a, b, -1, 1, step); if(cnt > 0) printf("Game %d: A wins.\n", cs); else printf("Game %d: B wins.\n", cs); } return 0; }