八皇后問題,是一個古老而著名的問題,問題如下:
在8×8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。
上邊是一個8*8的國際棋盤,可以看到棋盤中的每個格子都標有數字。每個數字都是兩位,十位數字表示該格子所在的行,而個位數字表示該格子所在的列。
這樣不難發現,處在同一行的兩個格子其十位數都相同,處在同一列的兩個格子其個位數都相同,處在同一斜線的兩個格子有:|兩個數字個位數的差|=|兩個數字十位數的差|。
主要的三個限制條件明白了,接下來我們選擇一種數據結構對“皇后”進行存儲。很明顯,我們可以選擇二維數組。但是還可以選擇一維數組。在這里我們選擇一維數組。
為什么選擇一維數組呢?
1.因為一行只放一個皇后,很符合我們一維數組的存放特性,即一個索引對應一個元素。
2.相對於二維數組,一位數組比較簡單,更適合初學者。
我們以皇后所在的行標作為一位數組的索引,皇后所在的列標作為該索引對應的元素,例如arr[3]=5,代表第三行的皇后在第五列。
下邊我們以python以及JAVA為例來解決這個問題。
num=0 def eight_queen(arr,finish_line=0): if finish_line == len(arr): #如果放置皇后成功的行數與數組中的元素個數一致(即棋盤的行數)則認為完成了一種擺法 global num #將上邊定義的num定義為全局變量 這樣才能在后邊對其進行自加操作 num+=1 print("第%s種擺法:" % num) for i in range(8): print((i,arr[i])) return # break for stand in range(len(arr)): #對整個列進行掃描,將列標的標號賦值給數組中對應的元素 arr[finish_line] = stand flag = True for line in range(finish_line): if arr[line] == stand or abs(arr[line]-stand) == finish_line-line: #有皇后與當前放置的皇后處於同一列或同一斜線上 flag = False # stand-=1 if flag==True: eight_queen(arr,finish_line+1) if __name__ == '__main__': eight_queen([None]*8) if num != 0: print("一共有%s種擺法" % num) else: print("無解")
對於初學者理解可能不是特別輕松,所以在這里我們總結一個的定式,再遇到此類問題時直接套用以下定式即可。下邊再講的相關問題我們就套用以下定式。
num=0 #全局變量代表方法的數量 def model(arr,finish_row=0): if finish_row == len(arr): #完成了一種方法 global num num+=1 return for x in range(循環邊界): 具體操作 #對列表的需求操作 flag = True for y in range(循環邊界): #找不符合條件的要求 if 找不符合要求的情況: flag = False if flag==True: #完成了一行擺放后遞歸調用 model(arr,finish_row+1) if __name__ == '__main__': model() #調用即可 print(num) #輸出方法數
我們再把它改寫為JAVA版:
public class eigthqueen { public static int num =0; //定義全局變量,表示擺法 public static void main(String[] args) { int [] arr = new int[8]; //以八皇后為例,定義對應長度的列表 int finish_row = 0; //初始完成的行數 equeen(arr,finish_row); System.out.println(); System.out.printf("一共%d種擺法",num); } static void equeen(int arr[],int finish_row){ boolean flag = true; if(finish_row==arr.length){ num++; System.out.println(); //初學java,比較笨的輸出方式 System.out.printf("第%d種擺法",num); System.out.println(); for(int i=0;i<arr.length;i++){ System.out.print("("); System.out.print(i); System.out.print(","); System.out.print(arr[i]); System.out.print(")"); } return; } for(int col=0;col<arr.length;col++){ arr[finish_row] = col; flag = true; for(int row=0;row<finish_row;row++){ if((arr[row]==col)||(Math.abs(arr[row]-col)==finish_row-row)){ //有皇后與當前放置的皇后在同一列或統一斜線的情況 flag = false; } } if(flag==true){ //完成一行皇后的放置 equeen(arr,finish_row+1); } } } }
同樣在JAVA中我們同樣也可以總結定式:
public class eigthqueen { public static int num =0; //定義全局變量,表示擺法 public static void main(String[] args) { int [] arr = new int[8]; //以八皇后為例,定義對應長度的列表 int finish_row = 0; //初始完成的行數 equeen(arr,finish_row); System.out.printf("一共%d種擺法",num); } static void equeen(int arr[],int finish_row){ boolean flag = true; if(finish_row==arr.length){ num++; return; } for(int col=0;col<arr.length;col++){ 具體操作 //對列表的需求操作 flag = true; for(int row=0;row<finish_row;row++){ if(找不符合要求的情況){ flag = false; } } if(flag==true){ //完成一小步驟方法后遞歸 equeen(arr,finish_row+1); } } } }
接下來幾篇我們要講的馬走日問題以及剪郵票等問題都可以套用這個定式來解決,這樣就可以更加熟悉對這個定式的使用。