八皇后问题
题目:在8×8的国际象棋上摆放八个皇后,使其不能相互攻击,即任意两个皇后不得处在同一行、同一列或者同一对角斜线上。下图中的每个黑色格子表示一个皇后,这就是一种符合条件的摆放方法。请求出总共有多少种摆法。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
这就是有名的八皇后问题。解决这个问题通常需要用递归,而递归对编程能力的要求比较高。因此有不少面试官青睐这个题目,用来考察应聘者的分析复杂问题的能力以及编程的能力。
由于八个皇后的任意两个不能处在同一行,那么这肯定是每一个皇后占据一行。于是我们可以定义一个数组ColumnIndex[8],数组中第i个数字表示位于第i行的皇后的列号。先把ColumnIndex的八个数字分别用0-7初始化,接下来我们要做的事情就是对数组ColumnIndex做全排列。由于我们是用不同的数字初始化数组中的数字,因此任意两个皇后肯定不同列。我们只需要判断得到的每一个排列对应的八个皇后是不是在同一对角斜线上,也就是数组的两个下标i和j,是不是i-j==ColumnIndex[i]-Column[j]或者j-i==ColumnIndex[i]-ColumnIndex[j]。
关于排列的详细讨论,详见本系列博客的第28篇,《字符串的排列》,这里不再赘述。
接下来就是写代码了。思路想清楚之后,编码并不是很难的事情。下面是一段参考代码:
1 int g_number = 0; 2 3 4 void EightQueen() 5 6 { 7 8 const int queens = 8; 9 10 int ColumnIndex[queens]; 11 12 for(int i = 0; i < queens; ++ i) 13 14 ColumnIndex[i] = i; 15 16 17 18 Permutation(ColumnIndex, queens, 0); 19 20 } 21 22 23 24 void Permutation(int ColumnIndex[], int length, int index) 25 26 { 27 28 if(index == length) 29 30 { 31 32 if(Check(ColumnIndex, length)) 33 34 { 35 36 ++ g_number; 37 38 PrintQueen(ColumnIndex, length); 39 40 } 41 42 } 43 44 else 45 46 { 47 48 for(int i = index; i < length; ++ i) 49 50 { 51 52 int temp = ColumnIndex[i]; 53 54 ColumnIndex[i] = ColumnIndex[index]; 55 56 ColumnIndex[index] = temp; 57 58 59 60 Permutation(ColumnIndex, length, index + 1); 61 62 63 64 temp = ColumnIndex[index]; 65 66 ColumnIndex[index] = ColumnIndex[i]; 67 68 ColumnIndex[i] = temp; 69 70 } 71 72 } 73 74 } 75 76 77 78 bool Check(int ColumnIndex[], int length) 79 80 { 81 82 for(int i = 0; i < length; ++ i) 83 84 { 85 86 for(int j = i + 1; j < length; ++ j) 87 88 { 89 90 if((i - j == ColumnIndex[i] - ColumnIndex[j]) 91 92 || (j - i == ColumnIndex[i] - ColumnIndex[j])) 93 94 return false; 95 96 } 97 98 } 99 100 101 102 return true; 103 104 } 105 106 107 108 void PrintQueen(int ColumnIndex[], int length) 109 110 { 111 112 printf("Solution %d\n", g_number); 113 114 115 116 for(int i = 0; i < length; ++i) 117 118 printf("%d\t", ColumnIndex[i]); 119 120 121 122 printf("\n"); 123 124 }
以上转自何海涛博客
上面的代码存在一个问题就是计算了很多无效解,而这些无效解可以通过剪枝去掉。
对以上的代码进行略微修改,可以得到一个带剪枝的n皇后问题,其实本质上就是一个带剪枝的回溯算法。
1 #include <iostream> 2 3 using namespace std; 4 5 int g_number = 0; 6 7 int Prune(int ColumnIndex[], int length, int index, int candidate_val) 8 { 9 for(int i=0;i<index;i++) 10 { 11 if((i - index == ColumnIndex[i] - candidate_val) 12 || (index - i == ColumnIndex[i] - candidate_val)) 13 return 1; 14 } 15 16 return 0; 17 } 18 void Permutation(int ColumnIndex[], int length, int index) 19 { 20 if(index == length) 21 { 22 g_number++; 23 } 24 else 25 { 26 for(int i = index; i < length; ++ i) 27 { 28 if (Prune(ColumnIndex,length,index,ColumnIndex[i])) 29 { 30 continue; 31 } 32 33 int temp = ColumnIndex[i]; 34 ColumnIndex[i] = ColumnIndex[index]; 35 ColumnIndex[index] = temp; 36 37 Permutation(ColumnIndex, length, index + 1); 38 39 temp = ColumnIndex[index]; 40 ColumnIndex[index] = ColumnIndex[i]; 41 ColumnIndex[i] = temp; 42 } 43 } 44 } 45 46 int main() 47 { 48 for(int queens=1;queens<12;queens++) 49 { 50 g_number = 0; 51 52 int *ColumnIndex = new int[queens]; 53 for(int i = 0; i < queens; ++ i) 54 ColumnIndex[i] = i; 55 56 Permutation(ColumnIndex, queens, 0); 57 58 cout<<"queens: "<<queens; 59 cout<<"\t solutions: " << g_number<<endl; 60 61 delete []ColumnIndex; 62 } 63 64 system("pause"); 65 66 return 0; 67 }
附上另外一个版本的n皇后回溯算法,转自unixfy的空间
1 // n-queens in C 2 3 #include <stdio.h> 4 #include <math.h> 5 6 #define MAXCANDIDATES 100 /* max possible next extensions */ 7 #define NMAX 100 /* maximum solution size */ 8 9 #define TRUE 1 10 #define FALSE 0 11 12 typedef int Bool; 13 typedef char* data; /* type to pass data to backtrack */ 14 15 Bool finished = FALSE; /* found all solutions yet? */ 16 17 int solution_count; /* how many solutions are there? */ 18 19 20 void process_solution() 21 { 22 solution_count ++; 23 } 24 25 int is_a_solution(int k, int n) 26 { 27 return (k == n); 28 } 29 30 31 /* What are possible elements of the next slot in the 8-queens 32 problem? 33 */ 34 35 void construct_candidates(int a[], int k, int n, int c[], int *ncandidates) 36 { 37 int i,j; /* counters */ 38 Bool legal_move; /* might the move be legal? */ 39 40 *ncandidates = 0; 41 for (i=1; i<=n; i++) { 42 legal_move = TRUE; 43 for (j=1; j<k; j++) { 44 if (abs(k-j) == abs(i-a[j])) /* diagonal threat */ 45 legal_move = FALSE; 46 if (i == a[j]) /* column threat */ 47 legal_move = FALSE; 48 } 49 if (legal_move == TRUE) { 50 c[*ncandidates] = i; 51 *ncandidates = *ncandidates + 1; 52 } 53 } 54 } 55 56 void backtrack(int a[], int k, int input) 57 { 58 int c[MAXCANDIDATES]; /* candidates for next position */ 59 int ncandidates; /* next position candidate count */ 60 int i; /* counter */ 61 62 if (is_a_solution(k, input)) 63 process_solution(); 64 else { 65 k = k+1; 66 construct_candidates(a,k,input,c,&ncandidates); 67 for (i=0; i<ncandidates; i++) { 68 a[k] = c[i]; 69 backtrack(a,k,input); 70 if (finished) return; /* terminate early */ 71 } 72 } 73 } 74 75 int main() 76 { 77 int a[NMAX]; /* solution vector */ 78 int i; /* counter */ 79 80 for (i=1; i<=12; i++) { 81 solution_count = 0; 82 backtrack(a,0,i); 83 printf("n=%d solution_count=%d\n",i,solution_count); 84 } 85 86 system("pause"); 87 88 return 0; 89 }
另外,根据class_c的博客八皇后问题还有其他解法:
最近学习算法分析,突然对八皇后问题十分感兴趣,下面是我研究一段时间后的思想汇总。希望能够引起各位算法爱好者的共鸣,当然如果有什么遗漏之处希望互相交流。
一:回溯法
这种算法想必学习计算机算法分析与设计的人都应该知道,说白了,这个算法就是一个搜索算法,对一棵树进行深度优先搜索,但是在搜索的过程中具有自动终止返 回上一层继续往下搜索的能力,这个算法其实就是一个搜索树,对部分节点进行了剪枝是一种可行的算法,对八皇后这样皇后数较少的问题还能够解决,如果皇后数 再大一点就无能为力了,当然我们通过这学习了一种算法。至于具体算法怎么实现在此就不一一详述了,因为大部分网站都有具体的源码,核心思想也基本相同,只 是编程应用的数据结构有所不同而已。
二:概率算法
这种算法网络上很少有,在此我只是提供一点思路供爱好者探讨。
基本思想:首先应用随机函数在一个8*8的矩阵中放入合适的4个皇后(即一半的皇后)然后再应用之前的回溯的方法进行搜索,随后循环这样的一个过程,当时 间容许的情况下,很快就可以找到满足所有的解。当然这种方法对回溯法只是进行了一点点的改动,但时间复杂度上将有很大的提高。算法源码在此也不予提供,有 兴趣的可以与我联系。
博主注:拉斯维加斯概率算法,算法如下
算法10.5——八皇后问题 1. 将数组x[8]初始化为0;试探次数count初始化为0; 2.for (i=1; i<=8; i++) 2.1 产生一个[1, 8] 的随机数j; 2.2 count=count+1,进行第count次试探; 2.3 若皇后i放置在位置j不发生冲突, 则x[i]=j;count=0;转步骤2放置下一个皇后; 2.4 若(count= =8),则无法放置皇后i,算法运行失败; 否则,转步骤2.1重新放置皇后i; 3. 将元素x[1]~x[8]作为八皇后问题的一个解输出;
三:A*算法
这种算法原本是人工智能里求解搜索问题的解法,但八皇后问题也同样是一个搜索问题,因此可以应用这种方法来进行求解。这里关于A*算法的定义以及一些概念 就不提供了,大家可以上网进行搜索,网上有很多关于A*算法的详细介绍。如果有兴趣的朋友可以借阅一本人工智能的书籍上面有关于A*算法求解八皇后问题的 详细解答过程,在此我就不多说了,我只是提供一种学习的思路。
四:广度优先搜索
这个是和回溯法搜索相反的一种方法,大家如果学过数据结构的应该知道,图的搜索算法有两种,一种是深度优先搜索,二种是广度优先搜索。而前面的回溯算法回 归到图搜索的本质后,发现其实是深度优先搜索,因此必定会有广度优先搜索解决八皇后问题,由于应用广度优先搜索解决八皇后问题比应用深度优先搜索其空间复 杂度要高很多,所以很少有人去使用这种方法,但是这里我们是来学习算法的,这种算法在此不适合,不一定在其他的问题中就不适合了,有兴趣的朋友可以参考任 何一本数据结构的数据上面都有关于广度优先搜索的应用。
总结:上面我对八皇后问题的这种算法只是起了一点抛砖引玉的作用,没有深入讲解,一是自己时间有限,二是自己研究的也不够彻底,在此只能提供一点点解题思 路希望大家看到后可以有一点前进的方向,最后说一下其实八皇后问题还可以应用一些更加高级的算法进行求解,例如遗传算法,蚁群算法,粒子群算法等等,这些 算法我还在研究中希望有兴趣的朋友可以和我交流。以上都仅为个人观点,如果错误之处请指正本人不胜感激。