Minęło trochę czasu od tych lat szkolnych. Dostałem pracę jako informatyk w szpitalu. Próbuję teraz przejść do faktycznego programowania. Pracuję teraz nad drzewami binarnymi i zastanawiałem się, jaki byłby najlepszy sposób określenia, czy drzewo jest zrównoważone pod względem wysokości.
Myślałem o czymś w związku z tym:
public boolean isBalanced(Node root){
if(root==null){
return true; //tree is empty
}
else{
int lh = root.left.height();
int rh = root.right.height();
if(lh - rh > 1 || rh - lh > 1){
return false;
}
}
return true;
}
Czy to dobra realizacja? czy coś mi brakuje?
java
algorithm
data-structures
binary-tree
user69514
źródło
źródło
Odpowiedzi:
Natknąłem się na to stare pytanie, szukając czegoś innego. Zauważyłem, że nigdy nie otrzymałeś pełnej odpowiedzi.
Sposobem rozwiązania tego problemu jest rozpoczęcie od napisania specyfikacji funkcji, którą próbujesz napisać.
Specyfikacja: O dobrze uformowanym drzewie binarnym mówi się, że jest „zrównoważone wysokością”, jeśli (1) jest puste lub (2) jego lewe i prawe elementy potomne mają zrównoważoną wysokość, a wysokość lewego drzewa mieści się w zakresie 1 od wysokość prawego drzewa.
Teraz, gdy masz już specyfikację, napisanie kodu jest banalne. Wystarczy postępować zgodnie ze specyfikacją:
Przetłumaczenie tego na wybrany język programowania powinno być banalne.
Dodatkowe ćwiczenie : ten naiwny szkic kodu przemierza drzewo zbyt wiele razy podczas obliczania wysokości. Czy możesz uczynić to bardziej wydajnym?
Super dodatkowe ćwiczenie : załóżmy, że drzewo jest bardzo niezrównoważone. Na przykład milion węzłów po jednej stronie i trzy po drugiej. Czy istnieje scenariusz, w którym ten algorytm rozwala stos? Czy możesz naprawić implementację tak, aby nigdy nie wysadzała stosu, nawet jeśli otrzymujesz masowo niezrównoważone drzewo?
AKTUALIZACJA : Donal Fellows wskazuje w swojej odpowiedzi, że istnieją różne definicje „zrównoważonego”, które można wybrać. Na przykład, można by przyjąć bardziej rygorystyczną definicję „zrównoważonego wzrostu” i wymagać, aby długość ścieżki do najbliższego pustego dziecka znajdowała się w obrębie jednej ze ścieżek do najdalszego pustego dziecka. Moja definicja jest mniej surowa i dlatego dopuszcza więcej drzew.
Można też być mniej restrykcyjne niż moja definicja; można powiedzieć, że zrównoważone drzewo to takie, w którym maksymalna długość ścieżki do pustego drzewa na każdej gałęzi różni się nie więcej niż o dwa, trzy lub o jakąś inną stałą. Albo, że maksymalna długość ścieżki jest ułamkiem minimalnej długości ścieżki, na przykład pół lub ćwiartka.
Zwykle to naprawdę nie ma znaczenia. Celem każdego algorytmu równoważenia drzewa jest zapewnienie, że nie skończysz w sytuacji, w której masz milion węzłów po jednej stronie i trzy po drugiej. Definicja Donala jest w teorii dobra, ale w praktyce wymyślanie algorytmu równoważenia drzewa, który spełnia ten poziom ścisłości, jest uciążliwe. Oszczędności wydajności zwykle nie uzasadniają kosztów wdrożenia. Spędzasz dużo czasu wykonując niepotrzebne przestawianie drzew, aby osiągnąć poziom równowagi, który w praktyce nie ma większego znaczenia. Kogo to obchodzi, jeśli czasami potrzeba czterdziestu gałęzi, aby dostać się do najdalszego liścia w milionie węzłów niedoskonale zrównoważonym drzewie, podczas gdy teoretycznie w idealnie zrównoważonym drzewie może zajmować tylko dwadzieścia? Chodzi o to, że nigdy nie potrzeba miliona. Przejście od najgorszego przypadku miliona do najgorszego przypadku czterdziestki jest zwykle wystarczająco dobre; nie musisz dążyć do optymalnego przypadku.
źródło
Równowaga to naprawdę subtelna właściwość; myślisz, że wiesz, co to jest, ale tak łatwo jest się pomylić. W szczególności, nawet (dobra) odpowiedź Erica Lipperta jest wykluczona. To dlatego, że pojęcie wysokości nie wystarczy. Musisz mieć pojęcie minimalnej i maksymalnej wysokości drzewa (gdzie minimalna wysokość to najmniejsza liczba kroków od korzenia do liścia, a maksymalna to ... cóż, masz obraz). Biorąc to pod uwagę, możemy zdefiniować równowagę jako:
(W rzeczywistości oznacza to, że same gałęzie są zrównoważone; możesz wybrać tę samą gałąź zarówno dla maksimum, jak i minimum).
Wszystko, co musisz zrobić, aby zweryfikować tę właściwość, to proste przejście przez drzewo, które śledzi aktualną głębokość. Za pierwszym razem, gdy cofasz się, daje to podstawową głębokość. Za każdym razem, gdy cofasz się, porównujesz nową głębokość z linią bazową
W kodzie:
Przypuszczam, że można by to zrobić bez użycia wzorca Obserwator, ale wydaje mi się, że łatwiej jest to rozumować w ten sposób.
[EDYCJA]: Dlaczego nie możesz po prostu wziąć wysokości z każdej strony. Rozważ to drzewo:
OK, trochę brudny, ale każda strona z korzenia jest zrównoważony:
C
jest głębokość 2,A
,B
,D
,E
to głębokość 3, aF
,G
,H
,J
to głębokość 4. Wysokość lewej gałęzi wynosi 2 (pamiętaj wysokość zmniejsza się wraz z ukośnymi gałąź), wysokość prawej gałęzi wynosi 3. Jednak całe drzewo nie jest zrównoważone, ponieważ istnieje różnica wysokości 2 międzyC
aF
. Potrzebujesz specyfikacji minimax (chociaż rzeczywisty algorytm może być mniej złożony, ponieważ powinny istnieć tylko dwie dozwolone wysokości).źródło
To tylko określa, czy najwyższy poziom drzewa jest zrównoważony. Oznacza to, że możesz mieć drzewo z dwiema długimi gałęziami z lewej i prawej strony, bez niczego pośrodku, i to wróci prawdą. Musisz rekurencyjnie sprawdzić
root.left
iroot.right
sprawdzić, czy są one również wewnętrznie zrównoważone, zanim zwrócisz wartość true.źródło
Dodatkowa odpowiedź na ćwiczenie. Proste rozwiązanie. Oczywiście w prawdziwej implementacji można zawinąć to lub coś, aby uniknąć wymagania od użytkownika uwzględnienia wysokości w odpowiedzi.
źródło
out height
" zmienną notację)Rozwiązanie po zamówieniu, przejdź przez drzewo tylko raz. Złożoność czasowa to O (n), przestrzeń to O (1), to lepsze niż rozwiązanie odgórne. Dam ci implementację wersji java.
źródło
left == -1
znaczy? Kiedy tak się stanie? Czy zakładamy, że rekurencyjne wywołanie implikuje, żeleft == -1
to prawda, jeśli wszystkie poddrzewa lewych dzieci są niezrównoważone?left == 1
oznacza, że lewe poddrzewo jest niezrównoważone, a następnie całe drzewo jest niezrównoważone. Nie musimy już sprawdzać odpowiedniego poddrzewa i możemy wrócić-1
.Definicja drzewa binarnego o zrównoważonej wysokości to:
Zatem puste drzewo binarne jest zawsze zrównoważone wysokością.
Niepuste drzewo binarne jest zrównoważone wysokością, jeśli:
Rozważ drzewo:
Jak widać, lewe poddrzewo
A
jest zrównoważone wysokością (ponieważ jest puste), podobnie jak jego prawe poddrzewo. Ale nadal drzewo nie jest zrównoważone pod względem wysokości, ponieważ warunek 3 nie jest spełniony, ponieważ wysokość lewego poddrzewa jest taka, jak0
wysokość prawego poddrzewa2
.Kolejne drzewo nie jest zrównoważone pod względem wysokości, mimo że wysokość lewego i prawego poddrzewa jest równa. Twój istniejący kod zwróci dla niego wartość true.
Więc słowo każdy w def jest bardzo ważne.
To zadziała:
Ideone Link
źródło
Jeśli drzewo binarne jest zbalansowane lub nie, można to sprawdzić za pomocą przemierzania kolejności poziomów:
źródło
To jest o wiele bardziej skomplikowane niż w rzeczywistości.
Algorytm wygląda następująco:
Niech B = głębokość węzła najniższego poziomu
Jeśli abs (AB) <= 1, to drzewo jest zrównoważone
źródło
To, jakie zrównoważone środki, zależy w pewnym stopniu od posiadanej konstrukcji. Na przykład drzewo B nie może mieć węzłów znajdujących się dalej niż pewna głębokość od korzenia lub mniej, wszystkie dane żyją na ustalonej głębokości od korzenia, ale mogą być niezrównoważone, jeśli rozmieszczenie liści na liściach -ale jeden węzeł jest nierówny. Listy przeskoków Nie mają pojęcia o równowadze, polegają zamiast tego na prawdopodobieństwie osiągnięcia przyzwoitych wyników. Drzewa Fibonacciego celowo tracą równowagę, opóźniając przywrócenie równowagi, aby osiągnąć lepszą wydajność asymptotyczną w zamian za czasami dłuższe aktualizacje. Drzewa AVL i czerwono-czarne dołączają metadane do każdego węzła, aby uzyskać niezmiennik równowagi głębi.
Wszystkie te i nie tylko struktury są obecne w standardowych bibliotekach większości popularnych systemów programowania (z wyjątkiem Pythona, RAGE!). Wdrożenie jednego lub dwóch jest dobrą praktyką programistyczną, ale prawdopodobnie nie jest to dobre wykorzystanie czasu na toczenie własnych do produkcji, chyba że twój problem ma jakąś szczególną wydajność, której nie muszą zaspokajać żadne gotowe kolekcje.
źródło
Uwaga 1: Wysokość dowolnego poddrzewa obliczana jest tylko raz.
Uwaga 2: Jeśli lewe poddrzewo jest niezrównoważone, wówczas pomijane jest obliczanie prawego poddrzewa, które może zawierać miliony elementów.
źródło
Równoważenie zwykle zależy od długości najdłuższej ścieżki w każdym kierunku. Powyższy algorytm nie zrobi tego za Ciebie.
Co próbujesz wdrożyć? Dookoła rosną samobalansujące się drzewa (AVL / czerwono-czarne). W rzeczywistości drzewa Java są zrównoważone.
źródło
Jeśli to do Twojej pracy , proponuję:
źródło
źródło
1
(tak samo dla minDepth). Jednak poprawna głębokość powinna być0
. Korzeń drzewa zawsze ma0
głębięOto kompletne, przetestowane rozwiązanie w C # (przepraszam, nie mam programisty Java) (po prostu skopiuj wklej w aplikacji konsolowej). Wiem, że definicja zbalansowanego jest różna, więc nie każdemu mogą spodobać się moje wyniki testu, ale proszę spojrzeć na nieco inne podejście do sprawdzania głębokości / wysokości w pętli rekurencyjnej i wychodzenia z pierwszego niedopasowania bez zapisywania wysokości / poziomu / głębokości węzła na każdym węźle (tylko utrzymywanie go w wywołaniu funkcji).
źródło
źródło
RE: @ lucky's rozwiązanie wykorzystujące BFS do przechodzenia przez kolejność poziomów.
Przechodzimy przez drzewo i zachowujemy odniesienie do vars poziomu min / max, które opisują minimalny poziom, na którym węzeł jest liściem.
Uważam, że rozwiązanie @lucky wymaga modyfikacji. Zgodnie z sugestią @codaddict, zamiast sprawdzać, czy węzeł jest liściem, musimy sprawdzić, czy ALBO lewy lub prawy element potomny jest pusty (nie oba). W przeciwnym razie algorytm uznałby to za prawidłowe zrównoważone drzewo:
W Pythonie:
Rozwiązanie to powinno spełniać wszystkie wymagania zawarte w pytaniu wstępnym, operując w czasie O (n) i przestrzeni O (n). Przepełnienie pamięci byłoby skierowane na stertę, a nie przepełnienie rekurencyjnego stosu wywołań.
Alternatywnie możemy początkowo przejść przez drzewo, aby iteracyjnie obliczyć + cache maksymalne wysokości dla każdego poddrzewa głównego. Następnie w kolejnym iteracyjnym przebiegu sprawdź, czy w pamięci podręcznej wysokości lewego i prawego poddrzewa dla każdego katalogu głównego nigdy nie różnią się o więcej niż jeden. To również działałoby w O (n) czasie i O (n) przestrzeni, ale iteracyjnie, aby nie powodować przepełnienia stosu.
źródło
Cóż, potrzebujesz sposobu, aby określić wysokość lewej i prawej strony i czy lewa i prawa są zrównoważone.
A ja po prostu
return height(node->left) == height(node->right);
Jeśli chodzi o pisanie
height
funkcji, przeczytaj: Zrozumienie rekurencjiźródło
O jakim drzewie mówisz? Istnieje samobilansującymi drzewa tam. Sprawdź ich algorytmy, w których określą, czy muszą zmienić kolejność drzewa, aby zachować równowagę.
źródło
Oto wersja oparta na ogólnym przechodzeniu w pierwszej kolejności w głąb. Powinien być szybszy niż inna poprawna odpowiedź i poradzić sobie ze wszystkimi wspomnianymi „wyzwaniami”. Przepraszam za styl, naprawdę nie znam Javy.
Nadal możesz to znacznie przyspieszyć, wracając wcześniej, jeśli maks. I min są ustawione i mają różnicę> 1.
źródło
źródło
źródło
Oto, czego próbowałem w dodatkowym ćwiczeniu Erica. Próbuję rozwinąć moje pętle rekurencyjne i wrócić, gdy tylko znajdę poddrzewo, które nie jest zrównoważone.
źródło
źródło
Puste drzewo ma równowagę wysokości. Niepuste drzewo binarne T jest równoważone, jeśli:
1) Lewe poddrzewo T jest zrównoważone
2) Prawe poddrzewo T jest zrównoważone
3) Różnica między wysokościami lewego i prawego poddrzewa jest nie większa niż 1.
Złożoność czasowa: O (n)
źródło
Aby uzyskać lepszą wydajność, szczególnie na dużych drzewach, możesz zapisać wysokość w każdym węźle, więc jest to kompromis między wydajnością przestrzeni a wydajnością:
Przykład wykonania dodawania i to samo w przypadku usuwania
źródło
źródło
Czy to nie zadziała?
Każde niezrównoważone drzewo zawsze by to zawiodło.
źródło