這篇博客分為兩部分。首先我會先講極大極小算法,然后在此基礎上進行改進給出進階版的Alpha-Beta剪枝算法以及代碼實現。文中配備b站講解的視頻,感興趣的可以看一下視頻講解,然后復習的時候拿着文章當作參考。
Minimax算法(極大極小算法)
概念
- 是一種找出最小失敗的可能的算法。意思就是兩個人下棋,A和B下棋,A想要自己的利益最大化(失敗的可能性最小),B想要A的利益最小化(B想要A輸)。這個算法以及接下來的Alpha-Beta剪枝都是一種對抗性搜索算法(兩個人互相對抗着下棋,倆人都想贏),是一種人工智能搜索的算法。掌握此算法后可以用來寫井字棋、五子棋等人工智能人機對抗下棋程序。
具體步驟
給你一顆二叉樹。告訴你小紫和小黑玩游戲。紫色和黑色圓圈代表他們兩個。我們是站在小紫的角度來描述的。小紫想要他自己的得分最大 所以小紫玩的時候,二叉樹的那一層叫MAX層。小黑想要小紫的得分最小化,小黑的叫做MIN層。我們總是讓小紫第一個開始,假設下面這個二叉樹,我們只知道葉子節點的值(別管為啥,先學好算法原理,后續如何應用這個算法我還打算繼續寫博客和錄視頻講解。):
在這里給出定義,MAX層的節點的值是什么??是它的子節點里面的最大值,MIN層的值是它的子節點里面的最小值。直接上圖。
MIN層是選擇其孩子節點中最小值。
MAX層選擇其孩子節點中的最大值。
算法概念就是這個樣子。
算法的輸入是構造的這一棵滿二叉樹,輸出是最上層MAX節點的值。
代碼實現
class Node{ //結點類
public:
const int value;
Node *left;
Node *right;
Node(int v,Node* l,Node* r):value(v),left(l),right(r) {};
};
為了遍歷這棵樹,首先我們得創建出來這棵樹對吧?但是你的代碼里沒有創建二叉樹這一部分啊。別急,一會給出完整代碼,現在先專注於這個算法是如何實現的。
我們的思路是,對於每一個節點,無論是max層的還是min層的,從下往上我們要知道這個節點的值。利用遞歸和設置一個min1和max1變量記錄。同時return的min1和max1也是上一層結點值。
int minimax(Node* position, bool who){
if(position->left == NULL){
return position->value;
} //葉子結點 結束遞歸。
if(who){// max
int max1 = INT_MIN;
int value = minimax(position->left,false);//左孩子
max1 = std::max(value,max1);
value = minimax(position->right,false);//右孩子
max1 = std::max(value,max1);
return max1;
}
else {//min
int min1 = INT_MAX;
int value = minimax(position->left,true);//左孩子
min1 = std::min(value,min1);
value = minimax(position->right,true);//右孩子
min1 = std::min(value,min1);
return min1;
}
}
Alpha-Beta剪枝算法
概念
在minimax算法的基礎上,為了縮減時間而進行的剪枝操作。
在原來的基礎上,有的層是MIN層,有的是MAX層。現在在原來max1值和min1值的基礎上設置alpha和beta。如圖,這是alpha和beta的初始值為負無窮和正無窮,通過遞歸的調用向下傳遞。在MAX層更新alpha值,在MIN層更新beta值。alpha和beta只向下傳遞(通過一層一層的遞歸調用,一開始那些最左邊的、一層一層往下傳的alpha和beta都是負無窮和正無窮。)
下圖,往父節點返回值。更新alpha。
下圖,我們快進了一些內容,直接跳到剪枝的部分。
當alpha<=beta時剪枝
代碼實現
int alpha_beta_pruning(Node* position, int alpha, int beta, bool who){
if(position->left == NULL){
return position->value;
}
if(who){// max
int max1 = INT_MIN;
int value = alpha_beta_pruning(position->left,alpha,beta,false);
max1 = std::max(value,max1);
alpha = std::max(alpha,max1);
if(beta <= alpha){
delete_subtree(position->right); //剪枝只發生在右邊
position->right = NULL;
return max1;
}
value = alpha_beta_pruning(position->right,alpha,beta,false);
max1 = std::max(value,max1);
return max1;
}
else {//min
int min1 = INT_MAX;
int value = alpha_beta_pruning(position->left,alpha,beta,true);
min1 = std::min(value,min1);
beta = std::min(beta,min1);
if(beta <= alpha){
delete_subtree(position->right); //剪枝只發生在右邊
position->right = NULL;
return min1;
}
value = alpha_beta_pruning(position->right,alpha,beta,true);
min1 = std::min(value,min1);
return min1;
}
}
剪枝時delete函數
void delete_subtree(Node* position) {
if (position -> left != NULL){
delete_subtree(position -> left);
position -> left = NULL;
}
if (position -> right != NULL){
delete_subtree(position -> right);
position -> right = NULL;
}
delete position;
}