1. 程式人生 > 實用技巧 >Minimax極大極小演算法、Alpha-Beta Pruning剪枝演算法

Minimax極大極小演算法、Alpha-Beta Pruning剪枝演算法

這篇部落格分為兩部分。首先我會先講極大極小演算法,然後在此基礎上進行改進給出進階版的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; 
  }