文章目录
一、树的简单了解
1.概念:
树是一种非线性数据结构,它是由 n (n>0) 个结点组成的一个具有层次结构的关系的集合。
形似一个倒置的树,根朝上,叶子朝下。
2.树的特点:
(1)树有一个特殊的节点,为根节点,根节点没有前驱结点。
(2)树是由递归方式定义的。
(3)树形结构中,子树之间不能有交集,否则不能称为树形结构。
二、树的结构概念
结点的度:一个结点含有子树的个数称为该结点的度; 如上图:A 的节点的度为 2。
树的度:一棵树中,所有结点度的最大值称为树的度; 如上图:树的度为 2 。
叶子结点或终端结点:度为0的结点称为叶结点; 如上图:D,H,F,G 为叶结点。
双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的
父结点, 如上图:A 为 B,C 的父节点。
孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点, 如上图:B,C 为 A 的孩子结点。
根结点:一棵树中,没有双亲结点的结点,如上图:A
结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推。
树的高度或深度:树中结点的最大层次,如上图:树的高度为4。
树的以下概念只需了解:
非终端结点或分支结点:度不为0的结点; 如上图:A,B,C,E 为分支结点
兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点
堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图:E,F 互为兄弟结点
结点的祖先:从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先
子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙
三、二叉树
1.二叉树的概念:二叉树可以为空,或是由一个根节点加上一个左子树和一个右子树组成。
注:
(1)二叉树不存在度大于2的节点。
(2)二叉树的结点元素是有顺序的,次序不能改变。
2.两种特殊的二叉树
(1)满二叉树:每层结点数都达到最大值,这棵树就是满二叉树。
(2)完全二叉树:对于深度为K的,有 n 个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从 0 至 n-1 的结点一 一对应时称之为完全二叉树。
四、代码实现二叉树
1.实现前准备
为了后续方法的实现更加方便,我们首先需要简单实现一下树的节点,并且简单创建一个树。
代码如下:
//树的每一个结点都是一个独立的部分,因此可以以内部类的形式实现。
static class TreeNode{
public char val;
public TreeNode left; //存储左孩子引用
public TreeNode right; //存储有孩子引用
public TreeNode(char val) {
this.val = val;
}
}
如图,我们利用上面创建的结点空间实现以下的二叉树。
代码如下:
//手动创建一个二叉树,便于测试
public TreeNode createTree(){
TreeNode A = new TreeNode('A');
TreeNode B = new TreeNode('B');
TreeNode C = new TreeNode('C');
TreeNode D = new TreeNode('D');
TreeNode E = new TreeNode('E');
TreeNode F = new TreeNode('F');
TreeNode G = new TreeNode('G');
TreeNode H = new TreeNode('H');
A.left = B;
A.right = C;
B.left = D;
B.right = E;
C.left = F;
C.right = G;
E.right = H;
return A; //返回树的根节点
}
之后,我们要了解有关二叉树,需要实现的一些方法,如下:
1.实现二叉树的遍历(前、中、后序)。
2.实现获取树中结点的个数。
3.实现获取叶子结点的个数。
4.获取第 k 层元素个数。
5.获取二叉树的高度。
6.检测值为 val 的结点是否存在。
2.实现二叉树的遍历
简单解释:
我们知道二叉树的遍历有三种,分别为,前,中,后序。
前序:根 --> 左–> 右。
中序:左 --> 根 --> 右。
后序:左 --> 右 --> 根。
注:有关树的方法实现最主要的就是递归方法的使用!
代码实现:
//前序遍历 根-->左-->右
void preOrder(TreeNode root){
if(root == null){
return;
}
System.out.print(root.val+" ");
preOrder(root.left);
preOrder(root.right);
}
//中序遍历 左-->根-->右
void inOrder(TreeNode root){
if(root == null){
return;
}
inOrder(root.left);
System.out.print(root.val+" ");
inOrder(root.right);
}
//后序遍历 左-->右-->根
void postOrde(TreeNode root){
if(root == null){
return;
}
postOrde(root.left);
postOrde(root.right);
System.out.print(root.val+" ");
}
详细解释:
这里,为了便于解释,我将上面的树进行了删减,以下图为例解释一下前序遍历的代码逻辑(其余两种遍历类似)。
结果为:ABDEC
3.实现获取树中结点的数量
简单解释:
获取树中的元素个数,最直接的方法就是定义一个计数器,通过遍历树中的元素实现累加。
代码实现:
// 获取树中节点的个数
public static int nodeSize = 0;
public int size(TreeNode root){
if(root == null){
return 0;
}
//每次递归遍历时首先实现计数增加
nodeSize++;
size(root.left);
size(root.right);
return nodeSize;
}
详细解释:
这里使用递归的方法,只要 root != null 优先实现对左树的遍历计数,然后在此基础上实现对右树元素进行遍历计数。最终返回个数和。
4.获取叶子结点的个数
简单解释:
叶子结点,就是指树中没有孩子结点的元素,也就是说,这样的节点左右均为 null 因此这也就成为了实现该方法的突破口。
代码如下:
public int getLeafNodeCount2(TreeNode root){
if(root == null){
return 0;
}
if(root.left == null && root.right == null){
return 1;
}
int tmp = getLeafNodeCount2(root.left) + getLeafNodeCount2(root.right);
return tmp;
}
详细解释:
同样这里使用了递归的方法,在递归前,先放出递归返回的条件,然后,进入递归,从整体看,将树分为左右两大部分,在进行细分,先让递归记录左边,在记录右边相加和,最终获得叶子结点的数量。
5.获取第K层元素的个数
简单解释:
如图所示:假设获取第 3 层元素数量。
对于 A 层,是第三层。对于 B,C 层,是第二层。对于 D,E,F,G 是第一层。
以此为判断条件,不难理解,可以在每次递归时让层数减少,当为 1 时就记录个数,最后返回即可。
代码实现:
// 获取第K层节点的个数
public int getKLevelNodeCount(TreeNode root,int k){
//这里需要注意当要查找的层数超过数高度的情况
if(root == null || k <= 0){
return 0;
}
if(k == 1){
return 1;
}
int tmp = getKLevelNodeCount(root.left,k-1) + getKLevelNodeCount(root.right,k-1);
return tmp;
}
详细解释:
因为只需要找某一层的结点个数,因此,只要到该层节点时返回 1 ,如果继续 k-1 ,k = 0 就会返回 0 。同样的,这里递归需要将整棵树看做左右两大部分,在细分到下面的左右子树,最后加和返回该层的数量。
6.获取二叉树的高度
简单解释:
要获取高度,同样的需要对二叉树进行深入遍历,因为二叉树只有左右两部分,树的高度无非就是左右部分进行比较,这里将树看做左右两大部分,分别深入,最终进行比较即可。
代码实现:
// 获取二叉树的高度
public int getHeight(TreeNode root){
if(root == null){
return 0;
}
int leftHigh = getHeight(root.left);
int rightHigh = getHeight(root.right);
return leftHigh > rightHigh ? leftHigh+1 : rightHigh+1;
}
详细解释:
同样,以 root 所指向为 null 为条件,将树分为左右两大部分,先对左树进行遍历,通过三目运算的判断,最终会返回出最长,高度最高的呢一串结点。
7.检测value值是否在该树中
简单解释:
同样,这个方法的实现也很简单,就是遍历整棵树,寻找对应的值后返回 root 即可。
代码实现:
// 检测值为value的元素是否存在
public TreeNode find(TreeNode root, int val){
if(root == null){
return null;
}
if(root.val == val){
return root;
}
TreeNode ret1 = find(root.left,val);
if(ret1 != null){
return ret1;
}
TreeNode ret2 = find(root.left,val);
if(ret2 != null){
return ret2;
}
return null;
}
详细解释:
与前面的方法相同,从整体上,将一棵树一分为二,首先,定义返回条件,这里需要定义两个结点来接受返回出来的目标值。只要最终返回的值符合条件,就将该地址传递出去即可,如果均不成立,呢没有找到目标值。
五、总结
简单总结:
在有关树的问题中,用的最多的就是递归的思想,这就需要我们用整体和部分的眼光来看问题,往往在深究深层是如何运行的时候常常会将问题复杂化,所以,子问题的思考方法在这里尤为重要!