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

时间:2019-02-16 16:37:50

标签: c# arrays algorithm recursion backtracking

我有一个二维数组,其中包含3种类型的元素:

  • C(污染物)
  • R(摇滚)
  • W(水)

规则是:

  

污染物可以透过水渗入,但不能透过岩石渗入。


假设我有以下输入数组:

WWWRW
CRRRR
RWWRW
WWWRR
WRRWC

因此,如果存在水,则上述数组中具有'C'作为其值的单元格可能会污染同一行和同一列。

所以输出将是这样的:

CCCRW
CRRRR
RWWRW
WWWRR
WRRCC

这是我在C#中的幼稚尝试:

public static string[,] GenerateContaminant(int row, int column, string[,] arr)
{
    string[,] result = new string[row, column];
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < column; j++)
        {
            result[i, j] = arr[i, j];
        }
    }

    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < column; j++)
        {
            if (result[i, j] == "C")
            {
                for (int a = 0; a < row; a++)
                {
                    if (result[i, a] == "W")
                    {
                        result[i, a] = "C";
                    } 
                }

                for (int b = 0; b < column; b++)
                {
                    if (result[b, j] == "W")
                    {
                        result[b, j] = "C";
                    }
                }
            }
        }
    }

    return result;
}

这也给了我错误的结果。


这是我正在使用的link

我知道我必须通过回溯和递归来解决此问题,但是我无法提出一种合适的算法来实现。

任何帮助将不胜感激。

3 个答案:

答案 0 :(得分:3)

在这里我不会解释洪水填充算法,因为您会发现很多有关此主题的在线教程。

解决该问题的另一种方法是重复渗透一个单元,直到不再发生移动为止。这不是很有效,但是很容易理解。

如果您将一些代码提取到辅助方法中,该解决方案将变得更具可读性。

此辅助方法通过注意不要超出边界来获取单元格的邻居。它使用C# Iterator

private static IEnumerable<char> NeighborsOf(char[,] env, int i, int j)
{
    if (i > 0) yield return env[i - 1, j];
    if (i < env.GetLength(0) - 1) yield return env[i + 1, j];
    if (j > 0) yield return env[i, j - 1];
    if (j < env.GetLength(1) - 1) yield return env[i, j + 1];
}

此方法将打印数组

private static void Print(char[,] env)
{
    Console.Clear();
    for (int i = 0; i < env.GetLength(0); i++) {
        for (int j = 0; j < env.GetLength(1); j++) {
            Console.Write(env[i, j]);
        }
        Console.WriteLine();
    }
    Thread.Sleep(1000);
}

解决方案

char[,] environment = {
    { 'W', 'W', 'W', 'R', 'W' },
    { 'C', 'R', 'R', 'R', 'R' },
    { 'R', 'W', 'W', 'R', 'W' },
    { 'W', 'W', 'W', 'R', 'R' },
    { 'W', 'R', 'R', 'W', 'C' }
};

var result = (char[,])environment.Clone(); // Creates a copy of the original.
bool didChange;
do {
    Print(result);
    didChange = false;
    for (int i = 0; i < result.GetLength(0); i++) {
        for (int j = 0; j < result.GetLength(1); j++) {
            if (result[i, j] == 'W' &&
                NeighborsOf(result, i, j).Any(n => n == 'C')) {
                result[i, j] = 'C';
                didChange = true;
            }
        }
    }
} while (didChange);

请注意,我不带污染物将其散布在周围,而是从水开始,看看周围是否有污染物。这样一来,每个循环最多可以进行一次分配。

您还可以在每个(主)循环上创建一个副本,以便及时获得逼真的模拟。就像现在的代码一样,污染物可能会在一个循环中连续分布在多个单元格中。

答案 1 :(得分:3)

由于这里的解决方案似乎是迭代的,因此我将提供另一个使用递归相对简单的解决方案。

算法

总而言之,此算法遍历2D数组中的所有n个元素,并在找到“ C”后开始递归过程,将所有相邻的“ W”元素转换为“ C”。如果没有相邻的“ W”元素,它将终止递归并继续进行遍历数组的迭代。它实质上以递归方式填充了阵列中应该污染的每个连接的组件。

效率

此算法遍历每个元素,然后有效地执行“深度优先”搜索,该搜索在没有相邻元素可被污染时终止。 DFS的终止很重要,它可以防止不必要的元素重复处理。该算法还可以对内存进行有效修改,因为它可以修改数组。

解决方案

public static void GenerateContaminant(string[,] arr)
{
    // Iterate through every element of the array and when you find a "C", 
    // propagate the contamination recursively to its neighbors.
    for (int row = 0; row < arr.GetLength(0); row++)
    {
        for (int col = 0; col < arr.GetLength(1); col++)
        {
            if (arr[row, col] == "C") {
                ContaminateRecurse(row, col, arr);
            }
        }
    }
    return;
}        

// Recurse from a "C" element and see if its neighbors should be contaminated.
public static void ContaminateRecurse(int row, int col, string[,] arr)
{
    arr[row, col] = "C";
    // Top Neighbor
    if (isValid(row-1, col, arr)) {
        ContaminateRecurse(row-1, col, arr);
    }
    // Bottom Neighbor
    if (isValid(row+1, col, arr)) {
        ContaminateRecurse(row+1, col, arr);
    }
    // Left Neighbor
    if (isValid(row, col-1, arr)) {
        ContaminateRecurse(row, col-1, arr);
    }
    // Right Neighbor
    if (isValid(row, col+1, arr)) {
        ContaminateRecurse(row, col+1, arr);
    }
    return;
}

// Makes sure that an element index is within the bounds of the array and is a 'W'.
public static bool isValid(int row, int col, string[,] arr) {
    if ((0 <= row && row < arr.GetLength(0)) && 
        (0 <= col && col < arr.GetLength(1)) && 
        arr[row,col] == "W") {
        return true;
    }
    else {
        return false;
    }
}

测试代码

这是我的解决方案,已从您的Rextester链接添加到完整的测试程序代码中。由于算法修改了传递给它的数组,因此我一开始就复制了输入数组。

//Rextester.Program.Main is the entry point for your code. Don't change it.
//Compiler version 4.0.30319.17929 for Microsoft (R) .NET Framework 4.5

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Rextester
{
    public class Program
    {
        public static void Main(string[] args)
        {
            // Set up input.
            string[,] arr = new string[,]
            {
                { "W", "W", "W", "R", "W" },
                { "C", "R", "R", "R", "R" },
                { "R", "W", "W", "R", "W" },
                { "W", "W", "W", "R", "R" },
                { "W", "R", "R", "W", "C" }
            };

            // Actual algorithm for solution. 
            // Make a copy for result since recursive solution will modify array in place.
            string[,] result = arr.Clone() as string[,];
            GenerateContaminant(result);

            printGrid("Input:", arr);
            printGrid("Output:", result);
        }


        public static void GenerateContaminant(string[,] arr)
        {
            // Iterate through every element of the array and when you find a "C", 
            // propagate the contamination recursively to its neighbors.
            for (int row = 0; row < arr.GetLength(0); row++)
            {
                for (int col = 0; col < arr.GetLength(1); col++)
                {
                    if (arr[row, col] == "C") {
                        ContaminateRecurse(row, col, arr);
                    }
                }
            }
            return;
        }        

        // Recurse from a "C" element and see if its neighbors should be contaminated.
        public static void ContaminateRecurse(int row, int col, string[,] arr)
        {
            arr[row, col] = "C";
            // Top Neighbor
            if (isValid(row-1, col, arr)) {
                ContaminateRecurse(row-1, col, arr);
            }
            // Bottom Neighbor
            if (isValid(row+1, col, arr)) {
                ContaminateRecurse(row+1, col, arr);
            }
            // Left Neighbor
            if (isValid(row, col-1, arr)) {
                ContaminateRecurse(row, col-1, arr);
            }
            // Right Neighbor
            if (isValid(row, col+1, arr)) {
                ContaminateRecurse(row, col+1, arr);
            }
            return;
        }

        // Makes sure that an element index is within the bounds of the array and is a 'W'.
        public static bool isValid(int row, int col, string[,] arr) {
            if ((0 <= row && row < arr.GetLength(0)) && 
                (0 <= col && col < arr.GetLength(1)) && 
                arr[row,col] == "W") {
                return true;
            }
            else {
                return false;
            }
        }

        // Write grid to console.
        public static void printGrid(string label, string[,] result) {
            Console.Write(label);
            Console.WriteLine();
            for (int rowValue = 0; rowValue < result.GetLength(0); rowValue++) {
                for (int colValue = 0; colValue < result.GetLength(1); colValue++) {
                    Console.Write(result[rowValue, colValue]);
                }
                Console.WriteLine();
            }
            Console.WriteLine();
        }

    }
}

结果:

Input:
WWWRW
CRRRR
RWWRW
WWWRR
WRRWC

Output:
CCCRW
CRRRR
RWWRW
WWWRR
WRRCC

答案 2 :(得分:0)

使用Lee Algorithm的改编即可轻松解决。

此适应也称为洪水填充see this answer for a code example

您基本上将使用堆栈或队列来保留受污染细胞的有效邻居。然后,您将添加邻居的邻居,直到到达停靠点为止(C单元附近不再有W单元)。