Czy drzewo można przemierzać bez rekurencji, stosu lub kolejki, a jedynie garść wskazówek?

15

Pół dekady temu siedziałem w klasie struktur danych, w której profesor oferował dodatkowy kredyt, jeśli ktokolwiek mógł przemierzać drzewo bez użycia rekurencji, stosu, kolejki itp. (Lub innych podobnych struktur danych) i tylko kilka wskazówek. Wpadłem na to, co uważałem za oczywistą odpowiedź na to pytanie, które ostatecznie zostało zaakceptowane przez profesora. Siedziałem w dyskretnej klasie matematycznej z innym profesorem w tym samym dziale - a on zapewnił, że niemożliwe jest przemierzanie drzewa bez rekurencji, stosu, kolejki itp. Oraz że moje rozwiązanie jest nieprawidłowe.

Czy to możliwe, czy niemożliwe? Dlaczego lub dlaczego nie?

Edycja: Aby dodać wyjaśnienia, zaimplementowałem to w drzewie binarnym, które miało trzy elementy - dane przechowywane w każdym węźle i wskaźniki do dwojga dzieci. Moje rozwiązanie można rozszerzyć na drzewa n-ary z kilkoma zmianami.

Mój nauczyciel struktur danych nie nałożył żadnych ograniczeń na mutowanie drzewa, i rzeczywiście dowiedziałem się później, że jego własnym rozwiązaniem było użycie wskaźników potomnych, aby skierować drzewo w górę. Mój dyskretny profesor matematyki powiedział, że jakakolwiek mutacja drzewa oznacza, że ​​nie jest to już drzewo zgodnie z matematyczną definicją drzewa, jego definicja wyklucza również wszelkie wskazówki dla rodziców - co pasowałoby do przypadku, w którym rozwiązałem je powyżej.

NL - Przeproś Monikę
źródło
3
Musisz określić ograniczenia. Czy mogę mutować drzewo? Jak reprezentowane jest drzewo? (Na przykład, czy każdy węzeł ma wskaźnik nadrzędny w stosunku do swojego nadrzędnego?) Odpowiedź będzie zależeć od konkretnych ograniczeń; bez określenia tych ograniczeń nie jest to dobrze postawiony problem.
DW
2
Sądzę, że przeciwnikiem, który profesorowie naprawdę chcieli wyrazić, było „z dodatkową przestrzenią ”. Ale jakie było twoje rozwiązanie? O(1)
Raphael

Odpowiedzi:

17

Wiele badań w tej dziedzinie zostało poddanych kopule, motywowanych metodą „taniego” przemieszczania się drzew i ogólnych struktur list w kontekście wywozu śmieci.

Gwintowane drzewo binarne to dostosowana reprezentacja drzew binarnych, w których niektóre wskaźniki zerowe są używane do łączenia się z następnymi węzłami w drzewie. Tych dodatkowych informacji można użyć do przejścia przez drzewo bez stosu. Konieczny jest jednak dodatkowy bit na węzeł, aby odróżnić wątki od wskaźników potomnych. Wikipedia: Tree_traversal

O ile wiem, drzewa binarne zaimplementowane przy użyciu wskaźników w zwykły sposób (lewy i prawy wskaźnik na węzeł) można przechodzić za pomocą metody wątków, w metodzie przypisanej Morrisowi . Wskaźniki NIL są tymczasowo ponownie używane do wątkowania ścieżki z powrotem do katalogu głównego. Sprytną częścią jest to, że podczas przechodzenia można odróżnić oryginalne krawędzie od tymczasowych połączeń nitek, wykorzystując sposób, w jaki tworzą one cykle na drzewie).

Dobra część: brak dodatkowej struktury danych. Zła część: nieco oszustwo, stos jest w sprytny sposób wewnątrz drzewa. Bardzo mądry.

Dowód ukrytego stosu pokazano w P. Mateti i R. Manghirmalani: Ponownie rozpatrzony algorytm przemierzania drzewa Morrisa DOI: 10.1016 / 0167-6423 (88) 90063-9

JM Morris: Przemierzanie drzew binarnych prosto i tanio. IPL 9 (1979) 197-200 DOI: 10.1016 / 0020-0190 (79) 90068-1

Jest też Lindstrom skanowanie . Ta metoda „obraca” trzy wskaźniki zaangażowane w każdym węźle (rodzic i dwoje dzieci). Jeśli chcesz wykonać przyzwoite algorytmy przedsprzedażowe lub po zamówieniu, potrzebujesz dodatkowych bitów na węzeł. Jeśli chcesz tylko odwiedzić wszystkie węzły (trzy razy, ale nigdy nie wiesz, którą wizytę wykonujesz), możesz to zrobić bez bitów.

G. Lindstrom: Skanowanie struktur list bez stosów i bitów znaczników. IPL 2 (1973) 47–51. DOI: 10.1016 / 0020-0190 (73) 90012-4

Być może najprostszym sposobem jest metoda Robsona . Tutaj stos potrzebny do klasycznego algorytmu jest przewleczony przez liście.

JM Robson: Ulepszony algorytm przechodzenia przez drzewa binarne bez stosu pomocniczego IPL 1 (1973) 149-152. 10.1016 / 0020-0190 (73) 90018-5

IPL = Listy przetwarzania informacji

Hendrik Jan
źródło
Podoba mi się również to rozwiązanie, choć nie jest to nic, co wymyśliłbym podczas pierwszego roku zajęć z informatyki. Tak, prawdopodobnie oszukiwanie zgodnie z zasadami mojego profesora.
NL - Przeproś Monikę
2
Czy możesz podać linki / referencje do strategii?
Raphael
1
Naprawdę złą stroną tej metody jest to, że nie można mieć więcej niż jednego przejścia w tym samym czasie.
Gilles 'SO - przestań być zły'
6

v

Yuval Filmus
źródło
Jest to podobne do rozwiązania zastosowanego przez profesora ds. Struktur danych, który zaproponował problem do jego rozwiązania. Dyskretny profesor matematyki sprzeciwił się, że „to stało się grafem, a nie drzewem”, jeśli istnieją wskazówki dla rodziców.
NL - Przeproś Monikę
@NathanLiddle: To zależy od użytej definicji drzewa (której nie podałeś). W „prawdziwym świecie” reprezentacja drzew Yuvala jest rozsądna, nawet jeśli teoria grafów mówi, że rzeczy, które definiuje, nie są oczywiście drzewami.
Raphael
@Raphael Tak, i spełnia wymagania oryginalnego profesora, więc jest to dla mnie akceptowalna odpowiedź.
NL - Przeproś Monikę
0

Moim rozwiązaniem było przejście do pierwszej hodowli za pomocą zagnieżdżonych pętli for, aby brutalnie wymusić na drzewie. W żaden sposób nie jest to wydajne i rzeczywiście rekurencyjna struktura danych, taka jak drzewo, prosi o rekurencyjne przechodzenie, ale pytanie nie brzmiało, czy drzewo można skutecznie przechodzić, to, czy było to w ogóle możliwe.

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

Dla kilku pierwszych poziomów wyglądałoby to tak, jak widać, operator bitowy w pseudokodzie po prostu decyduje o skręcie w lewo lub w prawo na drzewie binarnym:

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

W przypadku n-ary weźmiesz i% (maxChildren ** j) / j, aby określić, którą ścieżkę wybrać między 0 a maxChildren.

W każdym węźle n-ary musisz również sprawdzić, czy liczba dzieci jest większa niż maxChildren i odpowiednio ją zaktualizować.

NL - Przeproś Monikę
źródło
Jeśli chciałbyś użyć czegoś więcej niż binarnego, musiałbyś zastąpić magiczną liczbę 2 zmienną, która byłaby zwiększana, aby pasowała do maksimum dzieci, które widział, a zamiast operatorów bitowych musiałbyś podzielić przez tę samą zmienną podniesioną do moc głębokości drzewa, w którym byłeś.
NL - Przeproś Monikę
Jeśli dobrze rozumiem, to rozwiązanie wykorzystuje więcej niż O(1)dodatkowa przestrzeń. Jeśli drzewo jest zrównoważone, używaO(lgn) bitów (moglibyśmy to nazwać) O(1) słowa w modelu liczb całkowitych RAM), ale jeśli drzewo jest niezrównoważone, może użyć do O(n)dodatkowe bity, co jest nieuzasadnioną kwotą. Dlatego prawdopodobnie nie powinno się to kwalifikować, jeśli problem jest postawiony wystarczająco ostrożnie (np. To, co profesor prawdopodobnie miał na myśli, „używało co najwyżejO(1)dodatkowe miejsce ”). Na przykład, co robisz, jeśli depthprzekracza szerokość int?
DW
DW, profesor, który postawił problem, nie nałożył tego ograniczenia na problem, a tak bardzo przeszkadzało mi w mojej dyskusji z dyskretnym profesorem matematyki, że NIGDY nie uznał, że można nawet przejść drzewo bez rekurencji, stosu, lub w kolejce, bez względu na koszty. Jedyne, co pokazuje moje rozwiązanie, to to, że można zrobić wszystko iteracyjnie, co można zrobić rekurencyjnie, nawet jeśli usuniesz opcje stosu, kolejki itp.
NL - Przeproś Monikę
Trzeba powiedzieć, że jest nierozwiązywalny bez dodatkowej przestrzeni O (1), zupełnie inną jest zadeklarowanie problemu nierozwiązywalnego bez rekurencji, stosu lub kolejki. I właściwie, po zobaczeniu mojego kodu, dyskretny profesor matematyki nadal nie przyznałby się do tego, ponieważ powiedział, że „i” w pierwszej pętli for zajmuje miejsce w kolejce. Jak to na twardogłowych?
NL - Przeproś Monikę
1
@NathanLiddle, sprawdź ponownie. Twoja liczba całkowita ijest depthszeroka na bity. Jeśli depthjestΘ(n) (co może być w przypadku niezrównoważonych drzew), wtedy potrzebujesz Θ(n)miejsce do przechowywania liczb całkowitych i, więc rozwiązanie potrzebuje więcej niżO(1)dodatkowa przestrzeń.
DW