Colin’s Blog

A C++ Programmer

N queens Problem

This article will provide a fast way to find a solution for the famous N queens problem.

Note: we only try to find one possibility of all positions of chesses, NOT (ALL posibilities).

For example, when N=8, there are 92 ways to put the chesses. However, our algorithm just focus on finding one of these 92 ways.

If you still feels not familar with the problem we will solve. Please Google 8 queens problem and look down.

N=4

When N=4, there are only two ways to put these chesses.

10100
20001
31000
40010
10010
21000
30001
40100

Our program, as I just said, will try to find one of the two ways.

So, Here we go~

Code

  1#include <stdio.h>
  2#include <stdlib.h>
  3#include <time.h>
  4#include <string.h>
  5
  6#define N 123456789
  7
  8
  9// 最大能运行多少个皇后
 10
 11int output_ok;
 12
 13void swap_int(int *p, int *q)
 14{
 15    int temp;
 16    temp = *(p);
 17    *(p) = *(q);
 18    *(q) = temp;
 19}
 20
 21int swap_ok, change_of_collisions, temp, n;
 22// swap_ok判断是否交换两个皇后会使得collisions减小
 23// change_of_collisions计算交换前后的collisions的变化量
 24// temp用作交换两个变量的临时变量
 25// n为皇后数目
 26
 27void print_solve1(int *x, int n, FILE *fp) // 传入queen,n和fp
 28// 输出形象化的棋盘
 29// *代表皇后 -代表棋盘
 30{
 31    fprintf(fp, "\nBEGIN\n");
 32    for (int i = 1; i <= n; ++i)
 33    {
 34        for (int j = 1; j <= n; ++j)
 35        {
 36            if (j == x[i])
 37            {
 38                fprintf(fp, "*");
 39            }
 40            else
 41            {
 42                fprintf(fp, "-");
 43            }
 44        }
 45        fprintf(fp, "\n");
 46    }
 47    fprintf(fp, "\nEND\n\n\n");
 48}
 49
 50void print_solve(int *x, int n, FILE *fp) // 传入queen,n,fp
 51// 输出皇后数列到文件n-queens-out.txt
 52{
 53    fprintf(fp, "%d\n", n);
 54    for (int i = 1; i <= n; ++i)
 55    {
 56        fprintf(fp, "%d ", x[i]);
 57    }
 58    if (n <= 20)
 59    {
 60        print_solve1(x, n, fp);
 61    }
 62}
 63
 64int main()
 65{
 66    FILE *fp = fopen("n-queens-out.txt", "w");
 67
 68    int n;
 69    // n皇后
 70    printf("@Author: ZhangJia\n");
 71    printf("@university: SCGY of USTC\n");
 72    printf("@Github: Oyyko\n");
 73    printf("\n");
 74    printf("\n");
 75    printf("Please Input the number N where the board is N*N.\n");
 76    printf("which means we will have N queens\n");
 77    scanf("%d", &n);
 78
 79    printf("Do you want to output to\"n-queens-out.txt\"\n");
 80    printf("input 1 as yes ,other character as no\n");
 81    scanf("%d", &output_ok);
 82    if (output_ok != 1)
 83    {
 84        output_ok = 0;
 85    }
 86    printf("\nProgram START\n");
 87
 88    clock_t start, end;
 89    //用于记录时间
 90
 91    int *pre_queen = (int *)malloc(sizeof(int) * N);
 92    int *pre_dn = (int *)malloc(sizeof(int) * (2 * N - 1));
 93    int *pre_dp = (int *)malloc(sizeof(int) * (2 * N - 1));
 94    int *pre_attack = (int *)malloc(sizeof(int) * N);
 95
 96    int *queen = pre_queen - 1;
 97    int *dn = pre_dn - 2;
 98    int *dp = pre_dp - 1 + n;
 99    int *attack = pre_attack - 1;
100    // 四个pre数组开辟空间
101    // 之后由对应的指针指向它们中间的位置
102    // 从而满足算法运行对数组下标的要求(即可能会出现负的下标)
103    // 例如 queen[1] = pre_queen[0]
104    // 例如 queen[m] = pre_queen[m-1]
105    // 例如 dn[m]    = pre_dn[m-2]
106    // 例如 dp[m]    = pre_dp[m+n-1]
107    // 例如 dp[-2]   = pre_dp[-2+n-1]
108
109    // 数列queen[i]=j 表示第i行的皇后在第j列
110    // 数列dn[i]=k 表示斜率为负的对角线中编号为i的对角线上有k个皇后
111    // 数列dp[i]=k 表示斜率为正的对角线中编号为i的对角线上有k个皇后
112    // dn,dp的编号方法分别为(列号+行号),(列号-行号)
113    // 数列attack[i]=j 表示第i个被攻击的皇后在queen中的序号为j
114
115    memset(pre_dn, 0, sizeof(int) * (2 * n - 1));
116    memset(pre_dp, 0, sizeof(int) * (2 * n - 1));
117
118    int limit, collisions, number_of_attacks, loopcount;
119
120    collisions = 1;
121    start = clock();
122
123    while (collisions)
124    // 当评估函数为0时,不断执行,直到评估函数为0
125    // 评估函数为0也就是找到一组解
126    {
127        memset(pre_dn, 0, sizeof(int) * (2 * n - 1));
128        memset(pre_dp, 0, sizeof(int) * (2 * n - 1));
129        collisions = 0;
130
131        for (int i = 0; i <= n - 1; ++i)
132        {
133            pre_queen[i] = i + 1;
134        }
135        int Search_Max = (n << 2);
136        size_t x = -1;
137        x = (x >> 1);
138        int i, j;
139        int m;
140        int rand_max = n;
141        size_t rand_seed;
142        rand_seed = (size_t)time(NULL);
143        for (i = 1, j = 1; i <= Search_Max && j <= n; ++i)
144        {
145            // 我们的目标是在初始化阶段生成一个很好的初态
146            // 这样我们就能很快的爬山找到最优解
147            // 因为初态的collisions已经很小
148            // 因此爬山的过程会大幅度缩短
149            // 优化的方法就是在初始化的时候就尽可能的避免碰撞
150            // j的作用是衡量已经有多少个皇后被优化处理了,如果优化了n个,那么说明该停止优化了
151            // i的作用是限制优化阶段所采用的时间 每循环一次i就自加1
152            // 当i大于Search_Max的时候,强制退出优化过程
153            rand_seed *= 1103515245;
154            rand_seed += 12345;
155            rand_seed = rand_seed % (x + 1);
156            // LCG随机数生成器
157            // X(n+1) = (a * X(n) + c) % m
158            // 此处我采用a等于1103515245 c等于12345 的参数取法
159            // 该取法为gcc编译器的参数取值
160            // 为了合理的取值范围我采用了size_t类型
161            // 实际上就是生成一个 0~x的随机数
162
163            m = rand_max * ((double)rand_seed / (double)x) + j;
164            // m为 j~n之间的一个随机数
165            if (!(dn[queen[m] + j]) && !(dp[queen[m] - j]))
166            // 如果 皇后m 与 皇后j 交换可以减小评估函数的值
167            // 那么就交换m与j
168            {
169                swap_int(&queen[m], &queen[j]);
170                ++dn[queen[j] + j];
171                ++dp[queen[j] - j];
172                ++j;
173                --rand_max;
174            }
175        }
176        // 在优化完了j个皇后之后
177        // 打乱剩下的皇后,以使得初态更加平均
178        // 同时计算出所有dn与dp的值
179        for (i = j; i <= n; ++i)
180        {
181            m = rand() % rand_max + i;
182            swap_int(&queen[m], &queen[i]);
183            ++dn[queen[i] + i];
184            ++dp[queen[i] - i];
185            --rand_max;
186        }
187        // 以上完成了对整个棋盘的初始化
188
189        for (int i = 2; i <= 2 * n; ++i)
190        {
191            if (dn[i] > 1)
192                collisions += (dn[i] - 1);
193        }
194        for (int i = 1 - n; i <= n - 1; ++i)
195        {
196            if (dp[i] > 1)
197                collisions += (dp[i] - 1);
198        }
199        if (!collisions)
200        // 若恰好生成了满足要求的皇后排列,则输出答案并且结束程序
201        {
202            end = clock();
203            printf("It takes %f seconds to find a solotion\n", (double)(end - start) / CLOCKS_PER_SEC);
204            print_solve(queen, n, fp);
205            exit(0);
206        }
207
208        limit = collisions >> 1; // means limit=0.5*collisions
209                                 // limit的作用是评估何时适合重新计算attack数组,从而达到更快的运行速度。
210                                 // 如果每次交换都重新计算attack数组,那么开销过大.
211                                 // 为此我们采用设置阈值的方法
212                                 // 仅当collision<limit时,才重新计算attack。从而减小了不必要的损耗
213
214        // compute attack START
215
216        number_of_attacks = 0;
217        int k = 1;
218        for (int i = 1; i <= n; ++i)
219        {
220            if (dn[queen[i] + i] > 1 || dp[queen[i] - i] > 1)
221            {
222                attack[k++] = i;
223                ++number_of_attacks;
224            }
225        }
226        //compute attack END
227
228        loopcount = 0;
229        // loopcount用来判断何时随机重启
230        // 每次爬山都会增加loopcount的值
231        // 当loopcount比较大时,说明爬山法陷入了局部困境,需要进行随机重启
232
233        // Initialization END
234        // 初始化过程结束,下面开始爬山算法
235
236        while (loopcount < (n << 5))
237        {
238            for (int k = 1; k <= number_of_attacks; ++k)
239            {
240                int i = attack[k];
241                int j = ((rand() << 6) + rand()) % (n) + 1;
242                // 取一个被攻击的皇后和一个随机取得皇后,观察是否可以交换
243
244                swap_ok = 0;
245                change_of_collisions = 0;
246
247                change_of_collisions -= (dp[queen[i] - i] > 1);
248                change_of_collisions -= (dp[queen[j] - j] > 1);
249                change_of_collisions -= (dn[queen[i] + i] > 1);
250                change_of_collisions -= (dn[queen[j] + j] > 1);
251
252                change_of_collisions += (dn[queen[j] + i] >= 1);
253                change_of_collisions += (dn[queen[i] + j] >= 1);
254                change_of_collisions += (dp[queen[j] - i] >= 1);
255                change_of_collisions += (dp[queen[i] - j] >= 1);
256                // 计算评估函数的改变量
257                if (change_of_collisions < 0)
258                {
259                    if (!(queen[i] + i - queen[j] - j) && !(dp[queen[i] - j]))
260                    {
261                        change_of_collisions += 2;
262                    }
263                    if (!(queen[i] - i - queen[j] + j) && !(dn[queen[i] + j]))
264                    {
265                        change_of_collisions += 2;
266                    }
267                    if (change_of_collisions < 0)
268                    {
269                        // perform swap
270                        // 若改变量小于0,则执行交换
271                        --dn[queen[i] + i];
272                        --dp[queen[i] - i];
273                        --dn[queen[j] + j];
274                        --dp[queen[j] - j];
275                        ++dn[queen[j] + i];
276                        ++dn[queen[i] + j];
277                        ++dp[queen[j] - i];
278                        ++dp[queen[i] - j];
279
280                        temp = queen[j];
281                        queen[j] = queen[i];
282                        queen[i] = temp;
283
284                        collisions += change_of_collisions;
285
286                        if (!collisions)
287                        // 若找到了解,则输出答案并且结束程序
288                        {
289                            end = clock();
290                            printf("It takes %f seconds to find a solotion\n", (double)(end - start) / CLOCKS_PER_SEC);
291                            printf("Press any key to EXIT\n");
292                            if (output_ok)
293                                print_solve(queen, n, fp);
294                            getchar();
295                            getchar();
296                            exit(0);
297                        }
298                        if (collisions < limit)
299                        // 当棋盘变动较大时,重新计算attack数列
300                        {
301                            limit = collisions >> 1;
302                            // compute attack
303
304                            number_of_attacks = 0;
305                            int k = 1;
306                            for (int i = 1; i <= n; ++i)
307                            {
308                                if (dn[queen[i] + i] > 1 || dp[queen[i] - i] > 1)
309                                {
310                                    attack[k++] = i;
311                                    ++number_of_attacks;
312                                }
313                            }
314                        }
315                    }
316                }
317            }
318
319            loopcount = loopcount + number_of_attacks;
320        }
321    }
322
323    return 0;
324}