Чи можна обходити дерево без рекурсії, стека чи черги та лише жменьки покажчиків?


15

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

Отже, це можливо, чи неможливо? Чому або чому ні?

Редагувати: Щоб додати деякі пояснення, я реалізував це на двійковому дереві, яке містило три елементи - дані, що зберігаються на кожному вузлі, та покажчики на двох дітей. Моє рішення могло бути розповсюджене на n-ary дерева лише за допомогою декількох змін.

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


3
Вам потрібно вказати обмеження. Чи мені дозволяється мутувати дерево? Як представлено дерево? (Наприклад, чи має кожен вузол батьківський вказівник на свого батьківського?) Відповідь буде залежати від конкретних обмежень; без конкретизації цих обмежень це не є добре поставленою проблемою.
DW

2
Я думаю, контрант, який професори дуже хотіли висловити, був "з додатковий простір». Але яким було ваше рішення? О(1)
Рафаель

Відповіді:


17

Багато досліджень у цій галузі проводили купольні, мотивовані методом «дешевого» пересування дерев та загальних структур списків у контексті вивезення сміття.

Бінарне дерево з ниткою - це адаптоване зображення бінарних дерев, де деякі нульові покажчики використовуються для посилання на наступні вузли на дереві. Ця додаткова інформація може бути використана для обходу дерева без стопки. Однак додатковий біт на вузол необхідний для відрізнення потоків від дочірніх покажчиків. Вікіпедія: Tree_traversal

Наскільки я знаю, двійкові дерева, реалізовані за допомогою покажчиків звичайним способом (лівий і правий вказівник на вузол), можна пройти за допомогою методу потоків методом, приписаним Моррісу . Покажчики NIL тимчасово повторно використовуються для виведення шляху назад до кореня. Розумна частина полягає в тому, що під час обходу можна відрізнити вихідні краї від тимчасових посилань ниток, використовуючи спосіб формування циклів у дереві).

Гарна частина: відсутність додаткової структури даних. Погана частина: злегка обманюючи, стек вміло всередині дерева. Дуже розумний.

Доказ прихованого стека показаний у П. Матеті та Р. Мангірмалані: Алгоритм обходу дерева дерев Морріса переглянуто DOI: 10.1016 / 0167-6423 (88) 90063-9

Дж. М. Моріс: Подорож бінарних дерев просто та дешево. IPL 9 (1979) 197-200 DOI: 10.1016 / 0020-0190 (79) 90068-1

Потім також відбувається сканування Lindstrom . Цей метод "обертає" три покажчики, що беруть участь у кожному вузлі (батько та двоє дітей). Якщо ви хочете виконати будь-які гідні алгоритми попереднього замовлення або після замовлення, вам потрібні додаткові біти на вузол. Якщо ви просто хочете відвідати всі вузли (три рази, але ніколи не знаєте, який візит ви виконуєте), це можна зробити без бітів.

G. Lindstrom: Сканування структур списків без стеків або бітів тегів. IPL 2 (1973) 47-51. DOI: 10.1016 / 0020-0190 (73) 90012-4

Мабуть, найпростіший спосіб - це метод Робсона . Тут стек, необхідний для класичного алгоритму, прошивається через листя.

Дж. М. Робсон: Вдосконалений алгоритм переходу бінарних дерев без допоміжного стека IPL 1 (1973) 149-152. 10.1016 / 0020-0190 (73) 90018-5

IPL = Листи з обробки інформації


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

2
Чи можете ви надати посилання / посилання на стратегії?
Рафаель

1
Справжня погана частина цього методу полягає в тому, що ви не можете мати більше одного обходу за один раз.
Жил "ТАК - перестань бути злим"

6

v


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

@NathanLiddle: Це залежатиме від використовуваного дерева визначення (якого ви не давали). У "реальному світі" представлення дерева Юваля є розумним, навіть якщо теорія графіків скаже, що те, що він визначає, - це не дерева.
Рафаель

@Raphael Так, це відповідає оригінальним вимогам професора, тому для мене це прийнятна відповідь.
NL - Вибачте Моніку

0

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

Pseudocode:
root = pointer root 
depth = integer 0
finished = bool false
//If we n-ary tree also track how many children have been found 
//on the node with the most children for the purposes of this psuedocode 
//we'll assume a binary tree and insert a magic number of 2 so that we 
//can use bitwise operators instead of integer division 
while(!finished)
    ++depth
    treePosition = pointer root
    finished = true;
    for i := 0..2**depth
        for j := 0..depth
            if (i & j) //bitwise operator explained below
                // if right child doesn't exist break the loop
                treePosition = treePosition.rightChild
            else
                // if left child doesn't exist break the loop
                treePosition = treePosition.leftChild
        if j has any children
            finished = false
            do anything else you want when visiting the node

На перших кількох рівнях це виглядатиме так, як ви бачите, побітовий оператор у псевдокоді просто вирішує лівий або правий поворот на бінарне дерево:

2**1       0               1
2**2   00      01      10      11
2**3 000 001 010 011 100 101 110 111

Для n-ary ви б взяли i% (maxChildren ** j) / j, щоб визначити, який шлях слід пройти між 0 і maxChildren.

На кожному вузлі в n-ary вам також потрібно буде перевірити, чи кількість дітей більше maxChildren, і оновити його належним чином.


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

О(1)О(lgн)О(1)О(н)О(1)Наприклад, що робити, якщоdepthint

DW, професор, який поставив цю проблему, не обмежував цю проблему, і те, що мене так турбувало щодо мого обговорення з дискретним професором математики, - це те, що він НІКОЛИ не визнавав, що навіть можна було перерізати дерево без рекурсії, укладання, або черга, незалежно від вартості. Єдине, що моє рішення демонструє, це те, що можна робити що-небудь повторне, що можна зробити рекурсивно, навіть якщо ви видалите параметри стека, черги тощо
NL - Вибачтеся перед Монікою

Одна річ сказати, що вона нерозв’язна без додаткового простору O (1), зовсім інше - оголосити проблему нерозв’язною без рекурсії, стеку чи черги. І насправді, побачивши мій код, дискретний професор математики все-таки не поступився б точкою, оскільки він сказав, що "я" в першому циклі займає місце черги. Як це для жорстоких?
NL - Вибачте Моніку

1
idepthdepthΘ(н)Θ(н)iО(1)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.