Cela fait un moment depuis ces années d'école. J'ai trouvé un emploi de spécialiste informatique dans un hôpital. Essayer de bouger pour faire de la programmation réelle maintenant. Je travaille maintenant sur des arbres binaires, et je me demandais quel serait le meilleur moyen de déterminer si l'arbre est équilibré en hauteur.
Je pensais à quelque chose le long de ceci:
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;
}
Est-ce une bonne implémentation? ou est-ce que je manque quelque chose?
java
algorithm
data-structures
binary-tree
user69514
la source
la source
Réponses:
Je suis tombé sur cette vieille question en cherchant autre chose. Je remarque que vous n’avez jamais obtenu de réponse complète.
La manière de résoudre ce problème est de commencer par écrire une spécification pour la fonction que vous essayez d'écrire.
Spécification: Un arbre binaire bien formé est dit "équilibré en hauteur" si (1) il est vide, ou (2) ses enfants gauche et droit sont équilibrés en hauteur et la hauteur de l'arbre gauche est à moins de 1 hauteur de l'arbre droit.
Maintenant que vous avez la spécification, le code est simple à écrire. Suivez simplement les spécifications:
Traduire cela dans le langage de programmation de votre choix devrait être trivial.
Exercice bonus : ce schéma de code naïf parcourt l'arbre beaucoup trop de fois lors du calcul des hauteurs. Pouvez-vous le rendre plus efficace?
Exercice super bonus : supposons que l'arbre soit massivement déséquilibré. Comme, un million de nœuds de profondeur d'un côté et trois de profondeur de l'autre. Existe-t-il un scénario dans lequel cet algorithme fait exploser la pile? Pouvez-vous corriger l'implémentation pour qu'elle ne fasse jamais exploser la pile, même si on lui donne un arbre massivement déséquilibré?
MISE À JOUR : Donal Fellows souligne dans sa réponse qu'il existe différentes définitions de «équilibré» que l'on pourrait choisir. Par exemple, on pourrait prendre une définition plus stricte de «hauteur équilibrée», et exiger que la longueur du chemin vers l' enfant vide le plus proche se trouve dans l'un des chemins vers l' enfant vide le plus éloigné . Ma définition est moins stricte que cela, et admet donc plus d'arbres.
On peut aussi être moins strict que ma définition; on pourrait dire qu'un arbre équilibré est celui dans lequel la longueur maximale du chemin vers un arbre vide sur chaque branche ne diffère pas de plus de deux, ou trois, ou d'une autre constante. Ou que la longueur maximale du chemin est une fraction de la longueur minimale du chemin, comme un demi ou un quart.
Cela n'a vraiment pas d'importance en général. Le but de tout algorithme d'équilibrage d'arbres est de s'assurer que vous ne vous retrouvez pas dans la situation où vous avez un million de nœuds d'un côté et trois de l'autre. La définition de Donal est bonne en théorie, mais en pratique, il est difficile de trouver un algorithme d'équilibrage d'arbres qui répond à ce niveau de rigueur. Les économies de performances ne justifient généralement pas le coût de mise en œuvre. Vous passez beaucoup de temps à faire des réarrangements inutiles des arbres afin d'atteindre un niveau d'équilibre qui en pratique fait peu de différence. Qui se soucie si parfois il faut quarante branches pour atteindre la feuille la plus éloignée dans un arbre imparfaitement équilibré à millions de nœuds alors qu'il ne pourrait en théorie en prendre que vingt dans un arbre parfaitement équilibré? Le fait est qu'il ne faut jamais un million. Passer du pire des cas d'un million à un pire des cas de quarante est généralement suffisant; vous n'êtes pas obligé d'aller jusqu'au cas optimal.
la source
L'équilibre est une propriété vraiment subtile; vous pensez savoir ce que c'est, mais il est si facile de se tromper. En particulier, même la (bonne) réponse d'Eric Lippert est erronée. C'est parce que la notion de hauteur ne suffit pas. Vous devez avoir le concept des hauteurs minimales et maximales d'un arbre (où la hauteur minimale est le plus petit nombre de marches entre la racine et une feuille, et le maximum est ... eh bien, vous obtenez l'image). Compte tenu de cela, nous pouvons définir l'équilibre comme étant:
(Cela implique en fait que les branches sont elles-mêmes équilibrées; vous pouvez choisir la même branche pour le maximum et le minimum.)
Tout ce que vous devez faire pour vérifier cette propriété est une simple traversée d'arbre en gardant une trace de la profondeur actuelle. La première fois que vous revenez en arrière, cela vous donne une profondeur de référence. Chaque fois que vous revenez en arrière, vous comparez la nouvelle profondeur à la ligne de base
Dans du code:
Je suppose que vous pouvez le faire sans utiliser le modèle Observer, mais je trouve plus facile de raisonner de cette façon.
[EDIT]: Pourquoi vous ne pouvez pas simplement prendre la hauteur de chaque côté. Considérez cet arbre:
OK, un désordre de peu, mais chaque côté de la racine est équilibrée:
C
est la profondeur 2,A
,B
,D
,E
sont la profondeur 3, etF
,G
,H
,J
sont la profondeur 4. La hauteur de la branche gauche est 2 (souvenez - vous de la hauteur diminue à mesure que vous traversez la branche), la hauteur de la branche droite est 3. Pourtant, l'arbre global n'est pas équilibré car il y a une différence de hauteur de 2 entreC
etF
. Vous avez besoin d'une spécification minimax (bien que l'algorithme réel puisse être moins complexe car il ne devrait y avoir que deux hauteurs autorisées).la source
Cela détermine uniquement si le niveau supérieur de l'arborescence est équilibré. Autrement dit, vous pourriez avoir un arbre avec deux longues branches à l'extrême gauche et à l'extrême droite, sans rien au milieu, et cela reviendrait vrai. Vous devez vérifier de manière récursive les
root.left
etroot.right
pour voir s'ils sont également équilibrés en interne avant de renvoyer true.la source
Réponse d'exercice bonus. La solution simple. De toute évidence, dans une implémentation réelle, on pourrait envelopper ceci ou quelque chose pour éviter d'exiger que l'utilisateur inclue la hauteur dans sa réponse.
la source
out height
notation variable " ")Après la solution de commande, parcourez l'arbre une seule fois. La complexité temporelle est O (n), l'espace est O (1), c'est mieux que la solution descendante. Je vous donne une implémentation de version java.
la source
left == -1
dire? Quand cela serait-il jamais le cas? Supposons-nous que l'appel récursif implique que celeft == -1
soit vrai si tous les sous-arbres des enfants de gauche sont déséquilibrés?left == 1
signifie que le sous-arbre gauche est déséquilibré, alors l'arbre entier est déséquilibré. Nous n'avons plus besoin de vérifier le bon sous-arbre et pouvons revenir-1
.La définition d'un arbre binaire à hauteur équilibrée est:
Ainsi, un arbre binaire vide est toujours équilibré en hauteur.
Un arbre binaire non vide est équilibré en hauteur si:
Considérez l'arbre:
Comme on le voit, le sous-arbre gauche de
A
est équilibré en hauteur (car il est vide), tout comme son sous-arbre droit. Mais l'arbre n'est toujours pas équilibré en hauteur car la condition 3 n'est pas remplie car la hauteur du sous-arbre gauche l'est0
et la hauteur du sous-arbre droit l'est2
.L'arbre suivant n'est pas non plus équilibré en hauteur même si la hauteur du sous-arbre gauche et droit est égale. Votre code existant retournera vrai pour cela.
Donc, le mot tout dans la def est très important.
Cela fonctionnera:
Lien Ideone
la source
Si l'arbre binaire est équilibré ou non peut être vérifié par la traversée de l'ordre de niveau:
la source
Cela devient beaucoup plus compliqué qu'il ne l'est en réalité.
L'algorithme est le suivant:
Soit B = profondeur du nœud le plus bas
Si abs (AB) <= 1, alors l'arbre est équilibré
la source
Ce que signifie équilibré dépend un peu de la structure à portée de main. Par exemple, un arbre B ne peut pas avoir de nœuds à plus d'une certaine profondeur à partir de la racine, ou moins d'ailleurs, toutes les données vivent à une profondeur fixe à partir de la racine, mais il peut être déséquilibré si la distribution des feuilles aux feuilles -mais-un des nœuds est inégal. Skip-lists N'ont aucune notion d'équilibre, s'appuyant plutôt sur la probabilité d'atteindre des performances décentes. Les arbres de Fibonacci tombent délibérément en déséquilibre, reportant le rééquilibrage pour obtenir des performances asymptotiques supérieures en échange de mises à jour parfois plus longues. Les arbres AVL et Rouge-Noir attachent des métadonnées à chaque nœud pour atteindre un invariant d'équilibre de profondeur.
Toutes ces structures et bien d'autres sont présentes dans les bibliothèques standard de la plupart des systèmes de programmation courants (sauf python, RAGE!). Mettre en œuvre un ou deux est une bonne pratique de programmation, mais ce n'est probablement pas une bonne utilisation du temps pour lancer le vôtre pour la production, à moins que votre problème n'ait des performances particulières qui ne sont pas satisfaites par des collections standard.
la source
Remarque 1: La hauteur de tout sous-arbre n'est calculée qu'une seule fois.
Remarque 2: Si le sous-arbre de gauche est déséquilibré, alors le calcul du sous-arbre de droite, contenant potentiellement un million d'éléments, est ignoré.
la source
L'équilibrage dépend généralement de la longueur du chemin le plus long dans chaque direction. L'algorithme ci-dessus ne fera pas cela pour vous.
Qu'essayez-vous de mettre en œuvre? Il y a des arbres auto-équilibrés autour (AVL / Rouge-noir). En fait, les arbres Java sont équilibrés.
la source
Si c'est pour votre travail , je suggère:
la source
la source
1
(même chose pour minDepth). La profondeur correcte devrait cependant être0
.La racine d'un arbre a toujours de la0
profondeurVoici une solution complète et testée en C # (désolé, je ne suis pas un développeur Java) (il suffit de copier-coller dans l'application console). Je sais que la définition d'équilibré varie, donc tout le monde ne peut pas aimer mes résultats de test, mais veuillez simplement regarder l'approche légèrement différente de la vérification de la profondeur / hauteur dans une boucle récursive et de la sortie au premier décalage sans enregistrer la hauteur / niveau / profondeur du nœud sur chaque nœud (le maintenir uniquement dans un appel de fonction).
la source
la source
RE: la solution de @ lucky utilisant un BFS pour effectuer un parcours par ordre de niveau.
Nous parcourons l'arbre et gardons une référence aux vars min / max-level qui décrivent le niveau minimum auquel un nœud est une feuille.
Je pense que la solution @lucky nécessite une modification. Comme suggéré par @codaddict, plutôt que de vérifier si un nœud est une feuille, nous devons vérifier si SOIT les enfants gauche ou droit sont nuls (pas les deux). Sinon, l'algorithme considérerait cela comme un arbre équilibré valide:
En Python:
Cette solution doit satisfaire toutes les stipulations fournies dans la question initiale, fonctionnant en temps O (n) et en espace O (n). Le débordement de mémoire serait dirigé vers le tas plutôt que de souffler une pile d'appels récursive.
Alternativement, nous pourrions initialement parcourir l'arborescence pour calculer les hauteurs max + cache pour chaque sous-arbre racine de manière itérative. Ensuite, dans une autre exécution itérative, vérifiez si les hauteurs mises en cache des sous-arbres gauche et droit pour chaque racine ne diffèrent jamais de plus d'un. Cela fonctionnerait également dans le temps O (n) et dans l'espace O (n) mais de manière itérative pour ne pas provoquer de débordement de pile.
la source
Eh bien, vous avez besoin d'un moyen de déterminer les hauteurs de gauche et de droite, et si la gauche et la droite sont équilibrées.
Et je viens de
return height(node->left) == height(node->right);
Quant à l'écriture d'une
height
fonction, lisez: Comprendre la récursivitéla source
De quel genre d'arbre parlez-vous? Il y a des arbres auto-équilibrés là-bas. Vérifiez leurs algorithmes où ils déterminent s'ils doivent réorganiser l'arbre afin de maintenir l'équilibre.
la source
Voici une version basée sur un parcours générique en profondeur d'abord. Devrait être plus rapide que l'autre bonne réponse et gérer tous les «défis» mentionnés. Toutes mes excuses pour le style, je ne connais pas vraiment Java.
Vous pouvez toujours le rendre beaucoup plus rapide en revenant tôt si max et min sont tous les deux définis et ont une différence> 1.
la source
la source
la source
Voici ce que j'ai essayé pour l'exercice bonus d'Eric. J'essaye de dérouler mes boucles récursives et de revenir dès que je trouve qu'un sous-arbre n'est pas équilibré.
la source
la source
Un arbre vide est équilibré en hauteur. Un arbre binaire T non vide est équilibré si:
1) Le sous-arbre gauche de T est équilibré
2) Le sous-arbre droit de T est équilibré
3) La différence entre les hauteurs du sous-arbre gauche et du sous-arbre droit n'est pas supérieure à 1.
Complexité temporelle: O (n)
la source
Pour avoir de meilleures performances, spécialement sur les arbres énormes, vous pouvez enregistrer la hauteur de chaque nœud, c'est donc un compromis entre l'espace et les performances:
Exemple de mise en œuvre de l'ajout et de même pour la suppression
la source
la source
Cela ne fonctionnerait-il pas?
Tout arbre déséquilibré échouerait toujours.
la source