Рекурсивный алгоритм заливки
Я пытаюсь сделать алгоритм, который мог бы заполнить массив int в C#. В принципе, как инструмент заливки в MS Paint, у меня есть цвет, и если я выбираю координаты (x,y) в массиве, он заменяет все соседи тем же начальным цветом новым цветом.
Ex:
[0,0,0]
[0,1,0]
[1,1,0]
Если я поставлю 3 в (0,0), массив станет :
[3,3,3]
[3,1,3]
[1,1,3]
поэтому я попробовал его рекурсивно, и он работает, но не все время. На самом деле, у меня иногда есть " переполнение стека" ошибка (кажется подходящей). Вот мой код, было бы здорово, если бы вы могли сказать мне, что случилось :)
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;
}
спасибо !
3 ответов
Как насчет использования стека/очереди для управления оставшейся работы?
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));
}
это пример учебника, почему использование рекурсии не всегда уместно. Рекурсия отлично подходит для прохождения структур данных, которые по своей сути рекурсивны (например, деревья), но Ваш массив пиксельных данных нет.
Я предлагаю добавить счетчик в ваш код, чтобы напечатать, как часто fill()
вызывается функция. При каждом вызове функции ваша машина должна создать новый кадр в стеке в памяти (чтобы она знала, в какую позицию памяти она должна вернуться после того, как функция над.) Рекурсивный алгоритм заполнения изображения вызывает fill()
функция огромное количество раз, следовательно, стек будет расти очень быстро. Он имеет ограниченный размер, поэтому он будет переполняться для больших изображений.
решение заключается в реализации итеративного алгоритма заполнения, используя циклы вместо рекурсии для итерации по пикселям.
смотрите Википедию на рекурсия и переполнение стека, или "компьютерная графика, принципы и практика" Фоли и др. для более подробного ознакомления с основными алгоритмами компьютерной графики.
как уже было предложено, проблема заключается в количестве рекурсивных вызовов. На 32-битной машине у вас есть 4bytes для указателей, поэтому, если у вас есть изображение 512*512, и вы хотите заполнить все это, только указатели функций будут занимать 512*512*4bytes = 1 МБ памяти в вашем стеке. и это размер стека по умолчанию. Независимо от указателей функций, у вас есть еще 5 ссылок, которые вы передаете (array
, x
, y
, initialInt
, newInt
), которые все копируется с каждым вызовом как временные объекты и находится в стеке до завершения вызова функции. На изображении того же размера это другое 512*512*5*4bytes = 5 Мб памяти.
чтобы решить вашу проблему, вы можете изменить (та же ссылка, что и выше) размер стека.
кроме того, чтобы сохранить некоторое пространство стека, вы можете рассмотреть возможность обертывания параметров внутри одного объекта, в этом случае у вас будет только 4bits временной памяти на вызов, а не 20.
тем не менее, как было также указано, лучшее решение-переписать свой алгоритм итеративно.