皇后问题 - acgtyrant/Algorithm-and-Data-Structure GitHub Wiki

感觉皇后问题好高大上,用 C++ 十行代码实现,就可以有 15k 待遇

我想了一个小时,甚至边吃饭边想,发现这个可以用递归算法解决。简而言之,在当前 chessboard 对象里按一定的索引规律,找出依次相邻的两个空 board, 分别叫 first_empty_board, second_empty_board 吧。各自填充进一个皇后并更新整个 chessboard, 如此反复双弹递归,直到再也填充不下。如果填充的皇后数等于棋盘长度,那么这就是一个有效的解 solution,push 进全局变量 g_solutions 即可。

但时间复杂度高达指数阶!不过据说现在没有多项式时间复杂度算法,这么一来我在递归思想上应该合格了吧?(窃笑

刚开始动写代码,纠结用什么数据结构了半天。用二维数组?但传函数形参好麻烦;用 std::vector? 但代码肯定又冗长无比。后来我决定还是用后者好了,毕竟一样可以用索引操作符。不过 @innocentim 启发了我,可以把算法与数据结构分开做,优先解决好算法问题,即用最简单的固定长度一维全局数组,搞定算法后再优化数据结构也不迟。

不过我硬是用上了一个 std::vector<std::vector<bool>>. 具体代码如下。

#include <algorithm>
#include <cassert>
#include <set>
#include <utility>
#include <vector>

using BoardIndex = std::pair<int, int>;
std::pair<int, int> g_invalid_board(0, 0);
std::set<std::set<BoardIndex>> g_solutions;

std::pair<BoardIndex, BoardIndex> TwoEmptyBoard(
    std::vector<std::vector<bool>> cheesboard) {
  bool is_first_found = false;
  BoardIndex first_empty_board = g_invalid_board;
  BoardIndex second_empty_board = g_invalid_board;
  int row = 1;
  for (auto longboard : cheesboard) {
    int column = 1;
    for (auto board : longboard) {
      if (board == false && !is_first_found) {
        first_empty_board = std::pair<int, int>(row, column);
        is_first_found = true;
      } else {
        second_empty_board = std::pair<int, int>(row, column);
      }
      ++column;
    }
    ++row;
  }
  return std::pair<BoardIndex, BoardIndex>(
      first_empty_board,
      second_empty_board);
}

void FillBoard(
    BoardIndex queen_board,
    std::vector<std::vector<bool>> cheesboard) {
  int row = queen_board.first;
  int column = queen_board.second;
  for (auto iterator = cheesboard[row].begin();
      iterator != cheesboard[row].end();
      ++iterator) {
    *iterator = true;
  }
  for (auto iterator = cheesboard.begin();
      iterator != cheesboard.end();
      ++iterator) {
    (*iterator)[column] = true;
  }
}

void Solute(
    std::vector<std::vector<bool>> cheesboard,
    std::set<BoardIndex> solution) {
  assert(number >= 0);
  if (solution.size() == cheesboard.size()) {
    g_solutions.insert(solution);
    return;
  }
    
  auto two_empty_cheesboard = TwoEmptyBoard(cheesboard);
  auto temporary_cheesboard = cheesboard;  // use for second_empty_board
  auto first_empty_board = two_empty_cheesboard.first;
  if (first_empty_board != g_invalid_board) {
    FillBoard(first_empty_board, cheesboard);
    solution.insert(first_empty_board);
    Solute(cheesboard, solution);
  }
  auto second_empty_board = two_empty_cheesboard.second;
  if (second_empty_board != g_invalid_board) {
    FillBoard(second_empty_board, temporary_cheesboard);
    solution.insert(second_empty_board);
    Solute(temporary_cheesboard, solution);
  } 
}

// int main(int argc, char *argv[]) {
  // return 0;
// }

一共 79 行,事实上我在写 FillBoard 时就放弃继续实现了,横竖填充还好,但写填充斜行的代码就痛不欲生!于是连同 main 里的测试也不写了,如今我很惊叹究竟要用怎样巧夺天工的数据结构,才能代码仅十行就大破八皇后之问。此外此代码实现也有很多地方让我不安,比如用到了不少全局变量,那当分离式编译此代码时,其定义顺序行为未定义;Solute 找出两个空 board 的代码略冗余,有太多的复制构造销毁开销了;此外索引也很令人头痛,代码中有些索引值是错的。

GAME OVER. (:з」∠)

⚠️ **GitHub.com Fallback** ⚠️