N皇后算法问题
N皇后的递归算法其实并不是很难设计,但是要设计出比较高效的就比较有难度了.下面这个程序
是网上比较流行的算法.刚开始研究了好久才明白怎么回事.
其实作者很巧妙的利用了C系语言的位操作来存储递归中的各种状态,并把行操作和列操作都变成
一个东西来处理了.看算法的时候,心里一定要有一张N*N的比特图,这样好懂点,虽然作者并没有
直接用到比特矩阵来存储.
需说明的是该算法并没有打印出每一种解决方案,而是求出了方案的个数而已.
好好欣赏一下.
1 /************************************************************************/
2 /* 目前最快的N皇后递归解决方法 */
3 /************************************************************************/
4 #include <iostream>
5 using namespace std;
6 #include <time.h>
7 long sum=0,upperlim=1;
8 //该算法的思想是遍历每一行(虽然算法中没有直接体现行遍历),然后对每一行中的所有
9 //有效列(即与上面行已放置皇后在列和左右对角线方向上不会造成冲突的列)进行尝试
10 //row是代表已经遍历的列,1表示该列已被皇后占有了
11 void GetSum(long row, long rightdiagonal, long leftdiagonal)
12 {
13
14 if (row != upperlim)
15 {
16 //下面进行行遍历
17 //row表示当前行中每一列放置了皇后,则该列在之后的行遍历中均不能再放置皇后了
18 //rightdiagonal表示在下一行的尝试中当前放置点的左一列位置(下一行左一列的点与当前放置
19 //点刚好形成了右对角线,会造成冲突)不可用
20 //同样的leftdiagonal表示在下一行的尝试中当前放置点的右一列(下一行右一列的点与当前放置
21 //点刚好形成了左对角线,会造成冲突)不可用
22 long pos = upperlim & ~(row | leftdiagonal | rightdiagonal);
23 //这里得到该行中所有可以尝试,即有效的列(格),然后从右到左进行尝试
24 //如果遍历到的该行上的每一列已经都是无效的话则无需进行下一行的尝试了,方案失败.
25 while (pos)
26 {
27 //该位操作能得到一个二进制数组中最右面的1的位置,如pos = 00010110进行如下操作之后
28 //p = 00000010,p的作用是取得当前遍历的行中所有有效列的最右面的一列(格)(其实每次取
29 //最左的列也行的)
30 long p = pos & -pos;
31 pos -= p;
32 //p代表的是当前行上放置的列,递归调用函数遍历下一行
33 //此时需将本行的尝试的列在row中标记上表明下一行不可再在此列放置皇后了
34 //同时,在下一行中,本放置点的左一列和右一列也需标记为不可再放置
35 GetSum(row+p, (rightdiagonal+p)<<1, (leftdiagonal+p)>>1);
36 }
37 }
38 else //每一列都被皇后占领了,表示该方案可行
39 {
40 sum++;
41 }
42 }
43 int main(int argc, char *argv[])
44 {
45 time_t tm; int n=15;
46 if(argc!=1)
47 n=atoi(argv[1]);
48 tm=time(0);
49 if((n<1)||(n>32))
50 {
51 printf(" heh..I can't calculate that.\n");
52 exit(-1);
53 }
54
55 printf("%d Queens\n",n);
56 //upperlim中低n位为1,用于标识探索是否已完成
57 upperlim=(upperlim<<n) - 1;
58 GetSum(0,0,0);
59 printf("Number of solutions is %ld, %d seconds\n", sum,(int)(time(0)-tm));
60 return 0;
61 }
下面是8皇后(当然也可以扩展为N皇后,很简单自己想把)的另外一种比较巧妙的解决方法,该方法利用的
是string类型,设计的整体效率相对上面的算法低一点,但比较好懂.
是网上比较流行的算法.刚开始研究了好久才明白怎么回事.
其实作者很巧妙的利用了C系语言的位操作来存储递归中的各种状态,并把行操作和列操作都变成
一个东西来处理了.看算法的时候,心里一定要有一张N*N的比特图,这样好懂点,虽然作者并没有
直接用到比特矩阵来存储.
需说明的是该算法并没有打印出每一种解决方案,而是求出了方案的个数而已.
好好欣赏一下.
1 /************************************************************************/
2 /* 目前最快的N皇后递归解决方法 */
3 /************************************************************************/
4 #include <iostream>
5 using namespace std;
6 #include <time.h>
7 long sum=0,upperlim=1;
8 //该算法的思想是遍历每一行(虽然算法中没有直接体现行遍历),然后对每一行中的所有
9 //有效列(即与上面行已放置皇后在列和左右对角线方向上不会造成冲突的列)进行尝试
10 //row是代表已经遍历的列,1表示该列已被皇后占有了
11 void GetSum(long row, long rightdiagonal, long leftdiagonal)
12 {
13
14 if (row != upperlim)
15 {
16 //下面进行行遍历
17 //row表示当前行中每一列放置了皇后,则该列在之后的行遍历中均不能再放置皇后了
18 //rightdiagonal表示在下一行的尝试中当前放置点的左一列位置(下一行左一列的点与当前放置
19 //点刚好形成了右对角线,会造成冲突)不可用
20 //同样的leftdiagonal表示在下一行的尝试中当前放置点的右一列(下一行右一列的点与当前放置
21 //点刚好形成了左对角线,会造成冲突)不可用
22 long pos = upperlim & ~(row | leftdiagonal | rightdiagonal);
23 //这里得到该行中所有可以尝试,即有效的列(格),然后从右到左进行尝试
24 //如果遍历到的该行上的每一列已经都是无效的话则无需进行下一行的尝试了,方案失败.
25 while (pos)
26 {
27 //该位操作能得到一个二进制数组中最右面的1的位置,如pos = 00010110进行如下操作之后
28 //p = 00000010,p的作用是取得当前遍历的行中所有有效列的最右面的一列(格)(其实每次取
29 //最左的列也行的)
30 long p = pos & -pos;
31 pos -= p;
32 //p代表的是当前行上放置的列,递归调用函数遍历下一行
33 //此时需将本行的尝试的列在row中标记上表明下一行不可再在此列放置皇后了
34 //同时,在下一行中,本放置点的左一列和右一列也需标记为不可再放置
35 GetSum(row+p, (rightdiagonal+p)<<1, (leftdiagonal+p)>>1);
36 }
37 }
38 else //每一列都被皇后占领了,表示该方案可行
39 {
40 sum++;
41 }
42 }
43 int main(int argc, char *argv[])
44 {
45 time_t tm; int n=15;
46 if(argc!=1)
47 n=atoi(argv[1]);
48 tm=time(0);
49 if((n<1)||(n>32))
50 {
51 printf(" heh..I can't calculate that.\n");
52 exit(-1);
53 }
54
55 printf("%d Queens\n",n);
56 //upperlim中低n位为1,用于标识探索是否已完成
57 upperlim=(upperlim<<n) - 1;
58 GetSum(0,0,0);
59 printf("Number of solutions is %ld, %d seconds\n", sum,(int)(time(0)-tm));
60 return 0;
61 }
下面是8皇后(当然也可以扩展为N皇后,很简单自己想把)的另外一种比较巧妙的解决方法,该方法利用的
是string类型,设计的整体效率相对上面的算法低一点,但比较好懂.
1 #include <iostream>
2 #include <string>
3 using namespace std;
4 //t表示被占领的行,s表示未测试过的行
5 void queen(const string t, const string s)
6 {
7 if (s=="") printf("%s \n",t.c_str());
8 else
9 //第一个for循环测试当前测试列的每一行
10 for (int i=0; i<s.length(); i++) {
11 bool safe=true;
12 //第二个for循环测试当前点与之前点是否处于同一对角线上
13 //由于字符串的设计巧妙,使判断冲突情况(即皇后可以互相吃掉对方)限制在了对角线的情况上而已
14 for (int j=0;j<t.length();j++)
15 {
16 //列间隔等于行间隔,即为对角线关系
17 if (t.length()-j==abs(s[i]-t[j]))
18 {
19 safe=false;
20 break;
21 }
22 }
23 if (safe) //如果对角线没有冲突,探索下一列
24 queen(t+s[i], s.substr(0,i)+s.substr(i+1));
25 }
26 }
27
28 int main()
29 {
30 //string中的每一位代表一列,从左到右开始,而每一位中的数字代表了行号
31 //由于每个位的数字都是唯一的,因此可以保证已探索列和待探索列之间不会有
32 //行相同的情况.逻辑上也是一个二维矩阵
33 string s="01234567";
34 queen("",s);
35 exit(EXIT_SUCCESS);
36 return 0;
37 }
2 #include <string>
3 using namespace std;
4 //t表示被占领的行,s表示未测试过的行
5 void queen(const string t, const string s)
6 {
7 if (s=="") printf("%s \n",t.c_str());
8 else
9 //第一个for循环测试当前测试列的每一行
10 for (int i=0; i<s.length(); i++) {
11 bool safe=true;
12 //第二个for循环测试当前点与之前点是否处于同一对角线上
13 //由于字符串的设计巧妙,使判断冲突情况(即皇后可以互相吃掉对方)限制在了对角线的情况上而已
14 for (int j=0;j<t.length();j++)
15 {
16 //列间隔等于行间隔,即为对角线关系
17 if (t.length()-j==abs(s[i]-t[j]))
18 {
19 safe=false;
20 break;
21 }
22 }
23 if (safe) //如果对角线没有冲突,探索下一列
24 queen(t+s[i], s.substr(0,i)+s.substr(i+1));
25 }
26 }
27
28 int main()
29 {
30 //string中的每一位代表一列,从左到右开始,而每一位中的数字代表了行号
31 //由于每个位的数字都是唯一的,因此可以保证已探索列和待探索列之间不会有
32 //行相同的情况.逻辑上也是一个二维矩阵
33 string s="01234567";
34 queen("",s);
35 exit(EXIT_SUCCESS);
36 return 0;
37 }