二叉树学习——day1

遍历

前序遍历

前序遍历(Preorder Traversal)是二叉树遍历的一种方式。在前序遍历中,首先访问当前节点,然后按照左子树和右子树的顺序递归地进行前序遍历。

前序遍历的过程可以简单描述为:根节点 -> 左子树 -> 右子树
具体步骤如下:

  1. 访问当前节点(根节点)。
  2. 对当前节点的左子树进行前序遍历。
  3. 对当前节点的右子树进行前序遍历。

在前序遍历算法中,当函数访问完左子树后,它会递归返回到父节点,然后继续执行父节点的右子树部分。这是通过递归调用栈(Recursion Stack)的工作原理来实现的。

例:

      1
     / \
    2   3
   / \
  4   5

假设我们使用前序遍历算法来遍历这个树。开始时,我们从根节点1开始,然后:

  1. 访问1,然后递归遍历左子树,即节点2。
  2. 访问2,然后递归遍历左子树,即节点4。
  3. 访问4,没有左子树或右子树,返回到节点2。
  4. 继续遍历节点2的右子树,即节点5。
  5. 访问5,没有左子树或右子树,返回到节点2。
  6. 节点2的左子树和右子树都已经遍历完,返回到节点1。
  7. 继续遍历节点1的右子树,即节点3。
  8. 访问3,没有左子树或右子树,返回到节点1。
  9. 节点1的左子树和右子树都已经遍历完,整个遍历结束。
递归

定义一个递归函数,该函数从根节点开始,先访问当前节点,然后递归地遍历左子树和右子树。

例:

class TreeNode {  
    int data;  
    TreeNode left;  
    TreeNode right;  
  
    TreeNode(int data) {  
        this.data = data;  
    }  
}  
  
  
public class BinaryTreeDemo {  
    static void preOrderTraversal(TreeNode node) {  
        if (node == null) {  
            return;  
        }  
  
        System.out.print(node.data + " "); // 访问根节点  
        preOrderTraversal(node.left);      // 递归遍历左子树  
        preOrderTraversal(node.right);     // 递归遍历右子树  
    }  
  
    public static void main(String[] args) {  
        TreeNode root = new TreeNode(1);  
        root.left = new TreeNode(2);  
        root.right = new TreeNode(3);  
        root.left.left = new TreeNode(4);  
        root.left.right = new TreeNode(5);  
  
        preOrderTraversal(root); // 输出应该是:1 2 4 5 3  
    }  
}
迭代

遍历过程

  • 创建一个空栈。
  • 将根节点压入栈中。
  • 当栈不为空时,循环执行以下步骤:
    • 弹出栈顶元素,并访问该元素(例如,打印节点的值)。
    • 将节点的右子节点(如果有)压入栈中。
    • 将节点的左子节点(如果有)压入栈中。

这个过程中,先访问节点,然后将右子节点压栈,接着是左子节点压栈。这样做是因为栈是后进先出(LIFO)的结构,我们希望左子节点先于右子节点被处理,所以后压入栈。

非递归实现前序遍历通常需要使用栈来辅助操作。以下是非递归实现的示例:

import java.util.Stack;

void preOrderTraversalIterative(TreeNode root) {
    if (root == null) {
        return;
    }

    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);

    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        System.out.print(node.data + " ");

        if (node.right != null) {
            stack.push(node.right);
        }

        if (node.left != null) {
            stack.push(node.left);
        }
    }
}

在这个迭代版本中,我们首先检查根节点是否为空。然后,我们使用一个栈来存储接下来要访问的节点。每次从栈中弹出一个节点,我们就访问它,并按顺序将其右子节点和左子节点压入栈中。这个过程会持续直到栈为空,此时树上的所有节点都已按前序遍历的顺序访问完毕。

使用栈实现的迭代遍历是一种有效的方式,尤其是在需要避免递归带来的潜在栈溢出风险或寻求更高效的内存使用时。

特点

  • 使用一个栈。
  • 遍历时,先将右子节点压入栈,然后是左子节点。
  • 在节点弹出栈的时候立即访问。

用双端队列(Deque)来提高二叉树遍历的性能

void preOrderTraversalUsingDeque(TreeNode root) {  
    if (root == null) return;  
  
    Deque<TreeNode> deque = new ArrayDeque<>();  
    deque.addFirst(root);  
  
    while (!deque.isEmpty()) {  
        TreeNode node = deque.removeFirst();  
        System.out.print(node.data + " "); 
  
        if (node.right != null) deque.addFirst(node.right);  
        if (node.left != null) deque.addFirst(node.left);  
    }  
}
莫里斯遍历

在二叉树的前序遍历中,除了常见的递归和迭代方法外,还有一种称为“莫里斯遍历”(Morris Traversal)的方法。莫里斯遍历是一个高效的遍历算法,它的主要优点是在遍历树时不需要使用额外的栈空间或递归调用,因此它的空间复杂度为 O(1),即常数级别。这在处理大型数据结构时特别有用,因为它避免了栈溢出的风险并减少了内存使用。

基本思想:
莫里斯遍历的核心思想是利用叶子节点下的空闲指针指向某种形式的前驱或后继节点,从而避免使用额外的栈空间。在前序遍历中,这通常涉及到以下步骤:

  1. 初始化:从根节点开始。
  2. 左子树处理
    • 如果当前节点的左子节点不存在,处理当前节点并移动到其右子节点。
    • 如果当前节点的左子节点存在,则找到这个左子节点的最右侧的节点(当前节点的前驱)。
      • 如果前驱节点的右子节点为空,将其右子节点设置为当前节点,并移动到当前节点的左子节点。
      • 如果前驱节点的右子节点为当前节点,说明左子树已经被访问过,将前驱节点的右子节点重新设置为null,并处理当前节点,然后移动到当前节点的右子节点。
  3. 重复以上步骤,直到当前节点为空。
class TreeNode {
    int val;
    TreeNode left, right;

    TreeNode(int x) {
        val = x;
    }
}

public class MorrisTraversal {
    public void preOrderMorrisTraversal(TreeNode root) {
        TreeNode current, pre;

        if (root == null) {
            return;
        }

        current = root;
        while (current != null) {
            if (current.left == null) {
                System.out.print(current.val + " ");
                current = current.right;
            } else {
                /* 找到前驱节点 */
                pre = current.left;
                while (pre.right != null && pre.right != current) {
                    pre = pre.right;
                }

                /* 将前驱节点的右子节点设置为当前节点 */
                if (pre.right == null) {
                    pre.right = current;
                    System.out.print(current.val + " ");  // 前序遍历需要在这里打印节点
                    current = current.left;
                }

                /* 已经访问过左子树,需要重置前驱节点的右子节点,并移动到右子树 */
                else {
                    pre.right = null; // 重置
                    current = current.right;
                }
            }
        }
    }

    public static void main(String[] args) {
        MorrisTraversal tree = new MorrisTraversal();
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        root.left.left = new TreeNode(4);
        root.left.right = new TreeNode(5);

        tree.preOrderMorrisTraversal(root);
    }
}

中序遍历

中序遍历(Inorder Traversal)是二叉树遍历的另一种方式。在中序遍历中,先按照左子树的顺序递归地进行中序遍历,然后访问当前节点,最后按照右子树的顺序递归地进行中序遍历。

中序遍历的过程可以简单描述为:左子树 -> 根节点 -> 右子树

具体步骤如下:

  1. 对当前节点的左子树进行中序遍历。
  2. 访问当前节点(根节点)。
  3. 对当前节点的右子树进行中序遍历。

例:

      A
     / \
    B   C
   / \   \
  D   E   F

中序遍历结果为:D -> B -> E -> A -> C -> F

在这个示例中,我们首先遍历左子树 B,接着遍历左子树的左子树 D,然后访问根节点 A,再遍历左子树的右子树 E。然后回到根节点 A,遍历右子树 C,最后遍历右子树的右子树 F。

递归
void inOrderTraversal(TreeNode node) {
    if (node == null) {
        return;
    }

    inOrderTraversal(node.left);      // 递归遍历左子树
    System.out.print(node.data + " "); // 访问根节点
    inOrderTraversal(node.right);     // 递归遍历右子树
}

迭代

开始遍历

  • 从根节点开始,先将节点及其所有左子节点压入栈中,直到没有左子节点为止。
  • 弹出栈顶元素(这是当前最左边的节点),访问它。
  • 转向右子节点并重复上述过程。
import java.util.Stack;

void inOrderTraversalIterative(TreeNode root) {
    Stack<TreeNode> stack = new Stack<>();
    TreeNode current = root;

    while (current != null || !stack.isEmpty()) {
        while (current != null) {
            stack.push(current);
            current = current.left;
        }

        current = stack.pop();
        System.out.print(current.data + " ");
        current = current.right;
    }
}

在中序遍历的迭代实现中,栈用于保持节点的访问顺序,并且让算法能够“回溯”到具有未处理右子树的节点。这个过程持续进行,直到所有节点都被访问。通过这种方式,迭代方法可以有效地完成中序遍历,而不需要递归调用,这对于深度很大的树来说是一个优点,因为它可以避免栈溢出的风险。

特点

  • 使用一个栈。
  • 从根节点开始,首先遍历所有左子节点并将它们压入栈。
  • 节点从栈中弹出后才被访问,然后转向右子节点。

后序遍历

后序遍历(Postorder Traversal)是二叉树遍历的另一种方式。在后序遍历中,先按照左子树和右子树的顺序递归地进行后序遍历,然后访问当前节点。

后序遍历的过程可以简单描述为:左子树 -> 右子树 -> 根节点

具体步骤如下:

  1. 对当前节点的左子树进行后序遍历。
  2. 对当前节点的右子树进行后序遍历。
  3. 访问当前节点(根节点)。

例:

      A
     / \
    B   C
   / \   \
  D   E   F

后序遍历结果为:D -> E -> B -> F -> C -> A

在这个示例中,我们首先遍历左子树 B,接着遍历左子树的左子树 D,然后遍历左子树的右子树 E。然后回到根节点 A,遍历右子树 C,最后遍历右子树的右子树 F。最后,访问根节点 A。

递归

例:

void postOrderTraversal(TreeNode node) {
    if (node == null) {
        return;
    }

    postOrderTraversal(node.left);      // 递归遍历左子树
    postOrderTraversal(node.right);     // 递归遍历右子树
    System.out.print(node.data + " "); // 访问根节点
}
迭代

遍历过程

  • 将根节点压入第一个栈。
  • 当第一个栈不为空时,循环执行以下步骤:
    • 弹出栈顶元素,并将其压入第二个栈。
    • 将该节点的左子节点和右子节点(如果存在)依次压入第一个栈。
  • 当第一个栈为空时,从第二个栈中弹出元素并访问。
static void postOrderTraversalIterative(TreeNode root) {  
    if (root == null) {  
        return;  
    }  
  
    Stack<TreeNode> stack1 = new Stack<>();  
    Stack<TreeNode> stack2 = new Stack<>();  
    stack1.push(root);  
  
    while (!stack1.isEmpty()) {  
        TreeNode node = stack1.pop();  
        stack2.push(node);  
  
        if (node.left != null) {  
            stack1.push(node.left);  
        }  
  
        if (node.right != null) {  
            stack1.push(node.right);  
        }  
    }  
  
    while (!stack2.isEmpty()) {  
        TreeNode node = stack2.pop();  
        System.out.print(node.data + " ");  
    }  
}

使用这种方法,我们可以确保每个节点都按照后序遍历的顺序被访问。第一个栈用于按照“根-右-左”的顺序访问节点,而第二个栈则用于逆序输出,最终实现“左-右-根”的访问顺序。这种迭代方法特别有用,因为它避免了递归方法中可能的栈溢出风险,并且通常对内存的使用更加高效。

特点

  • 使用两个栈。
  • 第一个栈用于按“根-右-左”的顺序访问节点,第二个栈用于逆序输出,实现“左-右-根”的顺序。
  • 在第一个栈中,首先将左子节点压入栈,然后是右子节点。
  • 第二个栈用于逆序输出节点。

层序遍历

层序遍历是对树(特别是二叉树)的一种遍历方法,它按照树的每一层从上到下,每层从左到右的顺序进行。这种遍历方式通常使用队列来实现,因为队列是一种先进先出(FIFO)的数据结构,适合按层处理树的节点。

二叉树的存储方式有两种,分别是顺序表和链表。对于顺序表存储的二叉树,层次遍历是很容易实现的,因为二叉树中的结点本就是一层一层存储到顺序表中的。唯一需要注意的是,顺序表存储的只能是完全二叉树,普通二叉树必须先转换成完全二叉树后才能存储到顺序表中,因此在实现层次遍历的时候,需要逐个对顺序表中存储的结点进行甄别。

层次遍历用链表存储的二叉树,可以借助队列存储结构实现,具体方案是:

  1. 将根结点入队;
  2. 从队列的头部提取一个结点并访问它,将该结点的左孩子和右孩子依次入队;
  3. 重复执行第 2 步,直至队列为空;

层序遍历(Level Order Traversal)是二叉树遍历的一种方式,也称为广度优先遍历。在层序遍历中,按照层级从上到下逐层遍历二叉树的节点。

具体步骤如下:

  1. 将根节点放入队列。
  2. 从队列中取出一个节点并访问它。
  3. 将该节点的左子节点(如果有)放入队列。
  4. 将该节点的右子节点(如果有)放入队列。
  5. 重复步骤2-4,直到队列为空。

例:

      A
     / \
    B   C
   / \   \
  D   E   F

层序遍历结果为:A -> B -> C -> D -> E -> F

import java.util.Queue;
import java.util.LinkedList;

void levelOrderTraversal(TreeNode root) {
    if (root == null) {
        return;
    }

    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);

    while (!queue.isEmpty()) {
        TreeNode current = queue.poll();
        System.out.print(current.data + " ");

        if (current.left != null) {
            queue.add(current.left);
        }

        if (current.right != null) {
            queue.add(current.right);
        }
    }
}

BFS

DFS

逆推二叉树

前序和中序

要通过前序遍历和中序遍历的结果来逆推一个二叉树,需要遵循一定的步骤。这个过程可以分为以下几个关键步骤:

  1. 理解前序遍历和中序遍历:

    • 前序遍历:根节点->左子树->右子树的顺序遍历树的节点。
    • 中序遍历:左子树->根节点->右子树的顺序遍历树的节点。
  2. 根据前序遍历结果,确定根节点:

    • 前序遍历的结果会告诉我们根节点的值。第一个元素就是根节点的值。
  3. 根据根节点在中序遍历中的位置,将节点分成左子树和右子树:

    • 中序遍历的结果会告诉我们哪些节点在左子树,哪些节点在右子树。
    • 找到根节点在中序遍历中的位置,可以确定左子树和右子树的中序遍历结果。
  4. 递归构建左子树和右子树:

    • 对左子树和右子树分别进行递归构建,重复步骤1到3,直到所有节点都被处理完。

下面是一个示例,演示如何使用前序遍历和中序遍历的结果来逆推一个二叉树的过程:

假设给定的前序遍历结果为:[1, 2, 4, 5, 3, 6, 7],中序遍历结果为:[4, 2, 5, 1, 6, 3, 7]。

  1. 从前序遍历中确定根节点:根据前序遍历结果,根节点的值是1。

  2. 根据根节点在中序遍历中的位置,将节点分成左子树和右子树:

    • 从中序遍历结果中可以看出,根节点1的左边是左子树节点[4, 2, 5],右边是右子树节点[6, 3, 7]。
  3. 递归构建左子树和右子树:

    • 对于左子树:

      • 前序遍历结果为[2, 4, 5],中序遍历结果为[4, 2, 5]。
      • 从前序遍历中确定左子树的根节点为2。
      • 根据根节点在中序遍历中的位置,左子树的左子树为空,右子树为空,因为4、5都是叶子节点。
    • 对于右子树:

      • 前序遍历结果为[3, 6, 7],中序遍历结果为[6, 3, 7]。
      • 从前序遍历中确定右子树的根节点为3。
      • 根据根节点在中序遍历中的位置,右子树的左子树为空,右子树为空,因为6、7都是叶子节点。

最终,构建完成的二叉树如下所示:

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

这样就成功地通过前序遍历和中序遍历的结果逆推出了原始的二叉树结构。

要通过前序遍历和中序遍历的结果逆推二叉树,你可以使用递归算法。前序遍历的结果告诉我们根节点在哪里,而中序遍历的结果告诉我们左子树和右子树的分割点。下面是详细的步骤:

  1. 创建一个结构体来表示二叉树的节点。结构体应该包含一个值,以及指向左子树和右子树的指针。
struct TreeNode {
    int value;
    struct TreeNode* left;
    struct TreeNode* right;
};
  1. 创建一个逆推函数,该函数接受前序遍历和中序遍历的数组,以及数组的起始和结束索引。
struct TreeNode* buildTree(int* preorder, int preorderStart, int preorderEnd,
                           int* inorder, int inorderStart, int inorderEnd) {
    if (preorderStart > preorderEnd || inorderStart > inorderEnd) {
        return NULL; // 递归终止条件:子树为空
    }

    // 创建当前子树的根节点
    struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->value = preorder[preorderStart];

    // 在中序遍历数组中找到根节点的位置
    int rootIndex;
    for (rootIndex = inorderStart; rootIndex <= inorderEnd; rootIndex++) {
        if (inorder[rootIndex] == root->value) {
            break;
        }
    }

    // 计算左子树的大小
    int leftSubtreeSize = rootIndex - inorderStart;

    // 递归构建左子树和右子树
    root->left = buildTree(preorder, preorderStart + 1, preorderStart + leftSubtreeSize,
                           inorder, inorderStart, rootIndex - 1);
    root->right = buildTree(preorder, preorderStart + leftSubtreeSize + 1, preorderEnd,
                            inorder, rootIndex + 1, inorderEnd);

    return root;
}
  1. 创建一个包装函数,该函数接受前序和中序遍历的数组,并调用逆推函数进行树的构建。
struct TreeNode* buildTreeWrapper(int* preorder, int preorderSize, int* inorder, int inorderSize) {
    return buildTree(preorder, 0, preorderSize - 1, inorder, 0, inorderSize - 1);
}

现在,你可以使用buildTreeWrapper函数来逆推二叉树。只需将前序和中序遍历的数组传递给它,它将返回树的根节点。

示例用法:

int preorder[] = {3, 9, 20, 15, 7};
int inorder[] = {9, 3, 15, 20, 7};
int size = sizeof(preorder) / sizeof(preorder[0]);

struct TreeNode* root = buildTreeWrapper(preorder, size, inorder, size);

这将创建一个二叉树,其结构如下:

    3
   / \
  9  20
    /  \
   15   7
后序和中序

要通过后序遍历和中序遍历的结果来逆推一个二叉树,需要遵循一定的步骤。这个过程可以分为以下几个关键步骤:

  1. 理解后序遍历和中序遍历:

    • 后序遍历:左子树->右子树->根节点的顺序遍历树的节点。
    • 中序遍历:左子树->根节点->右子树的顺序遍历树的节点。
  2. 根据后序遍历结果,确定根节点:

    • 后序遍历的结果会告诉我们根节点的值。最后一个元素就是根节点的值。
  3. 根据根节点在中序遍历中的位置,将节点分成左子树和右子树:

    • 中序遍历的结果会告诉我们哪些节点在左子树,哪些节点在右子树。
    • 找到根节点在中序遍历中的位置,可以确定左子树和右子树的中序遍历结果。
  4. 递归构建左子树和右子树:

    • 对左子树和右子树分别进行递归构建,重复步骤1到3,直到所有节点都被处理完。

下面是一个示例,演示如何使用后序遍历和中序遍历的结果来逆推一个二叉树的过程:

假设给定的后序遍历结果为:[4, 5, 2, 6, 7, 3, 1],中序遍历结果为:[4, 2, 5, 1, 6, 3, 7]。

  1. 从后序遍历中确定根节点:根据后序遍历结果,根节点的值是1。

  2. 根据根节点在中序遍历中的位置,将节点分成左子树和右子树:

    • 从中序遍历结果中可以看出,根节点1的左边是左子树节点[4, 2, 5],右边是右子树节点[6, 3, 7]。
  3. 递归构建左子树和右子树:

    • 对于左子树:

      • 后序遍历结果为[4, 5, 2],中序遍历结果为[4, 2, 5]。
      • 从后序遍历中确定左子树的根节点为2。
      • 根据根节点在中序遍历中的位置,左子树的左子树为空,右子树为空,因为4、5都是叶子节点。
    • 对于右子树:

      • 后序遍历结果为[6, 7, 3],中序遍历结果为[6, 3, 7]。
      • 从后序遍历中确定右子树的根节点为3。
      • 根据根节点在中序遍历中的位置,右子树的左子树为空,右子树为空,因为6、7都是叶子节点。

最终,构建完成的二叉树如下所示:

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

这样就成功地通过后序遍历和中序遍历的结果逆推出了原始的二叉树结构。

要通过后序遍历和中序遍历的结果逆推二叉树,你也可以使用递归算法。后序遍历的结果告诉我们根节点在哪里,而中序遍历的结果告诉我们左子树和右子树的分割点。下面是详细的步骤:

  1. 创建一个结构体来表示二叉树的节点。结构体应该包含一个值,以及指向左子树和右子树的指针。
struct TreeNode {
    int value;
    struct TreeNode* left;
    struct TreeNode* right;
};
  1. 创建一个逆推函数,该函数接受后序遍历和中序遍历的数组,以及数组的起始和结束索引。
struct TreeNode* buildTree(int* postorder, int postorderStart, int postorderEnd,
                           int* inorder, int inorderStart, int inorderEnd) {
    if (postorderStart > postorderEnd || inorderStart > inorderEnd) {
        return NULL; // 递归终止条件:子树为空
    }

    // 创建当前子树的根节点
    struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->value = postorder[postorderEnd];

    // 在中序遍历数组中找到根节点的位置
    int rootIndex;
    for (rootIndex = inorderStart; rootIndex <= inorderEnd; rootIndex++) {
        if (inorder[rootIndex] == root->value) {
            break;
        }
    }

    // 计算右子树的大小
    int rightSubtreeSize = inorderEnd - rootIndex;

    // 递归构建左子树和右子树
    root->left = buildTree(postorder, postorderStart, postorderEnd - rightSubtreeSize - 1,
                           inorder, inorderStart, rootIndex - 1);
    root->right = buildTree(postorder, postorderEnd - rightSubtreeSize, postorderEnd - 1,
                            inorder, rootIndex + 1, inorderEnd);

    return root;
}
  1. 创建一个包装函数,该函数接受后序和中序遍历的数组,并调用逆推函数进行树的构建。
struct TreeNode* buildTreeWrapper(int* postorder, int postorderSize, int* inorder, int inorderSize) {
    return buildTree(postorder, 0, postorderSize - 1, inorder, 0, inorderSize - 1);
}

现在,你可以使用buildTreeWrapper函数来逆推二叉树。只需将后序和中序遍历的数组传递给它,它将返回树的根节点。

示例用法:

int postorder[] = {9, 15, 7, 20, 3};
int inorder[] = {9, 3, 15, 20, 7};
int size = sizeof(postorder) / sizeof(postorder[0]);

struct TreeNode* root = buildTreeWrapper(postorder, size, inorder, size);

这将创建一个二叉树,其结构如下:

    3
   / \
  9  20
    /  \
   15   7
层序和中序

要通过层序遍历和中序遍历的结果来逆推一个二叉树,需要遵循一定的步骤。这个过程可以分为以下几个关键步骤:

  1. 理解层序遍历和中序遍历:

    • 层序遍历:从树的根节点开始,逐层遍历树的节点,按照从左到右的顺序。通常使用队列来实现。
    • 中序遍历:按照左子树、根节点、右子树的顺序遍历树的节点。
  2. 根据中序遍历结果,确定根节点:

    • 中序遍历的结果会告诉我们树节点的相对顺序,以及哪些节点在左子树,哪些节点在右子树。
    • 中序遍历的第一个元素将是根节点。
  3. 根据根节点在层序遍历中的位置,将节点分成左子树和右子树:

    • 层序遍历的结果会告诉我们树节点的绝对位置,因此我们可以根据根节点在层序遍历中的位置,将节点分成左子树和右子树。
    • 左子树的节点在层序遍历中紧随在根节点之后,右子树的节点在左子树节点之后。
  4. 递归构建左子树和右子树:

    • 对左子树和右子树分别进行递归构建,重复步骤1到3,直到所有节点都被处理完。

下面是一个示例,演示如何使用层序遍历和中序遍历的结果来逆推一个二叉树的过程:

假设给定的层序遍历结果为:[1, 2, 3, 4, 5, 6, 7],中序遍历结果为:[4, 2, 5, 1, 6, 3, 7]。

  1. 从中序遍历中确定根节点:根据中序遍历结果,根节点是1。

  2. 根据根节点在层序遍历中的位置,将节点分成左子树和右子树:从层序遍历结果中,可以得知根节点1位于第一个位置,所以左子树节点为[2, 3],右子树节点为[4, 5, 6, 7]。

  3. 递归构建左子树和右子树:

    • 对于左子树:中序遍历结果为[2, 3],层序遍历结果为[2, 3]。

      • 根据中序遍历结果,根节点是2。
      • 根据层序遍历结果,左子树为空,右子树为空,因为2和3都是叶子节点。
    • 对于右子树:中序遍历结果为[4, 5, 6, 7],层序遍历结果为[4, 5, 6, 7]。

      • 根据中序遍历结果,根节点是3。
      • 根据层序遍历结果,右子树为空,左子树为空,因为4、5、6、7都是叶子节点。

最终,构建完成的二叉树如下所示:

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

这样就成功地通过层序遍历和中序遍历的结果逆推出了原始的二叉树结构。

要通过层序遍历和中序遍历的结果逆推二叉树,需要借助队列来构建二叉树。层序遍历的结果告诉我们节点的访问顺序,中序遍历的结果告诉我们节点的左右子树关系。

  1. 创建一个结构体来表示二叉树的节点。结构体应该包含一个值,以及指向左子树和右子树的指针。
struct TreeNode {
    int value;
    struct TreeNode* left;
    struct TreeNode* right;
};
  1. 创建一个逆推函数,该函数接受层序遍历和中序遍历的数组。
struct TreeNode* buildTree(int* levelorder, int levelorderSize, int* inorder, int inorderSize) {
    if (levelorderSize == 0 || inorderSize == 0) {
        return NULL; // 递归终止条件:子树为空
    }

    // 创建当前子树的根节点
    struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->value = levelorder[0];
    root->left = NULL;
    root->right = NULL;

    // 在中序遍历数组中找到根节点的位置
    int rootIndex;
    for (rootIndex = 0; rootIndex < inorderSize; rootIndex++) {
        if (inorder[rootIndex] == root->value) {
            break;
        }
    }

    // 根据根节点的位置,划分左子树和右子树的中序遍历结果
    int leftInorderSize = rootIndex;
    int rightInorderSize = inorderSize - rootIndex - 1;

    // 根据中序遍历的结果,划分层序遍历的结果
    int* leftLevelorder = (int*)malloc(leftInorderSize * sizeof(int));
    int* rightLevelorder = (int*)malloc(rightInorderSize * sizeof(int));
    int leftCount = 0;
    int rightCount = 0;

    for (int i = 1; i < levelorderSize; i++) {
        int value = levelorder[i];
        int inInorder = 0;

        // 判断层序遍历结果中的节点是否在左子树中
        for (int j = 0; j < leftInorderSize; j++) {
            if (inorder[j] == value) {
                inInorder = 1;
                break;
            }
        }

        if (inInorder) {
            leftLevelorder[leftCount++] = value;
        } else {
            rightLevelorder[rightCount++] = value;
        }
    }

    // 递归构建左子树和右子树
    root->left = buildTree(leftLevelorder, leftInorderSize, inorder, leftInorderSize);
    root->right = buildTree(rightLevelorder, rightInorderSize, inorder + rootIndex + 1, rightInorderSize);

    free(leftLevelorder);
    free(rightLevelorder);

    return root;
}
  1. 创建一个包装函数,该函数接受层序和中序遍历的数组,并调用逆推函数进行树的构建。
struct TreeNode* buildTreeWrapper(int* levelorder, int levelorderSize, int* inorder, int inorderSize) {
    return buildTree(levelorder, levelorderSize, inorder, inorderSize);
}

现在,你可以使用buildTreeWrapper函数来逆推二叉树。只需将层序和中序遍历的数组传递给它,它将返回树的根节点。

示例用法:

int levelorder[] = {3, 9, 20, 15, 7};
int inorder[] = {9, 3, 15, 20, 7};
int levelorderSize = sizeof(levelorder) / sizeof(levelorder[0]);
int inorderSize = sizeof(inorder) / sizeof(inorder[0]);

struct TreeNode* root = buildTreeWrapper(levelorder, levelorderSize, inorder, inorderSize);

这将创建一个二叉树,其结构如下:

    3
   / \
  9  20
    /  \
   15   7

复制二叉树

二叉树的复制是指创建一个与原始二叉树结构相同的新二叉树,其中包含与原始树相同的节点值。这可以通过递归或迭代方式来实现。在下面的示例中,我将使用C语言来演示如何递归地复制一个二叉树。

首先,我们需要定义二叉树节点的结构:

#include <stdio.h>
#include <stdlib.h>

// 定义二叉树节点结构
struct TreeNode {
    int data;
    struct TreeNode* left;
    struct TreeNode* right;
};

接下来,我们可以创建一个函数来复制二叉树:

// 复制二叉树的函数
struct TreeNode* copyTree(struct TreeNode* root) {
    if (root == NULL) {
        return NULL;
    }

    // 创建新节点,并复制原始节点的值
    struct TreeNode* newRoot = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    newRoot->data = root->data;
    newRoot->left = copyTree(root->left);   // 递归复制左子树
    newRoot->right = copyTree(root->right); // 递归复制右子树

    return newRoot;
}

上述函数递归地复制整棵树。当传入一个节点时,它会首先检查节点是否为NULL,如果是NULL,表示已经到达叶子节点或空树,直接返回NULL。如果节点不为NULL,它会创建一个新节点,将原始节点的值复制到新节点,然后递归调用copyTree函数来复制左子树和右子树,最后返回新树的根节点。

下面是一个示例用法:

int main() {
    // 创建一个示例二叉树
    struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->data = 1;
    root->left = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->left->data = 2;
    root->left->left = NULL;
    root->left->right = NULL;
    root->right = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->right->data = 3;
    root->right->left = NULL;
    root->right->right = NULL;

    // 复制二叉树
    struct TreeNode* copiedRoot = copyTree(root);

    // 打印原始二叉树和复制后的二叉树
    printf("Original Tree: %d\n", root->data);
    printf("Copied Tree: %d\n", copiedRoot->data);

    // 释放内存
    free(root);
    free(copiedRoot);

    return 0;
}

这段代码首先创建一个示例二叉树,然后使用copyTree函数复制它。最后,打印原始二叉树和复制后的二叉树以及释放内存以避免内存泄漏。注意,这只是一个简单的示例,实际中可能需要根据具体情况来扩展复制二叉树的函数。

  • 25
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值