一、树的基本概念
树是一种非线性结构,它是由n个有限结点组成一个具有层次关系的集合。
结点的度:一个结点含有子树的个数称为该结点的度。
树的度:一棵树中,所有结点度的最大值称为树的度。
根结点:一棵树中,没有双亲结点的结点。
结点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推。
树的高度或深度:树中结点的最大层次。
树通常的表示形式:双亲表示法、孩子表示法、孩子双亲表示法、孩子兄弟表示法等,较为常用的是孩子兄弟表示法:
class TreeNode {
int value; // 树中存储的数据
TreeNode firstChild; // 第一个孩子引用
TreeNode nextBrother; // 下一个兄弟引用
}
孩子表示法:
class TreeNode {
int val; // 数据域
TreeNode left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
TreeNode right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
}
孩子双亲表示法:
class TreeNode {
int val; // 数据域
TreeNode left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
TreeNode right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
TreeNode parent; // 当前节点的根节点
}
二、二叉树基本概念
一棵二叉树是结点的有限集合,该集合或者为空,或者由一个根节点加上两棵分别称为左子树和右子树的二叉树组成。二叉树中不存在度大于2的结点,二叉树子树有左右之分,因此二叉树是有序树。
2.1 特殊的二叉树
满二叉树:一棵二叉树,如果每层的结点数都达到最大值,则这棵树就是满二叉树。满二叉树的层数为k,且结点总数是2^k -1。
完全二叉树:由满二叉树引出来,相当于满二叉树的叶子结点从右往左连续删除结点得到的就是完全二叉树。
2.2 二叉树的性质
1、若根结点的层数为1,则一颗非空二叉树的第i层上最多有2^(i-1)(i>0)个结点;
2、二叉树的深度为k的时候,最多的结点数是2^k-1(k>=0);
3、对于任意一棵二叉树,如果叶子结点数为n0,度为2的非叶子结点个数为n2,则有n0=n2+1,也即叶子节点的个数总比度为2的结点个数多1;
推导过程:假设树总的结点个数为N,则树的边数为N-1:
N = n0+n1+n2;
N-1 = 0n0+1n1+2n2
得到:n0= n2+1
4、具有n个结点的完全二叉树的深度k为log2(n+1)向上取整;
5、对于具有n个结点的完全二叉树,按照从上至下从左至右的顺序对所有结点从0开始编号,假设孩子结点的下标是i则双亲结点的下标是:(i-1)/2。假设父亲结点的下标是i,左孩子:2i+1,右孩子:2*i+2。
6、对于完全二叉树,当总的结点个数为偶数时,那么度为1的结点个数为1,如果总的结点个数为奇数,那么树中就不存在度为1的结点。
2.3 二叉树遍历
遍历是指沿着某条搜索路线,依次对树中每一个结点做一次且仅做一次访问。遍历分为前序遍历、中序遍历、后序遍历。前序遍历:根左右。中序遍历:左根右。后序遍历:左右根。层序遍历:就是从二叉树的根节点出发,自上而下,自左至右逐层访问数的结点的过程。二叉树的大多操作都是基于多路递归来实现的。本文使用孩子表示法来表示构成数的结点。
class TreeNode {
public int value;
public TreeNode left;
public TreeNode right;
public TreeNode(int value) {
this.value = value;
}
}
前序遍历的代码实现(递归)
void preOrder(TreeNode root) {
if(root == null) {
return;
}
System.out.print(root.value+" ");
preOrder(root.left);
preOrder(root.right);
}
中序遍历的代码实现(递归)
void inOrder(TreeNode root) {
if(root == null) {
return;
}
inOrder(root.left);
System.out.print(root.value+" ");
inOrder(root.right);
}
后序遍历的代码实现(递归)
void postOrder(TreeNode root) {
if(root == null) {
return;
}
postOrder(root.left);
postOrder(root.right);
System.out.print(root.value+" ");
}
2.4 二叉树的基本操作
1、获取书中节点的个数
//法1:
int count = 0;//不能定义size()里面,不然每次都会新建count
int size(TreeNode root) {
if(root == null) {
return 0;
}
count++;
size(root.left);
size(root.right);
return count;
}
//法2:
int size2(TreeNode root) {
if(root == null) {
return 0;
}
return size(root.right) + size(root.left) + 1;
}
2、获取叶子结点的个数
//法1:
int leafcount = 0;
int getLeafNodeCount(TreeNode root) {
if(root == null) {
return 0;
}
if(root.left == null && root.right == null) {
leafcount++;
}
getLeafNodeCount(root.left);
getLeafNodeCount(root.right);
return leafcount;
}
int getLeafNodeCount2(TreeNode root) {
if(root == null) {
return 0;
}
if(root.left == null && root.right == null){
return 1;
}
return getLeafNodeCount2(root.left) + getLeafNodeCount2(root.right);
}
3、获取第K层结点的个数
int getKLevelNodeCount(TreeNode root,int k) {
if(root == null) {
return 0;
}
if(k == 1) {
return 1;
}
return getKLevelNodeCount(root.left,k-1) + getKLevelNodeCount(root.right,k-1);
}
4、获取二叉树的高度
二叉树的高度可以通过分别求左子树和右子树的大小,获取其中大的一个加1即可。
int getHeight(TreeNode root) {
if(root == null) {
return 0;
}
int left = getHeight(root.left);
int right = getHeight(root.right);//最好计算结果记录下来,否则多次计算迭代的次数就过多
return left > right ? left+1 : right+1;
}
5、检测值为value的元素是否存在
TreeNode find(TreeNode root, int val) {
if(root == null) {
return null;
}
if(root.value == val) {
return root;
}
TreeNode ret = find(root.left,val);
if(ret != null) {
return ret;
}
ret = find(root.right,val);
if(ret != null) {
return ret;
}
return null;
}
6、判断一棵树是不是完全二叉树
需要借助队列来解决。定义一个队列,并将根节点入队再出队并将孩子结点入队反复多次。记录出队的元素,当出队的为空时,判断队列里面是不是全空,如果全为空则是完全二叉树,如果不全为空就不是完全二叉树。
boolean isCompleteTree(TreeNode root) {
if(root == null) return true;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()) {
TreeNode cur = queue.poll();
if (cur != null) {
queue.offer(cur.left);
queue.offer(cur.right);
} else {
break;
}
}
//不能用isEmpty()直接判断的原因是,队列全为null时,返回值也是false,也即队列长度不为0
while(!queue.isEmpty()) {
TreeNode top = queue.peek();
if(top != null) {
return false;
}
queue.poll();
}
return true;
}
上述即为二叉树简单操作,下一篇将实现一些二叉树的进阶实现,欢迎批评指正!