八皇后问题—经典回溯算法

八皇后问题

八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。

回溯算法思想

回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。八皇后问题就是回溯算法的典型,第一步按照顺序放一个皇后,然后第二步符合要求放第2个皇后,如果没有位置符合要求,那么就要改变第一个皇后的位置,重新放第2个皇后的位置,直到找到符合条件的位置就可以了。回溯在迷宫搜索中使用很常见,就是这条路走不通,然后返回前一个路口,继续下一条路。回溯算法说白了就是穷举法。不过回溯算法使用剪枝函数,剪去一些不可能到达最终状态(即答案状态)的节点,从而减少状态空间树节点的生成。回溯法是一个既带有系统性又带有跳跃性的的搜索算法。它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。否则,进入该子树,继续按深度优先的策略进行搜索。回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。而回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题。

八皇后实现一

这是我从一篇博文读到的解法,帮助我首次理解八皇后问题的本质,对于初涉者个人认为非常友好,原文地址为:https://cloud.tencent.com/developer/article/1470987。代码注释非常清楚,在此就不文字解释了,具体实现如下:

#include <iostream>

int queenCell[8][8] = { 0 };
int queenPlace[8] = { 0 };
int count = 0;

void printQueen() {  //打印一个二维数组
  for (int i = 0; i < 8; ++i) {
    for (int j = 0; j < 8; ++j) {
      if (queenPlace[i] == j) {
        printf("Q ");
      } else {
        printf("* ");
      }
    }
    printf("\n");
  }
  printf("----count:%d-----\n", ++count);
}

void eightQueen(int row) {
  for (int col = 0; col < 8; ++col) {  //遍历列标,该层循环的作用是用于询问第row行,第column列是否可以放置皇后
    if (queenCell[row][col] == 0) {  //如果第row行,第col列可以放皇后
      for (int nextRow = row + 1; nextRow < 8; ++nextRow) {  // 该层for循环的作用是使其斜下方和正下方不为0
        queenCell[nextRow][col]++;  //将不同行的同一列(正下方)标记为非零,表示不能再在该列放置皇后了
        if (col - nextRow + row >= 0) {  //通过该层循环将第row行、第col列的正对角线上的位置标记为非零,
            queenCell[nextRow][col-nextRow+row]++;  //表示不能在这些位置放置皇后
        }
        if (col + nextRow - row < 8) {   //通过该层循环将第row行、第col列的反对角线上的位置标记为非零
            queenCell[nextRow][col+nextRow-row]++;
        }
      }
      queenPlace[row] = col;  //记录下第row行第column列放置了皇后
      if (row == 7) {  //如果各行都放置了皇后,也就是说如果皇后已放满,打印出皇后布局
        printQueen();
      } else {  //递归继续排列下一行皇后
        eightQueen(row+1);  
      }

      for (int nextRow = row + 1; nextRow < 8; ++nextRow) {  //回溯,使得在第row行的皇后不放在第col列,那放置在那一列? 
                                                             //答案是通过该算法的最外层循环,利用最外层for循环将皇后放在这一行的其他列
        //既然第row行、第col列不放置皇后了,则需要恢复正下方的不可用标记,将不同行的同一列的非零标记还原,也即恢复该位置的正下面的标记                                                            
        queenCell[nextRow][col]--;
        if (col - nextRow + row >= 0) {  //还原第row行、第column列的正对角线上的位置标记
            queenCell[nextRow][col-nextRow+row]--;
        }
        if (col + nextRow - row < 8) {  //还原第row行、第column列的反对角线上的位置标记
            queenCell[nextRow][col+nextRow-row]--;
        }
      }
    }
  }
}

int main() {
    eightQueen(0);
    return 0;
}

八皇后实现二

以下实现是极客时间王争的解法,对比解法一省了二维数组空间,其实更巧妙,思路也非常清晰,如果理解了八皇后问题的本质后建议采用该方法,代码实现如下:

#include <iostream>

int queenPlace[8] = { 8 };  //全局变量,下标表示行,值表示queen存储在那一列
int count = 0;  //计数器

void printQueen() {  //打印一个二维数组
  for (int i = 0; i < 8; ++i) {
    for (int j = 0; j < 8; ++j) {
      if (queenPlace[i] == j) {
        printf("Q ");
      } else {
        printf("* ");
      }
    }
    printf("\n");
  }
  printf("----count:%d-----\n", ++count);
}

bool isOk(int row, int col) {  //判断row行col列放置是否合适
  int leftUp = col - 1;  //左上对角线
  int rightUp = col + 1; //右上对角线
  for (int i = row - 1; i >= 0; --i) { 
    if (queenPlace[i] == col) return false;  //同列上的格子有皇后
    if (leftUp >= 0) {
      if (queenPlace[i] == leftUp) return false;   //左上对角线有皇后
    }
    if (rightUp < 8) {
      if (queenPlace[i] == rightUp) return false;  //右上对角线有皇后
    }
    --leftUp; ++rightUp;
  }
  return true;
}

void eightQueen(int row) {
  if (row == 8) {  //8个皇后都放置好,打印,无法递归返回
    printQueen();
    return;
  }
  for (int col = 0; col < 8; ++col) {  //每一行都有8种方法
    if (isOk(row, col)) {    //满足要求
      queenPlace[row] = col; //第row行的皇后放在col列
      eightQueen(row+1);     //考察下一行
    }
  }
}


int main() {
    eightQueen(0);return 0;
}

猜你喜欢

转载自www.cnblogs.com/evenleee/p/11946724.html