Як обрізати дерево, не використовуючи рекурсії?


19

У мене дуже велике дерево вузла пам'яті і мені потрібно об'їхати дерево. Передача повернених значень кожного дочірнього вузла їх батьківському вузлу. Це потрібно зробити до тих пір, поки всі вузли не мають міхура даних до кореневого вузла.

Траверсаль працює так.

private Data Execute(Node pNode)
{
    Data[] values = new Data[pNode.Children.Count];
    for(int i=0; i < pNode.Children.Count; i++)
    {
        values[i] = Execute(pNode.Children[i]);  // recursive
    }
    return pNode.Process(values);
}

public void Start(Node pRoot)
{
    Data result = Execute(pRoot);
}

Це добре працює, але я переживаю, що стек виклику обмежує розмір дерева вузлів.

Як код можна переписати так, щоб не здійснюватися рекурсивні дзвінки Execute?


8
Вам доведеться або підтримувати власний стек, щоб відслідковувати вузли, або змінювати форму дерева. Дивіться stackoverflow.com/q/5496464 та stackoverflow.com/q/4581576
Роберт Харві

1
Я також знайшов багато допомоги в цьому пошуку в Google , зокрема Морріс Траверсал .
Роберт Харві

@RobertHarvey дякую Роб, я не був впевнений, під якими термінами йде мова.
Реакційний

2
Ви можете бути здивовані потребами пам'яті, якби ви зробили математику. Наприклад, для ідеально збалансованого бінарного дерева теранода потрібен лише стек на глибину 40 записів.
Карл Білефельдт

@KarlBielefeldt Це передбачає, що дерево є ідеально збалансованим. Іноді потрібно моделювати дерева, які не врівноважені, і в такому випадку дуже легко пробити штабель.
Сервіс

Відповіді:


27

Ось реалізація обходу дерева загального призначення, яка не використовує рекурсії:

public static IEnumerable<T> Traverse<T>(T item, Func<T, IEnumerable<T>> childSelector)
{
    var stack = new Stack<T>();
    stack.Push(item);
    while (stack.Any())
    {
        var next = stack.Pop();
        yield return next;
        foreach (var child in childSelector(next))
            stack.Push(child);
    }
}

У вашому випадку ви можете назвати це так:

IEnumerable<Node> allNodes = Traverse(pRoot, node => node.Children);

Скористайтеся Queueзамість того, Stackщоб зробити подих спочатку, а не глибину спочатку, шукайте. Використовуйте PriorityQueueдля найкращого першого пошуку.


Я правильно вважаю, що це просто сплющить дерево в колекцію?
Реакційний

1
@MathewFoscarini Так, це його мета. Звичайно, його не потрібно обов'язково матеріалізувати у фактичну колекцію. Це просто послідовність. Ви можете переглядати його, щоб передавати дані, не потрібно витягувати весь набір даних у пам'ять.
Сервіс

Я не думаю, що це вирішує проблему.
Реагуючий

4
Він не просто обробляє графік, виконуючи незалежні операції, такі як пошук, він агрегує дані з дочірніх вузлів. Плоскість дерева руйнує інформацію про структуру, яка йому потрібна для здійснення агрегації.
Карл Білефельдт

1
FYI Я думаю, що це правильна відповідь, яку шукають більшість людей, гуглюючи на це питання. +1
Андерс Арпі

4

Якщо ви заздалегідь оцінили глибину дерева, можливо, для вашого випадку це достатньо, щоб адаптувати розмір штабеля? У C # з версії 2.0 це можливо, коли ви починаєте новий потік, дивіться тут:

http://www.atalasoft.com/cs/blogs/rickm/archive/2008/04/22/increasing-the-size-of-your-stack-net-memory-management-part-3.aspx

Таким чином ви можете зберегти свій рекурсивний код, не потребуючи реалізувати щось складніше. Звичайно, створення нерекурсивного рішення з власним стеком може зайняти більше часу та пам'яті, але я впевнений, що код буде не таким простим, як зараз.


Я щойно зробив швидкий тест. На своїй машині я міг здійснювати 14000 рекурсивних дзвінків, перш ніж досягти стак-потоку. Якщо дерево збалансоване, для зберігання 4 мільярдів вузлів потрібно 32 виклики. Якщо кожний вузол становить 1 байт (що це не буде), для зберігання збалансованого дерева висотою 32 ГБ знадобиться 4 ГБ,
Есбен Сков Педерсен

Я мав використовувати всі 14000 дзвінків у стеку. Дерево займе 2,6х10 ^ 4214 байт, якщо кожен вузол - один байт (який він не буде)
Есбен Сков Педерсен

-3

Ви не можете перемістити структуру даних у формі дерева без використання рекурсії - якщо ви не використовуєте кадри стека та виклики функцій, що надаються вашою мовою, вам в основному потрібно запрограмувати власні дзвінки стека та функції, і це навряд чи вам вдасться зробити це в межах мови більш ефективним способом, ніж письменники-компілятори на машині, на якій буде працювати ваша програма.

Тому, уникаючи рекурсії, боячись натрапити на обмеження ресурсів, зазвичай помилково. Безумовно, передчасна оптимізація ресурсів завжди хибно орієнтована, але в цьому випадку, ймовірно, навіть якщо ви виміряєте та підтвердите, що використання пам'яті є вузьким місцем, ви, ймовірно, не зможете покращити її, не опустившись до рівня письменник-укладач.


2
Це просто помилково. Напевне, можна обрізати дерево без використання рекурсії. Це навіть не важко . Ви також можете зробити це більш ефективно, досить тривіально, оскільки ви можете включити в явний стек лише стільки інформації, скільки ви впевнені, що вам потрібно для конкретного обходу, тоді як за допомогою рекурсії в кінцевому підсумку ви зберігаєте більше інформації, ніж вам насправді потрібно для багатьох справ.
Сервіс

2
Ця суперечка виникає раз у раз тут. Деякі плакати вважають, що прокрутка вашого власного стека не є рекурсією, а інші зазначають, що вони роблять те саме, що явно виконує те, що час виконання виконуватиметься неявно. Немає сенсу сперечатися щодо таких визначень.
Кіліан Фот

Як ви тоді визначаєте рекурсію? Я б визначив це як функцію, яка викликає себе в межах свого власного визначення. Ви можете, звичайно, обрізати дерево, навіть не роблячи цього, як я продемонстрував у своїй відповіді.
Сервіс

2
Хіба я злий за те, що насолоджуюся актом клацання головою на когось із такою високою оцінкою повторень? Це таке рідкісне задоволення на цьому веб-сайті.
Реакційний

2
Давай @Mat, це дитячі речі. Ви можете не погодитися, як, наприклад, якщо ви боїтесь вибуху на занадто глибоке дерево, це розумне занепокоєння. Можна просто так сказати.
Майк Данлаве
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.