目录
创建二叉树
实现思路
想要创建二叉树,首先就要有一个理论基础。
二叉树(binary tree)可以理解为子节点最多为2的树,也就是说每个节点(node)的度(degree)都不大于2。什么是度呢?度指的是一个节点拥有子树的数目。如下图,A点的度为2,因为它有两个节点B,C,同时,A也被称为根节点,因为该节点上面再没有点了。另外,我们可以看到,这棵二叉树有4层节点,换句话说,该树的深度为4。
那节点包含什么呢?节点包含了数据项以及指向子树节点的指针。我们应该注意到了H,I,L等在最后一层的节点,它们没有子节点。像这种度为0的节点,我们称之为叶节点(leaf)。
二叉树没有规定每个节点的度必须是二,于是这里就衍生出了几种二叉树。
满二叉树
第一种是满二叉树。如上图所示,满二叉树指的是度只有0或2的树,而且度为0的节点在同一层内。其实就是除了最后一层没有任何子节点外,每一层的节点都有两个子节点。那这满二叉树的总节点又是多少呢?让我们观察一下。深度为2的满二叉树总节点为3;深度为3的总节点是7;深度为4的总节点是15……想必大家都已经发现规律了,对于一个深度为h的满二叉树,其总节点数=2^h-1。由此我们也不难推出,满二叉树中每一层的节点数=2^(h-1)。
完全二叉树
第二种是完全二叉树。上图(图2)就是一个完全二叉树。完全二叉树由满二叉树衍生而来。若一个二叉树有h层,从1~h-1层的所有节点数都达到最大个数,且第 h 层所有的结点都连续集中在最左边,就是完全二叉树。比如回到图1,我们砍掉O点后它就变成了个完全二叉树。什么?还想砍?我们需要按照这个顺序砍:O->N->M……如果你砍完O后就心急火燎的把M砍掉了,这就不是一个完全二叉树了,因为M比N更靠左,要先砍N才能砍M。
![76d46929194fe7e24fdfe5e0b02a7f5a.png](https://i-blog.csdnimg.cn/blog_migrate/76d46929194fe7e24fdfe5e0b02a7f5a.png)
退化二叉树
第三种是退化二叉树。如上图,退化二叉树十分显著的特征就是:1.该树的度只有0或1; 2.要不所有节点都处于左侧,要不所有节点都处于右侧; 3.长相与链表神似。
平衡二叉树
注:上图来源于知乎文章:什么是平衡二叉树(AVL)
平衡二叉树(AVL)。上图就是一个平衡二叉树(图内的数字是每个节点内储存的数据)。平衡二叉树的左子树和右子树的高度之差的绝对值不超过1且它的左子树和右子树都是一颗平衡二叉树。
搜索二叉树 - 斐波那契数列
二叉搜查树。上述树的类型针对的是树的整体结构,而二叉搜查树针对的是二叉树节点的值的分配。在二叉搜查树中,每一个节点的左子节点都小于等于该节点的值,而右子节点大于该节点的值。搜查树,顾名思义,方便搜查。如果大家了解过二分查找(binary search)和线性查找( linear search),就会发现,这里二叉搜查树对值的分配就很符合二分查找的原理,而退化二叉树就对应这线性查找。毫无疑问,二叉搜查树在支持搜索节点值这方面是极具优越性的。我们一会初始化的也是二叉搜查树。
二叉树:数组/列表储存 - 节点对应索引
二叉树的储存形式。二叉树有两种储存方式。我们代码演示的是链表储存,实现起来相对直接且简单。其实数组储存也是比较主流的一种储存方式。大家可能会比较疑惑,毕竟一个节点只包含指针和数据项,怎么转换成有索引的数组元素呢?我们可以给每个节点标上索引。如果节点的索引是i,左子树就是i*2+1,右子树则是i*2+2。听起来比较抽象,我们看上图理解一下:(节点处标的是索引)
代码展示
package advance.dataStructure;
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {
}
TreeNode(int val) {//overload
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
};
//test
public class Main{
public static void main(String[] args) {
TreeNode node=new TreeNode(4);
System.out.println(node.left);
}
}
package advance.dataStructure;
import java.util.ArrayList;
import java.util.List;
import advance.algorithm.sortArray.Select;
public class BinaryTree_init {
public TreeNode root = null;
public BinaryTree_init(int data) {
this.root = new TreeNode(data);
}
public void addVal(int data) {//balanced binary tree
TreeNode node = new TreeNode(data);
TreeNode temp = this.root;
while (true) {
if (temp.val >= data) {
if (temp.left != null) {
temp = temp.left;
} else {
temp.left = node;
break;
}
} else {
if (temp.right != null) {
temp = temp.right;
} else {
temp.right = node;
break;
}
}
}
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();//创建列表对象
int[] arr = { 1, 5, 9, 7, 4, 2, 3 };
arr = Select.select(arr);
//将数组里的元素排序好后全部添加到列表中来
for (int e : arr)
list.add(e);
int mid = Math.round(arr.length / 2);
list.remove(mid);//删除中位数,因为该数要作根节点的值
//初始化二叉树并赋值
BinaryTree_init bt = new BinaryTree_init(arr[mid]);
for (int e : list)
bt.addVal(e);
//遍历二叉树
ErgordicBinaryTree obj = new ErgordicBinaryTree();
List<Integer> lst = obj.order(bt.root, 1);
for (int e : lst)
System.out.println(e);
}
}
代码讲解
treeNode这个语法和链表的listNode十分相似,除了treeNode有两个“next”,其他性质和listNode都差不多。另外,addVal()用的是链式存储,前序遍历。得到的树还是一个二叉树。
遍历二叉树
实现思路
遍历二叉树有三种主要实现思路,其中两种是迭代法。我们今天讲的思路是递归法,是这三种方法中最简单也最容易理解的。遍历有分为前序遍历,中序遍历和后序遍历。这三者有什么不同呢?我们可以看下图理解。(图中标的是每种遍历取节点数据项的顺序)
![cfa1a2b4ec7637eca67c87f5ef566997.jpeg](https://i-blog.csdnimg.cn/blog_migrate/cfa1a2b4ec7637eca67c87f5ef566997.jpeg)
前序遍历
前序遍历大家看了上图理解的估计也七七八八了,我们发现,前序遍历的顺序刚好就是平衡二叉树从小到大的节点值。
![15f6316d03fbe0dcfc542248536ad588.jpeg](https://i-blog.csdnimg.cn/blog_migrate/15f6316d03fbe0dcfc542248536ad588.jpeg)
中序遍历
中序遍历
![4f44b148a44b85bae78e2bc2f7d57a5f.jpeg](https://i-blog.csdnimg.cn/blog_migrate/4f44b148a44b85bae78e2bc2f7d57a5f.jpeg)
后序遍历
后序遍历在处理逆波兰表达式上很有效果。如果把第三层的节点值算作数,第二层节点算作加减而根节点为乘除的话,按照这个顺序遍历倒也是很方便
代码展示
package advance.dataStructure;
import java.util.ArrayList;
import java.util.List;
public class ErgordicBinaryTree {
public List<Integer> order(TreeNode root, int num) {
List<Integer> bt = new ArrayList<>();
switch (num) {
case 0:
preorder(root, bt);
break;
case 1:
inorder(root, bt);
break;
case 2:
postorder(root, bt);
break;
default:
break;
}
return bt;
}
private void preorder(TreeNode root, List<Integer> R) {
if (root == null) {
return;
}
R.add(root.val);
preorder(root.left, R);
preorder(root.right, R);
}
private void inorder(TreeNode root, List<Integer> R) {
if (root == null) {
return;
}
inorder(root.left, R);
R.add(root.val);
inorder(root.right, R);
}
private void postorder(TreeNode root, List<Integer> R) {
if (root == null) {
return;
}
postorder(root.left, R);
postorder(root.right, R);
R.add(root.val);
}
}
代码讲解