Java-数据结构-二叉树-基础 (o゚▽゚)o
文本目录:
❄️一、树形结构:
▶ 1、概念:
▶ 2、特殊的概念:
▶ 3、树的表示形式:
❄️二、二叉树:
▶ 1、概念:
▶ 2、两种特殊的二叉树:
➷ 1)、满二叉树:
➷ 2)、完全二叉树:
▶ 3、二叉树的性质:
➷ 1)性质一:
➷ 2)性质二:
➷ 3)性质三:
编辑
➷ 4)性质四:
➷ 5)性质五:
▶ 4、二叉树的存储:
▶ 5、二叉树的基本操作:
➷ 1)、二叉树的创建:
➷ 2)、二叉树的遍历:
•(1)、前中后序遍历:
•(2)、层序遍历:
➷ 3)、二叉树的基本操作:
•(1)、size(Node root):
•(2)、getLeafNodeCount(Node root):
•(3)、getKLevelNodeCount(Node root,int k):
•(4)、getHeight(Node root):
•(5)、find(Node root,char val):
•(6)、isCompleteTree(Node root):
❄️总结:
❄️一、树形结构:
在我们介绍二叉树之前呢,我们先来了解一下什么是树形结构。
▶ 1、概念:
树是一种 非线性的数据结构 ,其是由 n 个有限节点组成的一个具有层次关系的集合。其结构呢就像是一个倒挂着的一颗树,所以我们称之为树。
特点:1、有一个特殊的节点,称为根节点,并且根节点没有前驱节点。
2、除了根节点外,其余节点被分为 M 个互不相交的集合,其中呢每一个集合都是一颗 与树相似的子树,每个子树的根节点有且只有一个前驱,其中可以有0个或者多个后继结点。
3、 我们的树是递归实现的。
4、一颗有N个节点的树,有N - 1 条边。
这里要注意:树形结构中呢,我们的子树之间不能有交集,否则就不是树形结构了。
▶ 2、特殊的概念:
我们先来看个树形结构的图片:
☑ 结点的度:一个结点含有子树的个数称之为该结点的度。比如:上图中 A 的度为 6 。
☑ 树的度:一棵树中,所有 结点的度 的最大值称之为 树的度。比如:我们上图的树的度为 6
☑ 叶子结点或终端结点:度为 0 的结点称之为叶子结点。比如:我们上图的B、C、H、I、K....等
☑ 双亲结点或父结点:若一个结点含有子结点,则这个节点称之为其子结点的父结点。比如:我 们上图的 A 是 B 的父结点。
☑ 孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点。比如:B是A的孩子结 点
☑ 根结点:一棵树中没有双亲结点的结点。比如:A结点
☑ 结点的层次:从根结点开始定义起,根为第一层1,根的子结点为第2层,以此类推。
☑ 树的高度或者深度:树中结点的最大层次。比如:上图中树的高度为 4
接下来的概念呢,我们只需要了解即可:
☒ 非终端结点或分支结点:度不为 0 的结点。比如:A、D、E、F、G....等
☒ 兄弟结点:具有相同父结点的结点互相称之为兄弟结点。比如:B、C就是兄弟结点
☒ 堂兄弟结点:双亲在同一层的结点互为堂兄弟。比如:H、I 就是堂兄弟
☒ 结点的祖先:从根到该结点所经分支上的所有结点。比如:Q 的祖先就是J、E、A
☒ 子孙:以结点为根的子树中任意结点都称之为该结点的子孙。比如:所有结点都是A的子孙
☒ 森林:由m 棵互不相交的树组成的就是森林。
▶ 3、树的表示形式:
树结构呢,相对于线性结构呢就比较复杂了,我们存储表示起来就比较麻烦了。我们对于树有很多的表示形式,比如:双亲表示法、孩子表示法、孩子双亲表示法、孩子兄弟表示法。
我们来了解一下我们最常用的表示方法:孩子兄弟表示法。
class Node {
int val;
Node firstChild;//第一个孩子引用
Node nextBrother;//下一个兄弟引用
}
这个呢就是我们的孩子兄弟表示法。
❄️二、二叉树:
▶ 1、概念:
一颗二叉树是结点的一个有限集合,这个集合呢:
1、或者为空
2、或者是由一颗根结点加上两个子树,分别为 左子树 和 右子树 所构成的。
我们来看看二叉树的图片:
从上面的图片可知:1、二叉树不存在结点大于2的情况。
2、二叉树的子树有左右之分,次序不能颠倒,所以二叉树是有序的。
注意:我们二叉树可能出现几种情况:
▶ 2、两种特殊的二叉树:
➷ 1)、满二叉树:
一颗二叉树,如果每层的结点数都达到最大值,则这颗二叉树就是满二叉树。
就是 如果一颗二叉树的结点为 k,并且结点总数为 2^k - 1 个结点,则其就是满二叉树。
➷ 2)、完全二叉树:
完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树。
这样看文字可能不是很看懂,我们来看图片:
完全二叉树的存放顺序为:从上到下,从左到右,依次存放。
▶ 3、二叉树的性质:
➷ 1)性质一:
若规定根结点的层数为1,则一颗 非空二叉树 的第 i 层上最多有 2^(i - 1) 个结点。
➷ 2)性质二:
若规定只有根结点的二叉树的深度为1,则深度为 k 的二叉树的最大结点数为 2^k - 1
➷ 3)性质三:
对于任意的一颗二叉树,如果其叶子结点个数为n0,度为2的非叶子结点数为n2,则有n0 = n2 + 1
➷ 4)性质四:
具有 n 个结点的 完全二叉树 的 深度k 为 log(n+1) 向上取整,这里的 log是以2为底的。
➷ 5)性质五:
对于具有n个结点的完全二叉树,如果按照 从上至下从左至右 的顺序对所有节点从0开始编号,则对于序号为 i 的结点有:
☞ 若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
☞ 若2i+1<n,左孩子序号:2i+1,否则无左孩子
☞ 若2i+2<n,右孩子序号:2i+2,否则无右孩子
▶ 4、二叉树的存储:
二叉树的存储解构呢,分为:顺序存储 和 链式存储。
我们先来看 链式存储:是通过一个一个结点引用起来的,我们常见的有二叉和三叉表示形式。
//二叉
class Node {
int val;//数据域
Node left;//左孩子引用
Node right;//右孩子引用
}
//三叉
class Node {
int val;//数据域
Node left;//左孩子引用
Node right;//右孩子引用
Node parent;//当前结点的根结点
}
▶ 5、二叉树的基本操作:
➷ 1)、二叉树的创建:
我们这里呢只是进行简单的创建,手动的进行创建一个二叉树,之后我们再来写其操作,等到后面我们再来进行二叉树的真正的创建方法。
我们来手动创建这个二叉树,我们使用二叉的方式来进行创建。
public class BinaryTree {
static class TreeNode {
public char val;
public TreeNode right;
public TreeNode left;
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');
A.left = B;
A.right = C;
B.left = D;
B.right = E;
C.left = F;
C.right = G;
return A;
}
}
这样我们就把这个二叉树创建成功了,注意这里的二叉树创建不是真正的二叉树的创建。我们接下来看看二叉树的操作:
➷ 2)、二叉树的遍历:
对于我们的二叉树操作呢,最简单的就是遍历操作了,所谓的遍历:就是沿着某条搜索路线,依次对树中每个结点均做一次访问且只做一次访问。
•(1)、前中后序遍历:
我们在前中后序遍历中使用递归的方法进行遍历。
前序遍历:先访问根结点,之后访问根的左子树,最后根的右子树。
//前序遍历
public void preOrder(TreeNode root) {
if (root == null) {
return;
}
System.out.print(root.val + " ");
preOrder(root.left);
preOrder(root.right);
}
中序遍历:先访问根的左子树,之后访问根结点,最后为根的右子树。
//中序遍历
public void inOrder(TreeNode root) {
if (root == null) {
return;
}
inOrder(root.left);
System.out.print(root.val + " ");
inOrder(root.right);
}
后序遍历:先访问根的左子树,之后访问根的右子树,最后访问根结点。
//后序遍历
public void postOrder(TreeNode root) {
if (root == null) {
return;
}
postOrder(root.left);
postOrder(root.right);
System.out.print(root.val + " ");
}
我们来看这些遍历的运行结果,我们遍历的就是我们上面创建的二叉树。
•(2)、层序遍历:
我们除了前中后序遍历呢,我们还有层序遍历,就是先访问第一层的根结点,之后从上到下,从左到右依次访问结点,就是层序遍历。
比如我们创建的这个二叉树的层序遍历为:
A->B->C->D->E->F->G。
这里我们层序遍历,我们需要使用队列来配合实现,那么它是怎么做到的呢?我们来看看:
来看看代码:
//层序遍历
public void levelOrder(TreeNode root) {
if (root == null) {
return;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()) {
TreeNode cur = queue.poll();
System.out.print(cur.val + " ");
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
}
}
➷ 3)、二叉树的基本操作:
•(1)、size(Node root):
获得树中结点的个数。
我们有两种方法,我们第一种就是遍历二叉树,当 root 不为空的时候呢,我们的 usedSize 就++
第二种方法就是我们的 左子树结点+右子树结点+1,这个方法来求结点的个数。
我们来看第一种方法的代码:
//返回总结点的个数
public static int usedSize;//我们用于存放结点个数的
public int size1(TreeNode root) {
if (root == null) {
return 0;
}
usedSize++;
size1(root.left);//遍历左子树
size1(root.right);//遍历右子树
return usedSize;
}
在我们第一种方法中呢,我们的 usedSize 必须在方法的外面定义,因为要是在方法中的话呢,每次我们递归后,我们之前存放的结点的个数就不会保存了,所以我们要定义在外面。
我们来看看第二种方法:
代码:
public int size2(TreeNode root) {
if (root == null) {
return 0;
}
return size2(root.left) + size2(root.right) + 1;
}
•(2)、getLeafNodeCount(Node root):
获得叶子结点的个数。
我们这个同样可以使用两种方法来进行获得叶子结点的个数。第一种:使用 leafSize 来计数,当遇到左子树和右子树都为空的结点就是叶子结点。
第二种:左子树的叶子+右子树的叶子。
我们的这个方法和上面的方法是差不多的,所以这里我们直接看代码。
第一种:
//获得叶子结点的个数
//第一种
public static int leafSize;
public int getLeafNodeCount1(TreeNode root) {
if (root == null) {
return 0;
}
if (root.left == null && root.right == null) {
leafSize++;
}
getLeafNodeCount1(root.left);
getLeafNodeCount1(root.right);
return leafSize;
}
第二种:
//第二种
public 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)、getKLevelNodeCount(Node root,int k):
获得第k层的结点的个数。
我们求第k层的结点数呢,我们求 root的左子树的k-1层的结点 + root的右子树的k-1层的结点
这样求出来的就是我们的第k层结点数。我们来看看思路图:
我们来看代码的实现:
//获得第k层的结点的个数。
public 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)、getHeight(Node root):
获得 二叉树的高度。
我们需要求 左树的高度 和 右子树的高度的最大值 + 1。在这里我们求 左子树的高度 和 右子树的高度。我们也是使用递归的方法。
代码:
//获取二叉树的高度
public int getHeight(TreeNode root) {
if (root == null) {
return 0;
}
int leftHeight = getHeight(root.left);
int rightHeight = getHeight(root.right);
//谁高谁加一
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
•(5)、find(Node root,char val):
查看二叉树中有没有val这个值的结点。
这个方法呢,就是我们需要遍历一遍二叉树就可以了。
我们需要先判断我们的根是不是,如果不是,就去左子树中找,如果还没有,就从右子树中找,如果这里面都没有的话,那么就返回null。
//检测值为value的元素是否存在
public TreeNode find(TreeNode root,char val) {
if (root == null) {
return null;
}
//先判断根结点
if (root.val == val) {
return root;
}
//如果不是就去左子树中寻找
TreeNode leftNode = find(root.left,val);
if (leftNode != null) {
//如果我们在左子树中找到的话,我们在上面根的判断就返回了我们和val相等的结点,
//在这里我们只需要判断是否为空就可以了,不为空,就是val的这个结点,反之则没有
return leftNode;
}
//去右子树中寻找
TreeNode rightNode = find(root.right,val);
if (rightNode != null) {
//我们这里同上
return rightNode;
}
return null;
}
•(6)、isCompleteTree(Node root):
判断该二叉树是否是完全二叉树。
这个方法呢,我们也需要借助队列来实现,这个步骤和我们的层序遍历是差不多的,但是如果我们队列中两个结点中存在 null 的话,那么就不是完全二叉树。因为完全二叉树是从上到下从左到右依次存放,两个相邻的结点中间不可能会存在 null 。我们利用这个性质就可以判断出是否是完全二叉树了。我们来看思路图:
这里我们要注意的是,当我们的root为空的时候,也是完全二叉树。
我们来看代码:
//判断一棵树是不是完全二叉树
public 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;
}
}
while(!queue.isEmpty()) {
TreeNode peek = queue.peek();
if (peek != null) {
return false;
}
queue.poll();
}
return true;
}
❄️总结:
OK,到这里呢,我们的二叉树基础就到这里就结束了,我们接下来看看关于二叉树的相关的习题,并且在题中我们会看见我们 二叉树的创建方法。让我们期待下次的博客吧!!!拜拜啦~~~