如何通过回溯递归地修改数组元素?

Posted

技术标签:

【中文标题】如何通过回溯递归地修改数组元素?【英文标题】:How could I modify the array elements recursively with backtracking? 【发布时间】:2016-10-28 10:55:05 【问题描述】:

我写了一种 N-Queens 算法,只处理垂直和水平威胁检测。因此,它更像是一个 N-Towers 解决方案查找器。

为此,我使用递归。这是一个众所周知的算法。对于棋盘的每个方格,我放置一个塔。对于每个放置的塔,我尝试放置另一个塔(这是递归调用)。如果没有任何剩余的塔可以放置,则意味着程序已经找到了解决方案,递归级别必须返回。如果所有的棋盘都已经越过,还有剩余的塔要放置,这意味着程序没有找到解决方案,递归关卡必须返回。

我的递归函数有两个参数:必须放置的塔的数量和棋盘(字符串数组的数组;字符串等于“T”表示已在该棋盘的方格中放置了一个塔和“- " 表示方格为空)。

问题

我的算法似乎找到了所有的解决方案,并将它们显示为棋盘,使用“-”(如果运行良好,“T”)符号。这个符号在上面已经解释过了。

但是,即使解决方案的数量似乎正确,显示的解决方案/棋盘也仅包含“-”。

我认为我在递归调用中没有正确传递我的数组数组(即:棋盘)。

这个问题的说明

对于 2 个塔和一个 2*2 方格的棋盘,找到两个解决方案,这是正常的。但是只有“-”,没有出现“T”……这就是问题所在。确实:

--

--

代码:专注于我的递归函数

    /**
     * RECURSIVE FUNCTION. If there are still towers to place, this function tries to place them. If not, it means a
     * solution has been found : it's stored in an array (external to this function).
     * If this function can't place a tower, nothing happens.
     * Else, it places it and makes the recursive call.
     * Each recursion level does this for each next (to the placed tower) chessboard's squares.
     * @param number_of_left_towers how many remaining towers to place there are (if 0, it means a solution has been
     * found)
     * @param array_array_chessboard the chessboard
     * @returns Number the return is not important
     */
    function placeTower(number_of_left_towers, array_array_chessboard) 
        if (number_of_left_towers == 0) 
            return solutions.push(array_array_chessboard);
        

        for (var current_x = 0; current_x < number_of_lines; current_x++) 
            for (var current_y = 0; current_y < number_of_columns; current_y++) 
                if (array_array_chessboard[current_x][current_y] == "-" && canBePlaced(array_array_chessboard, current_x, current_y)) 
                    array_array_chessboard[current_x][current_y] = "T";
                    placeTower(number_of_left_towers - 1, array_array_chessboard);
                    array_array_chessboard[current_x][current_y] = "-";
                
            
        
    

代码:JSFiddle 与所有源代码

https://jsfiddle.net/btcj6uzp/

您还可以在下面找到相同的代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Recursive algorithm of the N-Towers</title>
</head>
<body>

<script type="text/javascript">
    /**
     * Finds all the solutions to the N-Towers algorithm.
     *
     * @param number_of_towers number of towers to try to place in the chessboard
     * @param number_of_lines chessboard's ones
     * @param number_of_columns chessboard's ones
     * @returns nTowersSolutions array containing all the solutions
     */
    function nTowersSolutions(number_of_towers, number_of_lines, number_of_columns) 
        /*
        NB
        "T" = "Tower" = presence of a tower in this square of the chessboard
        "-" = "nothing" = no tower in this square of the chessboard
        (used for both solutions displaying and finding)
         */

        var solutions = [];
        var array_array_chessboard = []; // Represents the chessboard
        for(var i = 0; i < number_of_lines; i++) 
            array_array_chessboard[i] =  new Array(number_of_columns);
            for(var j = 0; j < number_of_columns; j++) 
                array_array_chessboard[i][j] = "-"; // We initialize the chessboard with "-"
            
        

        /**
         * Uses HTML to display the found solutions, in the Web page
         */
        this.displaySolutions = function() 
            var body = document.body;
            solutions.forEach((array_array_chessboard) => 
                array_array_chessboard.forEach(function(array_chessboard) 
                    array_chessboard.forEach((square) => 
                        body.innerHTML += square; // New cell
                    );
                    body.innerHTML += "<br />"; // New line
                );
                body.innerHTML += "<br /><br />"; // New solution
            );
        ;

        /**
         * RECURSIVE FUNCTION. If there are still towers to place, this function tries to place them. If not, it means a
         * solution has been found : it's stored in an array (external to this function).
         * If this function can't place a tower, nothing happens.
         * Else, it places it and makes the recursive call.
         * Each recursion level does this for each next (to the placed tower) chessboard's squares.
         * @param number_of_left_towers how many remaining towers to place there are (if 0, it means a solution has been
         * found)
         * @param array_array_chessboard the chessboard
         * @returns Number the return is not important
         */
        function placeTower(number_of_left_towers, array_array_chessboard) 
            if (number_of_left_towers == 0) 
                return solutions.push(array_array_chessboard);
            

            for (var current_x = 0; current_x < number_of_lines; current_x++) 
                for (var current_y = 0; current_y < number_of_columns; current_y++) 
                    if (array_array_chessboard[current_x][current_y] == "-" && canBePlaced(array_array_chessboard, current_x, current_y)) 
                        array_array_chessboard[current_x][current_y] = "T";
                        placeTower(number_of_left_towers - 1, array_array_chessboard);
                        array_array_chessboard[current_x][current_y] = "-";
                    
                
            
        

        /**
         * Can this tower be placed ?
         * @param array_array_chessboard
         * @param new_x
         * @param new_y
         * @returns boolean
         */
        function canBePlaced(array_array_chessboard, new_x, new_y) 
            for(var i = 0; i < array_array_chessboard.length; i++) 
                for(var z = 0; z < array_array_chessboard[i].length; z++) 
                    if(array_array_chessboard[i][z] == "T"
                            && (
                                    new_x == z || new_y == i // Horizontal and vertical checks
                            )
                    ) 
                        return false;
                    
                
            
            return true;
        

        placeTower(number_of_towers, array_array_chessboard);
        return this;
    

    // <!-- CHANGE THESE PARAMETERS' VALUE TO TEST -->
    nTowersSolutions(2, 2, 2).displaySolutions();
</script>

</body>
</html>

【问题讨论】:

你的小提琴不适合我,箭头函数表达式抛出语法错误 Lambda 表示法是 ES6 的一部分,例如 Chrome v.54 支持:我使用后者,我的小提琴的执行没有任何问题 【参考方案1】:

您的问题很可能只有一个(二维)数组,它是全局的,所以最终您的解决方案都指向同一个数组,这将是它在我们的递归函数完全返回之前的最后一个状态.

array_array_chessboard[current_x][current_y] = "T";
placeTower(number_of_left_towers - 1, array_array_chessboard);
array_array_chessboard[current_x][current_y] = "-";

如果我对上述内容的理解正确,那么您就是(遍历所有位置,ish) 1) 将 T 分配给一个位置 2) 解决该位置所有带有 T 的棋盘 3) 将“-”分配给前一个位置

所以最后你有一个充满“-”的数组,所有的解决方案都指向同一个数组

尝试替换

return solutions.push(array_array_chessboard);

return solutions.push(JSON.decode(JSON.encode(array_array_chessboard)));

以上内容将对您的解决方案进行深拷贝,虽然它可能不是制作深拷贝的最有效方法,但它是一种简单的方法。如果您的算法需要非常快,您可能需要寻找一种更快的方法来克隆您的解决方案。

虽然我不能保证这会奏效,因为我无法运行你的小提琴

(也为了可读性,我建议你这样写你的回报:)

solutions.push(JSON.parse(JSON.stringify(array_array_chessboard)));
return;

编辑:为什么要在Array::from 上使用 JSON.parse+stringify:

如果你只是这样做

solutions.push(Array.from(array_array_chessboard));

第二个维度仍然会引用相同的数组,毕竟那是存储字符串数据的地方。

演示(请注意,您需要在 IE 中填充 Array.from,或者只是在不同的浏览器上尝试):

var arr1 = ["a"];
var arr2 = ["b"];
var metaArr = [arr1, arr2];
console.log(metaArr[0][0], metaArr[1][0]); // "a b"

var metaArrClone = Array.from(metaArr);
var metaArrClone[0][0] = "c";
console.log(metaArrClone[0][0]); // "c"
console.log(metaArr[0][0]); // "c"

var metaArrClone2 = JSON.parse(JSON.stringify(metaArr));
console.log(metaArrClone2[0][0]); // "c"
metaArrClone2[0][0] = "d";
console.log(metaArrClone2[0][0]); // "d"
console.log(metaArr[0][0]); // "c"

【讨论】:

所以你认为我应该在这个solutions.push 中传递一个new Array 吗?函数Array::fromArray::slice 和其他函数执行此操作;但是它并不能解决我的问题。 JSON::decodeJSON::encode,两者都使用过,会做同样的事情。 使用Array::fromArray::slice 有很大的不同,因为它们只会“深度”复制第一个维度,这在您的情况下完全没用。在 cmets 中详述并不完全正确,我将编辑我的答案 另请注意,我最初使用 JSON.encode/decode 在 IE 中不起作用,因此我将其更改为 parse/stringify 啊,谢谢,现在 T 出现了。但是,显示的解决方案是相同的。我不明白你的代码怎么可能? 此外,当我在 solution.push(JSON.parse(... 之前添加:console.log(array_array_chessboard) 时,它会显示 2 个数组,每个数组包含 2 个数组,其中仅包含“-”。没有出现 T。但如果我这样做:console.log(JSON.parse(...,T 出现(但 2 个解决方案是相同的......)。这真的很奇怪。和往常一样,我使用了 2*2 棋盘和 2 个塔。【参考方案2】:

您不需要将解决方案保留在递归函数之外。如果将解决方案保留在递归函数中并返回所有解决方案可能会更好,因此您无需担心函数外部的状态。

当然,如果您必须在递归函数返回之前使用找到的解决方案(也许您的棋盘很大),您的解决方案可能会更好。或者你可以使用生成器。

最好将这种逻辑与 ui 分开,因此首先关注解决方案,然后尝试在浏览器中或您想要的位置绘制结果,或者执行相反的操作。

你可以从下面的代码开始,但是在使用之前请检查它是否真的找到了所有的解决方案。

'use strict'
/* Finds all the solutions to the N-Towers algorithm.
 *
 * @param number_of_towers number of towers to try to place in the chessboard
 * @param number_of_lines chessboard's ones
 * @param number_of_columns chessboard's ones
 * @returns nTowersSolutions array containing all the solutions
 * "Tower" = presence of a tower in this square of the chessboard
 * "Nothing" = no tower in this square of the chessboard
 * "Blocked" = the cell is blocked
 */

function nTowersSolutions(number_of_towers, number_of_lines, number_of_columns) 
  var chessboard = _initChessboard(number_of_lines, number_of_columns);
  var solutions = _findAllSolution(chessboard, number_of_towers);
  return solutions;


// nuber, * -> array
var _newArrFromLenAndElement = function(length, element) 
  return Array.apply(null, Array(length)).map(function() return element; ); 
;

// number, number -> cheesboard
var _initChessboard = function(number_of_lines, number_of_columns) 
  var oneColumnChessboard = _newArrFromLenAndElement(number_of_lines, []);
  var chessboard = oneColumnChessboard.map(function()                                                                                                                                                      
    var line = _newArrFromLenAndElement(number_of_columns, 'Nothing');
    return line;
  ); 
  return chessboard;
;

// chessboard, line_index, column_index -> chessboard
var _placeTower = function(chessboard, line_index, column_index) 
  var ff = chessboard.map(function(line, index) 
    if (index === line_index) 
      return line.map(function()  return 'Blocked'; ); 
       
    else 
      return line.map(function(x, index)
        if(index===column_index)return'Blocked';
        elsereturn x; 
      ); 
       
  ); 
  ff[line_index][column_index] = 'Tower';
    return ff; 
;

// array[][] -> array[]
var _flatten = function(arr) 
  return [].concat.apply([], arr);
;

// *, array -> boolean
var _isInArray = function(value, array) 
    return array.indexOf(value) > -1; 
;

// cheesboard, numberm number -> array
// this could be a bruteforce recursive solution at your problem ( actually you have to check if
// it is correct )
// we need _lines_done for don't look for solutions already finded (if you have tried all the
// pattern with a tower in the first line you don't want try to place a tower in the first line)
var _findAllSolution = function(chessboard, number_of_towers, _lines_done, _deep) 
  _lines_done = _lines_done || [];
  _deep = _deep || 0;

  if (number_of_towers === 0)
    return chessboard;
  

  //for all the cells in the ceesboard try to place a tower
  var solutions = chessboard.map(function(line, line_index) 
    var solution = line.map(function(cell, column_index) 
      if (_isInArray(line_index, _lines_done)) 
        return 'alreadyVisitedLine';
      
      else if (cell === 'Nothing') 
        var fulfilledChessboard = _placeTower(chessboard, line_index, column_index);
        if (line_index > 0 && _deep === 0 && !(_isInArray(line_index - 1, _lines_done))) 
          _lines_done.push(line_index - 1);
        
        return _findAllSolution(fulfilledChessboard, number_of_towers -1, _lines_done, _deep + 1);
      
      else 
        return 'DeadEnd!';
      
    );
      return _flatten(solution);
  );

  var flatSolutions = _flatten(solutions);
  //you should .filter the solutions
  return _flatten(solutions);
;

var h = nTowersSolutions(2,2,2)
console.log(h)

【讨论】:

以上是关于如何通过回溯递归地修改数组元素?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过回溯和递归解决数独?

11.幂集计算[回溯递归]<一>

使用回溯递归编辑二维数组

递归与回溯10:子集,有重复元素

Swift递归回溯算法不起作用

求子集 递归加回溯