web-dev-qa-db-de.com

Wie lange dauert es bei einem Knoten, den gesamten Binärbaum zu brennen?

In einem Interview bekam ich ein Problem, bei dem ich herausfinden musste, wie lange ein binärer Baum vollständig abgebrannt ist, nachdem ein Knoten bereits in Brand geraten ist.

"Ein binärer Baum wird von einem Blattknoten aus gebrannt. Wie lang ist es (1 Sekunde, um von Knoten zu Knoten zu brennen), den gesamten Baum zu bekommen Verbrannt? Das Feuer wird sich auf alle Pfade ausbreiten von einem Knoten aus. "

Angenommen, Sie haben einen Baum wie diesen, wobei N der brennende Knoten ist. Das ist passiert in der ersten Sekunde, wo Sekunden s ist, also in den nullten s:

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

Nachdem eine Sekunde vergangen ist, wird der Baum mit weiteren gebrannten Knoten aktualisiert. Ein Beispiel für die nächste Sekunde (s + 1) sieht so aus:

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

Ein Beispiel für die nächste Sekunde (s + 2) sieht so aus:

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

Jetzt an der dritten Sekunde (s + 3) wird es so aussehen:

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

Mit demselben Muster wird der Baum eingebrannt (s + 7).

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

Nachdem ich mich ein wenig verstanden hatte, recherchierte ich ein wenig, um herauszufinden, wie es geht. Ich fand diesen coolen Artikel und habe es verfolgt und die Idee dahinter umgesetzt.

Mein Ansatz bestand darin, den Durchmesser zusammen mit der Höhe des Baums zu ermitteln, um nach dem am weitesten entfernten Knoten zu suchen. Wenn ich jedoch meine Funktionen implementiere, erhalte ich nur das Ergebnis des Startknotens bis zum Ende des gegebenen Knotens, ohne die vorherigen übergeordneten Knoten zu überprüfen. Hier ist meine Implementierung in 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)))

Die erwartete Ausgabe für dieses Beispiel sollte 6s sein (beginnend mit den 0s mit dem angegebenen Knoten). Aber wieder bekomme ich nicht den vollen Umfang des Baumes. Nach meinem eigenen Verständnis muss es mit allen Fällen funktionieren. Welche Suche wäre hier also hilfreich, DFS oder BFS? Ich denke, wenn ich das im Hinterkopf habe, werde ich zu meiner Lösung führen, aber wieder. Jedes Feedback wird geschätzt :)

7
Zeid Tisnes

Für diejenigen, die sich fragen, was mit diesem Beitrag passiert ist, wurde folgende Lösung verwendet:

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)))

Zeit: O(n)

Space: O(n)

Wenn Sie feststellen, hat jeder Knoten den Wert 1. Der Wert des Knotens spielt für dieses Problem keine Rolle. Es ist nur ein Wert darin. Der Grund, warum ich einen habe, ist, an einen zweiten (1-Sekunden-Knoten) zu denken. Danke an alle, die mir geholfen haben. Ich habe es genossen, alle Kommentare und Ansätze zu lesen, mit denen Sie sich unterhalten haben :). Wenn Sie eine bessere Idee haben, wie Sie den Code verbessern können, kommentieren Sie bitte unten!

0
Zeid Tisnes

Mir fällt ein, dass Sie Folgendes brauchen:

  1. Gibt an, ob sich der Startknoten links oder rechts vom Stamm befindet.
  2. Die Tiefe des Startknotens (nennen Sie dStart).
  3. Die Tiefe des Knotens, die am weitesten von der Wurzel am Zweig des Startknotens entfernt ist (d. H. Links oder rechts von der Wurzel). Wir nennen das dSameSide
  4. Tiefe des untersten gemeinsamen Vorfahren des Startknotens und des in # 3 angegebenen Knotens. (nennen Sie es dCommonAncestor)
  5. Tiefe des untersten Knotens auf der gegenüberliegenden Seite des Baums, dOppositeSide.

Sie können alle diese Informationen von einem einzigen Inorder-Durchlauf des Baums erhalten.

Die Anzahl der Schritte, die erforderlich sind, um vom Startknoten zum tiefsten Knoten auf dieser Seite des Baums zu gelangen, ist (dSameSide - dCommonAncestor) + (dStart - dCommonAncestor).

Die Anzahl der Schritte, die erforderlich sind, um vom Startknoten zum tiefsten Knoten auf der gegenüberliegenden Seite zu gelangen, ist (dStart + dOppositeSide).

Und die Anzahl der Schritte, die erforderlich sind, um den gesamten Baum zu brennen, ist das Maximum dieser beiden.

Ich überlasse Ihnen die Implementierung. Sie finden wahrscheinlich Wie finde ich den niedrigsten gemeinsamen Vorfahren zweier Knoten in einem binären Baum? hilfreich.

3
Jim Mischel

Dies kann mit einer rekursiven Funktion gelöst werden, die die Länge des Pfads vom aktuellen Knoten bis zum Startknoten (oder nur den längsten Pfad zu einem beliebigen Blatt, wenn der Startknoten nicht darunter liegt) zurückgibt.

Es kann auch der längste Pfad vom Startknoten zurückgegeben werden, sofern dieser gefunden wurde. Dies ist einfach die Summe der Funktion, die sowohl für das linke als auch für das rechte Kind aufgerufen wird (plus eine für den aktuellen Knoten).

Dies ist ähnlich zu der von m69 beschriebenen Lösung.

Dies läuft in O(n) - Zeit ab, da die Funktion in einer konstanten Zeit ausgeführt wird (wenn Sie die rekursiven Aufrufe ausschließen), und die Funktion wird höchstens dreimal für jeden Knoten (für den Knoten selbst und für dessen linken und.) Aufgerufen rechte Kinder, im Fall von Blattknoten).

Dies wird O(height) - Speicherplatz verwenden, da mit ihren Variablen nichts außer den Funktionsaufrufen gespeichert wird und die maximale Anzahl derjenigen, die wir zu einem bestimmten Zeitpunkt im Speicher haben können, der Rekursionstiefe entspricht (dh die Höhe des Baumes).

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]

Prüfung:

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

Lösung, die mit Nicht-Blatt-Startknoten arbeitet

Die Grundidee besteht darin, drei Rückgabewerte für jeden Knoten zu haben:

  • max, das ist der längste Pfad vom bisher erhaltenen Startknoten (oder None, falls der Startknoten noch nicht gesehen wurde).
  • above, dh die Anzahl der Knoten über dem Startknoten (oder None, falls der Startknoten noch nicht gesehen wurde).
  • below, der längste Pfad unter dem Startknoten (oder nur der längste Pfad vom aktuellen Knoten, wenn der Startknoten noch nicht gesehen wurde).

Die Berechnung von above und below aus den untergeordneten Teilbäumen ist recht einfach - siehe Code für Details.

Wir können den längsten Pfad max vom aktuellen Knoten als das Maximum von definieren:

  • Der längste Pfad, der vom Startknoten abwärts führt (der nur below ist)
  • und der längste Pfad, der den aktuellen Knoten enthält, der die Entfernung vom aktuellen Knoten zum Startknoten plus der längste Pfad im Teilbaum ohne den Startknoten (plus eins) ist.

Code: (Ersetzt oben die _recurse-Funktion)

# 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

Nehmen Sie das Beispiel unten. Zuerst durchqueren Sie die Wurzel bis zum brennenden Blatt (F): 

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

Bewegen Sie sich dann zum übergeordneten Knoten und nehmen Sie die Summe der Entfernung zum brennenden Blatt (1) und der Höhe des linken Teilbaums (3), die 4 beträgt: 

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

4 ist also das aktuelle Maximum. Gehen Sie nun zum übergeordneten Knoten und nehmen Sie die Summe der Entfernung zum brennenden Blatt (2) und der Tiefe des linken Unterbaums (1), die 3 beträgt: 

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

Das aktuelle Maximum bleibt also 4. Bewegen Sie sich nun zum übergeordneten Knoten und nehmen Sie die Summe der Entfernung zum brennenden Blatt (3) und der Tiefe des rechten Teilbaums (4) (7) 

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

Das neue Maximum ist 7, und wir haben den Wurzelknoten erreicht, also ist 7 die Antwort. Sie können überprüfen, indem Sie prüfen, welche Knoten nach x Sekunden in Brand sind: 

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

Hier ist ein Beispiel, bei dem die Wurzel nicht Teil des längsten Pfads ist: 

         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

Der größte Wert, der angetroffen wurde, war 4, im Elternteil des brennenden Blattes. 


Hier ist ein einfaches JavaScript-Code-Snippet (ich spreche kein Python, aber dies sollte als Pseudo-Code funktionieren). Es verwendet eine hart codierte Version des Baums im ersten Beispiel aus meiner Antwort. Wie Sie sehen, führt der Baum eine einzige Tiefenskala durch. 

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

Mit BFS geht das schnell:

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))
)

Ein binärer Baum ist nur eine spezielle Art von Diagramm, sodass Sie durch alle Knoten gehen und die Entfernung jedes Knotens zu dem Knoten verfolgen können, an dem der Brand begann. Das Ergebnis ist die höchste Entfernung, die Sie gesehen haben.

1
fafl

Dies ist mein Ansatz. Basierend auf dem Knoten, auf dem sich das Blatt links oder rechts befindet, haben Sie zwei Möglichkeiten:

  • erforsche den Baum nach unten
  • erforsche den Baum auf der anderen Seite 

Diese zwei Möglichkeiten definieren zwei Wege. Der längste Pfad ist die Antwort auf das Problem (der längste Pfad zwischen dem ausgewählten Blatt und einem anderen Blatt). In dieser Abbildung ist es am besten für einen bestimmten Brennpunkt (rot) und den Knoten mit der Blattreferenz (blau) zu verstehen.

 FIGURE

Programmatisch untersuchen wir den Baum, bis wir den Knoten finden, der den Verweis auf das Blatt hat. In diesem Fall berechnen wir den Pfad, der den Rest des Baums (auf der Seite des ursprünglichen Baums, der das Blatt enthält) erforscht, und geben 1 zurück (um den Pfad zur anderen Seite mit der Rekursion zurück zu erstellen). 

1
Mauro Pucheta

Im Folgenden finden Sie eine der Lösungen, um die Zeit zu ermitteln, die zum Brennen des Baums bei einem gegebenen Quellknoten benötigt wird (der ein Leave-Knoten oder ein Non-Leave-Knoten sein kann).

Lösungsansatz ist wie folgt:

1) Finde den Quellknoten im Baum und finde die Höhe des Knotens (hier speichern wir ihn in der Variablen "sourceDepth")

2) Für alle Vorfahren des angegebenen Quellknotens

 ->Take distance from the source node and present node 

 ->Find the height of the opposite subtree in which the source is not present

 ->Add both of the above + 1 (for the Edge between ancestor and sub tree).Lets call this d

3) Nehmen Sie das Maximum aller d aus Schritt 2 und sourceDepth aus Schritt 1, was die erforderliche Antwort ist.

Für das folgende Beispiel sei source 3:

     7
    / \
   8   4
  / \   \
 10   9   3
    / \   \
   0   11   2
             \
              1

die Tiefe der Quelle (d. h. 3) ist: sourceDepth = 2

Alle Vorfahren der Quelle sind [7, 4]

Für Vorfahren 4:

der Abstand von der Quelle beträgt 1 und es gibt keinen Unterbaum in der entgegengesetzten Richtung der Quelle (d. h. die Quelle befindet sich im rechten Unterbaum und es gibt keinen linken Unterbaum). Also ist d hier 1.

Für Vorfahren 7

die Entfernung von der Quelle beträgt 2 und die Höhe des Unterbaums in entgegengesetzter Richtung zur Quelle beträgt 2. Also ist d hier 2 + 2 + 1 = 5. (1 ist für die Kante zwischen 7 und 8)

Knoten 7, rechter Unterbaum, für den die Höhe = 2 ist

   8   
  / \  
 10   9  
    / \  
   0   11 

Die Lösung in diesem Fall ist Max von (2,1,5), was 5 ist. Die Antwort lautet also 5

Java-Implementierung der obigen Lösung ist:

static int max = Integer.MIN_VALUE;

private static int find(TreeNode<Integer> root, int source, int sourceDepth) {

    if (root == null) {
        return -1;
    }

    if (root.getData() == source) {
        sourceDepth = getDepth(root);
        return 0;
    }

    int left = find(root.getLeft(), source, sourceDepth);
    if (left != -1) {
        int rightDepth = getDepth(root.getRight()) + 1;
        max = Math.max(rightDepth + left + 1, sourceDepth);
        return left + 1;
    }

    int right = find(root.getRight(), source, sourceDepth);
    if (right != -1) {
        int leftDepth = getDepth(root.getRight()) + 1;
        max = Math.max(leftDepth + right + 1, sourceDepth);
        return right + 1;
    }

    return -1;
}

private static int getDepth(TreeNode<Integer> root) {

    if (root == null) {
        return -1;
    }

    return Math.max(getDepth(root.getLeft()), getDepth(root.getRight())) + 1;
}

Hier kann als Quelle ein beliebiger Leave Node angegeben werden, der die hier angeforderte Antwort liefert.

0
Farman