数据结构和算法5-非线性-树

数据结构和算法系列共八篇

先介绍个工具

1.概念

1-1.什么是树

树是由结点或顶点和边组成的(可能是非线性的)且不存在着任何环的一种数据结构。
没有结点的树称为空(null或empty)树。一棵非空的树包括一个根结点,还(很可能)有多个附加结点,所有结点构成一个多级分层结构。

img

1-2.度

度(degree)分为

  • 节点的度, 含有几个子节点(儿子)
  • degree为0的节点,称为叶子节点leaf
  • 最上层的曾为根节点root
  • degree不为0的节点,除根以外称为内部节点
  • 树的度,各节点的最大degree称为树的degree (m叉树)

img

1-3.深度

深度(depth)又叫层次(level),root为第一层,其子节点为第二层,子的子为第三层…以此类推

img

1-4.树的分类

顺序分

如果树的节点是有顺序的,那就是有序树,没有顺序的则是无序的

节点分

按树所有node里,某node的子node最多树为m,那就是m叉树 (以最多的为基准,即树的degree)

树的个数分

多棵树组成一个森林,一个树只是特殊的森林。

1-5.节点间关系

直接关系

  • 父节点:一个节点直接前节点
  • 子节点:一个节点直接后节点
  • 兄弟节点:同一父节点的,其他节点

间接关系

  • 祖先:从根到该节点所经分支上的所有节点;
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
  • 堂兄弟节点:双亲在同一层的节点互为堂兄弟

img

2.二叉树

二叉树,是m叉树为2的特殊树(即:树的degree小于等于2)。

2-1.二叉树特性

img

有兴趣的可以看下其证明

2-2.二叉树分类

2-2-1.满二叉树

高度为k并且有 2 k + 1 − 1 2^{k+1}-1 2k+11个结点的二叉树。

img

2-2-2.完全二叉树

基于满二叉树,在最下层从最右侧起去掉相邻的若干叶子结点,得到的二叉树称为完全二叉树。

img

2-3.二叉树的存储结构

  • 顺序数组存储
  • 链结构存储
2-3-1.顺序数组存储

如果是完全二叉树,用顺序存储很好。但是不是完全二叉树的话,中间会有空留,占用空间。

img

img

2-3-2.链结构存储

链表结构比较适合二叉树存储。node节点结构:左子节点、数据、右子节点

这样的数据结构,只能保证父找到子,不能保证子找父的。(如需子找到父,那还得需要多加个父节点存储)

img

3.遍历

二叉树遍历根据root的位置分三种

  • 前序遍历:根-左-右 DLR
  • 中序遍历:左-根-右 LDR
  • 后序遍历:左-右-根 LRD

img

3-1.前序遍历

心法口诀:首先访问根,再先序遍历左子树,最后先序遍历右子树

img

传统分析

1.找到root-1,然后左边子node-4,右边子node-1。得到1 4_ 2_
2.再分析左边的node-4,node-4的左子node没有,右子node为node-5。得到1 4 5 2_
3.左边没有,在分析右边node-2,node-2的左子node-3,右子node-6。得到1 4 5 2 3 6 _
4.再分析node-3的,左右子node都没有,是leaf
5.再分析node-6,左子node没有,右子node-7。得到1 4 5 2 3 6 7

个人技巧

img

注意上面箭头方向。从最左上,每次一层,所得的结果就是前序结果。

用咱的小技巧去试试前面的二叉树吧,看是不是得到一样的结果。

3-2.中序遍历

心法口诀:首先中序遍历左子树,再访问根,最后中序遍历右子树

img

传统分析

1.找到root-1放中间,然后左边子node-4放左侧,右边子node-1放右侧。得到_4_ 1 2_(此时下划线是4和2的)
2.再分析左边的node-4,node-4的左子node没有,右子node为node-5。得到4 5 1 _2_(此时下划线是2的)
3.左边没有,在分析右边node-2,node-2的左子node-3放左侧,右子node-6放右测。得到4 5 1 _3_2_6_(此时下划线是3和6的)
4.再分析node-3的,左右子node都没有,是leaf。4 5 1 3 2_6_(3没有左右,那么就将3的左右下划线删去)
5.再分析node-6,左子node没有,右子node-7放右测。得到4 5 1 3 2 6 7

个人技巧

img

因为根在中间,那不就是跟我们二叉树一样吗!!!(左根右,LDR),所以直接把二叉树压平到一条线,不就是需要的顺序吗? 是不是so easy😀!
我相信你看到这,肯定想将文章拉到最后,要给我一个大大的赞👍或者来段评论啥的(去吧,不拦你😄,记得回来找我,接着往下看)。

用咱的小技巧去试试前面的二叉树吧,看是不是得到一样的结果。

3-3.后序遍历

心法口诀:首先中序遍历左子树,再中序遍历右子树,最后访问根

img

传统分析

1.找到root-1放最后,然后左边子node-4放左1侧,右边子node-1放左2侧。得到_4 _2 1(此时下划线是4和2的)
2.再分析左边的node-4,node-4的左子node没有,右子node为node-5放左2侧。得到5 4 _2 1(此时下划线是2的)
3.左边没有,在分析右边node-2,node-2的左子node-3放左1侧,右子node-6放左2测。得到5 4 _3 _6 2 1(此时下划线是3和6的)
4.再分析node-3的,左右子node都没有,是leaf。5 4 3 _6 2 1(3没有左右,那么就将3的左右下划线删去)
5.再分析node-6,左子node没有,右子node-7放左2侧。得到5 4 3 7 6 2 1

个人技巧

img

这个小技巧没上面个两种直观。但也能出来,比较啰嗦。说下,能理解的就用,不理解的话,还是使用传统方式

口诀:由左到右,由下往上的顺序(左的优先级比下高)。先左看再右看最后向前看齐(跟中国式过马路似的,先左再右看,最后前看。下面右解释),每遇到root有分叉,将root放局部最后位置。(因为是后序遍历,所以遇到是root的,直接放到局部域最后位置)

  1. 6虽比9层次低,但是6比9靠左,左的优先级比下高。6开始,往上找祖先,直到遇到4分叉了。先左看再右看,最后向前看。将2放局部最后。结果为:7 6 4 _ 2 (此时下划线是2的)
  2. 继续找左分支上,最左且最下层没有处理过的node为9。往上走找祖先,直到2没遇到分叉。结果为:7 6 4 9 8 5 2
  3. 2继续找祖先,遇到了1分支。将1放局部最后。结果为:7 6 4 9 8 5 2 _ 1 (此时下划线是1的,代表1右节点遍历数据)
  4. 看右测的最左且最下层是10,往上找祖先,直到遇到1分叉。结果为:7 6 4 9 8 5 2 10 12 11 3 1

这有几点说明:

  • 左的优先级比下高:6虽比9层次低,但是6比9靠左,左的优先级比下高。(虽然9在第5层,6和4在第4和3层)。因为左比层次(下)优先级高。
  • 先左看再右看,最后向前看:意思6是4的左节点,7在4的右子节点上。那是先6再7最后4的顺序。

说下马路过程:(大家都知道中国交通规则是靠右行的),直接上图吧

img

1.过马路,还没过一半前,先左边看,因为汽车是靠右行的,防止有车冲过来。
2.过完一半后,再左边看,防止另一半车再冲过来
3.过完马路了,左右都安全了,最后往前看走路

4.代码实战

4-1.接口

接口

public interface ITree {
    /**
     * 是否空树
     *
     * @return
     */
    public boolean isEmpty();

    /**
     * 树结点数量
     *
     * @return
     */
    public int size();

    /**
     * 获取二叉树的高度
     *
     * @return
     */
    public int getHeight();

    /**
     * 查询指定值的结点
     *
     * @param value
     * @return
     */
    public TreeNode findKey(int value); // 查找

    /**
     * 前序递归遍历
     */
    public void preOrderTraverse();

    /**
     * 中序遍历递归操作
     */
    public void inOrderTraverse();

    /**
     * 后序遍历递归操作
     */
    public void postOrderTraverse();

    /**
     * 中序遍历非递归操作
     * 1)对于任意节点current,若该节点不为空则将该节点压栈,并将左子树节点置为current,重复此操作,直到current为空。
     * 2)若左子树为空,栈顶节点出栈,访问节点后将该节点的右子树置为current
     * 3) 重复1、2步操作,直到current为空且栈内节点为空。
     */
    public void inOrderByStack();

    /**
     * 前序遍历非递归操作
     * 1)对于任意节点current,若该节点不为空则访问该节点后再将节点压栈,并将左子树节点置为current,重复此操作,直到current为空。
     * 2)若左子树为空,栈顶节点出栈,将该节点的右子树置为current
     * 3) 重复1、2步操作,直到current为空且栈内节点为空。
     */
    public void preOrderByStack();

    /**
     * 后序遍历非递归操作
     * 1)对于任意节点current,若该节点不为空则访问该节点后再将节点压栈,并将左子树节点置为current,重复此操作,直到current为空。
     * 2)若左子树为空,取栈顶节点的右子树,如果右子树为空或右子树刚访问过,则访问该节点,并将preNode置为该节点
     * 3) 重复1、2步操作,直到current为空且栈内节点为空。
     */
    public void postOrderByStack();

    /**
     * 按照层次遍历二叉树
     */
    public void levelOrderByQueue();

}

二叉node节点定义

public class TreeNode {

    private Object data;

    private TreeNode leftChild;

    private TreeNode rightChild;

    public TreeNode(Object data, TreeNode leftChild, TreeNode rightChild) {
        this.data = data;
        this.leftChild = leftChild;
        this.rightChild = rightChild;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public TreeNode getLeftChild() {
        return leftChild;
    }

    public void setLeftChild(TreeNode leftChild) {
        this.leftChild = leftChild;
    }

    public TreeNode getRightChild() {
        return rightChild;
    }

    public void setRightChild(TreeNode rightChild) {
        this.rightChild = rightChild;
    }

    @Override
    public String toString() {
        return "TreeNode{" + "data=" + data + ", leftChild=" + leftChild.getData() + ", rightChild=" + rightChild.getData() + '}';
    }
}

4-2.实现代码

整体实现代码最后整体给出,这先每个片段给出,并分析。

分析尽我最大解释,实在不懂,那就得靠自己悟了

4-2-1.空判断
public class MyListBinaryTree implements ITree {

    private TreeNode root;

    public MyListBinaryTree(TreeNode root) {
        this.root = root;
    }

    @Override
    public boolean isEmpty() {
        return root == null;
    }
}

只要判断root是否为空

4-2-2.大小
public int size() {
        return size(this.root);
    }

private int size(TreeNode node) {
    // 为null时返回0,不为null时加一
    if (node == null) {
        return 0;
    }
    int leftSize = size(node.getLeftChild());
    int rightSize = size(node.getRightChild());
    // 左右子节点都会掉一次,所以只要node为null的只要自己加一就行
    return leftSize + rightSize + 1;
}

使用递归方式,每次将left child和 right child传进去。空的时候,就返回0,否则加一

4-2-3.树深度
@Override
    public int getHeight() {
        // 求深度,即求从root出来的child node谁最长
        return getHeight(root);
    }

private int getHeight(TreeNode node) {
    if (node == null) {
        return 0;
    }
    // 左子节点
    int leftNum = getHeight(node.getLeftChild());

    // 右子节点
    int rightNum = getHeight(node.getLeftChild());

    // 返回大的+1
    return leftNum > rightNum ? leftNum + 1 : rightNum + 1;
}

使用递归方式,每次将left child和 right child传进去。空的时候,就返回0,否则将左右返回值中较大的加一

4-2-4.查找
@Override
    public TreeNode findKey(Object value) {
        return this.findKey(value, root);
    }

private TreeNode findKey(Object value, TreeNode node) {
    if (node == null) {
        return null;
    }

    if (node.getData().equals(value)) {
        return node;
    }

    TreeNode leftNode = findKey(value, node.getLeftChild());

    TreeNode rightNode = findKey(value, node.getRightChild());

    if (leftNode != null) {
        return leftNode;
    }

    if (rightNode != null) {
        return rightNode;
    }

    return null;
}

递归查找

4-2-5.前序遍历

递归方式实现

@Override
public void preOrderTraverse() {
    this.preOrderTraverse(root);
}

private void preOrderTraverse(TreeNode node) {
    if (node == null) {
        return;
    }
    // d输出
    System.out.print(node.getData() + " ");
    // l处理
    preOrderTraverse(node.getLeftChild());
    // r处理
    preOrderTraverse(node.getRightChild());
}

img

擦,不知从何讲起呀。(理解一眼就明白,不明白的,言语也解释不了😂)
递归:
1.给出终止条件,node为空结束
2.将根节点传给方法,因为前序遍历,所以要将d先输出
3.再将左子节点传给方法,作为根继续处理
4.将右子节点也传给递归方法,作为根继续处理

栈方式实现

@Override
public void preOrderByStack() {
    Deque<TreeNode> stack = new LinkedList<>();
    if (root == null) {
        return;
    }

    // 心法:以当前node移动,根据DLR、LDR、LRD的顺序去如何存取到stack中去
    // 指向树的当前移动node点
    TreeNode current = root;
    List<Object> result = new ArrayList<>();
    while (current != null || !stack.isEmpty()) {
        // 将current node的所有左子节点全放到栈里,一致循环直到左子node为null
        while (current != null) {
            // 输出
            result.add(current.getData());
            stack.push(current);
            // 要移动current node的位置给current node (即left node)
            current = current.getLeftChild();
        }

        // 如果栈不为空,那就从栈中弹出栈顶元素
        // 将current node的right node给 current node,供下次循环找其所有的left node
        if (!stack.isEmpty()) {
            current = stack.pop();
            current = current.getRightChild();
        }
    }
    System.out.println(result);
}

img

上面代码有注释
大体思路:
从根起,将所有左节点全入栈中(从栈顶到栈尾为:6、4、2、1),因为前序遍历,所以这时就要输出了,此时current为空跳出内部的while循环
到达if判断,从栈中弹出栈顶6,并赋值给current,并将6的右子节点7再赋给current(没有),此时4为栈顶。
循环一次while,将4弹出栈,将4右子节点给current,此时2成为栈顶了。
再做一次while循环,将7下面所有节点处理一边。发现没有,继续弹出2…(以此类推)

4-2-6.中序遍历

递归方式实现

@Override
public void inOrderTraverse() {
    this.inOrderTraverse(root);
}

private void inOrderTraverse(TreeNode node) {
    if (node == null) {
        return;
    }
    // l处理
    inOrderTraverse(node.getLeftChild());
    // d输出
    System.out.print(node.getData() + " ");
    // r处理
    inOrderTraverse(node.getRightChild());
}

img

递归:
1.给出终止条件,node为空结束
2.将根节点传给方法,因为中序遍历,所以要将d先放l和r中间

栈方式实现

@Override
public void inOrderByStack() {
    Deque<TreeNode> stack = new LinkedList<>();
    if (root == null) {
        return;
    }

    // 心法:以当前node移动,根据DLR、LDR、LRD的顺序去如何存取到stack中去
    // 指向树的当前移动node点
    TreeNode current = root;
    List<Object> result = new ArrayList<>();
    while (current != null || !stack.isEmpty()) {
        // 将current node的所有左子节点全放到栈里,一致循环直到左子node为null
        while (current != null) {
            stack.push(current);
            // 要移动current node的位置给current node (即left node)
            current = current.getLeftChild();
        }

        // 如果栈不为空,那就从栈中弹出栈顶元素,输出
        // 将current node的right node给 current node,供下次循环找其所有的left node
        if (!stack.isEmpty()) {
            current = stack.pop();
            result.add(current.getData());
            current = current.getRightChild();
        }
    }
    System.out.println(result);
}

img

上面代码有注释
大体思路:
从根起,将所有左节点全入栈中(从栈顶到栈尾为:6、4、2、1),此时current为空跳出内部的while循环
到达if判断,从栈中弹出栈顶6,并赋值给current,因为是中序遍历,所以弹栈时就要输出,再将6的右子节点给current(为null),此时4成为栈顶。
再做一次while循环,从栈中弹出栈顶4, 将4的右子节点7再赋给current, 此是2成为了栈顶
再做一次while循环,将7下面所有节点处理一边。发现没有,继续弹出2…(以此类推)

4-2-7.后序遍历

递归方式实现

@Override
public void postOrderTraverse() {
    this.postOrderTraverse(root);
}

private void postOrderTraverse(TreeNode node) {
    if (node == null) {
        return;
    }
    // l处理
    postOrderTraverse(node.getLeftChild());
    // r处理
    postOrderTraverse(node.getRightChild());
    // d输出
    System.out.print(node.getData() + " ");
}

img

递归:
1.给出终止条件,node为空结束
2.将根节点传给方法,因为中序遍历,所以要将d先放l和r后面

栈方式实现1

@Override
public void postOrderByStack() {

    // 方式一:
    Deque<TreeNode> stack = new LinkedList<>();
    if (root == null) {
        return;
    }
    List<Object> result = new ArrayList<>();
    // 指向树的当前移动node点
    TreeNode current = null;
    // 指向当前移动node的其中一个子节点(即,栈上的上一个栈顶)
    TreeNode tmpChildNode = null;

    stack.push(root);
    while (!stack.isEmpty()) {
        current = stack.peek();
        if ((current.getLeftChild() == null && current.getRightChild() == null) || current.getLeftChild() == tmpChildNode || current
                .getRightChild() == tmpChildNode) {
            result.add(current.getData());
            stack.pop();
            tmpChildNode = current;
        } else {
            // 先添加right child node ,因为栈是LIFO
            if (current.getRightChild() != null) {
                stack.push(current.getRightChild());
            }
            // 再添加left child node ,因为栈是LIFO
            if (current.getLeftChild() != null) {
                stack.push(current.getLeftChild());
            }
        }
    }
    System.out.println(result);
}

img

思想:
将节点压到栈中,然后通过栈顶元素去判断有没子节点,有则入栈,没有则出栈。
出栈时打印结果。注意打印的条件两个

  • 没有子节点,即leaf,需要打印
  • 出栈的节点的有子节点,但是前一次出栈的节点,也需要打印

栈方式实现2

 @Override
public void postOrderByStack() {
    // 方式二
    Deque<TreeNode> stack = new LinkedList<>();
    if (root == null) {
        return;
    }

    // 心法:以当前node移动,根据DLR、LDR、LRD的顺序去如何存取到stack中去
    // 指向树的当前移动node点
    TreeNode current = root;
    List<Object> result = new ArrayList<>();
    while (current != null || !stack.isEmpty()) {
        // 将current node的所有右子节点全放到栈里,一致循环直到右子node为null
        while (current != null) {
            stack.push(current);
            // 要移动current node的位置给current node (即right node)
            result.add(current.getData());
            current = current.getRightChild();
        }

        // 如果栈不为空,那就从栈中弹出栈顶元素,输出
        // 将current node的left node给 current node,供下次循环找其所有的right node
        if (!stack.isEmpty()) {
            current = stack.pop();
            current = current.getLeftChild();
        }
    }
    // 反转下结果
    Collections.reverse(result);
    System.out.println(result);
}

img

思路:
此要放结合前序栈方式遍历来理解(前序栈方式,以左子节点为先,一直while循环压榨),而这后序栈方式,以右为先进行压栈
不过最后结果饭的,需要反转一下

4-2-8.水平遍历
@Override
public void levelOrderByQueue() {
    Deque<TreeNode> queue = new LinkedList<>();
    if (root == null) {
        return;
    }
    List<Object> result = new ArrayList<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        int len = queue.size();
        // 遍历将stack里元素弹出
        for (int i = 0; i < len; i++) {
            TreeNode node = queue.poll();
            result.add(node.getData());
            TreeNode leftChild = node.getLeftChild();
            TreeNode rightChild = node.getRightChild();
            if (leftChild != null) {
                queue.offer(leftChild);
            }
            if (rightChild != null) {
                queue.offer(rightChild);
            }
        }
    }
    System.out.println(result);
}

img

水平遍历要借助队列的特性来处理,将每层的放进queue里队尾,从头取出(每次while加一层数据,需要取将queue里数据都取出)

4-3.整体代码

package com.iworkh.da.lesson03;

import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

/**
 * 自定义二叉树
 *
 * @author: iworkh-沐雨云楼
 * @date: 2020-07-02
 */
public class MyListBinaryTree implements ITree {

    private TreeNode root;

    public MyListBinaryTree(TreeNode root) {
        this.root = root;
    }

    @Override
    public boolean isEmpty() {
        return root == null;
    }

    @Override
    public int size() {
        return size(this.root);
    }

    private int size(TreeNode node) {
        // 为null时返回0,不为null时加一
        if (node == null) {
            return 0;
        }
        int leftSize = size(node.getLeftChild());
        int rightSize = size(node.getRightChild());
        // 左右子节点都会掉一次,所以只要node为null的只要自己加一就行
        return leftSize + rightSize + 1;
    }

    @Override
    public int getHeight() {
        // 求深度,即求从root出来的child node谁最长
        return getHeight(root);
    }

    private int getHeight(TreeNode node) {
        if (node == null) {
            return 0;
        }
        // 左子节点
        int leftNum = getHeight(node.getLeftChild());

        // 右子节点
        int rightNum = getHeight(node.getLeftChild());

        // 返回大的+1
        return leftNum > rightNum ? leftNum + 1 : rightNum + 1;
    }

    @Override
    public TreeNode findKey(Object value) {
        return this.findKey(value, root);
    }

    private TreeNode findKey(Object value, TreeNode node) {
        if (node == null) {
            return null;
        }

        if (node.getData().equals(value)) {
            return node;
        }

        TreeNode leftNode = findKey(value, node.getLeftChild());

        TreeNode rightNode = findKey(value, node.getRightChild());

        if (leftNode != null) {
            return leftNode;
        }

        if (rightNode != null) {
            return rightNode;
        }

        return null;
    }

    @Override
    public void preOrderTraverse() {
        this.preOrderTraverse(root);
    }

    private void preOrderTraverse(TreeNode node) {
        if (node == null) {
            return;
        }
        // d输出
        System.out.print(node.getData() + " ");
        // l处理
        preOrderTraverse(node.getLeftChild());
        // r处理
        preOrderTraverse(node.getRightChild());
    }

    @Override
    public void inOrderTraverse() {
        this.inOrderTraverse(root);
    }

    private void inOrderTraverse(TreeNode node) {
        if (node == null) {
            return;
        }
        // l处理
        inOrderTraverse(node.getLeftChild());
        // d输出
        System.out.print(node.getData() + " ");
        // r处理
        inOrderTraverse(node.getRightChild());
    }

    @Override
    public void postOrderTraverse() {
        this.postOrderTraverse(root);
    }

    private void postOrderTraverse(TreeNode node) {
        if (node == null) {
            return;
        }
        // l处理
        postOrderTraverse(node.getLeftChild());
        // r处理
        postOrderTraverse(node.getRightChild());
        // d输出
        System.out.print(node.getData() + " ");
    }

    @Override
    public void preOrderByStack() {
        Deque<TreeNode> stack = new LinkedList<>();
        if (root == null) {
            return;
        }

        // 心法:以当前node移动,根据DLR、LDR、LRD的顺序去如何存取到stack中去
        // 指向树的当前移动node点
        TreeNode current = root;
        List<Object> result = new ArrayList<>();
        while (current != null || !stack.isEmpty()) {
            // 将current node的所有左子节点全放到栈里,一致循环直到左子node为null
            while (current != null) {
                // 输出
                result.add(current.getData());
                stack.push(current);
                // 要移动current node的位置给current node (即left node)
                current = current.getLeftChild();
            }

            // 如果栈不为空,那就从栈中弹出栈顶元素
            // 将current node的right node给 current node,供下次循环找其所有的left node
            if (!stack.isEmpty()) {
                current = stack.pop();
                current = current.getRightChild();
            }
        }
        System.out.println(result);
    }

    @Override
    public void inOrderByStack() {
        Deque<TreeNode> stack = new LinkedList<>();
        if (root == null) {
            return;
        }

        // 心法:以当前node移动,根据DLR、LDR、LRD的顺序去如何存取到stack中去
        // 指向树的当前移动node点
        TreeNode current = root;
        List<Object> result = new ArrayList<>();
        while (current != null || !stack.isEmpty()) {
            // 将current node的所有左子节点全放到栈里,一致循环直到左子node为null
            while (current != null) {
                stack.push(current);
                // 要移动current node的位置给current node (即left node)
                current = current.getLeftChild();
            }

            // 如果栈不为空,那就从栈中弹出栈顶元素,输出
            // 将current node的right node给 current node,供下次循环找其所有的left node
            if (!stack.isEmpty()) {
                current = stack.pop();
                result.add(current.getData());
                current = current.getRightChild();
            }
        }
        System.out.println(result);
    }

    @Override
    public void postOrderByStack() {

        // 方式一:
        Deque<TreeNode> stack = new LinkedList<>();
        if (root == null) {
            return;
        }
        List<Object> result = new ArrayList<>();
        // 指向树的当前移动node点
        TreeNode current = null;
        // 指向当前移动node的其中一个子节点(即,栈上的上一个栈顶)
        TreeNode tmpChildNode = null;

        stack.push(root);
        while (!stack.isEmpty()) {
            current = stack.peek();
            if ((current.getLeftChild() == null && current.getRightChild() == null) || current.getLeftChild() == tmpChildNode || current
                    .getRightChild() == tmpChildNode) {
                result.add(current.getData());
                stack.pop();
                tmpChildNode = current;
            } else {
                // 先添加right child node ,因为栈是LIFO
                if (current.getRightChild() != null) {
                    stack.push(current.getRightChild());
                }
                // 再添加left child node ,因为栈是LIFO
                if (current.getLeftChild() != null) {
                    stack.push(current.getLeftChild());
                }
            }
        }
        System.out.println(result);


        // 方式二
        // Deque<TreeNode> stack = new LinkedList<>();
        // if (root == null) {
        //     return;
        // }
        //
        // // 心法:以当前node移动,根据DLR、LDR、LRD的顺序去如何存取到stack中去
        // // 指向树的当前移动node点
        // TreeNode current = root;
        // List<Object> result = new ArrayList<>();
        // while (current != null || !stack.isEmpty()) {
        //     // 将current node的所有右子节点全放到栈里,一致循环直到右子node为null
        //     while (current != null) {
        //         stack.push(current);
        //         // 要移动current node的位置给current node (即right node)
        //         result.add(current.getData());
        //         current = current.getRightChild();
        //     }
        //
        //     // 如果栈不为空,那就从栈中弹出栈顶元素,输出
        //     // 将current node的left node给 current node,供下次循环找其所有的right node
        //     if (!stack.isEmpty()) {
        //         current = stack.pop();
        //         current = current.getLeftChild();
        //     }
        // }
        // // 反转下结果
        // Collections.reverse(result);
        // System.out.println(result);
    }

    @Override
    public void levelOrderByQueue() {
        Deque<TreeNode> queue = new LinkedList<>();
        if (root == null) {
            return;
        }
        List<Object> result = new ArrayList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int len = queue.size();
            // 遍历将stack里元素弹出
            for (int i = 0; i < len; i++) {
                TreeNode node = queue.poll();
                result.add(node.getData());
                TreeNode leftChild = node.getLeftChild();
                TreeNode rightChild = node.getRightChild();
                if (leftChild != null) {
                    queue.offer(leftChild);
                }
                if (rightChild != null) {
                    queue.offer(rightChild);
                }
            }
        }
        System.out.println(result);
    }
}

4-4.测试代码

造的tree就是我们前面文章一直分析的树

img

public class MyListBinaryTreeTest {
    public static void main(String[] args) {
        TreeNode root = genBTree();

        MyListBinaryTree myListBinaryTree = new MyListBinaryTree(root);

        System.out.println("是否为空:" + myListBinaryTree.isEmpty());
        System.out.println("树的高度:" + myListBinaryTree.getHeight());
        System.out.println("树节点数:" + myListBinaryTree.size());

        System.out.println("查找3: " + myListBinaryTree.findKey(3));
        System.out.println("查找20:" + myListBinaryTree.findKey(20));

        System.out.println("前序递归遍历:");
        myListBinaryTree.preOrderTraverse();
        System.out.println("");
        System.out.println("中序递归遍历:");
        myListBinaryTree.inOrderTraverse();
        System.out.println("");
        System.out.println("后序递归遍历:");
        myListBinaryTree.postOrderTraverse();
        System.out.println("");

        System.out.println("前序栈遍历:");
        myListBinaryTree.preOrderByStack();
        System.out.println("");
        System.out.println("中序栈遍历:");
        myListBinaryTree.inOrderByStack();
        System.out.println("");
        System.out.println("后序栈遍历:");
        myListBinaryTree.postOrderByStack();
        System.out.println("");
        System.out.println("水平队列遍历:");
        myListBinaryTree.levelOrderByQueue();
    }

    private static TreeNode genBTree() {
        TreeNode treeNode6 = new TreeNode(6, null, null);
        TreeNode treeNode7 = new TreeNode(7, null, null);
        TreeNode treeNode4 = new TreeNode(4, treeNode6, treeNode7);

        TreeNode treeNode9 = new TreeNode(9, null, null);
        TreeNode treeNode8 = new TreeNode(8, null, treeNode9);
        TreeNode treeNode5 = new TreeNode(5, treeNode8, null);

        TreeNode treeNode2 = new TreeNode(2, treeNode4, treeNode5);

        TreeNode treeNode10 = new TreeNode(10, null, null);
        TreeNode treeNode12 = new TreeNode(12, null, null);
        TreeNode treeNode11 = new TreeNode(11, null, treeNode12);

        TreeNode treeNode3 = new TreeNode(3, treeNode10, treeNode11);

        return new TreeNode(1, treeNode2, treeNode3);
    }
}

结果

是否为空:false
树的高度:4
树节点数:12
查找3: TreeNode{data=3, leftChild=10, rightChild=11}
查找20:null
前序递归遍历:
1 2 4 6 7 5 8 9 3 10 11 12 
中序递归遍历:
6 4 7 2 8 9 5 1 10 3 11 12 
后序递归遍历:
6 7 4 9 8 5 2 10 12 11 3 1 
前序栈遍历:
[1, 2, 4, 6, 7, 5, 8, 9, 3, 10, 11, 12]

中序栈遍历:
[6, 4, 7, 2, 8, 9, 5, 1, 10, 3, 11, 12]

后序栈遍历:
[6, 7, 4, 9, 8, 5, 2, 10, 12, 11, 3, 1]

水平队列遍历:
[1, 2, 3, 4, 5, 10, 11, 6, 7, 8, 12, 9]

5.链接

6.推荐

能读到文章最后,首先得谢谢您对本文的肯定,你的肯定是对博主最大的鼓励。

你觉本文有帮助,那就点个👍
你有疑问,那就留下您的💬
怕把我弄丢了,那就把我⭐
电脑不方便看,那就把发到你📲

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值