- 树的概念
- 二叉树 - Binary Tree 是每个节点最多只有两个分支(即不存在分支度大于 2 的节点)的树结构。">二叉树 - Binary Tree 是每个节点最多只有两个分支(即不存在分支度大于 2 的节点)的树结构。
- 分类
">分类 - 遍历
- 树的判定
树的概念
节点:根节点、父节点、子节点、兄弟节点
空树:一棵树可以没有任何节点
一棵树可以只有1个节点,也就是只有根节点
子树:左子树、右子树
节点的度(degree):子树的个数
树的度:所有节点度中的最大值
叶子节点(leaf):度为 0 的节点
非叶子节点:度不为 0 的节点
层数(level):根节点在第1层,根节点的子节点在第2层,以此类推(有些教程也从第0层开始计算)
节点的深度(depth):从根节点到当前节点的唯一路径上的节点总数
节点的高度(height):从当前节点到最远叶子节点的路径上的节点总数
树的深度:所有节点深度中的最大值
树的高度:所有节点高度中的最大值
树的深度等于树的高度
有序树,无序树,森林
有序树
- 树中任意节点的子节点之间有顺序关系
无序树
- 树中任意节点的子节点之间没有顺序关系,也称为“自由树”
森林
- 由m(m≥0)棵互不相交的树组成的集合**
二叉树 - Binary Tree 是每个节点最多只有两个分支(即不存在分支度大于 2 的节点)的树结构。
性质
- 非空二叉树第 i 层最多 2 个结点 (i >= 1)
- 深度为 k 的二叉树最多 2 - 1 个结点 (k >= 1)
- 在任意一棵二叉树中,度为0的结点(即叶子结点)总比度为2(有两个子节点)的结点多一个
- 具有n个节点的二叉树,其深度至少为log2n+1,其中,log2n表示取log2n的整数部分
- 对于含 n 个结点的完全二叉树中编号为 i (1 <= i <= n) 的结点
- 满二叉树
- 完全二叉树(堆)
- 大顶堆:根 >= 左 && 根 >= 右
- 小顶堆:根 <= 左 && 根 <= 右
- 二叉查找树(二叉排序树):左 < 根 < 右
- 平衡二叉树 : 左子树树高 - 右子树树高 | <= 1
- (AVL树)
- 红黑树
- SBT
最小失衡树:平衡二叉树插入新结点导致失衡的子树:调整:
假设满二叉树的高度为
(
),那么
- 第
层的节点数量为:
- 叶子节点数量为:
- 总节点数量
,
,
,
的底为2.
- 在同样高度的二叉树中,满二叉树的叶子节点数量最多,总节点数量最多
-
完全二叉树(Complete Binary Tree)
概念:叶子节点只会出现最后 2 层,且最后 1 层的叶子结点都靠左对齐
完全二叉树与满二叉树是很相似的,所以也可以这么定义,完全二叉树:对节点从上至下、左至右开始编号,其所有编号都能与相同高度的满二叉树中的编号对应
小结论:1、完全二叉树从根结点至倒数第2层是一棵满二叉树,2、满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树
性质: 度为 1 的节点只有左子树,度为1的节点要么是 1 个,要么是 0 个
- 同样节点数量的二叉树,完全二叉树的高度最小(从上往下,从左往右满排布)
- 假设完全二叉树的高度为h(h ≥ 1),那么有:
- 至少有2h−12^{h−1}2h−1个节点, 202^020 + 212^121 + 222^222 + … + 2h−22^{h-2}2h−2 + 1
- 最多有2h−12^h−12h−1个节点( 满二叉树 ), 202^020 + 212^121 + 222^222 + … + 2h−12^{h-1}2h−1
- 假设总节点数量为 n
- 2h−12^{h−1}2h−1 <= 2h2^h2h
- h−1h-1h−1 <= log2n\log_2nlog2n < hhh
- hhh = foor (log2n)+1(\log_2n) + 1(log2n)+1
floor
是向下取整,另外,ceiling
是向上取整
-
平衡二叉树(AVL树)
性质
| 左子树树高 - 右子树树高 | <= 1
- 平衡二叉树必定是二叉搜索树,反之则不一定
- 最小二叉平衡树的节点的公式:
F(n)=F(n-1)+F(n-2)+1
(1 是根节点,F(n-1) 是左子树的节点数量,F(n-2) 是右子树的节点数量)
最小失衡树
平衡二叉树插入新结点导致失衡的子树
调整:
- LL 型:根的左孩子右旋
- RR 型:根的右孩子左旋
- LR 型:根的左孩子左旋,再右旋
- RL 型:右孩子的左子树,先右旋,再左旋
遍历
- 前序遍历: 首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树
- 中序遍历: 首先遍历左子树,然后访问根结点,最后遍历右子树。在遍历左、右子树时,仍然先遍历左子树,再访问根结点,最后遍历右子树
后序遍历: 首先遍历左子树,然后遍历右子树,最后访问根结点。在遍历左、右子树时,仍然先遍历左子树,然后遍历右子树,最后遍历根结点
深度优先搜索: 顾名思义,查找时深度优先,从根结点访问最远的结点直到找到所有节点。前序,中序和后序遍历都是深度优先遍历的特例,可以借助栈实现,也可以递归实现
- 广度优先搜索: 广度优先遍历会先访问离根节点最近的节点,二叉树的广度优先遍历又称按层次遍历。算法借助队列实现
https://blog.csdn.net/Monster_ii/article/details/82115772 非递归遍历图解
public void preorder() {
if (root == null) {
return;
}
Stack<Node<E>> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
Node<E> node = stack.pop();
System.out.print(node.element + " ");
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
}
public void inorder() {
if (root == null) {
return;
}
Stack<Node<E>> stack = new Stack<>();
Node<E> node = root;
while (node != null || !stack.isEmpty()) {
while (node != null) {
stack.push(node);
node = node.left;
}
node = stack.pop();
System.out.print(node.element + " ");
node = node.right;
}
}
public void postorder() {
Node<E> node = this.root;
Stack<Node<E>> stack = new Stack<>();
Node<E> lastVisited = null;
while (node != null || !stack.isEmpty()) {
while (node != null) {
stack.push(node);
node = node.left;
}
node = stack.pop();
if (node.right == null || node.right == lastVisited) {
System.out.print(node.element + " ");
lastVisited = node;
node = null;
}else {
stack.push(node);
node = node.right;
}
}
}
//广度优先遍历
private static void bfs(BinaryTree.Node tn) {
if (tn == null) {
return;
}
Queue<BinaryTree.Node> queue = new LinkedList<>();
queue.offer(tn);
int ans = 0;
while (!queue.isEmpty()) {
BinaryTree.Node treeNode = queue.poll();
System.out.print(treeNode.data + "\t");
if (treeNode.leftNode != null) {
queue.offer(treeNode.leftNode);
}
if (treeNode.rightNode != null) {
queue.offer(treeNode.rightNode);
}
}
}
树的判定
完全二叉树的判定
实现步骤:/**
* 判断是否为完全二叉树 —— (层序遍历)
* @return
*/
public boolean isComplete() {
if (root == null) return false;
Queue<Node<E>> queue = new LinkedList<>();
queue.offer(root);
boolean leaf = false;
while (!queue.isEmpty()) {
Node<E> node = queue.poll();
if (leaf && !node.isLeaf()) return false;
if (node.left != null) {
queue.offer(node.left);
} else if (node.right != null) {
//相当于node.left == null && node.right != null
return false;
}
if (node.right != null) {
queue.offer(node.right);
} else {
//node.left == null && node.right == null
//node.left != null && node.right == null
// 后面遍历的节点都必须是叶子节点
leaf = true;
}
}
return true;
}
真二叉树的判定
实现步骤:
1、利用队列先进先出的特性
2、利用真二叉树的节点,要么度为0,要么度为2的特点
3、在层序遍历的时候,判断每层的节点数量,如果levelSize % 2 != 0
,返回flase
4、结合上面层序遍历的图解,就会发现,每一层的节点遍历完后,队列中的节点数量size
等于下一层的节点数量,而第一层只有根节点 ``` /**- 判断是否为真二叉树 —— (层序遍历)
- @return
*/
public boolean isProper (){
if (root == null) return false;
// 存储着每一层的元素数量
int levelSize = 1;
Queue
> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) {
} return true; }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) {
//每一层访问完后,下一层的节点个数是队列的size
levelSize = queue.size();
if (levelSize % 2 != 0) return false;
}
<a name="BMf8p"></a>
## 树的高度
树的高度实际上就是树的层数,与上面的判定真二叉树很接近,只需要在设置一个`height`,在遍历完每一层的时候,`height++`,结束遍历后,返回的就是树的高度<br />树的高度实际上所有子节点的高度中最大的一个,然后再 + 1,这样的思路,很容易以递归的方式实现,所以计算树的高度,有递归和迭代两种方法,这里贴出递归的方法,因为迭代的方法就是上面判定二叉树的方法做点小改动,就不贴出来了,需要的话,自行下载源码。
/**
- 计算树的高度
- @return / public int height(){ //递归法 return heightByRecursive(root); //迭代法 //return heightByIteration(); } /*
- (递归法)获取传入节点的高度
- @param node
- @return
*/
private int heightByRecursive(Node
node){ if (node == null) return 0; return 1 + Math.max(heightByRecursive(node.left),heightByRecursive(node.right)); }
<a name="fRRrD"></a>
## 前驱与后继
<a name="Xd4xt"></a>
### 寻找前驱节点
<br />根据上面的判定条件给出实现代码:
/**
- 获取传入节点的前驱节点
- @param node
- @return
*/
protected Node
predecessor(Node node) { if (node == null) return null; // 前驱节点在左子树当中(left.right.right.right….) Node p = node.left; if (p != null) {
} // 从父节点、祖父节点中寻找前驱节点 while (node.parent != null && node == node.parent.left) {while (p.right != null) {
p = p.right;
}
return p;
} // node.parent == null // node == node.parent.right return node.parent; }node = node.parent;
<a name="8qe70"></a>
### 寻找后继节点
<br />根据上面的判定条件给出实现代码:
/**
* 获取传入节点的后继节点
*
* @param node
* @return
*/
protected Node<E> successor(Node<E> node) {
if (node == null) {
return null;
}
// 后继节点在右子树当中(right.left.left.left....)
Node<E> p = node.right;
if (p != null) {
while (p.left != null) {
p = p.left;
}
return p;
}
// 从父节点、祖父节点中寻找后继节点
while (node.parent != null && node == node.parent.right) {
node = node.parent;
}
return node.parent;
}
``
找出前驱或者后继节点有什么用,不知道你有没有看到方法的访问修饰符:
protected,实际上着两个方法都不是给用户调用的,正如我们的疑惑一样,用户不知道怎么用,用来干嘛,实际上这是为继承二叉树
BinaryTree`类的子类所使用的,其作用是在删除度为 2 的时,将找到的前驱或者后继节点用来替代的