结点: 包含一个数据元素及若干指向子树的分支
结点的度:含有孩子结点的个数
结点的层次:由根结点开始,根结点层次为 0
树的深度:由根结点开始,根结点深度为 0
树的高度:由叶子结点向根结点,取最大值,叶子结点高度为 0
叶子结点(终端结点):没有孩子结点
分支结点(非终端结点):有孩子结点
根结点:没有父亲结点
子树:一个分支结点及其后辈组成子树
父亲结点、孩子结点、兄弟结点
前辈、后辈
链表是特殊的树,树是特殊的图
一个树满足:
- 每个结点的孩子结点数不大于 2
- 每个结点的孩子次序不能任意颠倒
则称为二叉树,其左边孩子结点称为左孩子,右边孩子结点称为右孩子
除了叶结点外每一个结点都有左右子叶且叶结点都处在最底层的二叉树,即每一层结点树都达到最大
树的左右子树的高度差不超过1的数,空树也是平衡二叉树的一种
具有下列性质的二叉树
public class TreeNode {
TreeNode left;
TreeNode right;
Object value;
}
前序、中序、后序遍历每个结点最多被访问两次,时间复杂度为 O(n)
层序遍历每个结点被访问一次,时间复杂度为 O(n)
前序遍历
public List<Integer> preOrder( ) {
List<Integer> values = new ArrayList<>( );
values.add((int) value);
if (left != null) values.addAll(left.preOrder( ));
if (right != null) values.addAll(right.preOrder( ));
return values;
}
中序
public List<Integer> inOrder( ) {
List<Integer> values = new ArrayList<>( );
if (left != null) values.addAll(left.inOrder( ));
values.add((int) value);
if (right != null) values.addAll(right.inOrder( ));
return values;
}
后序遍历
public List<Integer> postOrder( ) {
List<Integer> values = new ArrayList<>( );
if (left != null) values.addAll(left.postOrder( ));
if (right != null) values.addAll(right.postOrder( ));
values.add((int) value);
return values;
}
层序遍历
public List<TreeNode> layerOrder( ) {
List<TreeNode> values = new ArrayList<>( );
values.add(this);
for (int i = 0; i < values.size( ); i++) {
TreeNode node = values.get(i);
if (node.left != null) values.add(node.left);
if (node.right != null) values.add(node.right);
}
return values;
}
public int nodeNum( ) {
int count = 0;
if (value != null) count = 1;
if (left != null) {
count += left.nodeNum( );
}
if (right != null) {
count += right.nodeNum( );
}
return count;
}
public List leafNode( ) {// 前序
List<Integer> values = new ArrayList<>( );
if (left == null && right == null) values.add((int) value);
if (left != null) values.addAll(left.leafNode( ));
if (right != null) values.addAll(right.leafNode( ));
return values;
}
public int leafNodeNum( ) {// 前序
int count = 0;
if (left == null && right == null) count++;
if (left != null) count += left.leafNodeNum( );
if (right != null) count += right.leafNodeNum( );
return count;
}
public int getHeight( ) {
int l = 0, r = 0;
if (left != null) l = left.getHeight( );// 左子树高度
if (right != null) r = right.getHeight( );// 右子树高度
return (Math.max(l, r));
}
横向打印
public void printTree(int layer) {
if (right != null) right.printTree(layer + 1);
for (int i = 0; i < layer; i++)
System.out.print("-|-");
System.out.println(value);
if (left != null) left.printTree(layer + 1);
}
也叫二叉排序树、二叉查找树
二叉搜索树,要么是一颗空树,要么具有以下性质
中序遍历得到有序数列
public class TreeNode {
TreeNode left;
TreeNode right;
Object value;// 默认 null
}
判断二叉树是不是二叉搜索树
中序遍历,判断是否得到有序数列
public boolean isValid( ) {
List<Integer> list = inOrder( );// 中序遍历结果
for (int i = 0; i < list.size( ) - 1; i++) {
if (list.get(i) >= list.get(i + 1)) return false;
}
return true;
}
递归判断大小关系是否合理
public boolean isValid( ) {
boolean l = true, r = true;
if (left != null && (int) value <= (int) left.value) return false;
if (right != null && (int) value >= (int) right.value) return false;
if (right != null) r = right.isValid( );
if (left != null) l = left.isValid( );
return r && l;
}
public void add(Object o) {
if (value == null) value = o;
else if ((int) value - (int) o > 0) {// 小的放左边
left = (null == left) ? new TreeNode( ) : left;
left.add(o);// 递归
} else if ((int) value - (int) o < 0) {// 大的放右边
right = (null == right) ? new TreeNode( ) : right;
right.add(o);// 递归
}
}
递归方式
public TreeNode find(int i) {
if (value == null) return null;
if ((int) value == i) return this;
if (left != null && i < (int) value) return left.find(i);
if (right != null && i > (int) value) return right.find(i);
return null;
}
循环方式
public TreeNode findByLoop(int i) {
TreeNode node = this;
while (node != null) {
if (i > (int) node.value) node = node.right;
else if (i < (int) node.value) node = node.left;
else return node;
}
return null;
}
public void delete(int i) {
TreeNode pre = null;// 父亲结点
TreeNode node = this;
while (node != null && (int) node.value != i) {
pre = node;
if (i > (int) node.value) node = node.right;
else if (i < (int) node.value) node = node.left;
}
if (node == null) return;// 没找到要删除的结点
if (pre != null) {
// 要删除的结点无子结点
if (node.right == null && node.left == null) {
if (pre.left == node) pre.left = null;
else pre.right = null;
return;
}
// 要删除的结点有一个子结点
if (node.right == null && node.left != null) {
if (pre.left == node) pre.left = node.left;
else pre.right = node.left;
return;
} else if (node.right != null && node.left == null) {
if (pre.left == node) pre.left = node.right;
else pre.right = node.right;
return;
}
// 要删除的结点有两个或以上的结点
if (node.right != null && node.left != null) {
TreeNode rightMin = node.right;
while (rightMin.left != null) rightMin = rightMin.left;
delete((int) rightMin.value);
if (pre.left == node) pre.left = rightMin;
else pre.right = rightMin;
rightMin.left = node.left;
rightMin.right = node.right;
return;
}
} else {
// 要删除的是根节点
TreeNode rightMin = node.right;
while (rightMin.left != null) rightMin = rightMin.left;
delete((int) rightMin.value);
this.value=rightMin.value;
return;
}
}
平衡二叉树的初衷是为了解决二叉搜索树动态更新导致的性能退化
红黑树是一种近似平衡的二叉树,可以让性能退化不会太严重
红黑树的高度
graph TB
root((黑))-->b11((黑))
root((黑))-->b12((黑))
b11((黑))-->r21((红))
b11((黑))-->r22((红))
b12((黑))-->b23((黑))
b12((黑))-->b24((黑))
r21((红))-->b31((黑))
r21((红))-->b32((黑))
r22((红))-->b33((黑))
r22((红))-->b34((黑))
Root((黑))-->B11((黑))
Root((黑))-->B12((黑))
B11((黑))-->B21((黑))
B11((黑))-->B22((黑))
B11((黑))-->B23((黑))
B11((黑))-->B24((黑))
B12((黑))-->B25((黑))
B12((黑))-->B26((黑))
红黑树高度近似为 $2long_2n$
红黑树的高度只比高度平衡的 AVL 树的高度 $log_2n$ 大了一倍
在性能上,下降得并不多,实际上红黑树的性能更好
查找、插入、删除时间复杂度都为 $O(logn)$, 很多编程语言中的 Map 类型都是通过红黑树来实现的
AVL 树是一种高度平衡的二叉树,查找效率非常高,但是为了维持高度平衡,每次插入、删除都要做调整,比较耗时。对于有频繁的插入、删除操作的数据集合,不适合使用 AVL 树