数据结构(Tree) — 二叉树

一、二叉树的遍历

二叉树的遍历(搜索)方式主要有两种,一种是深度优先搜索(DFS)、另一种是广度优先搜索(BFS)。深度优先搜索又分为三种遍历方式:前序遍历、中序遍历、后序遍历。

1. 前序遍历(DFS)

- 递归

public static void preOrderTraveral(Node rootNode) {
    if (rootNode == null) {
        return;
    }
    System.out.println("Node:" + rootNode.value);
    preOrderTraveral(rootNode.leftNode);
    preOrderTraveral(rootNode.rightNode);
}

- 非递归

// 方法一
public static void preOrderTraveralWithStack(Node rootNode) {
	Stack<Node> stack = new Stack<>();
    stack.push(rootNode);
    while (!stack.isEmpty()) {
        Node curNode = stack.pop();
        System.out.println("Node:" + curNode.value);
        if (curNode.rightNode != null) {
            stack.push(curNode.rightNode);
        }
        if (curNode.leftNode != null) {
            stack.push(curNode.leftNode);
        }
    } 
}

// 方法二
public static void preOrderTraveralWithStack(Node rootNode) {
    Stack<Node> stack = new Stack<>();
    Node curNode = rootNode;

    while (curNode != null || !stack.isEmpty()) {
        while (curNode != null) {
            System.out.println("Node:" + curNode.value);
            stack.push(curNode);
            curNode = curNode.leftNode;
        }
        if (!stack.isEmpty()) {
            Node node = stack.pop();
            curNode = node.rightNode;
        }
    }
}

2. 中序遍历(DFS)

- 递归

public static void inOrderTraveral(Node rootNode) {
    if (rootNode == null) {
        return;
    }
    inOrderTraveral(rootNode.leftNode);
    System.out.println("Node:" + rootNode.value);
    inOrderTraveral(rootNode.rightNode);
}

- 非递归

public static void inOrderTraveralWithStack(Node rootNode) {
    Stack<Node> stack = new Stack<>();
    Node curNode = rootNode;

    while (curNode != null || !stack.isEmpty()) {
        while (curNode != null) {
            stack.push(curNode);
            curNode = curNode.leftNode;
        }
        if (!stack.isEmpty()) {
            Node node = stack.pop();
            System.out.println("Node:" + node.value);
            curNode = node.rightNode;
        }
    }
}

3. 后序遍历(DFS)

- 递归

public static void postOrderTraveral(Node rootNode) {
    if (rootNode == null) {
        return;
    }
    postOrderTraveral(rootNode.leftNode);
    postOrderTraveral(rootNode.rightNode);
    System.out.println("Node:" + rootNode.value);
}

- 非递归

二叉树从左往右的后序遍历,可以理解为是从右往左的前序遍历

public static void postOrderTraveralWithStack(Node rootNode) {
    Stack<Node> stack1 = new Stack<>();
    Stack<Node> stack2 = new Stack<>();
    Node curNode = rootNode;

    while (curNode != null || !stack1.isEmpty()) {
        while (curNode != null) {
            stack1.push(curNode);
            stack2.push(curNode);
            curNode = curNode.rightNode;
        }

        if (!stack1.isEmpty()) {
            Node popNode = stack1.pop();
            curNode = popNode.leftNode;
        }
    }
    while (!stack2.isEmpty()) {
        System.out.println("Node:" + stack2.pop().value);
    }
}

4. 层序遍历(BFS)

广度优先搜索利用队列来实现层序遍历。

public static void BFSTraveral(Node rootNode) {
    LinkedList<Node> queue = new LinkedList<>();
    queue.offer(rootNode);
    while (!queue.isEmpty()) {
        Node popNode = queue.poll();
        System.out.println("Node:" + popNode.value);
        if (popNode.leftNode != null) {
            queue.offer(popNode.leftNode);
        }
        if (popNode.rightNode != null) {
            queue.offer(popNode.rightNode);
        }
    }
}

二、二叉树的构建

如构建一个如下结构的二叉树。
在这里插入图片描述

前序遍历: [4, 2, 1, 3, 6, 5, 7]
中序遍历: [1, 2, 3, 4, 5, 6, 7]
后序遍历: [1, 3, 2, 5, 7, 6. 4]

各个数组的特点:

  • 前序遍历数组:数组的首个元素即为当前的Node节点。
  • 中序遍历数组:用于计算出当前Node节点的左右子树个数。
  • 后序遍历数组:数组的最后一个元素即为当前的Node节点。

1. 已知前序遍历和中序遍历顺序

实现原理:

根据前序遍历数组的顺序,以及中序数组计算出的左右子树的个数来构建二叉树。因为构建二叉树的过程是先构建根节点,再构建左右节点,这与二叉树前序遍历递归写法的思想是一致的。

以上面的二叉树为例来推到实现步骤:

  1. 因为前序遍历数组的首个元素4为根节点,所以先构建根节点Node。
  2. 计算4在中序遍历数组的下标位置。此处下标为3,所以跟节点4左侧的子树节点个数为3个 (即前序遍历对应左子树的值为 [2, 1, 3] );右侧子树节点个数为3 (即前序遍历对应右子树的值为 [6, 5, 7] )。
  3. 计算根节点4的左节点:利用分治的思想进行递归。我们可以理解为根节点4的左节点2为其自身左右节点(节点1和节点3)的父节点。
    • 相当于求子数组对应的二叉树。子数组前序遍历:[2, 1, 3];子数组中序遍历:[1, 2, 3]。
  4. 计算根节点4的右节点:利用分治的思想进行递归。我们可以理解为根节点4的右节点6为其自身左右节点(节点5和节点7)的父节点。
  5. 返回根节点Node。
public static Node buildTree(int[] preOrder, int[] inOrder) {
    int len = preOrder.length;
    // 利用Map来记录中序遍历数组中每个元素的位置。
    HashMap indexMap = new HashMap<Integer, Integer>();
    for (int i = 0; i < len; i++) {
        indexMap.put(inOrder[i], i);
    }
    return buildTree(preOrder, inOrder, 0, len - 1, 0, len - 1, indexMap);
}

public static Node buildTree(int[] preOrder, int[] inOrder,
                              int preOrderLeft, int preOrderRight,
                              int inOrderLeft, int inOrderRight, 
                              HashMap<Integer, Integer> indexMap) {
    // 退出条件
    if (preOrderLeft > preOrderRight) {
        return null;
    }
    // 1.先把根节点建立出来。
    Node root = new Node(preOrder[preOrderLeft]);
    int inOrderRoot = indexMap.get(root.value);
    // 2.得到子树中的节点数目。
    int subtreeSize = inOrderRoot - inOrderLeft;
    // 3.递归地构造左子树,并连接到根节点。
    // 先序遍历中「从 左边界+1 开始的 subtreeSize」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素。
    root.leftNode = buildTree(preOrder, inOrder,
            	preOrderLeft + 1, preOrderLeft + subtreeSize,
            	inOrderLeft, inOrderRoot - 1, indexMap);
    // 4.递归地构造右子树,并连接到根节点
    // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素。
    root.rightNode = buildTree(preOrder, inOrder,
            	preOrderLeft + subtreeSize + 1, preOrderRight,
             	inOrderRoot + 1, inOrderRight, indexMap);
    return root;
}

2. 已知中序遍历和后序遍历顺序

实现原理:

根据后序遍历数组的顺序,以及中序数组计算出的左右子树的个数来构建二叉树。这与利用前序和中序来构建二叉树的方法类似,不同点在于后续遍历数组中根节点的位置在数组最后一个(而不是数组第一个元素)。

以上面的二叉树为例来推到实现步骤:

  1. 因为后序遍历数组的最后一个元素4为根节点,所以先构建根节点Node。
  2. 计算4在中序遍历数组的下标位置。此处下标为3,所以跟节点4左侧的子树节点个数为3个 (即后序遍历对应左子树的值为 [1, 3, 2] );右侧子树节点个数为3 (即后序遍历对应右子树的值为 [5, 7, 6] )。
  3. 计算根节点4的左节点:利用分治的思想进行递归。我们可以理解为根节点4的左节点2为其自身左右节点(节点1和节点3)的父节点。
    • 相当于求子数组对应的二叉树。子数组前序遍历:[1, 3, 2];子数组中序遍历:[1, 2, 3]。
  4. 计算根节点4的右节点:利用分治的思想进行递归。我们可以理解为根节点4的右节点6为其自身左右节点(节点5和节点7)的父节点。
  5. 返回根节点Node。
public static Node buildTree(int[] postOrder, int[] inOrder) {
    int len = postOrder.length;
    // 利用Map来记录中序遍历数组中每个元素的位置。
    HashMap indexMap = new HashMap<Integer, Integer>();
    for (int i = 0; i < len; i++) {
        indexMap.put(inOrder[i], i);
    }
    return buildTree(postOrder, inOrder, 0, len - 1, 0, len - 1, indexMap);
}

/**
 * postOrder:后序遍历
 * inOrder:中序遍历
 */
public static Node buildTree(int[] postOrder, int[] inOrder,
                              int postOrderLeft, int postOrderRight,
                              int inOrderLeft, int inOrderRight, 
                              HashMap<Integer, Integer> indexMap) {
    // 退出条件
    if (postOrderLeft > postOrderRight) {
        return null;
    }
    // 1.先把根节点建立出来。
    Node root = new Node(preOrder[postOrderRight]);
    int inOrderRoot = indexMap.get(root.value);
    // 2.得到子树中的节点数目。
    int subtreeSize = inOrderRoot - inOrderLeft;
    // 3.递归地构造左子树,并连接到根节点。
    // 后序遍历中「从 左边界 开始的 subtreeSize」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素。
    root.leftNode = buildTree(postOrder, inOrder,
            	postOrderLeft, (postOrderLeft + subtreeSize - 1),
            	inOrderLeft, inOrderRoot - 1, indexMap);
    // 4.递归地构造右子树,并连接到根节点
    // 后序遍历中「从 左边界+左子树节点数目 开始到 右边界-1」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素。
    root.rightNode = buildTree(postOrder, inOrder,
            	postOrderLeft + subtreeSize, postOrderRight - 1,
             	inOrderRoot + 1, inOrderRight, indexMap);
    return root;
}

3. 已知前序遍历和后序遍历顺序

无法构建二叉树,因为无法确定左右子树的个数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值