持续学习&持续更新中…
学习态度:脚踏实地
数据结构-二叉树
树形结构
以文件夹的分类为例,如果我们将各种不同类型的文件按照其类型分别放在不同的文件夹中,可以大大提高我们寻找文件的速度。
树形结构无论是考试还是面试都很重要。
以后的课程都离不开树形结构。
树的基本概念01
节点
- 节点:图示中的每一个圆圈(元素)都是节点。
- 根节点:图示中的圆圈1就是根节点,一棵树最多只有一个根节点。
- 父节点:圆圈1就是圆圈2、3、4、5、6的父节点;圆圈2就是圆圈21和圆圈22的父节点。
- 子节点:圆圈2、3、4、5、6就是圆圈1的子节点;圆圈21和圆圈22就是圆圈2的子节点。
- 兄弟节点:同一个节点下的所有圆圈(元素)都是兄弟节点,例如圆圈2、3、4、5、6都是兄弟节点。也就是说,兄弟节点拥有相同的父节点。
子树
图示子树:
左右子树:
上图中,红笔圈出来的部分就分别是圆圈2的左右子树。
树的基本概念02
层数
节点的深度
圆圈31的深度是3;
圆圈2的深度是2;
节点的高度
有序树、无序树、森林
该课程不学习森林相关知识
二叉树的基本概念
二叉树是有序树
-
这里的有序是指区分左右子树:左子树和右子树不是同一颗树。
-
二叉树的特点:二叉树是(严格)有序的;二叉树是(严格)区分左右子树的。
二叉树的几种形态
二叉树的性质
二叉树的性质—演算
声明:
- 令二叉树的边数为T
- 节点总数为n
- 度为0(叶子结点)的节点个数为n0,度为1的节点个数为n1,度为2的节点个数为n2
边数的计算方式一:
-
节点的边数和节点的度数相同:
- 也就是说:
- 如果某个节点的度为2那么这个节点就有两条边
- 如果某个节点的度为1那么这个节点就有一条边
- 如果某个节点的度为0(叶子结点)那么这个节点就没有边
-
得到:T = n1 + 2 * n2
边数的计算方式二:
-
如果反过来思考一下节点的边数,会有新的发现:除了根节点以外的每一个节点,其头顶都会有一条边。
-
得到:T = n - 1 = n0 + n1 + n2 - 1
那么可以有如下演算:
- n1 + 2 * n2 = n0 + n1 + n2 - 1
- 2 * n2 = n0 + n2 - 1
- n2 = n0 - 1
真二叉树
满二叉树
完全二叉树
节点从上往下、从左到右排布的二叉树就是完全二叉树。如果排满了,就是满二叉树。
如果对完全二叉树从上往下、从左到右,按照顺序开始编号的话,这个完全二叉树的编号都会和它对应的满二叉树的编号完全一致。
完全二叉树的性质01
n和h关系的推导
下面的公式是如何得出的?
-
总结点数量:n
-
完全二叉树高度:h
h - 1 <= log2n < h
h = floor(log2n) + 1
分析:
-
首先,h肯定是一个整数(树的高度)。n是总结点数量。
-
log2n求出来不一定是一个整数,有可能是一个浮点数。
当log2n求出来是浮点数的时候
-
假设当log2n求出来是4.8:
-
要满足
h - 1 <= log2n < h
,那么此时,h求出来一定是5; -
假设当log2n求出来是7.4的时候,此时h求出来一定是8。
-
由此可以推出:
h = log2n向上取整
或者h = log2n向下取整 + 1
当log2n求出来是整数的时候
-
假设当log2n求出来是5:
-
要满足
h - 1 <= log2n < h
,那么此时,h求出来一定是6; -
假设当log2n求出来是7的时候,此时h求出来一定是8。
-
由此可以推出:
h = log2n + 1
n和h的关系推导总结
-
上述推导出了三个公式:
h = log2n向上取整
(log2n为浮点数)h = log2n向下取整 + 1
(log2n为浮点数)h = log2n + 1
(log2n为整数)
-
log2n为整数时:
h = log2n + 1
-
而log2n为浮点数时,有两个公式,很明显,我们应该使用公式2,因为公式2和公式3(log2n为整数时)都有共同的+1操作。
-
又由于(在Java、C中)整数向下取整还是该整数,所以我们可以最终使用公式2作为n与h的关系公式。
完全二叉树的性质02
对节点从0开始编号(常用)
对节点从1开始编号
完全二叉树—面试题
面试题:求完全二叉树叶子结点的个数
推导n0的总结公式:
非叶子结点的推导过程和n0的推导过程类似,这里只推导一下n0总结的公式。
向上取整和向下取整一般来说是可以互相切换的,但是编程中默认就是向下取整,因此一般都使用向下取整。
二叉树的遍历
前序遍历:先遍历root,后遍历left、right。(root、left、right)
中序遍历:先遍历left,中间遍历root,最后遍历right。(left、root、right)
后序遍历:先遍历left,然后遍历right,最后遍历root。(left、right、root)
前序遍历
中序遍历
后序遍历
层序遍历
层序遍历代码:
// 层序遍历
public void leverOrderTraversal(BinarySearchTree.Visitor<E> visitor) {
traversalCheck(visitor);
final java.util.Queue<BinarySearchTree.Node<E>> queue = new LinkedList<>();
queue.offer(root);
BinarySearchTree.Node<E> node;
while (!queue.isEmpty()) {
node = queue.poll();
visitor.visit(node.element);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
System.out.println();
}
前中后序遍历—递归实现
前序遍历
// 前序遍历
public void preorderTraversal(BinarySearchTree.Visitor<E> visitor) {
preorderTraversal(root, visitor);
System.out.println();
}
private void preorderTraversal(BinarySearchTree.Node<E> node, BinarySearchTree.Visitor<E> visitor) {
if (node == null || visitor == null) return;
visitor.visit(node.element);
preorderTraversal(node.left, visitor);
preorderTraversal(node.right, visitor);
}
中序遍历
// 中序遍历
public void inorderTraversal(BinarySearchTree.Visitor<E> visitor) {
inorderTraversal(root, visitor);
System.out.println();
}
private void inorderTraversal(BinarySearchTree.Node<E> node, BinarySearchTree.Visitor<E> visitor) {
if (node == null) return;
inorderTraversal(node.left, visitor);
visitor.visit(node.element);
inorderTraversal(node.right, visitor);
}
后序遍历
// 后序遍历
public void postorderTraversal(BinarySearchTree.Visitor<E> visitor) {
postorderTraversal(root, visitor);
System.out.println();
}
private void postorderTraversal(BinarySearchTree.Node<E> node, BinarySearchTree.Visitor<E> visitor) {
if (node == null) return;
postorderTraversal(node.left, visitor);
postorderTraversal(node.right, visitor);
visitor.visit(node.element);
}
前中后序遍历—非递归实现
前序遍历
方法一:
// 非递归前序遍历 (方法一实现)
public void preorderTraversal(BinarySearchTree.Visitor<E> visitor) {
if (visitor == null) return;
Stack<Node<E>> stack = new LinkedListStack<>();
Node<E> node = root;
while (true) {
if (node != null) {
visitor.visit(node.element);
// 如果有右子节点,就将右子节点入栈
if (node.right != null) stack.push(node.right);
node = node.left; // 一路往左走
} else {
// node为null时,准备访问先前节点的右子节点
if (!stack.isEmpty()) {
// 让拿出的节点继续执行 node != null 时的操作
node = stack.pop();
} else {
return;
}
}
}
}
方法二:
// 非递归前序遍历 (方法二实现)
public void preorderTraversal(BinarySearchTree.Visitor<E> visitor) {
if (visitor == null || root == null) return;
Stack<Node<E>> stack = new LinkedListStack<>();
stack.push(root);
while (!stack.isEmpty()) {
Node<E> node = stack.pop();
visitor.visit(node.element);
if (node.right != null) stack.push(node.right);
if (node.left != null) stack.push(node.left);
}
}
中序遍历
// 非递归中序遍历
public void inorderTraversal(BinarySearchTree.Visitor<E> visitor) {
if (null == visitor) return;
Stack<Node<E>> stack = new LinkedListStack<>();
Node<E> node = root;
while (true) {
if (node != null) {
stack.push(node);
node = node.left;
} else {
if (!stack.isEmpty()) {
node = stack.pop();
visitor.visit(node.element);
node = node.right;
} else {
return;
}
}
}
}
后序遍历
// 非递归后序遍历
public void postorderTraversal(BinarySearchTree.Visitor<E> visitor) {
if (null == visitor || root == null) return;
Node<E> prev = null;
Stack<Node<E>> stack = new LinkedListStack<>();
stack.push(root);
while (!stack.isEmpty()) {
Node<E> node = stack.top();
if (node.isLeaf() || (prev != null && prev.parent == node)) {
Node<E> leaf = stack.pop();
visitor.visit(leaf.element);
prev = leaf;
} else {
if (node.right != null) stack.push(node.right);
if (node.left != null) stack.push(node.left);
}
}
}
前驱结点(predecessor)
// 寻找前驱节点
protected Node<E> predecessor(Node<E> node) {
if (node == null) return null;
Node<E> p = node.left;
if (p != null) {
while (p.right != null) {
p = p.right;
}
return p;
}
while (node.parent != null && node.parent.right != node) {
node = node.parent;
}
return node.parent;
}
后继节点(successor)
// 后继节点
protected Node<E> successor(Node<E> node) {
if (node == null) return null;
Node<E> s = node.right;
if (s != null) {
while (s.left != null) {
s = s.left;
}
return s;
}
while (node.parent != null && node != node.parent.left) {
node = node.parent;
}
return node.parent;
}
遍历的应用
根据遍历结果重构二叉树
图示:前序+中序重构二叉树(后序+中序同理):
计算二叉树的高度
非递归实现:
public int height() {
if (root == null) return 0;
final Queue<Node<E>> queue = new LinkedList<>();
queue.offer(root);
int height = 0;
int levelSize = 1;
while (!queue.isEmpty()) {
Node<E> node = queue.poll();
levelSize--;
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
if (levelSize == 0) {
levelSize = queue.size();
height++;
}
}
return height;
}
递归实现:
递归实现高度计算
public int height() {
return height(root);
}
private int height(Node<E> node) {
if (node == null) return 0;
return Math.max(height(node.left), height(node.right)) + 1;
}
判断一棵树是否是完全二叉树
public boolean isComplete() {
if (root == null) return true;
boolean leaf = false;
final Queue<Node<E>> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
Node<E> node = queue.poll();
if (leaf) { // 要求后面的都是叶子结点
if (!node.isLeaf()) return false;
continue;
}
if (node.hasTwoChildren()) {
queue.offer(node.left);
queue.offer(node.right);
} else if (node.left == null && node.right != null) {
return false;
} else {
// left != null right == null
// left == null right == null
leaf = true;
if (node.left != null) queue.offer(node.left);
}
}
return true;
}
国外二叉树的叫法
向上取整、向下取整
向下取整就是指省略小数部分的意思。(只取整数部分)
向上取整就是指取大于该小数的最小整数。(整数部分 + 1)
参考
小码哥李明杰老师课程: 恋上数据结构与算法 第一季.
小码哥李明杰老师博客: M了个J.
本文完,感谢您的关注支持!