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 相关文章推荐
FileUpload 控件 禁止手动输入或粘贴的实现代码
Apr 07 Javascript
JS实现网页游戏中滑块响应鼠标点击移动效果
Oct 19 Javascript
超实用的JavaScript表单代码段
Feb 26 Javascript
全面理解JavaScript中的闭包
May 12 Javascript
BootStrap实现带有增删改查功能的表格(DEMO详解)
Oct 26 Javascript
Jquery给当前页或者跳转后页面的导航栏添加选中后样式的实例
Dec 08 Javascript
jQuery Validate验证框架详解(推荐)
Dec 17 Javascript
node.js操作mysql简单实例
May 25 Javascript
基于jQuery实现的单行公告活动轮播效果
Aug 23 jQuery
js匿名函数使用&amp;传参(实例)
Sep 08 Javascript
jQuery替换节点元素的操作方法
Mar 18 jQuery
Vue3 实现双盒子定位Overlay的示例
Dec 22 Vue.js
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的memcached客户端memcached
2011/06/14 PHP
php代码书写习惯优化小结
2013/06/20 PHP
php中apc缓存使用示例
2013/12/25 PHP
php+xml结合Ajax实现点赞功能完整实例
2015/01/30 PHP
Zend Framework教程之Loader以及PluginLoader用法详解
2016/03/09 PHP
php实现将base64格式图片保存在指定目录的方法
2016/10/13 PHP
php删除二维数组中的重复值方法
2018/03/12 PHP
农历与西历对照
2006/09/06 Javascript
javascript中AJAX用法实例分析
2015/01/30 Javascript
JavaScript中split与join函数的进阶使用技巧
2016/05/03 Javascript
Ionic2系列之使用DeepLinker实现指定页面URL
2016/11/21 Javascript
基于jQuery代码实现圆形菜单展开收缩效果
2017/02/13 Javascript
使用JS中的Replace()方法遇到的问题小结
2017/10/20 Javascript
快速处理vue渲染前的显示问题
2018/03/05 Javascript
JavaScript实现的简单加密解密操作示例
2018/06/01 Javascript
Vue下路由History模式打包后页面空白的解决方法
2018/06/29 Javascript
Vue cli3 库模式搭建组件库并发布到 npm的流程
2018/10/12 Javascript
浅谈KOA2 Restful方式路由初探
2019/03/14 Javascript
vue解决花括号数据绑定不成功的问题
2019/10/30 Javascript
[05:20]2018DOTA2亚洲邀请赛主赛事第三日战况回顾 LGD率先挺进胜者组决赛
2018/04/06 DOTA
在Python中处理日期和时间的基本知识点整理汇总
2015/05/22 Python
Python获取运行目录与当前脚本目录的方法
2015/06/01 Python
Python常用时间操作总结【取得当前时间、时间函数、应用等】
2017/05/11 Python
对Python3.6 IDLE常用快捷键介绍
2018/07/16 Python
python实现控制电脑鼠标和键盘,登录QQ的方法示例
2019/07/06 Python
python实现门限回归方式
2020/02/29 Python
python关于倒排列的知识点总结
2020/10/13 Python
塔吉特百货公司官网:Target
2017/04/27 全球购物
全球领先的在线cosplay服装商店:RoleCosplay
2020/01/18 全球购物
使用useBean标志初始化BEAN时如何接受初始化参数
2012/02/11 面试题
PHP使用Redis队列执行定时任务实例讲解
2021/03/24 PHP
个人实用简单的自我评价
2013/10/19 职场文书
海南地接欢迎词
2014/01/14 职场文书
诉讼代理人授权委托书
2014/10/11 职场文书
关于感恩的素材句子(38句)
2019/11/11 职场文书
python神经网络学习 使用Keras进行回归运算
2022/05/04 Python