javascript解三阶幻方(九宫格)


Posted in Javascript onApril 22, 2015

谜题:三阶幻方, 试将1~9这9个不同整数填入一个3×3的表格,使得每行、每列以及每条对角线上的数字之和相同。

策略:穷举搜索。列出所有的整数填充方案,然后进行过滤。

亮点为递归函数getPermutation的设计,文章最后给出了几个非递归算法

// 递归算法,很巧妙,但太费资源
function getPermutation(arr) {
  if (arr.length == 1) {
    return [arr];
  }
  var permutation = [];
  for (var i = 0; i < arr.length; i++) {
    var firstEle = arr[i];         //取第一个元素
    var arrClone = arr.slice(0);      //复制数组
    arrClone.splice(i, 1);         //删除第一个元素,减少数组规模
    var childPermutation = getPermutation(arrClone);//递归
    for (var j = 0; j < childPermutation.length; j++) {
      childPermutation[j].unshift(firstEle);   //将取出元素插入回去
    }
    permutation = permutation.concat(childPermutation);
  }
  return permutation;
}

function validateCandidate(candidate) {
  var sum = candidate[0] + candidate[1] + candidate[2];
  for (var i = 0; i < 3; i++) {
    if (!(sumOfLine(candidate, i) == sum && sumOfColumn(candidate, i) == sum)) {
      return false;
    }
  }
  if (sumOfDiagonal(candidate, true) == sum && sumOfDiagonal(candidate, false) == sum) {
    return true;
  }
  return false;
}
function sumOfLine(candidate, line) {
  return candidate[line * 3] + candidate[line * 3 + 1] + candidate[line * 3 + 2];
}
function sumOfColumn(candidate, col) {
  return candidate[col] + candidate[col + 3] + candidate[col + 6];
}
function sumOfDiagonal(candidate, isForwardSlash) {
  return isForwardSlash ? candidate[2] + candidate[4] + candidate[6] : candidate[0] + candidate[4] + candidate[8];
}

var permutation = getPermutation([1, 2, 3, 4, 5, 6, 7, 8, 9]);
var candidate;
for (var i = 0; i < permutation.length; i++) {
  candidate = permutation[i];
  if (validateCandidate(candidate)) {
    break;
  } else {
    candidate = null;
  }
}
if (candidate) {
  console.log(candidate);
} else {
  console.log('No valid result found');
}

//求模(非递归)全排列算法

/*
算法的具体示例:
*求4个元素["a", "b", "c", "d"]的全排列, 共循环4!=24次,可从任意>=0的整数index开始循环,每次累加1,直到循环完index+23后结束;
*假设index=13(或13+24,13+224,13+3*24…),因为共4个元素,故迭代4次,则得到的这一个排列的过程为:
*第1次迭代,13/1,商=13,余数=0,故第1个元素插入第0个位置(即下标为0),得["a"];
*第2次迭代,13/2, 商=6,余数=1,故第2个元素插入第1个位置(即下标为1),得["a", "b"];
*第3次迭代,6/3, 商=2,余数=0,故第3个元素插入第0个位置(即下标为0),得["c", "a", "b"];
*第4次迭代,2/4,商=0,余数=2, 故第4个元素插入第2个位置(即下标为2),得["c", "a", "d", "b"];
*/

function perm(arr) {
  var result = new Array(arr.length);
  var fac = 1;
  for (var i = 2; i <= arr.length; i++)  //根据数组长度计算出排列个数
    fac *= i;
  for (var index = 0; index < fac; index++) { //每一个index对应一个排列
    var t = index;
    for (i = 1; i <= arr.length; i++) {   //确定每个数的位置
      var w = t % i;
      for (var j = i - 1; j > w; j--)   //移位,为result[w]留出空间
        result[j] = result[j - 1];
      result[w] = arr[i - 1];
      t = Math.floor(t / i);
    }
    if (validateCandidate(result)) {
      console.log(result);
      break;
    }
  }
}
perm([1, 2, 3, 4, 5, 6, 7, 8, 9]);
//很巧妙的回溯算法,非递归解决全排列

function seek(index, n) {
  var flag = false, m = n; //flag为找到位置排列的标志,m保存正在搜索哪个位置,index[n]为元素(位置编码)
  do {
    index[n]++;    //设置当前位置元素
    if (index[n] == index.length) //已无位置可用
      index[n--] = -1; //重置当前位置,回退到上一个位置
    else if (!(function () {
        for (var i = 0; i < n; i++)  //判断当前位置的设置是否与前面位置冲突
          if (index[i] == index[n]) return true;//冲突,直接回到循环前面重新设置元素值
        return false;  //不冲突,看当前位置是否是队列尾,是,找到一个排列;否,当前位置后移
      })()) //该位置未被选择
      if (m == n) //当前位置搜索完成
        flag = true;
      else
        n++;  //当前及以前的位置元素已经排好,位置后移
  } while (!flag && n >= 0)
  return flag;
}
function perm(arr) {
  var index = new Array(arr.length);
  for (var i = 0; i < index.length; i++)
    index[i] = -1;
  for (i = 0; i < index.length - 1; i++)
    seek(index, i);  //初始化为1,2,3,...,-1 ,最后一位元素为-1;注意是从小到大的,若元素不为数字,可以理解为其位置下标
  while (seek(index, index.length - 1)) {
    var temp = [];
    for (i = 0; i < index.length; i++)
      temp.push(arr[index[i]]);
    if (validateCandidate(temp)) {
      console.log(temp);
      break;
    }
  }
}
perm([1, 2, 3, 4, 5, 6, 7, 8, 9]);

/*
全排列(非递归求顺序)算法
1、建立位置数组,即对位置进行排列,排列成功后转换为元素的排列;
2、按如下算法求全排列:
设P是1~n(位置编号)的一个全排列:p = p1,p2...pn = p1,p2...pj-1,pj,pj+1...pk-1,pk,pk+1...pn
(1)从排列的尾部开始,找出第一个比右边位置编号小的索引j(j从首部开始计算),即j = max{i | pi < pi+1}
(2)在pj的右边的位置编号中,找出所有比pj大的位置编号中最小的位置编号的索引k,即 k = max{i | pi > pj}
pj右边的位置编号是从右至左递增的,因此k是所有大于pj的位置编号中索引最大的
(3)交换pj与pk
(4)再将pj+1...pk-1,pk,pk+1...pn翻转得到排列p' = p1,p2...pj-1,pj,pn...pk+1,pk,pk-1...pj+1
(5)p'便是排列p的下一个排列

例如:
24310是位置编号0~4的一个排列,求它下一个排列的步骤如下:
(1)从右至左找出排列中第一个比右边数字小的数字2;
(2)在该数字后的数字中找出比2大的数中最小的一个3;
(3)将2与3交换得到34210;
(4)将原来2(当前3)后面的所有数字翻转,即翻转4210,得30124;
(5)求得24310的下一个排列为30124。
*/

function swap(arr, i, j) {
  var t = arr[i];
  arr[i] = arr[j];
  arr[j] = t;

}
function sort(index) {
  for (var j = index.length - 2; j >= 0 && index[j] > index[j + 1]; j--)
    ; //本循环从位置数组的末尾开始,找到第一个左边小于右边的位置,即j
  if (j < 0) return false; //已完成全部排列
  for (var k = index.length - 1; index[k] < index[j]; k--)
    ; //本循环从位置数组的末尾开始,找到比j位置大的位置中最小的,即k
  swap(index, j, k);
  for (j = j + 1, k = index.length - 1; j < k; j++, k--)
    swap(index, j, k); //本循环翻转j+1到末尾的所有位置
  return true;
}
function perm(arr) {
  var index = new Array(arr.length);
  for (var i = 0; i < index.length; i++)
    index[i] = i;
  do {
    var temp = [];
    for (i = 0; i < index.length; i++)
      temp.push(arr[index[i]]);
    if (validateCandidate(temp)) {
      console.log(temp);
      break;
    }
  } while (sort(index));
}
perm([1, 2, 3, 4, 5, 6, 7, 8, 9]);

以上所述就是本文的全部内容了,希望大家能够喜欢。

Javascript 相关文章推荐
JS 建立对象的方法
Apr 21 Javascript
JavaScript 编写匿名函数的几种方法
Feb 21 Javascript
JavaScript实现页面滚动图片加载(仿lazyload效果)
Jul 22 Javascript
js特殊字符转义介绍
Nov 05 Javascript
jQuery遍历之next()、nextAll()方法使用实例
Nov 08 Javascript
举例讲解Node.js中的Writable对象
Jul 29 Javascript
jquery插件tytabs.jquery.min.js实现渐变TAB选项卡效果
Aug 25 Javascript
Angular2学习笔记——详解路由器模型(Router)
Dec 02 Javascript
微信小程序 解决swiper不显示图片的方法
Jan 04 Javascript
bootstrap table单元格新增行并编辑
May 19 Javascript
mac上node.js环境的安装测试
Jul 03 Javascript
js自定义弹框插件的封装
Aug 24 Javascript
javascript递归回溯法解八皇后问题
Apr 22 #Javascript
使用C++为node.js写扩展模块
Apr 22 #Javascript
node.js 使用ejs模板引擎时后缀换成.html
Apr 22 #Javascript
JavaScript模拟深蓝vs卡斯帕罗夫的国际象棋对局示例
Apr 22 #Javascript
jscript读写二进制文件的方法
Apr 22 #Javascript
javascript格式化json显示实例分析
Apr 21 #Javascript
JavaScript汉诺塔问题解决方法
Apr 21 #Javascript
You might like
发款php蜘蛛统计插件只要有mysql就可用
2010/10/12 PHP
修改ThinkPHP缓存为Memcache的方法
2014/06/25 PHP
封装ThinkPHP的一个文件上传方法实例
2014/10/31 PHP
Android App中DrawerLayout抽屉效果的菜单编写实例
2016/03/21 PHP
Laravel5.4简单实现app接口Api Token认证方法
2019/08/29 PHP
关于文本限制字数的js代码
2007/04/02 Javascript
ArrayList类(增强版)
2007/04/04 Javascript
直接生成打开窗口代码,不必下载
2008/05/14 Javascript
Jquery下的26个实用小技巧(jQuery tips, tricks &amp; solutions)
2010/03/01 Javascript
精通Javascript系列之Javascript基础篇
2011/06/07 Javascript
jQuery队列操作方法实例
2014/06/11 Javascript
JavaScript中获取时间的函数集
2016/08/16 Javascript
BootStrap栅格系统、表单样式与按钮样式源码解析
2017/01/20 Javascript
详解Node.js access_token的获取、存储及更新
2017/06/20 Javascript
jQuery+SpringMVC中的复选框选择与传值实例
2018/01/08 jQuery
JS实现textarea通过换行或者回车把多行数字分割成数组并且去掉数组中空的值
2018/10/29 Javascript
一文快速了解JQuery中的AJAX
2019/05/31 jQuery
electron实现静默打印的示例代码
2019/08/12 Javascript
[01:17:47]TNC vs VGJ.S 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
python3批量删除豆瓣分组下的好友的实现代码
2016/06/07 Python
Python列表list内建函数用法实例分析【insert、remove、index、pop等】
2017/07/24 Python
Python OpenCV实现鼠标画框效果
2020/08/19 Python
Python openpyxl读取单元格字体颜色过程解析
2019/09/03 Python
利用Python产生加密表和解密表的实现方法
2019/10/15 Python
Python使用Opencv实现图像特征检测与匹配的方法
2019/10/30 Python
python随机模块random的22种函数(小结)
2020/05/15 Python
Python pandas 列转行操作详解(类似hive中explode方法)
2020/05/18 Python
使用HTML5在网页中嵌入音频和视频播放的基本方法
2016/02/22 HTML / CSS
德购商城:德国进口直邮商城
2017/06/13 全球购物
Booking.com英国官网:全球酒店在线预订网站
2018/04/21 全球购物
《陋室铭》教学反思
2014/02/26 职场文书
质量提升方案
2014/06/16 职场文书
国家机关领导干部民主生活会对照检查材料思想汇报
2014/09/17 职场文书
2014年国庆节演讲稿
2014/09/19 职场文书
2015年个人工作总结报告
2015/04/25 职场文书
体育委员竞选稿
2015/11/21 职场文书