我正在尝试制作一个可以在c#中填充int数组的算法。基本上,作为MS Paint中的填充工具,我有一个颜色,如果我在数组中选择(x,y)坐标,它会用新颜色替换所有具有相同初始颜色的邻居。
前:
[0,0,0]
[0,1,0]
[1,1,0]
如果我将3放入(0,0),则数组变为:
[3,3,3]
[3,1,3]
[1,1,3]
所以我在递归中尝试了它并且确实有效,但并非总是如此。实际上,我有时会出现“Stack Overflow”错误(似乎合适)。 这是我的代码,如果你能告诉我什么是错的,那就太好了。)
public int[,] fill(int[,] array, int x, int y, int initialInt, int newInt)
{
if (array[x, y] == initialInt)
{
array[x, y] = newInt;
if (x < array.GetLength(0) - 1)
array = fill(array, (x + 1), y, initialInt, newInt);
if (x > 0)
array = fill(array, (x - 1), y, initialInt, newInt);
if (y < array.GetLength(1) - 1)
array = fill(array, x, (y + 1), initialInt, newInt);
if (y > 0)
array = fill(array, x, (y - 1), initialInt, newInt);
}
return array;
}
谢谢!
答案 0 :(得分:6)
如何使用堆栈/队列来管理剩余的工作?
public void Fill(int[,] array, int x, int y, int newInt)
{
int initial = array[x,y];
Queue<Tuple<int,int>> queue = new Queue<Tuple<int,int>>();
queue.Push(new Tuple<int, int>(x, y));
while (queue.Any())
{
Tuple<int, int> point = queue.Dequeue();
if (array[point.Value1, point.Value2] != initial)
continue;
array[point.Value1, point.Value2] = newInt;
EnqueueIfMatches(array, queue, point.Value1 - 1, point.Value2, initial);
EnqueueIfMatches(array, queue, point.Value1 + 1, point.Value2, initial);
EnqueueIfMatches(array, queue, point.Value1, point.Value2 - 1, initial);
EnqueueIfMatches(array, queue, point.Value1, point.Value2 + 1, initial);
}
}
private void EnqueueIfMatches(int[,] array, Queue<Tuple<int, int>> queue, int x, int y, int initial)
{
if (x < 0 || x >= array.GetLength(0) || y < 0 || y >= array.GetLength(1))
return;
if (array[x, y] == initial)
queue.Enqueue(new Tuple<int, int>(x, y));
}
答案 1 :(得分:1)
这是一个教科书示例,说明为什么使用递归并不总是合适的。递归对于遍历本质上递归的数据结构(例如树)很有用,但是你的像素数据数组不是。
我建议在代码中添加一个计数器,以打印调用fill()
函数的频率。在每次函数调用时,您的机器必须在内存中的堆栈上创建一个新帧(因此它知道在函数结束后它必须返回的内存位置)。递归图像填充算法会多次调用fill()
函数,因此堆栈将非常快速地增长。它的尺寸有限,因此它会溢出以获得更大的图片。
解决方案是实现迭代填充算法,使用循环而不是递归来迭代像素。
参见recursion和stack overflows上的维基百科,或Foley等人的“计算机图形学,原理与实践”。有关基本计算机图形算法的更详细介绍。
答案 2 :(得分:0)
正如已经建议的那样,问题在于递归调用的数量。在32位机器上你有4个字节的指针,所以如果你有一个512 * 512的图像并且你想要填满整个东西,那么单独的函数指针将占用你的堆栈上的512 * 512 * 4bytes = 1MB内存。 And that is the default size of the stack。无论函数指针如何,您都会传递另外5个引用(array
,x
,y
,initialInt
,newInt
),这些引用都会被复制作为临时对象调用,并且在函数调用终止之前处于堆栈中。在相同大小的图像上,这些是另一个512 * 512 * 5 * 4bytes = 5MB的内存。
要解决您的问题,您可以modify(与上面相同的链接)堆栈的大小。
另外,为了节省一些堆栈空间,您可以考虑将参数包装在单个对象中,在这种情况下,每次调用只有4位临时内存,而不是20位。
尽管如此,最好的解决方案是迭代重写算法。