Как избежать рекурсивной функции?

Например, если мы обходим довольно большое дерево с помощью следующей функции, возможно, что мы получаем переполнение стека.

void inorder(node* n)
{
  if(n == null) return;
  inorder(n->l);
  n->print();
  inorder(n->r); 
}

Как добавить условие или что-то в функцию для предотвращения такого переполнения?

Ответ 1

Нет никакого переносного решения, кроме как путем замены рекурсии с явным управлением стеком (используя std::vector<Node*>). Непереносимо вы можете отслеживать глубину с использованием статической переменной; если вы знаете максимальный стек размер и размер стека каждой рекурсии, тогда вы можете убедитесь, что глубина не превышает этого.

Многие системы, такие как Linux и Solaris, не могут знать максимальная глубина стека вверх, так как стек выделен динамически. По крайней мере, в Linux и Solaris, однако, один раз память была выделена стеку, она останется выделенной и остаются затронутыми в стеке. Таким образом, вы можете глубоко в начале программы (возможно, сбой, но прежде чем что-либо сделать), а затем проверить это значение позже:

static char const* lowerBound = nullptr;

//  At start-up...
void
preallocateStack( int currentCount ) {
{
    char dummyToTakeSpace[1000];
    -- currentCount;
    if ( currentCount <= 0 ) {
        lowerBound = dummyToTakeSpace;
    } else {
        preallocateStack( currentCount - 1 );
    }
}

void
checkStack()
{
    char dummyForAddress;
    if ( &dummyForAddress < lowerBound ) {
        throw std::bad_alloc();   //  Or something more specific.
    }
}

Вы заметите, что есть несколько случаев undefined/неопределенное поведение, плавающее в этом коде, но Я использовал его успешно несколько раз (под Solaris на Sparc, но Linux на ПК работает точно так же в этом отношение). Фактически, он будет работать практически в любой системе, где: - стек растет, и - локальные переменные выделяются в стеке. Таким образом, он также будет работать на Windows, но если он не сможет выделите исходный стек, вам придется переписываться, а не просто запустите программу в тот момент, когда меньше активности на (или изменить ulimits) (поскольку размер стека в Windows фиксируется во время соединения).

EDIT:

Один комментарий относительно использования явного стека: некоторые системы (включая Linux, по умолчанию) overcommit, что означает что вы не можете надежно получить ошибку из памяти, когда расширение std::vector<>; система сообщит std::vector<>, что память есть, а затем дать запрограммируйте нарушение сегмента при попытке получить к нему доступ.

Ответ 2

рассмотрите итерацию по рекурсии, если это действительно проблема.

http://en.wikipedia.org/wiki/Tree_traversal

см. код psedo для итерации iterativeInorder iterativePreorder iterativePostorder

В основном используйте свой собственный список как структуру данных стека в цикле while, вы можете эффективно заменить рекурсию функции.

Ответ 3

Дело в рекурсии заключается в том, что вы никогда не сможете гарантировать, что она никогда не переполнит стек, если вы не можете поместить некоторые ограничения как (минимального) размера памяти, так и (максимального) размера ввода. Однако вы можете гарантировать, что он переполнит стек, если у вас есть бесконечный цикл...

Я вижу ваш "if() return;" и поэтому вы должны избегать бесконечных циклов, так как каждая ветвь вашего дерева заканчивается нулем. Таким образом, одна из возможностей - неправильный ввод, когда какая-либо ветвь дерева никогда не достигает нуля. (Это произойдет, например, если у вас есть петля в структуре данных дерева.)

Единственная другая возможность, которую я вижу, это то, что ваша структура данных дерева просто слишком велика для объема доступной памяти стека. (N.B. это виртуальная память, и пространство подкачки может быть использовано, поэтому это не обязательно проблема нехватки ОЗУ.) Если это произойдет, вам может потребоваться какой-то другой алгоритмический подход, который не является рекурсивным. Хотя ваша функция имеет небольшой объем памяти, поэтому, если вы не пропустили какую-либо дополнительную обработку, которую она делает, ваше дерево действительно должно быть ДЕЙСТВИТЕЛЬНО ГЛУБОКО, чтобы это было проблемой. (N.B. это максимальная глубина, что проблема здесь, а не общее количество узлов.)

Ответ 4

Вы можете увеличить размер стека для своей ОС. Обычно это настраивается через ulimit, если вы находитесь в среде, подобной Unix.

например. на Linux вы можете сделать ulimit -s unlimited, который установит размер стека на "неограниченный", хотя у IIRC есть жесткий предел, и вы не можете посвятить всю свою память одному процессу (хотя в одном из ответов в ссылках ниже упоминается неограниченный количество).

Мои предложения состоят в том, чтобы запустить ulimit -s, который предоставит вам текущий размер стека, и если вы все равно получите переполнение стека вдвое, пока не получите удовольствие.

Посмотрите здесь, здесь и здесь для более подробного обсуждения размера стека и способа его обновления.

Ответ 5

Если у вас очень большое дерево, и вы сталкиваетесь с проблемами с обходом стека с помощью рекурсивных обходов, проблема в том, что у вас нет сбалансированного дерева. Первое предложение - попробовать сбалансированное двоичное дерево, такое как дерево красного дерева или дерево AVL, или дерево с более чем двумя детьми на node, например дерево B+. Библиотека С++ предоставляет std::map<> и std::set<>, которые обычно реализуются с использованием сбалансированного дерева.

Однако один простой способ избежать рекурсивных обходов в порядке - это изменить ваше дерево для потоковой передачи. То есть, используйте правый указатель листовых узлов, указывающий следующий node. Обход такого дерева будет выглядеть примерно так:

n = find_first(n);
while (! is_null(n)) {
    n->print();
    if (n->is_leaf()) n = n->r;
    else n = find_first(n->r);
}

Ответ 6

Вы можете добавить статическую переменную, чтобы отслеживать время вызова функции. Если он приблизится к тому, что, по вашему мнению, приведет к сбою вашей системы, выполните некоторую процедуру, чтобы уведомить пользователя об ошибке.

Ответ 7

Небольшой прототип изменений, которые могут быть сделаны путем сопоставления другой переменной int с рекурсивной функцией. Вы можете передать переменную в качестве аргумента функции, начинающейся с нуля, по умолчанию в корне и уменьшать ее по мере того, как вы идите вниз дерево...

Недостаток: это решение происходит за счет накладных расходов переменной int, выделяемой для каждого node.

void inorder(node* n,int counter)
{
 if(counter<limit) //limit specified as per system limit
 {
  if(n == null) return;
  inorder(n->l,counter-1);
  n->print();
  inorder(n->r,counter-1);
 }
 else
  n->print();
}

рассмотреть для дальнейших исследований: Хотя проблема может быть не в обходной ситуации, если рассматривать только рекурсию. И можно было бы избежать с лучшим созданием дерева и обновлением. проверьте концепцию сбалансированных деревьев, если они еще не рассмотрены.