C# 非线性递归计数形状

C# 非线性递归计数形状,c#,recursion,C#,Recursion,我必须创建一个程序,使用递归计算存储在文本文件中的形状数量,然后输出文件中的形状数量 文本文件将包含点和X的矩形排列 X的形状由空间分隔。点代表将一个形状与另一个形状分开的空白区域。要定义一个形状,任何给定的X都与其上方、下方、左侧和右侧的任何其他X属于相同的形状。对角线上的任意两个X都不相连 例如,在此文本文件中有6个形状: 18 44 ............................................ ..................................

我必须创建一个程序,使用递归计算存储在文本文件中的形状数量,然后输出文件中的形状数量

文本文件将包含点和X的矩形排列

X的形状由空间分隔。点代表将一个形状与另一个形状分开的空白区域。要定义一个形状,任何给定的X都与其上方、下方、左侧和右侧的任何其他X属于相同的形状。对角线上的任意两个X都不相连

例如,在此文本文件中有6个形状:

18
44
............................................
............................................
.......XXXXXX...............................
.....XXXXXXXXXXXX...........X...............
....XXXXXXXXXXXXXXX.........XXXXXXXXXX......
.......XXXXXXXXXXXXXX.......................
...............XXXXXX.......................
...............XXXX.......XXXXXXX...........
...........XXXX..........XXX..XXXX..........
......XXXXXXXXXXXXXX........................
.......XXXXXXXXXXXX.........................
............................................
.............XXXXXXXXXXXXXXXXXXXXXX.........
.............XX.................XXX.........
.............XX...XXXXXXXX......XXX.........
.............XX...XXXXXXXX......XXX.........
.............XX.................XXX.........
.............XXXXXXXXXXXXXXXXXXXXXX.........
这是我到目前为止计算形状数量的代码,但是它似乎无法正确计算它们,因为我的最终输出一直是1而不是6

    namespace MazeWin
    {
    public partial class frmMain : Form
    {
        //private class variables
        char[,] CountShapes;
        int rows;
        int cols;
        Grid grid;
        int Counter = 0;

        public frmMain()
        {
            InitializeComponent();

            //global variables
            rows = 0;
            cols = 0;
        }

        private void mnuFileOpen_Click(object sender, EventArgs e)
        {
            //get file from Open Dialog box
            OpenFileDialog fd = new OpenFileDialog();

            if (fd.ShowDialog() == DialogResult.OK)
            {
                //load the file
                StreamReader sr = new StreamReader(fd.OpenFile());
                rows = int.Parse(sr.ReadLine());
                cols = int.Parse(sr.ReadLine());

                //initialize the CountShapes array
                CountShapes = new char[rows, cols];
                grid = new Grid(new Size(cols, rows), 20, new Point(20, 40));

                //populate the CountShapes array
                for (int r = 0; r < rows; r++)
                {
                    string line = sr.ReadLine();
                    for (int c = 0; c < cols; c++)
                    {
                        CountShapes[r, c] = line[c];
                    }
                }

                //configure grid so each cell is drawn properly
                ConfigureGrid();

                //resize form to grid height and width
                this.Width = this.cols * grid.CellSize + 60;
                this.Height = this.rows * grid.CellSize + 80;

                //tell form to redraw
                this.Refresh();
            }    
        }

        private void ConfigureGrid()
        {
            for (int r = 0; r < this.rows; r++)
            {
                for (int c = 0; c < this.cols; c++)
                {
                    //change colour of cell depending on what
                    //is in it
                    if (CountShapes[r, c] == '.')
                        grid.GetCell(r, c).BackColor = Color.LightGray;
                    else if (CountShapes[r, c] == 'X')
                        grid.GetCell(r, c).BackColor = Color.Purple;
                }
            }
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            if (grid != null)
                grid.Draw(e.Graphics);
        }

        private bool SolveMaze(int r, int c)
        {                
            if (r < 0 || c < 0 || r >= this.rows || c >= this.cols)
            {
                Counter++;
                return false;
            }

            if (CountShapes[r, c] == '.')
            {

                return false;
            }                                    
                //move in all directions to find the end point
                if (SolveMaze(r + 1, c)) return true; //go Down
                if (SolveMaze(r, c + 1)) return true; //go Right
                if (SolveMaze(r, c - 1)) return true; //go Left
                if (SolveMaze(r - 1, c)) return true; //go Up

            //if made to here we are blocked in all direction
            //leave a marker because we will back out

                Counter++;    
                return true;                
        }

        private void mnuFileSolve_Click(object sender, EventArgs e)
        {
            //solve maze
            SolveMaze(0, 0);
            ConfigureGrid();

            MessageBox.Show("" + Counter);

            this.Refresh();
        }        
    }
}
namespace MazeWin
{
公共部分类名称:表单
{
//私有类变量
字符[,]计数形状;
int行;
int cols;
网格;
int计数器=0;
公共财政收入()
{
初始化组件();
//全局变量
行=0;
cols=0;
}
私有void mnuFileOpen_单击(对象发送者,事件参数e)
{
//“从打开中获取文件”对话框
OpenFileDialog fd=新建OpenFileDialog();
如果(fd.ShowDialog()==DialogResult.OK)
{
//加载文件
StreamReader sr=新的StreamReader(fd.OpenFile());
rows=int.Parse(sr.ReadLine());
cols=int.Parse(sr.ReadLine());
//初始化CountShapes数组
CountShapes=新字符[行,列];
网格=新网格(新大小(列、行)、20、新点(20、40));
//填充CountShapes数组
对于(int r=0;r=this.rows | | c>=this.cols)
{
计数器++;
返回false;
}
if(CountShapes[r,c]='.'))
{
返回false;
}                                    
//向各个方向移动以找到终点
如果(r+1,c))返回true;//向下
if(SolveMaze(r,c+1))返回true;//向右走
if(SolveMaze(r,c-1))返回true;//向左走
如果(r-1,c))返回true;//向上
//如果到了这里,我们会被封锁在各个方向
//留下一个记号笔,因为我们会退后
计数器++;
返回true;
}
私有void mnuFileSolve_单击(对象发送方,事件参数e)
{
//解迷宫
溶解迷宫(0,0);
配置网格();
MessageBox.Show(“+”计数器);
这个。刷新();
}        
}
}
请注意:正在从另一个文件访问网格类。


另外,我使用的是一个计数器,它对递归不是很好,所以你们对我如何从代码中删除它,但保持相同的功能有什么建议吗?

它失败了,因为如果[0,0]上的元素不是“X”,它将立即退出。为了数一数形状,我会在瓷砖上做标记。例如,您可以对克隆阵列上的这些形状进行计数,并将计数的形状更改回“.”:

private int SolveMaze(char[,] maze)
{
    var numberOfShapes = 0;
    for (int r = 0; r < rows; r++)
    {
        for (int c = 0; c < cols; c++)
        {
            if (maze[r, c] == 'X')
            {
                numberOfShapes++;
                CleanTheShape(maze, r, c);
            }
        }
    }
    return numberOfShapes;
}

private void CleanTheShape(char[,] maze, int r, int c)
{
    if (r < 0 || c < 0 || r >= this.rows || c >= this.cols)
    {
        return;
    }
    if (maze[r, c] == '.') return;

    maze[r, c] = '.';

    CleanTheShape(maze, r + 1, c);
    CleanTheShape(maze, r, c + 1);
    CleanTheShape(maze, r, c - 1);
    CleanTheShape(maze, r - 1, c);
}
使用这种方法,你将计算迷宫中的每一块瓷砖,如果它是X,你将开始一个递归例程来清理这个形状,这样它就不会再次计算。我已经更新了结果,现在你可以摆脱你的全局计数器了。

看看这个:

struct Point { public int X, Y; }
HashSet<Point> FindShapes(int x, int y, string[] lines, HashSet<Point> alreadyProcessed, HashSet<Point> currentShape = null)
{
    var thisPoint = new Point { X = x, Y = y };
    if (alreadyProcessed.Contains(thisPoint))
        return null; //Already processed
    if (lines.Length <= x || lines[x].Length <= y)
        return null; //Invalid co-ordinate
    if (lines[x][y] != 'X')
        return null; //Not an 'X'

    if (currentShape == null) //If currentShape is null, it means we're the first call to enter this shape
        currentShape = new HashSet<Point>();

    currentShape.Add(thisPoint);     //Mark this point as part of the shape
    alreadyProcessed.Add(thisPoint); //Mark this point processed

    FindShapes(x + 1, y, lines, alreadyProcessed, currentShape); //Check to the right
    FindShapes(x - 1, y, lines, alreadyProcessed, currentShape); //Check to the left
    FindShapes(x, y + 1, lines, alreadyProcessed, currentShape); //Check below
    FindShapes(x, y - 1, lines, alreadyProcessed, currentShape); //Check above

    return currentShape; //Return the set back
}
结构点{public int X,Y;} HashSet-FindShapes(int x,int y,string[]行,HashSet-alreadyProcessed,HashSet-currentShape=null) { var thisPoint=新点{X=X,Y=Y}; if(alreadyProcessed.Contains(thisPoint)) 返回null;//已处理
如果(lines.Length其他答案解决了您的特定问题。我的答案是非递归版本。我确实理解它对您没有帮助,因为您正在学习递归,但我在这里发布它,因为其他人可能会发现它从通用算法和数据结构的角度来看很有用

有一种叫做算法的东西,下面是C#中的一个例子

我们可以使用类似的实现,如下所示:

/// <summary>
/// A UnionFindNode represents a set of nodes that it is a member of.
/// 
/// You can get the unique representative node of the set a given node is in by using the Find method.
/// Two nodes are in the same set when their Find methods return the same representative.
/// The IsUnionedWith method will check if two nodes' sets are the same (i.e. the nodes have the same representative).
///
/// You can merge the sets two nodes are in by using the Union operation.
/// There is no way to split sets after they have been merged.
/// </summary>
public class UnionFindNode<T>
{
    private UnionFindNode<T> _parent;
    private uint _rank;

    /// <summary>
    /// Creates a new disjoint node, representative of a set containing only the new node.
    /// </summary>
    public UnionFindNode(T value)
    {
        _parent = this;
        Value = value;
    }

    /// <summary>
    /// Returns the current representative of the set this node is in.
    /// Note that the representative is only accurate untl the next Union operation.
    /// </summary>
    public UnionFindNode<T> Find()
    {
        if (!ReferenceEquals(_parent, this)) _parent = _parent.Find();
        return _parent;
    }

    /// <summary>
    /// Determines whether or not this node and the other node are in the same set.
    /// </summary>
    public bool IsUnionedWith(UnionFindNode<T> other)
    {
        if (other == null) throw new ArgumentNullException("other");
        return ReferenceEquals(Find(), other.Find());
    }

    /// <summary>
    /// Merges the sets represented by this node and the other node into a single set.
    /// Returns whether or not the nodes were disjoint before the union operation (i.e. if the operation had an effect).
    /// </summary>
    /// <returns>True when the union had an effect, false when the nodes were already in the same set.</returns>
    public bool Union(UnionFindNode<T> other)
    {
        if (other == null) throw new ArgumentNullException("other");
        var root1 = this.Find();
        var root2 = other.Find();
        if (ReferenceEquals(root1, root2)) return false;

        if (root1._rank < root2._rank)
        {
            root1._parent = root2;
        }
        else if (root1._rank > root2._rank)
        {
            root2._parent = root1;
        }
        else
        {
            root2._parent = root1;
            root1._rank++;
        }
        return true;
    }

    public T Value { get; set; }
}
Console.WriteLine(Solver.Solve(@"yourpath.txt",'X'));
在这里,我们使用Union Find组合其中具有相同值的所有单元格
/// <summary>
/// A UnionFindNode represents a set of nodes that it is a member of.
/// 
/// You can get the unique representative node of the set a given node is in by using the Find method.
/// Two nodes are in the same set when their Find methods return the same representative.
/// The IsUnionedWith method will check if two nodes' sets are the same (i.e. the nodes have the same representative).
///
/// You can merge the sets two nodes are in by using the Union operation.
/// There is no way to split sets after they have been merged.
/// </summary>
public class UnionFindNode<T>
{
    private UnionFindNode<T> _parent;
    private uint _rank;

    /// <summary>
    /// Creates a new disjoint node, representative of a set containing only the new node.
    /// </summary>
    public UnionFindNode(T value)
    {
        _parent = this;
        Value = value;
    }

    /// <summary>
    /// Returns the current representative of the set this node is in.
    /// Note that the representative is only accurate untl the next Union operation.
    /// </summary>
    public UnionFindNode<T> Find()
    {
        if (!ReferenceEquals(_parent, this)) _parent = _parent.Find();
        return _parent;
    }

    /// <summary>
    /// Determines whether or not this node and the other node are in the same set.
    /// </summary>
    public bool IsUnionedWith(UnionFindNode<T> other)
    {
        if (other == null) throw new ArgumentNullException("other");
        return ReferenceEquals(Find(), other.Find());
    }

    /// <summary>
    /// Merges the sets represented by this node and the other node into a single set.
    /// Returns whether or not the nodes were disjoint before the union operation (i.e. if the operation had an effect).
    /// </summary>
    /// <returns>True when the union had an effect, false when the nodes were already in the same set.</returns>
    public bool Union(UnionFindNode<T> other)
    {
        if (other == null) throw new ArgumentNullException("other");
        var root1 = this.Find();
        var root2 = other.Find();
        if (ReferenceEquals(root1, root2)) return false;

        if (root1._rank < root2._rank)
        {
            root1._parent = root2;
        }
        else if (root1._rank > root2._rank)
        {
            root2._parent = root1;
        }
        else
        {
            root2._parent = root1;
            root1._rank++;
        }
        return true;
    }

    public T Value { get; set; }
}
public static class Solver
{
    public static int Solve(string fileName, char shapeChar)
    {
        string[] lines = File.ReadAllLines(fileName);
        var rows = int.Parse(lines[0]);
        var cols = int.Parse(lines[1]);
        lines = lines.Skip(2).ToArray();

        UnionFindNode<char>[] nodes = new UnionFindNode<char>[rows * cols];

        for (int r = 0; r < rows; r++)
        {
            string line = lines[r];
            for (int c = 0; c < cols; c++)
            {
                UnionFindNode<char> current = new UnionFindNode<char>(line[c]);
                nodes[c*rows + r] = current;
                if (c > 0)
                {
                    Combine(current, nodes[(c - 1) * rows + r], current.Value);
                }
                if (r > 0)
                {
                    Combine(current, nodes[c * rows + r - 1], current.Value);
                }
            }
        }

        return nodes.Where(x => x.Value == shapeChar).Select(x => x.Find()).Distinct().Count();
    }

    private static void Combine(UnionFindNode<char> current, UnionFindNode<char> n, char shapeChar)
    {
        if (n.Value == shapeChar)
        {
            n.Union(current);
        }
    }
}
Console.WriteLine(Solver.Solve(@"yourpath.txt",'X'));