web-dev-qa-db-ja.com

ノードが与えられた場合、バイナリツリー全体を書き込むのにどれくらい時間がかかりますか?

模擬インタビューの1つで問題が発生しました。特定のノードの1つが既に起動してから、バイナリツリーが完全に燃え尽きるまでの時間を見つける必要がありました。

"リーフツリーからバイナリツリーが燃え始めます。ツリー全体が燃えるまでの時間(ノードからノードへの焼き付けに1秒)はどのくらいですか?火はノードからすべてのパスに広がります。 "

次のようなツリーがあるとします。Nは起動中のノードです。これは、秒がsである最初の秒で発生するため、0番目の秒で発生します。

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

1秒が経過すると、ツリーはより多くの書き込みノードで更新されます。次の秒(s + 1)の例は次のようになります。

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

次の秒(s + 2)の例は次のようになります。

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

これで、3秒目(s + 3)は次のようになります。

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

同じパターンで、ツリーは(s + 7)で焼き付けられます

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

少し理解した後、私はそれを行う方法を見つけるために小さな研究をしました。このクールな article を見つけて、それをフォローして、背後にあるアイデアを実装しました。

私のアプローチは、最も遠いノードからノードを探すために、ツリーの高さとともに直径を見つけることでした。ただし、関数を実装すると、開始ノードから指定ノードの終わりまでの結果しか得られません前の親ノードをチェックせずに。 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)))

この例で予想される出力は6秒です(指定されたノードの0から開始)。しかし、再び、私はツリーの全範囲を取得していません。私自身の理解では、すべてのケースで動作する必要があります。そこで、ここで役立つ検索は、DFSまたはBFSでしょうか?これを念頭に置くことで、解決策に導くことができると思いますが、繰り返します。フィードバックは大歓迎です:)

11
Zeid Tisnes

この投稿で何が起こったのか疑問に思う人のために、使用されたソリューションはこれでした:

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 __name__ == '__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)))

時間:O(n)

スペース:O(n)

気付いた場合、各ノードの値は1です。ノードの値はこの問題には関係ありません。それはその中のある値を表しているだけです。私が1つ持っている理由は、2番目(1秒のノード)を考えるためです。私を助けてくれたみんなに感謝します。私はあなたたちが話していたコメントとアプローチのすべてを読んで楽しんだ:)。コードの改善方法についてより良いアイデアをお持ちの場合は、以下にコメントしてください。

1
Zeid Tisnes

あなたには次のものが必要だと思います。

  1. 開始ノードがルートの左側か右側か。
  2. 開始ノードの深さ(dStartと呼びます)。
  3. 開始ノードのブランチ上のルートから最も遠いノードの深さ(つまり、ルートの左または右)。それをdSameSideと呼びます
  4. 開始ノードと#3で識別されたノードの最低共通祖先の深さ。 (dCommonAncestorと呼びます)
  5. ツリーの反対側の最下位ノードの深さ、dOppositeSide

この情報はすべて、ツリーの単一の順序走査から取得できます。

開始ノードからツリーのその側の最も深いノードまでのステップ数は(dSameSide - dCommonAncestor) + (dStart - dCommonAncestor)

開始ノードから反対側の最も深いノードまでのステップ数は(dStart + dOppositeSide)

そして、ツリー全体を焼くために必要なステップの数は、これらの最大数です。

実装はあなたにお任せします。おそらく バイナリツリーの2つのノードの最も低い共通の祖先を見つける方法 役立つでしょう。

4
Jim Mischel

これは、現在のノードから開始ノードまでのパスの長さを返す再帰関数を使用して解決できます(開始ノードがその下にない場合は、リーフへの最長パスのみ)。

開始ノードからこれまでの最長パスを返すようにすることもできます。開始パスが見つかった場合、これは左右の子(現在のノードに対して1つ)で呼び出された関数の合計です。

これは、 m69 で説明されているソリューションに似ています。

これは、O(n)時間で実行されます。関数は一定の時間で実行されるため(再帰呼び出しを除外する場合)、関数はノードごとに最大3回呼び出されます。そして、その左と右の子については、葉ノードの場合)。

これはO(height) spaceを使用します。変数を使用して関数呼び出し以外に何も保存せず、任意の時点でメモリに保持できる最大数は再帰の深さに等しい(つまり、木の高さ)。

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]

テスト:

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

葉以外の開始ノードで動作するソリューション

基本的な考え方は、各ノードに3つの戻り値を持たせることです。

  • max。これは、これまでに取得した開始ノードからの最長パスです(開始ノードがまだ表示されていない場合はNone)。
  • above、これは開始ノードの上のノードの数です(開始ノードがまだ表示されていない場合はNone)。
  • below。これは、開始ノードの下の最長パスです(開始ノードがまだ表示されていない場合は、現在のノードからの最長パスです)。

子サブツリーからaboveおよびbelowを計算するのはかなり簡単です-詳細についてはコードを参照してください。

現在のノードからの最長パスmaxを最大値として定義できます。

  • 開始ノードから下に向かう最長パス(ちょうどbelow
  • そして、現在のノードを含む最長パス。現在のノードから開始ノードまでの距離に、開始ノードのないサブツリー内の最長パス(プラス1)を加えます。

コード:(_recurse上記の関数)

# 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

以下の例をご覧ください。最初に、火の根から葉まで移動します(F):

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

次に、親ノードまで移動し、燃える葉までの距離(1)と左のサブツリーの高さ(3)の合計4を取得します。

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

したがって、4が現在の最大値です。次に、親ノードまで移動し、燃える葉までの距離(2)と左のサブツリーの深さ(1)の合計、つまり3を取得します。

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

したがって、現在の最大値は4のままです。親ノードまで移動し、燃える葉までの距離(3)と右側のサブツリーの深さ(4)の合計7を取得します。

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

新しい最大値は7で、ルートノードに到達したので、7秒が答えです。x秒後に起動しているノードを確認することで確認できます。

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

ルートが最長パスの一部ではない例を次に示します。

         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

遭遇した最大値は、燃えている葉の親で4でした。


これは簡単なJavaScriptコードスニペットです(Pythonは話せませんが、これは擬似コードとして機能するはずです)。私の答えからの最初の例のツリーのハードコーディングされたバージョンを使用します。ご覧のとおり、ツリーの深さ優先走査を1回行います。

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

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

バイナリツリーは特別な種類のグラフであるため、すべてのノードを調べて、各ノードから火災が始まったノードまでの距離を追跡できます。結果は、あなたが見た中で最も高い距離です。

1
fafl

これが私のアプローチです。葉が左または右にあるノードに基づいて、次の2つの可能性があります。

  • 木を探索する
  • 木を反対側に探索する

この2つの可能性により、2つのパスが定義されます。最長パスは、問題に対する答えです(選択したリーフと他のリーフとの間の最長パス)。この図では、特定の書き込み(赤)ノードとリーフ参照を持つノード(青)について最もよく理解されています。

FIGURE

プログラム的に、リーフへの参照を持つノードが見つかるまでツリーを探索します。その場合、ツリーの残りの部分(リーフがある元のツリーの側)を探索するパスを計算し、1を返します(再帰を使用して反対側へのパスを作成します)。

1
Mauro Pucheta

以下は、ソースノード(リーブノードまたは非リーブノード)を指定して、ツリーの書き込みにかかる時間を見つけるためのソリューションの1つです。

解決するアプローチは次のとおりです。

1)ツリーでソースノードを見つけ、ノードの高さを見つけます(ここでは変数 "sourceDepth"に格納しています)

2)指定されたソースノードのすべての祖先

 ->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)ステップ2のすべてのdの最大値とステップ1のsourceDepthを取得します。これは必須の回答です。

以下の例では、sourceを3にします。

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

ソースの深さ(3)は、sourceDepth = 2

ソースのすべての祖先は[7、4]です

祖先4の場合:

ソースからの距離は1で、ソースの反対方向にサブツリーはありません(つまり、ソースは右サブツリーにあり、左サブツリーはありません)。 dは1です。

先祖のために7

ソースからの距離は2で、ソースの反対方向のサブツリーの高さは2です。したがって、ここのdは2 + 2 + 1 = 5です。 (1は7〜8のエッジ用です)

ノード7の高さ= 2の右サブツリー

   8   
  / \  
 10   9  
    / \  
   0   11 

この場合の解決策は、最大の(2,1,5)5です。答えは5です

上記のソリューションのJava実装は次のとおりです。

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;
}

ここで、ソースは、ここで要求された必須の回答を提供する任意のLeaveノードにすることができます。

0
Farman