JavaScript解八皇后问题的方法总结


Posted in Javascript onJune 12, 2016

关于八皇后问题的 JavaScript 解法,总觉得是需要学习一下算法的,哪天要用到的时候发现真不会就尴尬了

背景
八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上

八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为 n×n ,而皇后个数也变成n 。当且仅当n = 1或n ≥ 4时问题有解

盲目的枚举算法
通过N重循环,枚举满足约束条件的解(八重循环代码好多,这里进行四重循环),找到四个皇后的所有可能位置,然后再整个棋盘里判断这四个皇后是否会直接吃掉彼此,程序思想比较简单

function check1(arr, n) {
  for(var i = 0; i < n; i++) {
    for(var j = i + 1; j < n; j++) {
      if((arr[i] == arr[j]) || Math.abs(arr[i] - arr[j]) == j - i) {
        return false;
      }
    }
  }
  return true;
}
function queen1() {
  var arr = [];

  for(arr[0] = 1; arr[0] <= 4; arr[0]++) {
    for(arr[1] = 1; arr[1] <= 4; arr[1]++) {
      for(arr[2] = 1; arr[2] <= 4; arr[2]++) {
        for(arr[3] = 1; arr[3] <= 4; arr[3]++) {
          if(!check1(arr, 4)) {
            continue;
          } else {
            console.log(arr);
          }
        }
      }
    }
  }
}

queen1();
//[ 2, 4, 1, 3 ]
//[ 3, 1, 4, 2 ]

关于结果,在 4*4 的棋盘里,四个皇后都不可能是在一排, arr[0] 到 arr[3] 分别对应四个皇后,数组的下标与下标对应的值即皇后在棋盘中的位置

回溯法
『走不通,就回头』,在适当节点判断是否符合,不符合就不再进行这条支路上的探索

function check2(arr, n) {
  for(var i = 0; i <= n - 1; i++) {
    if((Math.abs(arr[i] - arr[n]) == n - i) || (arr[i] == arr[n])) {
      return false;
    }
  }
  return true;
}

function queen2() {
  var arr = [];

  for(arr[0] = 1; arr[0] <= 4; arr[0]++) {
    for(arr[1] = 1; arr[1] <= 4; arr[1]++) {
      if(!check2(arr, 1)) continue; //摆两个皇后产生冲突的情况
      for(arr[2] = 1; arr[2] <= 4; arr[2]++) {
        if(!check2(arr, 2)) continue; //摆三个皇后产生冲突的情况
        for(arr[3] = 1; arr[3] <= 4; arr[3]++) {
          if(!check2(arr, 3)) {
            continue;
          } else {
            console.log(arr);
          }
        }
      }
    }
  }
}

queen2();
//[ 2, 4, 1, 3 ]
//[ 3, 1, 4, 2 ]

非递归回溯法
算法框架:

while(k > 0 『有路可走』 and 『未达到目标』) { // k > 0 有路可走
  if(k > n) { // 搜索到叶子节点
    // 搜索到一个解,输出
  } else {
    //a[k]第一个可能的值
    while(『a[k]在不满足约束条件且在搜索空间内』) {
      // a[k]下一个可能的值
    }
    if(『a[k]在搜索空间内』) {
      // 标示占用的资源
      // k = k + 1;
    } else {
      // 清理所占的状态空间
      // k = k - 1;
    }
  }
}

具体代码如下,最外层while下面包含两部分,一部分是对当前皇后可能值的遍历,另一部分是决定是进入下一层还是回溯上一层

function backdate(n) {
  var arr = [];

  var k = 1; // 第n的皇后
  arr[0] = 1;

  while(k > 0) {

    arr[k-1] = arr[k-1] + 1;
    while((arr[k-1] <= n) && (!check2(arr, k-1))) {
      arr[k-1] = arr[k-1] + 1;
    }
    // 这个皇后满足了约束条件,进行下一步判断

    if(arr[k-1] <= n) {
      if(k == n) { // 第n个皇后
        console.log(arr);
      } else {
        k = k + 1; // 下一个皇后
        arr[k-1] = 0;
      }
    } else {
      k = k - 1; // 回溯,上一个皇后
    }
  }
}

backdate(4);
//[ 2, 4, 1, 3 ]
//[ 3, 1, 4, 2 ]

递归回溯法
递归调用大大减少了代码量,也增加了程序的可读性

var arr = [], n = 4;
function backtrack(k) {
  if(k > n) {
    console.log(arr);
  } else {
    for(var i = 1;i <= n; i++) {
      arr[k-1] = i;
      if(check2(arr, k-1)) {
        backtrack(k + 1);
      }
    }
  }
}

backtrack(1);
//[ 2, 4, 1, 3 ]
//[ 3, 1, 4, 2 ]

华而不实的amb
什么是 amb ?给它一个数据列表,它能返回满足约束条件的成功情况的一种方式,没有成功情况就会失败,当然,它可以返回所有的成功情况。笔者写了上面那么多的重点,就是为了在这里推荐这个amb算法,它适合处理简单的回溯场景,很有趣,让我们来看看它是怎么工作的

首先来处理一个小问题,寻找相邻字符串:拿到几组字符串数组,每个数组拿出一个字符串,前一个字符串的末位字符与后一个字符串的首位字符相同,满足条件则输出这组新取出来的字符串

ambRun(function(amb, fail) {

  // 约束条件方法
  function linked(s1, s2) {
    return s1.slice(-1) == s2.slice(0, 1);
  }

  // 注入数据列表
  var w1 = amb(["the", "that", "a"]);
  var w2 = amb(["frog", "elephant", "thing"]);
  var w3 = amb(["walked", "treaded", "grows"]);
  var w4 = amb(["slowly", "quickly"]);

  // 执行程序
  if (!(linked(w1, w2) && linked(w2, w3) && linked(w3, w4))) fail();

  console.log([w1, w2, w3, w4].join(' '));
  // "that thing grows slowly"
});

看起来超级简洁有没有!不过使用的前提是,你不在乎性能,它真的是很浪费时间!

下面是它的 javascript 实现,有兴趣可以研究研究它是怎么把回溯抽出来的

function ambRun(func) {
  var choices = [];
  var index;

  function amb(values) {
    if (values.length == 0) {
      fail();
    }
    if (index == choices.length) {
      choices.push({i: 0,
        count: values.length});
    }
    var choice = choices[index++];
    return values[choice.i];
  }

  function fail() { throw fail; }

  while (true) {
    try {
      index = 0;
      return func(amb, fail);
    } catch (e) {
      if (e != fail) {
        throw e;
      }
      var choice;

      while ((choice = choices.pop()) && ++choice.i == choice.count) {}
      if (choice == undefined) {
        return undefined;
      }
      choices.push(choice);
    }
  }
}

以及使用 amb 实现的八皇后问题的具体代码

ambRun(function(amb, fail){
  var N = 4;
  var arr = [];
  var turn = [];
  for(var n = 0; n < N; n++) {
    turn[turn.length] = n + 1;
  }
  while(n--) {
    arr[arr.length] = amb(turn);
  }
  for (var i = 0; i < N; ++i) {
    for (var j = i + 1; j < N; ++j) {
      var a = arr[i], b = arr[j];
      if (a == b || Math.abs(a - b) == j - i) fail();
    }
  }
  console.log(arr);
  fail();
});

八皇后问题的JavaScript解法
这是八皇后问题的JavaScript解法,整个程序都没用for循环,都是靠递归来实现的,充分运用了Array对象的map, reduce, filter, concat, slice方法

'use strict';
var queens = function (boarderSize) {
 // 用递归生成一个start到end的Array
 var interval = function (start, end) {
  if (start > end) { return []; }
  return interval(start, end - 1).concat(end);
 };
 // 检查一个组合是否有效
 var isValid = function (queenCol) {
  // 检查两个位置是否有冲突
  var isSafe = function (pointA, pointB) {
   var slope = (pointA.row - pointB.row) / (pointA.col - pointB.col);
   if ((0 === slope) || (1 === slope) || (-1 === slope)) { return false; }
   return true;
  };
  var len = queenCol.length;
  var pointToCompare = {
   row: queenCol[len - 1],
   col: len
  };
  // 先slice出除了最后一列的数组,然后依次测试每列的点和待测点是否有冲突,最后合并测试结果
  return queenCol
   .slice(0, len - 1)
   .map(function (row, index) {
    return isSafe({row: row, col: index + 1}, pointToCompare);
   })
   .reduce(function (a, b) {
    return a && b;
   });
 };
 // 递归地去一列一列生成符合规则的组合
 var queenCols = function (size) {
  if (1 === size) {
   return interval(1, boarderSize).map(function (i) { return [i]; });
  }
  // 先把之前所有符合规则的列组成的集合再扩展一列,然后用reduce降维,最后用isValid过滤掉不符合规则的组合
  return queenCols(size - 1)
   .map(function (queenCol) {
    return interval(1, boarderSize).map(function (row) {
     return queenCol.concat(row);
    });
   })
   .reduce(function (a, b) {
    return a.concat(b);
   })
   .filter(isValid);
 };
 // queens函数入口
 return queenCols(boarderSize);
};

console.log(queens(8));
// 输出结果:
// [ [ 1, 5, 8, 6, 3, 7, 2, 4 ],
//  [ 1, 6, 8, 3, 7, 4, 2, 5 ],
//  ...
//  [ 8, 3, 1, 6, 2, 5, 7, 4 ],
//  [ 8, 4, 1, 3, 6, 2, 7, 5 ] ]

PS:延伸的N皇后问题
当 1848 年国际象棋玩家 Max Bezzel 提出八皇后问题(eight queens puzzle)时,他恐怕怎么也想不到,100 多年以后,这个问题竟然成为了编程学习中最重要的必修课之一。八皇后问题听上去非常简单:把八个皇后放在国际象棋棋盘上,使得这八个皇后互相之间不攻击(国际象棋棋盘是一个 8×8 的方阵,皇后则可以朝横竖斜八个方向中的任意一个方向走任意多步)。虽然这个问题一共有 92 个解,但要想徒手找出一个解来也并不容易。下图就是其中一个解:

JavaScript解八皇后问题的方法总结

八皇后问题有很多变种,不过再怎么也不会比下面这个变种版本更帅:请你设计一种方案,在一个无穷大的棋盘的每一行每一列里都放置一个皇后,使得所有皇后互相之间都不攻击。具体地说,假设这个棋盘的左下角在原点处,从下到上有无穷多行,从左到右有无穷多列,你需要找出一个全体正整数的排列方式 a1, a2, a3, … ,使得当你把第一个皇后放在第一行的第 a1 列,把第二个皇后放在第二行的第 a2 列,等等,那么任意两个皇后之间都不会互相攻击。

JavaScript解八皇后问题的方法总结

下面给出一个非常简单巧妙的构造。首先,我们给出五皇后问题的一个解。并且非常重要的是,其中一个皇后占据了最左下角的那个格子。

JavaScript解八皇后问题的方法总结

接下来,我们把五皇后的解扩展到 25 皇后,而依据则是五皇后本身的布局:

JavaScript解八皇后问题的方法总结

样一来,同一组里的五个皇后显然不会互相攻击,不同组的皇后之间显然也不会互相攻击,这便是一个满足要求的 25 皇后解了。注意到,在扩展之后,之前已经填好的部分并未改变。
再接下来怎么办呢?没错,我们又把 25 皇后的解复制成五份,再次按照五皇后的布局来排列,从而扩展到 125 皇后!

JavaScript解八皇后问题的方法总结

像这样不断地根据已填的部分,成倍地向外扩展,便能生成一个无穷皇后问题的解。

Javascript 相关文章推荐
JS函数重载的解决方案
May 13 Javascript
node.js中的fs.utimes方法使用说明
Dec 15 Javascript
使用js实现的简单拖拽效果
Mar 18 Javascript
javascript算法题:求任意一个1-9位不重复的N位数在该组合中的大小排列序号
Apr 01 Javascript
jQuery实现的调整表格行tr上下顺序
Jan 10 Javascript
有关JS中的0,null,undefined,[],{},'''''''',false之间的关系
Feb 14 Javascript
使用yeoman构建angular应用的方法
Aug 14 Javascript
关于Ajax的原理以及代码封装详解
Sep 08 Javascript
jquery鼠标悬停导航下划线滑出效果
Sep 29 jQuery
jquery实现侧边栏左右伸缩效果的示例
Dec 19 jQuery
从零开始在NPM上发布一个Vue组件的方法步骤
Dec 20 Javascript
webpack4实现不同的导出类型
Apr 09 Javascript
jQuery遍历json的方法(推荐)
Jun 12 #Javascript
jQuery移动端图片上传组件
Jun 12 #Javascript
jQuery通过ajax请求php遍历json数组到table中的代码(推荐)
Jun 12 #Javascript
JavaScript中实现键值对应的字典与哈希表结构的示例
Jun 12 #Javascript
JavaScript中输出信息的方法(信息确认框-提示输入框-文档流输出)
Jun 12 #Javascript
JS中常用的输出方式(五种)
Jun 12 #Javascript
Node.js环境下JavaScript实现单链表与双链表结构
Jun 12 #Javascript
You might like
PHP用户验证和标签推荐的简单使用
2016/10/31 PHP
php使用redis的几种常见操作方式和用法示例
2020/02/20 PHP
图片上传判断及预览脚本的效果实例
2013/08/07 Javascript
onkeyup,onkeydown和onkeypress的区别介绍
2013/10/21 Javascript
用jquery等比例控制图片宽高的具体实现
2014/01/28 Javascript
Javascript中的getUTCDay()方法使用详解
2015/06/10 Javascript
Boostrap模态窗口的学习小结
2016/03/28 Javascript
DropDownList控件绑定数据源的三种方法
2016/12/24 Javascript
SpringMVC+bootstrap table实例详解
2017/06/02 Javascript
详解Vue结合后台的列表增删改案例
2018/08/21 Javascript
傻瓜式解读koa中间件处理模块koa-compose的使用
2018/10/30 Javascript
细述Javascript的加法运算符的具体使用
2019/10/18 Javascript
JS页面动态绘图工具SVG,Canvas,VML介简介
2020/10/16 Javascript
[01:23]2019完美世界全国高校联赛(春季赛)合肥全国总决赛
2019/06/10 DOTA
[04:22]DOTA2大事件之护国神翼
2020/08/14 DOTA
Python实现递归遍历文件夹并删除文件
2016/04/18 Python
Python饼状图的绘制实例
2019/01/15 Python
python控制台实现tab补全和清屏的例子
2019/08/20 Python
python操作cfg配置文件方式
2019/12/22 Python
如何解决tensorflow恢复模型的特定值时出错
2020/02/06 Python
解决ROC曲线画出来只有一个点的问题
2020/02/28 Python
Python基于pillow库实现生成图片水印
2020/09/14 Python
Django自带用户认证系统使用方法解析
2020/11/12 Python
ebookers英国:隶属全球最大的在线旅游公司Expedia
2017/12/28 全球购物
印尼值得信赖的在线交易网站:Bukalapak
2019/03/11 全球购物
医院护士的求职信范文
2013/12/26 职场文书
酒吧创业计划书
2014/01/18 职场文书
高中自我评价范文
2014/01/27 职场文书
《北大荒的秋天》教学反思
2014/04/14 职场文书
抽样调查项目计划书
2014/04/24 职场文书
护理学专业求职信
2014/06/29 职场文书
2014矛盾纠纷排查调处工作总结
2014/12/09 职场文书
与死神共舞观后感
2015/06/15 职场文书
元宵节晚会主持词
2015/07/01 职场文书
保护动物的宣传语
2015/07/13 职场文书
苹果macOS 13开发者预览版Beta 8发布 正式版10月发布
2022/09/23 数码科技