web-dev-qa-db-fra.com

Avec un nœud, combien de temps faudra-t-il pour graver tout l’arbre binaire?

Lors d'un de mes entretiens simulés, un problème me posait la question de savoir combien de temps un arbre binaire serait complètement détruit après qu'un nœud donné est déjà en feu.

"Un arbre binaire commence à graver depuis un nœud feuille. Quel est le temps (Il ne faut qu'une seconde pour graver d'un nœud à l'autre) pour que l'arbre soit complet Brûlé? Le feu se propage à tous les chemins un nœud. "

Supposons que vous ayez un arbre comme celui-ci, où N est le nœud en feu. C'est se passe dans la première seconde, où secondes est s, donc dans le zeroth s:

           1
       /       \
      1          1
    /  \          \
   1    1          1
      /   \         \
     1     N         1
                      \
                       1

Après une seconde, l’arborescence sera mise à jour avec davantage de nœuds brûlés . Un exemple de la seconde suivante (s + 1) sera comme ceci:

           1
       /       \
      1          1
    /  \          \
   1    N          1
      /   \         \
     1     N         1
                      \
                       1

Un exemple de la seconde suivante (s + 2) sera comme ceci:

           1
       /       \
      N          1
    /  \          \
   1    N          1
      /   \         \
     N     N         1
                      \
                       1  

Maintenant à la troisième seconde (s + 3) sera comme ceci:

           N
       /       \
      N          1
    /  \          \
   N    N          1
      /   \         \
     N     N         1
                      \
                       1

Avec le même motif, l’arbre sera brûlé (s + 7)

           N
       /       \
      N          N
    /  \          \
   N    N          N
      /   \         \
     N     N         N
                      \
                       N

Après avoir compris un peu, j'ai fait une petite recherche pour savoir comment le faire. J'ai trouvé ce cool { article } _ et l'ai suivi et mis en œuvre l'idée derrière.

Mon approche consistait à trouver le diamètre, ainsi que la hauteur de l'arbre, pour rechercher le nœud le plus éloigné. Toutefois, lorsque j'ai implémenté mes fonctions, le résultat du nœud de départ est renvoyé à la fin du nœud donné sans vérifier les nœuds parents précédents. Voici mon implémentation dans Python 3:

# Tree class
class Node:
    def __init__(self, key):
        self.left = None
        self.right = None
        self.value = key

# Maximum height of a tree
def maxHeight(root):
    if root is None:
        return 0
    else:
        return 1 + max(maxHeight(root.left), maxHeight(root.right))

# Diameter of the tree
def maxDiameter(root):
    how_long = 0
    if root is None:
        return 0
    else:
        root_diameter = maxHeight(root.left) + maxHeight(root.right)

        left_diameter = maxDiameter(root.left)
        right_diameter = maxDiameter(root.right)
        how_long = max(max(left_diameter, right_diameter), root_diameter)
        return how_long

# Sample code
root = Node(1)
root.left = Node(1)
root.right = Node(1)
root.left.left = Node(1)
root.left.right = Node(1)
root.left.right.left = Node(1)
root.left.right.right = Node(1)
root.right.right = Node(1)
root.right.right.right = Node(1)
root.right.right.right.right = Node(1)
print ("Starting from the given node, it will take %ds to burn the whole tree" % (maxDiameter(root.left.right)))

La sortie attendue pour cet exemple doit être de 6 s (à partir des 0 avec le nœud donné). Mais encore une fois, je n'obtiens pas toute la portée de l'arbre. De ma propre compréhension, cela doit fonctionner avec tous les cas. Alors, quelle recherche serait utile ici, DFS ou BFS? Je pense que cela me guidera vers ma solution, mais encore une fois. Tout commentaire est apprécié :)

7
Zeid Tisnes

Pour ceux qui se demandent ce qui s'est passé avec ce post, la solution utilisée était la suivante:

LeafSide = []

class Node:
    """Tree class."""

    def __init__(self, key):
        """Declare values of a node."""
        self.left = None
        self.right = None
        self.value = key


def leafHeight(root, leaf):
    """Height of the leaf."""
    if root is None:
        return 0
    else:
        if root.left is leaf:
            aux = 1 + leafHeight(root.right, leaf)
            LeafSide.append(aux)
            return 1
        if root.right is leaf:
            aux = 1 + leafHeight(root.left, leaf)
            LeafSide.append(aux)
            return 1
        return 1 + max(leafHeight(root.left, leaf), leafHeight(root.right, leaf))


def timeBurn(root, leaf):
    """How long will it take to burn the the node to furthest node."""
    hl = leafHeight(root.left, leaf)
    hr = leafHeight(root.right, leaf)
    opposite_LeafSide = 1 + hl + hr
    return max(opposite_LeafSide, LeafSide[0])


if __== '__main__':
    root = Node(1)
    root.left = Node(1)
    root.right = Node(1)
    root.left.left = Node(1)
    root.left.right = Node(1)
    root.left.right.left = Node(1)
    root.left.right.right = Node(1)
    root.right.right = Node(1)
    root.right.right.right = Node(1)
    root.right.right.right.right = Node(1)
    print ("Starting from the given node, it will take %ds to burn the whole tree" % (timeBurn(root, root.left.right)))

Heure: O(n)

Espace: O(n)

Si vous remarquez, chaque nœud a la valeur 1. La valeur du nœud n'a pas d'importance pour ce problème. Cela représente simplement une valeur en elle. La raison pour laquelle j'en ai une est de penser à une seconde (nœud 1 seconde). Merci à tous ceux qui m'ont aidé. J'ai aimé lire tous les commentaires et les approches que vous discutiez :). Si vous avez une meilleure idée pour améliorer le code, n'hésitez pas à commenter ci-dessous!

0
Zeid Tisnes

Il me semble que vous avez besoin des éléments suivants:

  1. Si le nœud de départ est à gauche ou à droite de la racine.
  2. La profondeur du nœud de départ (appelez-le dStart).
  3. La profondeur du nœud le plus éloigné de la racine sur la branche du nœud de départ (c'est-à-dire à gauche ou à droite de la racine). Nous appellerons cela dSameSide
  4. Profondeur de l'ancêtre commun le plus bas du nœud de départ et du nœud identifié en n ° 3. (appelez-le dCommonAncestor)
  5. Profondeur du noeud le plus bas sur le côté opposé de l'arbre, dOppositeSide.

Vous pouvez obtenir toutes ces informations à partir d’une seule traversée de l’arbre.

Le nombre d'étapes à suivre pour aller du nœud de départ au nœud le plus profond de ce côté de l'arborescence est (dSameSide - dCommonAncestor) + (dStart - dCommonAncestor).

Le nombre d'étapes à suivre pour aller du nœud de départ au nœud le plus profond du côté opposé est (dStart + dOppositeSide).

Et le nombre d'étapes nécessaires pour brûler l'arbre entier est le maximum de ces deux.

Je vous laisse la mise en œuvre. Vous trouverez probablement Comment trouver l'ancêtre commun le plus bas de deux nœuds dans un arbre binaire? utile.

3
Jim Mischel

Cela peut être résolu en utilisant une fonction récursive qui renvoie la longueur du chemin du nœud actuel au nœud de départ (ou simplement le chemin le plus long d'une feuille si le nœud de départ n'est pas en dessous).

Nous pouvons également lui demander de retourner le plus long chemin si éloigné du nœud de départ, s'il est trouvé, ce qui est simplement la somme de la fonction appelée à la fois sur les enfants gauche et droit (plus un, pour le nœud actuel).

Ceci est similaire à la solution décrite par m69 .

Cela s'exécute à O(n) heure, car la fonction est exécutée à temps constant (si vous excluez les appels récursifs), et la fonction est appelée au plus trois fois pour chaque noeud (pour le noeud lui-même et pour ses gauche et enfants droits, dans le cas de nœuds feuilles).

Ceci utilisera O(height) espace, car nous ne stockons rien en dehors des appels de fonction avec leurs variables, et le nombre maximum de ceux que nous pouvons avoir en mémoire à un moment donné est égal à la profondeur de récursivité (c.-à-d. la hauteur de l'arbre).

class Node:
    def __init__(self, key):
        self.left = None
        self.right = None
        self.value = key

# returns a Tuple (max = the longest path so far, dist = current path)
def _recurse(node, start):
    if node is None:
        return (None, 0)
    else:
        max_left, dist_left = _recurse(node.left, start)
        max_right, dist_right = _recurse(node.right, start)
        # this node is the starting node
        if node == start:
            return (0, 0)
        # the starting node is in left or right
        Elif max_right is not None or max_left is not None:
            return (dist_right + dist_left + 1,
                    (dist_left if max_right is None else dist_right) + 1)
        # we haven't seen the starting node
        else:
            return (None, max(dist_left, dist_right) + 1)

def time_to_burn(root, start):
    return _recurse(root, start)[0]

Tester:

root = Node(1)
root.left = Node(1)
root.right = Node(1)
root.left.left = Node(1)
root.left.right = Node(1)
root.left.right.left = Node(1)
root.left.right.right = Node(1)
root.right.right = Node(1)
root.right.right.right = Node(1)
root.right.right.right.right = Node(1)

>>> time_to_burn(root, root.left.right.right)
7

Solution qui fonctionne avec des nœuds de départ non-feuilles

L'idée de base est d'avoir 3 valeurs de retour pour chaque nœud:

  • max, qui est le chemin le plus long depuis le noeud de départ obtenu jusqu'à présent (ou None si nous n'avons pas encore vu le noeud de départ).
  • above, qui est le nombre de nœuds au-dessus du nœud de départ (ou None si nous n'avons pas encore vu le nœud de départ).
  • below, qui est le plus long chemin sous le nœud de départ (ou simplement le plus long chemin du nœud actuel si nous n'avons pas encore vu le nœud de départ).

Le calcul de above et below à partir des sous-arborescences enfants est assez simple - voir le code pour plus de détails.

Nous pouvons définir le plus long chemin max à partir du nœud actuel comme le maximum de:

  • Le plus long chemin descendant du noeud de départ (qui est juste below)
  • et le plus long chemin qui inclut le nœud actuel, qui sera la distance entre le nœud actuel et le nœud de départ plus le plus long chemin de la sous-arborescence sans le nœud de départ (plus un).

Code: (remplace la fonction _recurse ci-dessus)

# returns a Tuple (max, above, below)
def _recurse(node, start):
    if node is None:
        return (None, None, 0)
    else:
        max_left, above_left, below_left = _recurse(node.left, start)
        max_right, above_right, below_right = _recurse(node.right, start)
        # this node is the starting node
        if node == start:
            below = max(below_left, below_right)
            return (below, 0, below)
        # the starting node is in left or right
        Elif above_right is not None or above_left is not None:
            return (max((0 if above_right is None else above_right) + below_left,
                        (0 if above_left is None else above_left) + below_right) + 1,
                    (above_right if above_left is None else above_left) + 1,
                    below_right if above_left is None else below_left)
        # we haven't seen the starting node
        else:
            return (None, None, max(below_left, below_right) + 1)

>>> time_to_burn(root, root.left.right)
6
2
Dukeling

Prenons l'exemple ci-dessous. d'abord, traversez de la racine à la feuille en feu (F): 

     N
    / \
   N   N
  / \   \
 N   N   N
    / \   \
   N   F   N
  / \       \
 N   N       N
      \
       N

Ensuite, déplacez-vous vers son nœud parent et prenez la somme de la distance qui le sépare de la feuille en feu (1) et de la hauteur du sous-arbre de gauche (3), qui est 4: 

     N
    / \
   N   N
  / \   \
 N   4   N
    / \   \
   3   1   N
  / \       \
 N   2       N
      \
       1

Donc 4 est le maximum actuel. Maintenant, déplacez-vous jusqu'au nœud parent et prenez la somme de la distance qui sépare la feuille en feu (2) et de la profondeur du sous-arbre de gauche (1), qui est 3: 

     N
    / \
   3   N
  / \   \
 1   2   N
    / \   \
   N   1   N
  / \       \
 N   N       N
      \
       N

Donc, le maximum actuel reste 4. Maintenant, déplacez-vous jusqu'au nœud parent et prenez la somme de la distance de la feuille en feu (3) et de la profondeur du sous-arbre de droite (4), qui est 7: 

     7
    / \
   3   4
  / \   \
 N   2   3
    / \   \
   N   1   2
  / \       \
 N   N       1
      \
       N

Le nouveau maximum est 7, et nous avons atteint le nœud racine. La réponse est donc 7. Vous pouvez vérifier quels nœuds sont en feu au bout de x secondes: 

     3
    / \
   2   4
  / \   \
 3   1   5
    / \   \
   2   0   6
  / \       \
 3   3       7
      \
       4

Voici un exemple où la racine ne fait pas partie du chemin le plus long: 

         N            N            3                  2
        / \          / \          / \                / \
       N   N        4   N        2   1              1   3
      / \          / \          / \                / \
     N   F        3   1        N   1              2   0
    /            /            /                  /
   N            2            N                  3
  /            /            /                  /
 N            1            N                  4

La plus grande valeur rencontrée était 4, chez le parent de la feuille en feu. 


Voici un extrait de code JavaScript simple (je ne parle pas Python, mais cela devrait fonctionner comme un pseudo-code). Il utilise une version codée en dur de l'arbre dans le premier exemple de ma réponse. Comme vous le verrez, il effectue une traversée unique de l’arbre en profondeur. 

function burn(root) {
    var maximum = 0;
    traverse(root);
    return maximum;

    function traverse(node) {
        if (node.onfire) {
            return {steps: 1, onfire: true};
        }
        var l = node.left ? traverse(node.left) : {steps: 0};
        var r = node.right ? traverse(node.right) : {steps: 0};
        if (l.onfire || r.onfire) {
            maximum = Math.max(maximum, l.steps + r.steps);
            return {steps: (l.onfire ? l.steps : r.steps) + 1, onfire: true};
        }
        return {steps: Math.max(l.steps, r.steps) + 1};
    }
}

var tree = {left: {left: {left: null, right: null}, right: {left: {left: {left: null, right: null}, right: {left: null, right: {left: null, right: null}}}, right: {left: null, right: null, onfire:true}}}, right: {left: null, right: {left: null, right: {left: null, right: {left: null, right: null}}}}}
document.write(burn(tree));

1
m69

Cela peut être fait rapidement avec BFS:

class Node:
    def __init__(self, value):
        self.left = None
        self.right = None
        self.parent = None
        self.value = value

    def set_left(self, other):
        self.left = other
        other.parent = self

    def set_right(self, other):
        self.right = other
        other.parent = self

def get_distance_to_furthest(node):
    visited = set()
    queue = [(node, 0)]
    max_d = 0
    while queue:
        node, d = queue.pop(0)

        if node in visited:
            continue
        visited.add(node)

        max_d = max(d, max_d)

        if node.left:
            queue.append((node.left, d + 1))
        if node.right:
            queue.append((node.right, d + 1))
        if node.parent:
            queue.append((node.parent, d + 1))

    return max_d


# Sample code
root = Node(1)
root.set_left(Node(1))
root.set_right(Node(1))
root.left.set_left(Node(1))
root.left.set_right(Node(1))
root.left.right.set_left(Node(1))
root.left.right.set_right(Node(1))
root.right.set_right(Node(1))
root.right.right.set_right(Node(1))
root.right.right.right.set_right(Node(1))
print(
    "Starting from the given node, it will take %ds to burn the whole tree"
    % (get_distance_to_furthest(root.left.right))
)

Un arbre binaire est simplement un type de graphique spécial. Vous pouvez ainsi parcourir tous les nœuds et garder une trace de la distance entre chaque nœud et le nœud où l'incendie a commencé. Le résultat est la plus grande distance que vous avez vue.

1
fafl

Celui-ci est mon approche. Basé sur le nœud qui a la feuille à gauche ou à droite, vous avez deux possibilités:

  • explorer l'arbre en bas
  • explorez l'arbre de l'autre côté 

Ces deux possibilités définissent deux chemins. Le plus long chemin est la réponse au problème (le plus long chemin entre la feuille sélectionnée et toute autre feuille). Ceci est mieux compris dans cette figure sur un noeud de gravure donné (rouge) et sur le noeud qui a la référence de feuille (bleu)

 FIGURE

Par programme, nous explorons l’arbre jusqu’à trouver le nœud qui a la référence à la feuille. Dans ce cas, nous calculons le chemin qui explore le reste de l'arbre (du côté de l'arbre d'origine qui a la feuille) et retournons 1 (pour créer le chemin de l'autre côté avec la récursivité). 

1
Mauro Pucheta