zoukankan      html  css  js  c++  java
  • 八皇后问题的两个高效的算法(回溯与递归)

    序言

    八皇后问题是一个经典的问题,在一个N*N的棋盘上放置N个皇后,每行一个并使其不能互相攻击(同一行、同一列、同一斜线上的皇后都会自动攻击)。

    求解八皇后问题是算法中回溯法应用的一个经典案例

           回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。

          在现实中,有很多问题往往需要我们把其所有可能穷举出来,然后从中找出满足某种要求的可能或最优的情况,从而得到整个问题的解。回溯算法就是解决这种问题的“通用算法”,有“万能算法”之称。N皇后问题在N增大时就是这样一个解空间很大的问题,所以比较适合用这种方法求解。这也是N皇后问题的传统解法,很经典。

          下面是算法的高级伪码描述,这里用一个N*N的矩阵来存储棋盘:

          1) 算法开始, 清空棋盘,当前行设为第一行,当前列设为第一列

          2) 在当前行,当前列的位置上判断是否满足条件(即保证经过这一点的行,列与斜线上都没有两个皇后),若不满足,跳到第4步

          3) 在当前位置上满足条件的情形:

                     在当前位置放一个皇后,若当前行是最后一行,记录一个解;

                     若当前行不是最后一行,当前行设为下一行, 当前列设为当前行的第一个待测位置;

                     若当前行是最后一行,当前列不是最后一列,当前列设为下一列;

                     若当前行是最后一行,当前列是最后一列,回溯,即清空当前行及以下各行的棋盘,然后,当前行设为上一行,当前列设为当前行的下一个待测位置;

                    以上返回到第2步

          4) 在当前位置上不满足条件的情形:

                    若当前列不是最后一列,当前列设为下一列,返回到第2步;

                    若当前列是最后一列了,回溯,即,若当前行已经是第一行了,算法退出,否则,清空当前行及以下各行的棋盘,然后,当前行设为上一行,当前列设为当前行的下一个待测位置,返回到第2步; 

            算法的基本原理是上面这个样子,但不同的是用的数据结构不同,检查某个位置是否满足条件的方法也不同。为了提高效率,有各种优化策略,如多线程,多分配内存表示棋盘等。

    使用递归时的核心算法

    //放置皇后到棋盘上
    void place(int k, int n)
    {
    int j;
    if (k > n)
    print(n); //递归出口
    else
    for (j = 1; j <= n; j++)   //试探第k行的每一个列
    if (find(k, j))
    {
    q[k] = j;   //保存位置
    place(k + 1, n);  //接着下一行
    }
    }

    回溯算法的核心算法


    void eight_queen(int line) {
    //在数组中为0-7列
    for (int list = 0; list < 8; list++) {
    //对于固定的行列,检查是否和之前的皇后位置冲突
    if (Check(line, list)) {
    //不冲突,以行为下标的数组位置记录列数
    Queenes[line] = list;
    //如果最后一样也不冲突,证明为一个正确的摆法
    if (line == 7) {
    //统计摆法的Counts加1
    Counts++;
    //输出这个摆法
    print();
    //每次成功,都要将数组重归为0
    Queenes[line] = 0;
    return;
    }
    //继续判断下一样皇后的摆法,递归
    eight_queen(line + 1);
    //不管成功失败,该位置都要重新归0,以便重复使用。
    Queenes[line] = 0;
    }
    }
    }

    八个皇后在8x8棋盘上共有4,426,165,368(64C8)种摆放方法,但只有92个互不相同的解。如果将旋转和对称的解归为一种的话,则一共有12个独立解,具体如下:

     

    完整代码:

    /*
    递归法实现
    */
    #include <stdio.h>
    #include <stdlib.h>

    const int N = 20;   //最多放皇后的个数
    int q[N];         //i表示皇后所在的行号,
     //q[i]表示皇后所在的列号
    int cont = 0;     //统计解的个数
    //输出一个解
    void print(int n)
    {
    int i, j;
    cont++;
    printf("第%d个解:", cont);
    for (i = 1; i <= n; i++)
    printf("(%d,%d) ", i, q[i]);
    printf(" ");
    for (i = 1; i <= n; i++)        //行
    {
    for (j = 1; j <= n; j++)    //列
    {
    if (q[i] != j)
    printf("x ");
    else
    printf("Q ");
    }
    printf(" ");
    }
    }
    //检验第i行的k列上是否可以摆放皇后
    int find(int i, int k)
    {
    int j = 1;
    while (j < i)  //j=1~i-1是已经放置了皇后的行
    {
    //第j行的皇后是否在k列或(j,q[j])与(i,k)是否在斜线上
    if (q[j] == k || abs(j - i) == abs(q[j] - k))
    return 0;
    j++;
    }
    return 1;
    }
    //放置皇后到棋盘上
    void place(int k, int n)
    {
    int j;
    if (k > n)
    print(n); //递归出口
    else
    for (j = 1; j <= n; j++)   //试探第k行的每一个列
    if (find(k, j))
    {
    q[k] = j;   //保存位置
    place(k + 1, n);  //接着下一行
    }
    }
    int main(void)
    {
    int n;
    printf("请输入皇后的个数(n<=20),n=:");
    scanf("%d", &n);
    if (n > 20)
    printf("n值太大,不能求解! ");
    else
    {
    printf("%d皇后问题求解如下(每列的皇后所在的行数): ", n);
    place(1, n);        //问题从最初状态解起
    printf(" ");
    }
    system("pause");
    return 0;
    }

    /*
    回溯法实现
    */
    #include <stdio.h>
    int Queenes[8] = { 0 }, Counts = 0;
    int Check(int line, int list) {
    //遍历该行之前的所有行
    for (int index = 0; index < line; index++) {
    //挨个取出前面行中皇后所在位置的列坐标
    int data = Queenes[index];
    //如果在同一列,该位置不能放
    if (list == data) {
    return 0;
    }
    //如果当前位置的斜上方有皇后,在一条斜线上,也不行
    if ((index + data) == (line + list)) {
    return 0;
    }
    //如果当前位置的斜下方有皇后,在一条斜线上,也不行
    if ((index - data) == (line - list)) {
    return 0;
    }
    }
    //如果以上情况都不是,当前位置就可以放皇后
    return 1;
    }
    //输出语句
    void print()
    {
    for (int line = 0; line < 8; line++)
    {
    int list;
    for (list = 0; list < Queenes[line]; list++)
    printf("0");
    printf("#");
    for (list = Queenes[line] + 1; list < 8; list++) {
    printf("0");
    }
    printf(" ");
    }
    printf("================ ");
    }
    void eight_queen(int line) {
    //在数组中为0-7列
    for (int list = 0; list < 8; list++) {
    //对于固定的行列,检查是否和之前的皇后位置冲突
    if (Check(line, list)) {
    //不冲突,以行为下标的数组位置记录列数
    Queenes[line] = list;
    //如果最后一样也不冲突,证明为一个正确的摆法
    if (line == 7) {
    //统计摆法的Counts加1
    Counts++;
    //输出这个摆法
    print();
    //每次成功,都要将数组重归为0
    Queenes[line] = 0;
    return;
    }
    //继续判断下一样皇后的摆法,递归
    eight_queen(line + 1);
    //不管成功失败,该位置都要重新归0,以便重复使用。
    Queenes[line] = 0;
    }
    }
    }
    int main() {
    //调用回溯函数,参数0表示从棋盘的第一行开始判断
    eight_queen(0);
    printf("摆放的方式有%d种", Counts);
    return 0;
    }
  • 相关阅读:
    1.Android 视图及View绘制分析笔记之setContentView
    Android 6.0
    include、merge 、ViewStub
    Vitamio视频播放器
    EventBus 二
    EventBus 一
    ZJOI2002 昂贵的聘礼
    [POI2009]WIE-Hexer
    UVA 11440 Help Tomisu
    洛谷 2448 无尽的生命
  • 原文地址:https://www.cnblogs.com/RioTian/p/12327657.html
Copyright © 2011-2022 走看看