文章目录
树
如下图:
基本概念
A 为 B 父节点, B为A的子节点
B,C,D父节点为同一个,他们成为兄弟节点
根节点:没有父节点的节点,比如E
叶子节点(叶节点):没有子节点的节点,比如GHIJ
高度
高度,从下往上看,比如第一层楼高,第二层楼高。计数从0开始。
深度
深度,从上往下看,比如水的深度。计数从0开始。
层
层,与深度类似,只不过从1开始。
二叉树(Binary Tree)
每个节点,最多有两个”叉“,也就是两个子节点,分别是左子节点和右子节点。并不要求都有两个子节点,可以只有一个左子节点或者只有一个右子节点。
满二叉树
上图编号2中,除叶子节点外,每个节点都有左右两个子节点,这种树叫满二叉树。
完全二叉树
上图编号3中,有以下三个特点:
-
叶子节点都在最底下两层
-
最后一层的叶子节点都靠左排列(如果遇到一个结点,左孩子不为空,右孩子为空;或者左右孩子都为空;则该节点之后的队列中的结点都为叶子节点;该树才是完全二叉树,否则就不是完全二叉树)
-
除了最后一层,其他层的节点个数都要达到最大
存储二叉树
链式存储: 基于链表
顺序存储:基于数组
根节点放于下标i=1的位置,那么左子节点位置为2*i
, 右子节点位置为2*i
+1,
反过来,节点为i, 则,父节点的位置为 i/2
, 完全二叉树,为便于理解,一般从下标为1开始,浪费了
一个而空间。
而,非完全二叉树,浪费的空则较多:
比较
- 链式储存法,需要保存左右两个子节点的指针位置,多一些空间
- 顺序存储,如果不是完全二叉树,会浪费角度的空间
- 数组与链表的区别:
- 数组对cpu友好
- 数组随机访问快,空间占用少,但是需要连续内存
二叉树的遍历
为什么分三种遍历?个人思考是,有些数据存储是有序的,比如平衡二叉树,通过中序遍历,就能得到从小到大的有序数据。
前序遍历
概念
根节点 -> 左子节点 -> 右子节点
代码
采用递归: 递归三条件:
-
问题可分解为子问题
-
子问题的求解方法与父问题一致,只是数据规模不同而已
-
存在终止条件
写出递归代码步骤:
- 大问题化为小问题,屏蔽嵌套逻辑,从某一层开始
- 写出递推公式
- 找到终止条件
- 翻译成代码
/** * a * b c * d e f g * 前序遍历: m -> l -> r. * 递推公式:print(r) -> printOrder(r.left) -> printOrder(r.right) * 终止条件:节点为叶子节点(getLeft == null && getRight == null). 只打印自己. * a->b->d->e->c->f->g * * 复杂度:一个节点,最多两次(第一次遍历算一次;递归回来,算一次) * 最好:O(n) * 最坏:O(n) * 平均:O(n) * @param root */ public static void before(Node root) { if(root == null) { return; } System.out.print(root); before(root.getLeft()); before(root.getRight()); // or // if(root != null) { // System.out.print(root); // } // // if(root.getLeft() == null && root.getRight() == null) { // return; // } // // before(root.getLeft()); // before(root.getRight()); } @Data @NoArgsConstructor @AllArgsConstructor private static class Node { private String data; private Node left; private Node right; public Node(String data) { this.data = data; } @Override public String toString() { return data + "->"; } }
中序遍历
概念
左子节点 -> 根节点 -> 右子节点
代码
/**
* a
* b c
* d e f g
* 中序遍历: l -> m -> r.
* 递推公式:printOrder(r.left) -> print(r)-> printOrder(r.right)
* 终止条件:节点为叶子节点(getLeft == null && getRight == null). 只打印自己.
* d->b->e->a->f->c->g
*
* 复杂度:一个节点,最多两次(第一次遍历算一次;递归回来,算一次)
* 最好:O(n)
* 最坏:O(n)
* 平均:O(n)
* @param root
*/
public static void middle(Node root) {
if (root == null) {
return;
}
middle(root.getLeft());
System.out.print(root);
middle(root.getRight());
}
后序遍历
概念
左子节点 -> 右子节点 -> 根节点
代码
/**
* a
* b c
* d e f g
* 后序遍历: l -> r -> m.
* 递推公式:printOrder(r.left) -> printOrder(r.right) -> print(r)
* 终止条件:节点为叶子节点(getLeft == null && getRight == null). 只打印自己.
* d->e->b->f->g->c->a
*
* 复杂度:一个节点,最多两次(第一次遍历算一次;递归回来,算一次)
* 最好:O(n)
* 最坏:O(n)
* 平均:O(n)
* @param root
*/
public static void after(Node root) {
if (root == null) {
return;
}
after(root.getLeft());
after(root.getRight());
System.out.print(root);
}