我试图制作一个程序,使用回溯在迷宫中找到哈密顿路径。它应该返回编号为迷宫的迷宫上的路径。问题是当一个堆栈倒退时,其中一个变量(它是迷宫的表示)从调用继承,而其他变量(即使它们以相同的方式被声明),也不会(这很好)。我尝试了几个解决方法,包括通过创建一个单独的类来实例化,我包含了调试消息。这是代码,有一些注释可以提供帮助。
using System;
namespace ConsoleApplication1
{
//I made a separate class for the function
class btr
{
public short[,] mz = new short[,] { };//tried to pull the variable out of the function, no success
public void bt(int i, int j, int l)
{
bool ok;
ok = true;
Console.WriteLine("in" + '\n' + Program.print(mz, l) + 'i' + i + 'j' + j + '\n'); //debug message for entering
if (i > 0 && mz[i - 1, j] == 0)
{
ok = false;
mz[i, j] = 1; // 1 aka go up
var x = new btr { };
//my attempt to avoid the problem by instantiating the function, no success...
x.mz = mz;
x.bt(i - 1, j, l);
//When this function exits the mz variable is copied to this one. Same for all the ifs below
}
if (j > 0 && mz[i, j - 1] == 0)
{
ok = false;
mz[i, j] = 2; //2 aka go left
var x = new btr { };
x.mz = mz;
x.bt(i, j - 1, l);
}
if (i < l && mz[i + 1, j] == 0)
{
ok = false;
mz[i, j] = 3;//3 aka go down
var x = new btr { };
x.mz = mz;
x.bt(i + 1, j, l);
}
if (j < l && mz[i, j + 1] == 0)
{
ok = false;
mz[i, j] = 4;//4 aka go right
var x = new btr { };
x.mz = mz;
x.bt(i, j + 1, l);
}
Console.WriteLine("out" + '\n' + Program.print(mz, l) + 'i' + i + 'j' + j + '\n'); //debug message for exiting
if (ok) //this is for printing the solution when it is found
{
mz[i, j] = 8;// 8 aka the end
foreach (int x in mz)
{
if (x == 0) { ok = false; break; }
}
if (ok)
Console.WriteLine("result" + '\n' + Program.print(mz, l));
}
}
}
class Program
{//this is just for preparing the first call
static short[,] test = new short[2, 2] { { 0, 0}, { 0, 0} };
static void Main(string[] args)
{
var x= new btr { };
x.mz = test;
x.bt(0,0,1);
}
public static string print(short[,] vr,int l)//casts my array into a string that can be printed
{
string s = "";
for (int i = 0; i <= l; i++)
{
for (int j = 0; j <= l; j++)
{
s += vr[i,j];
}
s += '\n';
}
return s;
}
}
}
我作为测试给出了没有任何障碍的2x2迷宫(由测试声明全0表示)并且它应该输出2个解决方案,它仅输出一个并且解决方案被“注入”到堆栈中。这是输出:
in
00
00
i0j0
in
30
00
i1j0
in
30
40
i1j1
in
30
41
i0j1
out
30
41
i0j1
result
38
41
out
38
41
i1j1
out
38
41
i1j0
out
38
41
i0j0
正如您所看到的,当函数退出迷宫时仍然是38 41而不是逐渐回落到00 00,因此可以计算出更多的解决方案。我和j不受影响。
答案 0 :(得分:1)
我同意在处理递归时不变性是一种有用的技术,特别是在需要反向跟踪的情况下。也就是说,它可以在处理数组时产生不必要的开销,特别是如果它们很大。
在您的场景中,因为您在进行递归调用之前知道要修改的数组的哪个元素,所以您可以在递归调用返回后简单地重置该值。从某种意义上说,您正在利用调用堆栈来保留状态 - 在这种情况下,是已修改的数组元素的i
和j
- 并使用它来恢复递归后的状态调用
看起来像这样:
public void bt(int i, int j, int l)
{
bool ok;
ok = true;
Console.WriteLine("in" + '\n' + Program.print(mz, l) + 'i' + i + 'j' + j + '\n'); //debug message for entering
if (i > 0 && mz[i - 1, j] == 0)
{
ok = false;
mz[i, j] = 1; // 1 aka go up
bt(i - 1, j, l);
mz[i, j] = 0;
//When this function exits the mz variable is copied to this one. Same for all the ifs below
}
if (j > 0 && mz[i, j - 1] == 0)
{
ok = false;
mz[i, j] = 2; //2 aka go left
bt(i, j - 1, l);
mz[i, j] = 0;
}
if (i < l && mz[i + 1, j] == 0)
{
ok = false;
mz[i, j] = 3;//3 aka go down
bt(i + 1, j, l);
mz[i, j] = 0;
}
if (j < l && mz[i, j + 1] == 0)
{
ok = false;
mz[i, j] = 4;//4 aka go right
bt(i, j + 1, l);
mz[i, j] = 0;
}
Console.WriteLine("out" + '\n' + Program.print(mz, l) + 'i' + i + 'j' + j + '\n'); //debug message for exiting
if (ok) //this is for printing the solution when it is found
{
mz[i, j] = 8;// 8 aka the end
foreach (int x in mz)
{
if (x == 0) { ok = false; break; }
}
if (ok)
Console.WriteLine("result" + '\n' + Program.print(mz, l));
}
}
每次通话后注意mz[i, j] = 0;
。
如果您愿意,甚至可以将mz
变量放回方法参数列表中。您只处理数组的单个实例,因此将它保留为类字段或方法参数并不重要。
答案 1 :(得分:0)
实际原因是在我的递归中,数组不是值类型,所以它通过引用传递,指向相同的实例。
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/reference-types
不影响变量i和j的原因是它们是值类型,因此按值传递。
因此有两种解决方案:在每次调用后克隆字段(慢速)或在退出时将其返回到原始状态(在所有情况下都不可能,但是在这种情况下是这样)