给定一个节点,要刻录整个二叉树需要多长时间?

时间:2018-10-12 22:34:01

标签: python-3.x algorithm class recursion binary-tree

在我的一次模拟采访中,我遇到一个问题,我不得不找出一个给定的节点已经着火后,二叉树将完全烧掉多长时间。

  

“从叶节点开始烧写二叉树。什么是   时间(从一个节点到另一个节点的刻录时间为1秒),它花费到整个树   烧了吗火会蔓延到节点上的所有路径。

假设您有一棵这样的树,其中N是着火的节点。这是   发生在第一秒,其中秒是s,所以发生在零秒s:

           1
       /       \
      1          1
    /  \          \
   1    1          1
      /   \         \
     1     N         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  

现在在第三秒(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)))

此示例的预期输出应为6s(从给定节点的0s开始)。但是同样,我没有得到树的全部范围。以我自己的理解,它必须适用于所有情况。因此,什么搜索对您有帮助,DFS或 BFS?我认为牢记这一点将引导我朝着解决方案的方向前进,但是再次。任何反馈意见表示赞赏:)

9 个答案:

答案 0 :(得分:3)

我想到您需要以下条件:

  1. 起始节点位于根的左侧还是右侧。
  2. 起始节点的深度(称为dStart)。
  3. 距离起始节点分支上的根最远的节点深度(即根的左侧或右侧)。我们称其为dSameSide
  4. 起始节点和#3中标识的节点的最低公共祖先的深度。 (将其命名为dCommonAncestor
  5. 树的另一侧最低节点dOppositeSide的深度。

您可以从树的单个有序遍历中获得所有这些信息。

从起始节点到树的那侧最深节点所需的步骤数为(dSameSide - dCommonAncestor) + (dStart - dCommonAncestor)

从起始节点到另一侧的最深节点所需的步骤数为(dStart + dOppositeSide)

燃烧整棵树所需的步骤数是这两个树中的最大值。

我将把实施留给您。您可能会发现How to find the lowest common ancestor of two nodes in any binary tree?有用。

答案 1 :(得分:2)

这可以使用递归函数来解决,该函数返回从当前节点到起始节点的路径的长度(如果起始节点不在其下方,则返回到任何叶子的最长路径)。

如果找到的话,我们还可以让它返回到起始节点为止的最长路径,这仅仅是在左右两个子节点上调用的函数的总和(对于当前节点,加一个)。

这类似于m69描述的解决方案。

此函数以O(n)的时间运行,因为函数以恒定的时间运行(如果不包括递归调用),则每个节点(对于节点本身以及它的左右)最多调用3次子节点(对于叶节点)。

这将使用O(height)空间,因为除了函数调用及其变量外,我们不存储任何内容,并且在任何给定时间可以在内存中存储的最大数量等于递归深度(即树的高度)。

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,它是起始节点下方的最长路径(如果尚未看到起始节点,则为距当前节点的最长路径)。

从子树中计算abovebelow很简单-有关详细信息,请参见代码。

我们可以将距离当前节点的最长路径max定义为最大值:

  • 从起始节点(即below)开始向下的最长路径
  • 以及包括当前节点的最长路径,这将是从当前节点到起始节点的距离加上不包含起始节点的子树中的最长路径(加一)。

代码:(取代上面的_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 :(得分:1)

以下面的例子为例;首先,从根部着火到叶子(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: / p>

     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,但这应该用作伪代码)。在我回答的第一个示例中,它使用树的硬编码版本。如您所见,它将对树进行一次深度优先遍历。

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

答案 3 :(得分:1)

可以使用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))
)

二叉树只是一种特殊的图形,因此您可以遍历所有节点并跟踪每个节点到火灾开始的节点之间的距离。结果是您看到的最大距离。

答案 4 :(得分:1)

这是我的方法。根据左侧或右侧都有叶子的节点,有两种可能性:

  • 把树砍下来
  • 将树探索到另一侧

这两种可能性定义了两条路径。最长的路径是问题的答案(所选叶与任何其他叶之间的最长路径)。在给定的烧伤(红色)节点和具有叶引用(蓝色)的节点上,最好能理解该图

FIGURE

通过编程,我们探索树,直到找到引用叶的节点。在那种情况下,我们计算探索树的其余部分的路径(在具有叶子的原始树的一侧)并返回1(以创建具有递归返回方向的另一侧的路径)。

答案 5 :(得分:1)

对于那些想知道这篇文章发生了什么的人,使用的解决方案是:

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秒节点)。感谢所有帮助我的人。我喜欢阅读你们正在谈论的所有评论和方法:)。如果您对如何改进代码有更好的主意,请在下面随意评论!

答案 6 :(得分:0)

以下是找到燃烧树所需时间的解决方案之一,给定一个源节点(可以是离开节点或非离开节点)

要解决的方法如下:

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之间的边缘)

高度为2的节点7右子树

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

在这里可以将任何离开节点作为来源,这将给出此处要求的答案。

答案 7 :(得分:-2)

//C++ implementation
#include <bits/stdc++.h>
using namespace std;
//Constructing tree
struct Node {
    int data;
    struct Node *left,*right;
    Node(int el){
        data=el;
        left=NULL;right=NULL;
    }
};
typedef struct Node Node;
Node *root=NULL;

//Constructing tree
void getparent(Node *n,int el,Node **temp){
    if(n==NULL)return;
    if(n->data==el){
        *temp=n;
    }
    getparent(n->left,el,temp);
    getparent(n->right,el,temp);
}

//Constructing tree
void create(){
    int el;
    cin>>el;
    Node *p = new Node(el);
    if(root==NULL){
        root=p;
    }else{
        Node *temp;
        int ch;
        cin>>ch;
        getparent(root,ch,&temp);
        if(temp->left==NULL){
            temp->left=p;
        }
        else{
            temp->right=p;
        }
    }
}
//Inorder traversal of tree
void print(Node *n){
    if(n!=NULL){
        print(n->left);
        cout<<n->data<<" ";
        print(n->right);
    }
}
//Height of tree from nth node
int height(Node *n){
    if(n==NULL)return 0;
    return max( height(n->left),height(n->right) )+1;
}

//Code For calculating max time in seconds when burnt at node with value k
int diameter(Node *n,int el,int *maxx){
    if(n!=NULL ){
        if(n->data==el)return  1;
        else {
            if(diameter(n->left,el,maxx)>0){
                if(*maxx<1+diameter(n->left,el,maxx)+height(n->right) )
                                *maxx=1+diameter(n->left,el,maxx)+height(n->right);
                return 1+diameter(n->left,el,maxx);
            }else if(diameter(n->right,el,maxx)>0) {
                if(*maxx<1+diameter(n->right,el,maxx)+height(n->left) )
                                *maxx=1+diameter(n->right,el,maxx)+height(n->left);
                return 1+diameter(n->right,el,maxx);
            }
            return 0;
        }
    }
    return 0;
}

int main() {
    int n;
    cin>>n;
    for(int i=0;i<n;i++){
        create();
    }
    print(root);
    cout<<"\n";
    int k;
    cin>>k;
    int maxx=0;
    diameter(root,k,&maxx);
    cout<<"Time taken will be : "<<maxx<<"\n";
}


//It is working fine . I made the tree to make it understandable.

答案 8 :(得分:-4)

这不是我的头上的事,但平均答案是ln(n),因为这与在排序的二叉树中搜索完全相同。

编辑:我不正确。我当时在想“从X到Y的最快路径”(即ln(n)),但是实际上这是“从X到任何东西的最长路径”。这不是二进制搜索。