Обход n-арного дерева без использования рекурсии

Как я могу пройти по n-арному дереву без использования рекурсии?

Рекурсивный способ:

traverse(Node node)
{
    if(node == null)
        return;

    for(Node child : node.getChilds()) {
        traverse(child);
    }
}

person ako    schedule 13.05.2011    source источник
comment
Любой рекурсивный алгоритм можно свести к циклу + стеку, так что это не сильное ограничение. Попробуйте любой из алгоритмов, которые можно найти в Google. .   -  person Matthew Scharley    schedule 13.05.2011
comment
У меня хорошее настроение, поэтому я перевела вам ваш алгоритм. В целом, это не так уж сложно сделать, и быстрый поиск в Google мог бы найти много результатов для этого. На данный момент в истории обход дерева является очень четко определенной проблемой.   -  person Matthew Scharley    schedule 13.05.2011
comment
Просто интересно @ako. Почему вы хотите сделать это без рекурсии? Есть ли какая-то причина или это просто вопрос, чтобы заработать репутацию?   -  person Mihran Hovsepyan    schedule 13.05.2011
comment
@mihran, зачем пользователю присоединяться к переполнению стека, чтобы получить репутацию на сайте, который он раньше не использовал?   -  person Nicolas78    schedule 14.05.2011


Ответы (3)


Вы можете сделать это без рекурсии и без стека. Но вам нужно добавить два дополнительных указателя на узел:

  1. Родительский узел. Так что вы можете вернуться к родителю, если вы закончили.
  2. Текущий дочерний узел, чтобы вы знали, какой из них выбрать следующим.

    • For each node, you handle all the kids.
    • Если ребенок обрабатывается, вы проверяете, есть ли следующий ребенок, и обрабатываете его (обновление текущего).
    • Если все дети обработаны, вернитесь к родителю.
    • Если узел равен NULL, выйти.

С псевдокодом это выглядит так:

traverse(Node node) {
  while (node) {
    if (node->current <= MAX_CHILD) {
      Node prev = node;
      if (node->child[node->current]) {
        node = node->child[node->current];
      }
      prev->current++;
    } else {
      // Do your thing with the node.
      node->current = 0; // Reset counter for next traversal.
      node = node->parent;
    }
  }
}
person Toon Krijthe    schedule 13.05.2011
comment
Хорошо, теперь я понял. Однако это решение накладывает очень много ограничений на структуру данных. И это даже не говоря о многопоточности. - person Björn Pollex; 13.05.2011
comment
@ Space_C0wb0y, правда, это просто еще один способ показать, что рекурсивный путь самый ясный. - person Toon Krijthe; 14.05.2011

То, что вы делаете, по сути является DFS дерева. Вы можете устранить рекурсию, используя стек:

traverse(Node node) {
    if (node==NULL)
        return;

    stack<Node> stk;
    stk.push(node);

    while (!stk.empty()) {
        Node top = stk.pop();
        for (Node child in top.getChildren()) {
            stk.push(child);
        }
        process(top);
    }
}

Если вы хотите, чтобы BFS использовала очередь:

traverse(Node node) {
    if (node==NULL)
        return;

    queue<Node> que;
    que.addRear(node);

    while (!que.empty()) {
        Node front = que.deleteFront();
        for (Node child in front.getChildren()) {
            que.addRear(child);
        }
        process(front);
    }
}

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

person BiGYaN    schedule 15.05.2011
comment
BFS = поиск в ширину, DFS = поиск в глубину - person Marduk; 30.06.2021

Язык не указан, поэтому в псевдопсевдокоде:

traverse(Node node)
{
  List<Node> nodes = [node];

  while (nodes.notEmpty) {
    Node n = nodes.shift();

    for (Node child in n.getChildren()) {
      nodes.add(child);
    }

    // do stuff with n, maybe
  }
}

Обратите внимание, что это обход в ширину, в отличие от обхода в глубину, указанного в вопросе. Вы должны иметь возможность выполнять обход в глубину, popизвлекая последний элемент из nodes списка вместо shiftизвлекая первый.

person Matthew Scharley    schedule 13.05.2011
comment
если вы используете pop вместо shift, это не будет DFS, так как он все равно будет начинаться с корневого узла. DFS начинается с самого глубокого (левого) листа. - person Marco M.; 26.09.2012
comment
@МаркоМ. Решение OP также сначала будет искать корень, поскольку оно использует хвостовую рекурсию. Я думаю, что это достаточно близко для большинства практических приложений. - person Matthew Scharley; 27.09.2012