什么是树?
1、基本概念
二叉树是我们生活中最常见的非线性结构。最常见的应用就是我们经常使用的文件分类。如图:
在c盘中存在着很多文件和文件夹,文件夹又可以细分为其他的文件夹和文件。对于图中的c盘,就相当于树的跟,而其他的文件夹就相当于树枝,然后树枝上有分出许多其他的树枝,以此组成一个树形结构。相比于顺序表,链表这样的结构,树形结构能够更好的分类,一般拥有更高的访问效率。
存在树,那么也同样存在非树。如果在存在一个节点指向同一层的结点,就称为非树。如图:
2、结构特征
类似于如链表的头结点,树形结构的根节点是读取树,操作树的最基本入口。
树中的A结点和链表的head结点都起着不可替代的作用。
3、基本特性
以下图为例子:
结点的度:每一个节点含有子树的个数,例如图中的A结点,存在BCDE这四棵子树,其中B存在FGH这三棵子树。所以A节点的度为4,B结点的度为3。
树的度:一棵树的所有的结点的度的集合中,最大值为这整棵树的度(树的结点中的最大的度)
叶子结点(终端结点):度为0的结点。例如图中的M结点、结点等。
父结点(双亲结点):一个结点的上一级结点,成为这个结点的父结点,例如上图中,A是B的父结点。
孩子结点(子结点):和父亲结点类似,一个结点如果含有其下一级,那么这个结点的下一级就称为这个结点的孩子结点。
根节点:也就是整棵树的起点,如上图中的A结点。
结点的层次:如果根节点的层次为1,那么根节点的子节点BCDE的层次就为2,以此类推。
树的深度(高度):树的结点的最大层次。
非终端结点(分支结点):度不为0的结点。
祖先:从根到该结点所经分支上的所有结点。例如,A结点是所有结点的祖先。
子孙:以某结点为根的子树中任一结点都称为该结点的子孙。
森林:由多棵不相交的树组成的系统,称为森林。
4、代码结构
class TreeNode {
int val;
TreeNode firstChild;
TreeNode secondChild;
// .......TreeNode fifthchild;
// .......
}
在树的三链表示中还存在一个指向父亲结点的指针变量。
特殊的树-二叉树
从上面的介绍不难看出, 二叉树是结点的度的最大值为2的树。代码结构为:
class TreeNode {
int val;
TreeNode leftChild;
TreeNode rightChild;
}
如果leftChild和rightChild有一个不为空,则这个结点为分支节点。如果leftChild和rightChild都为空,则这个结点为叶结点。val用来表示存放的数据。如图:
- 满二叉树
定义:一棵二叉树,它的每一层的节点数都达到最大值,则称这个二叉树为满二叉树,如果一棵满二叉树层数为k(根节点的层数为1),那么它一共拥有个结点,例如:
图中的层数3,节点数为。
- 完全二叉树
定义:一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。
例如:
3、二叉树的性质
除了根节点外,每一个节点都有都有一条边和其父结点相连,若一个有n个结点,那么就一共有n-1条边。
若规定根节点的层数为1,则一棵二叉树的第n层最多含有(n≥1)个结点。
假设一棵树有n个结点,设叶结点(度为1的结点)个数为 n0, 度为2的非叶结点个数为 n2,由第一个性质可知,总的节点数为 == 边的个数+ 1。度为2的结点可以衍生出两条边,度为1 的结点可以衍生出1条边,所以:
n0 + n1 + n2 = 2*n2 + 1*n1 + 0*n0 + 1
n0 = n2 + 1
即: 对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1
具有n个结点的完全二叉树的深度k为上取整
二叉树的基本操作
1、二叉树结构实现
public class BinaryTree {0
// 二叉树结点的定义
public static class BTNode {
int val;
BTNode leftChild;
BTNode rightChild;
public BTNode(int val) {
this.val = val;
}
}
// 简单创建一棵树,方便后面遍历
public void test() {
BTNode A = new BTNode(1);
BTNode B = new BTNode(2);
BTNode C = new BTNode(4);
BTNode D = new BTNode(3);
BTNode E = new BTNode(5);
BTNode F = new BTNode(6);
A.leftChild = B;
A.rightChild = C;
B.leftChild = D;
C.leftChild = E;
C.rightChild = F;
}
}
实现并创建一棵简单的二叉树
2、前中后序遍历
所谓二叉树遍历,就是顺着某一种固定的路线,来依次对二叉树的每一个节点的数据进行访问,以此打印或者获取二叉树的存储内容。
如何访问? 对于每一棵树,都有一个根节点作为入口点,然后每一个结点,都存放有对应的左孩子和有孩子的引用变量,以此来访问他们,然后左孩子和有孩子中又存放着其对应的左孩子和右孩子,依次形成递归。结束访问,只需要访问到其孩子的引用为空(null)即可。
根据根节点 的访问先后次序,可以分为以下遍历方式:
NLR:前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点--->根的左子树--->根的右子树。
LNR:中序遍历(Inorder Traversal)——根的左子树--->根节点--->根的右子树。
LRN:后序遍历(Postorder Traversal)——根的左子树--->根的右子树--->根节点。
前序遍历:1 2 3 4 5 6
中序遍历:3 2 1 5 4 6
后序遍历:3 2 5 6 4 1
3、层序遍历
层序遍历:按照每一层的层次关系进行遍历,而不是通过递归的父子关系。如上图,
层次遍历的结果为:1 2 4 3 5 6
遍历思路:创建一个队列,首先遍历根节点,将根节点放入队列中,然后出列并输出打印出列结点,随后将出列结点的左孩子(leftChild)和右孩子(rightChild)放入队列中,随后的每一次遍历,都将当前打印的结点的左孩子和右孩子放入队列中,直到队列为空。
因为356结点的左孩子右孩子都为null,也就是没有左孩子和右孩子,那么就直接出队列然后输出打印
public void levelOrder(BTNode root) {
Queue<BTNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()) {
BTNode out = queue.poll();
if (out.leftChild != null) {
queue.offer(out.leftChild);
}
if (out.rightChild != null) {
queue.offer(out.rightChild);
}
System.out.print(out.val + " ");
}
}
注意此处,传入的为二叉树的根节点。
输出结果为:
4、获取树中结点个数size()
public int size(BTNode root) {
if (root == null) {
return 0;
}
return size(root.leftChild) + size(root.rightChild) + 1;
}
return 中的1代表当前的结点,然后去访问左子树,和右子树的结点个数。如果某个结点为null,则直接返回0.
5、获取叶子节点的个数
叶子结点及度为0的结点,也就是leftChild和rightChild引用为null的结点。
计数法,可以使用两个函数来实现:
private int count = 0;
public int leafCount1(BTNode root) {
count = 0;
functionLeafCount(root);
return count;
}
public void functionLeafCount(BTNode root) {
if (root == null) {
return;
}
if (root.leftChild == null && root.rightChild == null) {
count++;
return;
}
functionLeafCount(root.leftChild);
functionLeafCount(root.rightChild);
}
函数递归:
public int leafCount2(BTNode root) {
if (root == null) {
return 0;
}
if (root.leftChild == null && root.rightChild == null) {
return 1;
}
return leafCount1(root.leftChild) + leafCount2(root.rightChild);
}
6、获取第k层的结点个数
设根节点的层数为1
public int getLevelCount(BTNode root,int k) {
if (root == null) {
return 0;
}
if (k == 1) {
return 1;
}
return getLevelCount(root.leftChild, k - 1) + getLevelCount(root.rightChild, k - 1);
}
传入参数k,然后递归每一层将 k - 1,当k == 1 的时候,就是我们所需要的层数
7、获取二叉树高度
二叉树的高度可以简单化为 :
左子树的高度 + 右子树的高度 + 1
public int getheight(BTNode root) {
if (root == null) {
return 0;
}
int leftHeight = getheight(root.leftChild);
int rightHeight = getheight(root.rightChild);
return Math.max(leftHeight, rightHeight) + 1;
}
8、检测值为value的元素是否存在
public boolean exitValueOf(BTNode root, int val) {
if (root == null) {
return false;
}
if (root.val == val) {
return true;
}
return exitValueOf(root.leftChild, val) || exitValueOf(root.rightChild, val);
}
递归思路,首先判断入口点的值是否等于val,然后再去判断左子树,和右子树是否存在val。
如果root为空,则说明不含val的值的结点,返回false,如果找到了就返回true。
9、判断一棵树是不是完全二叉树
完全二叉树定义:一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。
public boolean isCompleteTree(BTNode root) {
if (root == null ) {
return true;
}
Queue<BTNode> queue = new LinkedList<>();
boolean startLeaf = false;
queue.offer(root);
while(!queue.isEmpty()) {
BTNode cur = queue.poll();
BTNode left = cur.leftChild;
BTNode right = cur.rightChild;
if (left == null && right != null) {
return false;
}
if (left == null || right == null) {
startLeaf = true;
}
if (startLeaf && (left != null || right != null)) {
return false;
}
if (left != null) {
queue.offer(left);
}
if (right != null) {
queue.offer(right);
}
}
return true;
}