返回

从LeetCode刷剑指offer:轻松重建二叉树的七种方法

前端

重建二叉树:7 种经典算法详解

一、重建二叉树的意义

二叉树是一种广泛应用于计算机科学中的数据结构,它由节点及其左右子树组成。重建二叉树是指根据其遍历序列(例如前序遍历或中序遍历)来构建一棵完整的二叉树。重建二叉树在算法、数据结构和计算机科学领域有着重要的应用,如构建哈夫曼树、二叉搜索树和二叉堆等。

二、重建二叉树的七种方法

1. 递归法(前序遍历 + 中序遍历)

思路:

  • 前序遍历的第一个节点为根节点。
  • 在中序遍历中找到根节点,将其左侧节点作为左子树,右侧节点作为右子树。
  • 分别对左子树和右子树递归执行以上步骤,直到重建出整个二叉树。

代码示例:

def build_tree(preorder, inorder):
    if not preorder or not inorder:
        return None

    root_val = preorder[0]
    root_idx = inorder.index(root_val)

    left_subtree = build_tree(preorder[1:root_idx+1], inorder[:root_idx])
    right_subtree = build_tree(preorder[root_idx+1:], inorder[root_idx+1:])

    return TreeNode(root_val, left_subtree, right_subtree)

2. 递归法(后序遍历 + 中序遍历)

思路:

  • 后序遍历的最后一个节点为根节点。
  • 在中序遍历中找到根节点,将其左侧节点作为左子树,右侧节点作为右子树。
  • 分别对左子树和右子树递归执行以上步骤,直到重建出整个二叉树。

代码示例:

def build_tree(postorder, inorder):
    if not postorder or not inorder:
        return None

    root_val = postorder[-1]
    root_idx = inorder.index(root_val)

    left_subtree = build_tree(postorder[:root_idx], inorder[:root_idx])
    right_subtree = build_tree(postorder[root_idx:-1], inorder[root_idx+1:])

    return TreeNode(root_val, left_subtree, right_subtree)

3. 非递归法(前序遍历 + 中序遍历)

思路:

  • 使用两个栈,一个保存前序遍历序列,另一个保存中序遍历序列。
  • 从前序栈中弹出第一个节点作为根节点。
  • 在中序栈中找到根节点,将其左侧节点作为左子树,右侧节点作为右子树。
  • 将左子树和右子树的根节点分别压入前序栈。
  • 重复以上步骤,直到前序栈为空。

代码示例:

def build_tree(preorder, inorder):
    if not preorder or not inorder:
        return None

    pre_stack = [preorder[0]]
    in_stack = [inorder[0]]

    while pre_stack:
        root_val = pre_stack.pop()
        root_idx = in_stack.index(root_val)

        left_subtree = build_tree(preorder[1:root_idx+1], inorder[:root_idx])
        right_subtree = build_tree(preorder[root_idx+1:], inorder[root_idx+1:])

        in_stack.pop()
        pre_stack.extend(right_subtree, left_subtree)

    return TreeNode(root_val, left_subtree, right_subtree)

4. 非递归法(后序遍历 + 中序遍历)

思路:

  • 使用两个栈,一个保存后序遍历序列,另一个保存中序遍历序列。
  • 从后序栈中弹出最后一个节点作为根节点。
  • 在中序栈中找到根节点,将其左侧节点作为左子树,右侧节点作为右子树。
  • 将左子树和右子树的根节点分别压入后序栈。
  • 重复以上步骤,直到后序栈为空。

代码示例:

def build_tree(postorder, inorder):
    if not postorder or not inorder:
        return None

    post_stack = [postorder[-1]]
    in_stack = [inorder[0]]

    while post_stack:
        root_val = post_stack.pop()
        root_idx = in_stack.index(root_val)

        left_subtree = build_tree(postorder[:root_idx], inorder[:root_idx])
        right_subtree = build_tree(postorder[root_idx:-1], inorder[root_idx+1:])

        in_stack.pop()
        post_stack.extend(left_subtree, right_subtree)

    return TreeNode(root_val, left_subtree, right_subtree)

5. 层序遍历法

思路:

  • 将前序遍历和中序遍历序列同时存储在一个队列中。
  • 从队列中取出第一个节点作为根节点。
  • 在队列中找到根节点的左子树和右子树的根节点。
  • 将左子树和右子树的根节点分别压入队列。
  • 重复以上步骤,直到队列为空。

代码示例:

def build_tree(preorder, inorder):
    if not preorder or not inorder:
        return None

    queue = [TreeNode(preorder[0])]
    pre_idx = 1
    in_idx = 0

    while queue and pre_idx < len(preorder):
        node = queue.pop(0)

        if node.val == inorder[in_idx]:
            in_idx += 1

            if pre_idx < len(preorder):
                node.left = TreeNode(preorder[pre_idx])
                queue.append(node.left)
                pre_idx += 1

        if node.val == inorder[in_idx]:
            in_idx += 1

            if pre_idx < len(preorder):
                node.right = TreeNode(preorder[pre_idx])
                queue.append(node.right)
                pre_idx += 1

    return queue[0]

6. 哈夫曼树法

思路:

  • 将前序遍历和中序遍历序列同时存储在一个优先队列中,权重为节点的频率。
  • 从优先队列中取出权重最小的两个节点,作为左子树和右子树的根节点。
  • 将左子树和右子树的根节点合并为一个新的节点,权重为左子树和右子树权重的和。
  • 将新节点压入优先队列。
  • 重复以上步骤,直到优先队列中只有一个节点。

代码示例:

import heapq

def build_tree(preorder, inorder):
    if not preorder or not inorder:
        return None

    freq = {}
    for val in preorder:
        freq[val] = freq.get(val, 0) + 1

    pq = []
    for val, f in freq.items():
        heapq.heappush(pq, (f, val))

    while len(pq) > 1:
        f1, val1 = heapq.heappop(pq)
        f2, val2 = heapq.heappop(pq)

        new_node = TreeNode((val1, val2), None, None)
        heapq.heappush(pq, (f1+f2, new_node))

    return pq[0][1]

7. 二叉搜索树法

思路:

  • 将前序遍历序列存储在一个栈中。
  • 从栈中弹出第一个节点作为根节点。
  • 在栈中找到根节点的左子树和右子树的根节点。
  • 将左子树和右子树的根节点分别压入栈。
  • 重复以上步骤,直到栈为空。

代码示例:

def build_tree(preorder):
    if not preorder:
        return None

    stack = [TreeNode(preorder[0])]
    pre_idx = 1

    while stack and pre_idx < len(preorder):
        node = stack[-1]

        if node.val > preorder[pre_idx]:
            node.left = TreeNode(preorder[pre_idx])
            stack.append(node.left)
            pre_idx += 1
        else:
            node = stack.pop()
            node.right = TreeNode(preorder[pre_idx])
            stack.append(node.right)
            pre_idx += 1

    return stack[0]

三、常见问题解答

1. 如何选择重建二叉树的方法?

选择哪种重建