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