数据结构——树与二叉树:核心概念全解析 - 实践
目录
一: 树型结构(了解)
1.1 概念
树是⼀种⾮线性的数据结构,它是由n(n>=0)个有限结点组成⼀个具有层次关系的集合。把它叫做树是因为它看起来像⼀棵倒挂的树,也就是说它是根朝上,⽽叶朝下的。它具有以下的特点:
•
有⼀个特殊的结点,称为根结点,根结点没有前驱结点
•
除根结点外,其余结点被分成M(M > 0)个互不相交的集合T1、T2、......、Tm,其中每⼀个集合Ti
(1 <= i <= m) ⼜是⼀棵与树类似的⼦树。每棵⼦树的根结点有且只有⼀个前驱,可以有0个或多个后继
树是递归定义的。
注意:树形结构中,⼦树之间不能有交集,否则就不是树形结构
树形结构:
某个节点,往下能够延申出多个节点,就把延申出的节点叫做子节点,当前节点就叫做父节点。
但是,如何一个父节点可以包含多个子节点。任何子节点只有一个父节点。
1.2 概念(重要)
结点的度:⼀个结点含有⼦树的个数称为该结点的度; 如上图:A的度为6
树的度:⼀棵树中,所有结点度的最⼤值称为树的度; 如上图:树的度为6
叶⼦结点或终端结点:(没有子节点)度为0的结点称为叶结点; 如上图:B、C、H、I...等节点为叶结点
双亲结点或⽗结点:若⼀个结点含有⼦结点,则这个结点称为其⼦结点的⽗结点; 如上图:A是B的⽗结点
孩⼦结点或⼦结点:⼀个结点含有的⼦树的根结点称为该结点的⼦结点; 如上图:B是A的孩⼦结点
根结点:⼀棵树中,没有双亲结点的结点;如上图:A
结点的层次:从根开始定义起,根为第1层,根的⼦结点为第2层,以此类推树的⾼度或深度:树中结点的最⼤层次; 如上图:树的⾼度为4
树的以下概念只需了解,在看书时只要知道是什么意思即可:
⾮终端结点或分⽀结点:度不为0的结点; 如上图:D、E、F、G...等节点为分⽀结点
兄弟结点:具有相同⽗结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点
堂兄弟结点:双亲在同⼀层的结点互为堂兄弟;如上图:H、I互为兄弟结点
结点的祖先:从根到该结点所经分⽀上的所有结点;如上图:A是所有结点的祖先
⼦孙:以某结点为根的⼦树中任⼀结点都称为该结点的⼦孙。如上图:所有结点都是A的⼦孙
森林:由m(m>=0)棵互不相交的树组成的集合称为森林(多个树)
1.3 树的表⽰形式(了解)
树结构相对线性表就⽐较复杂了,要存储表⽰起来就⽐较⿇烦了,实际中树有很多种表⽰⽅式,如:
双亲表⽰法(一般不使用),孩⼦表⽰法(常见表示方法)、孩⼦双亲表⽰法、孩⼦兄弟表⽰法等等。我们这⾥就简单的了解其中最常⽤的孩⼦兄弟表⽰法。双亲: class Node{ String val; Node parent; } 孩子表示法: class{ String val; Listchilderen; } 孩子双亲表示:(功能最强大) class Node{ String val; List childern Node parentl; } 孩子兄弟: class Node{ String val; Node firstChild; List brotherNodes; } 对于树来说:
1:有一个专门的类
2:拿到根节点,后续的节点就能拿到
二:⼆叉树(重点)
2.1 概念
⼀棵⼆叉树是结点的⼀个有限集合,该集合:
1. 或者为空
2. 或者是由⼀个根节点加上两棵别称为左⼦树和右⼦树的⼆叉树组成
从上图可以看出:
1⼆叉树不存在度⼤于2的结点(只能小于等于2)
2⼆叉树的⼦树有左右之分,次序不能颠倒,因此⼆叉树是有序树
注意:对于任意的⼆叉树都是由以下⼏种情况复合⽽成的:
2.2 两种特殊的⼆叉树
1:满⼆叉树
⼀棵⼆叉树,如果每层的结点数都达到最⼤值,则这棵⼆叉树就是满⼆叉树。也就是
说,如果⼀棵⼆叉树的层数为K,且结点总数是 ,则它就是满⼆叉树。
(整棵树,没有存在一个分支的情况下)
2:完全二叉树
完全⼆叉树是效率很⾼的数据结构,完全⼆叉树是由满⼆叉树⽽引出来的。对于深度
为K的,有n个结点的⼆叉树,当且仅当其每⼀个结点都与深度为K的满⼆叉树中编号从0⾄n-1的结
点⼀ 对应时称之为完全⼆叉树。 要注意的是满⼆叉树是⼀种特殊的完全⼆叉树。
(相当于是在满二叉树的基础上,缺失右下角!!)
2.3 ⼆叉树的性质
1 若规定根结点的层数为1,则⼀棵⾮空⼆叉树的第i层上最多有(i>0)个结点
找规律 1 1
2 2
3 4
4 8
最多有2^(i-1)个节点 i为层数
2 若规定只有根结点的⼆叉树的深度为1,则深度为K的⼆叉树的最⼤结点数是(k>=0)
总节点数
1 1
2 3
3 7
4 15
2^-1
3 对任何⼀棵⼆叉树, 如果其叶结点个数为 n0, 度为2的⾮叶结点个数为 n2,则有n0=n2+14具有n个结点的完全⼆叉树的深度k为上取整
5 对于具有n个结点的完全⼆叉树,如果按照从上⾄下从左⾄右的顺序对所有节点从0开始编号,则对于序号为i的结点有:
若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,⽆双亲结点若2i+1<n,左孩⼦序号:2i+1,否则⽆左孩⼦
若2i+2<n,右孩⼦序号:2i+2,否则⽆右孩⼦
2.4 ⼆叉树的存储
⼆叉树的存储结构分为:顺序存储和类似于链表的链式存储。
⼆叉树的链式存储是通过⼀个⼀个的节点引⽤起来的,常⻅的表⽰⽅式有⼆叉和三叉表⽰⽅式,具体如下:孩子表示法: class Node{ String val; Node left;//左子树 Node right;右子树 } 如果那个树没有,那就设为空 // 孩⼦双亲表⽰法 class Node { int val; // 数据域 Node left; // 左孩⼦的引⽤,常常代表左孩⼦为根的整棵左⼦树 Node right; // 右孩⼦的引⽤,常常代表右孩⼦为根的整棵右⼦树 Node parent; // 当前节点的根节点 }
2.5 二叉树的实现
1 :前置说明
在学习⼆叉树的基本操作前,需先要创建⼀棵⼆叉树,然后才能学习其相关的基本操作。由于现在⼤家对⼆叉树结构掌握还不够深⼊,为了降低⼤家学习成本,此处⼿动快速创建⼀棵简单的⼆叉树,快速进⼊⼆叉树操作学习,等⼆叉树结构了解的差不多时,我们反过头再来研究⼆叉树真正的创建⽅式。
package tree; class Node { String val; Node left;//左子树 Node right;//右子树 public Node(String val) { this.val = val; this.left = null; this.right = null; } }class Tree{ public Node root; } public class Test1 { //通过这个方法手动创建一棵树 public static Node createTree() { //先把所有节点都创建出来 Node A = new Node("A"); Node B = new Node("B"); Node C = new Node("C"); Node D = new Node("D"); Node E = new Node("E"); Node F = new Node("F"); Node G = new Node("G"); //然后把他们连起来 A.left = B; A.right = C; B.left = D; B.right = E; C.left = F; C.right = G; //最后返回根节点 return A; } public static void main(String[] args) { //root就表示树的根节点 //Node root = new Node("A"); // //也可以这样斌表示树 就是封装一下 // Tree tree = new Tree(); Node root = createTree(); } }注意:上述代码并不是创建⼆叉树的⽅式,真正创建⼆叉树⽅式后序详解重点讲解。
再看⼆叉树基本操作前,再回顾下⼆叉树的概念,⼆叉树是:
1. 空树
2. ⾮空:根节点,根节点的左⼦树、根节点的右⼦树组成的。
2 :⼆叉树的遍历
遍历:把一个集合中所有节点,按照一定次序,全都访问一遍,访问过程中不重不漏
四种遍历方式:
先序遍历:基于递归
从根节点出发
1先访问当前节点的值
2递归的针对左子树进行先序遍历(一直到底。null就返回)
3递归的针对右子树进行先序遍历
中序遍历
1先递归左子树
2访问根节点
3后递归右子树
后序遍历
1先递归左子树
2再递归右子树
3打印节点规律:
1:站在根节点的
先序来说,第一个元素就是根节点
后续来说,最后一个元素就是根节点
2:站在子树的角度
先序遍历中的子树部分,第一个元素也是子树的根节点
后续遍历的字数部分,最后一个元素,也是子树的根节点
3:对于中序来说,先后序就知道根节点,如何中序根节点左侧就是左子树、右边就是右子树
层序遍历
按照层为维度,一层一层访问
代码实现:(代码都有注解)
package tree; import java.util.LinkedList; import java.util.Queue; class Node { String val; Node left;//左子树 Node right;//右子树 public Node(String val) { this.val = val; this.left = null; this.right = null; } }class Tree{ public Node root; } public class Test1 { //通过这个方法手动创建一棵树 public static Node createTree() { //先把所有节点都创建出来 Node A = new Node("A"); Node B = new Node("B"); Node C = new Node("C"); Node D = new Node("D"); Node E = new Node("E"); Node F = new Node("F"); Node G = new Node("G"); //然后把他们连起来 A.left = B; A.right = C; B.left = D; B.right = E; C.left = F; C.right = G; //最后返回根节点 return A; } //先序遍历,访问操作就是打印,先访问根节点,然后左子树,最后右子树 //递归实现 public static void preOrder(Node root) { if(root==null){ return ; //空树,返回 } System.out.print(root.val); //先访问根节点 preOrder(root.left); //左子树 preOrder(root.right); //右子树 } //中序遍历 就是先左子树,然后访问根节点,最后右子树 public static void inOrder(Node root) { if(root==null){ return ; //空树,返回 } inOrder(root.left); //左子树 System.out.print(root.val); //访问根节点 inOrder(root.right); //右子树、 } //后序遍历 就是先左子树,然后右子树,最后访问根节点 public static void postOrder(Node root) { if(root==null){ return ; //空树,返回 } postOrder(root.left); //左子树 postOrder(root.right); //右子树 System.out.print(root.val); //访问根节点 } //层序遍历 就是按层遍历树,从上到下,从左到右 public static void levelOrder(Node root) { if (root == null) { return; //空树,返回 } //创建一个队列 Queuequeue = new LinkedList<>(); queue.add(root); //根节点入队 while (!queue.isEmpty()) { Node node = queue.poll(); //队首出队 System.out.print(node.val); //访问节点 if (node.left != null) { queue.add(node.left); //左子树入队 } if (node.right != null) { queue.add(node.right); //右子树入队 } } } public static void main(String[] args) { //root就表示树的根节点 //Node root = new Node("A"); // //也可以这样斌表示树 就是封装一下 // Tree tree = new Tree(); Node root = createTree(); //先序遍历 preOrder(root); System.out.println(); //中序遍历 inOrder(root); //后序遍历 System.out.println(); postOrder(root); //层序遍历 System.out.println(); levelOrder(root); } }
3 :⼆叉树的基本操作
二叉树的基本操作:
// 获取树中节点的个数int size(Node root);// 获取叶⼦节点的个数int getLeafNodeCount(Node root);// ⼦问题思路-求叶⼦结点个数// 获取第K层节点的个数int getKLevelNodeCount(Node root,int k);// 获取⼆叉树的⾼度int getHeight(Node root);// 检测值为value的元素是否存在Node find(Node root, int val);//层序遍历void levelOrder(Node root);// 判断⼀棵树是不是完全⼆叉树boolean isCompleteTree(Node root);这些都在一下代码代码中都有注释,如果有不理解的可以在评论区哦·。。// 获取树中节点的个数 int size(Node root){ //方法1:public static int size = 0;//如果在递归函数中修改全局变量,会导致结果不准确,所以用一个静态变量来记录节点个数 public static int size(Node root) { if (root == null) { return 0; //空树,返回0 } // int size =0; // size++; // size(root.left); // size(root.right);//这个方法会导致结果不准确,因为递归函数中修改了全局变量size,导致结果不准确 // return size;//也可以这样写; return 1 + size(root.left) + size(root.right);//方法二:递归计算左右子树的节点个数,然后加上根节点的个数 //根节点+左+右=总节点数 } // 获取叶⼦节点的个数 int getLeafNodeCount(Node root); //就是左子树和右子树都为空的节点 public static int getleafSize(Node root){ if(root==null){ return 0; } if(root.left==null&&root.right==null){ return 1; } //如果左右子树都不为空,则递归计算左右子树的叶子节点个数 return getleafSize(root.left)+getleafSize(root.right); } // 获取第K层节点的个数 int getKLevelNodeCount(Node root,int k); public static int getKLevelNodeCount(Node root,int k) { if (root == null) { return 0; } if(k<1){ System.out.println("k值必须大于0"); } if(k==1){//如果是第1层,则只需要返回根节点 return 1; } return getKLevelNodeCount(root.left,k-1)+getKLevelNodeCount(root.right,k-1); //要计算第k层的节点个数,就是需要计算k-1层递归的返回值 } // 获取⼆叉树的⾼度 int getHeight(Node root); //求左子树和右子树的最大值高度,然后加上根节点的高度 public static int getHeight(Node root) { if(root==null){ return 0; } int leftHeight=getHeight(root.left); int rightHeight=getHeight(root.right); if(leftHeight>rightHeight){ return leftHeight+1; } else { return rightHeight+1; } } // 检测值为value的元素是否存在 Node find(Node root, int val); public static Node find(Node root, String value){ if(root==null){ return null; } if(root.val.equals(value)){ return root; } Node left=find(root.left,value); if(left!=null){ return left; } return find(root.right,value); // Node right=find(root.right,value); // if(right!=null){ // return right; // } // return null; } //层序遍历 void levelOrder(Node root); 前面已经写过 // 判断⼀棵树是不是完全⼆叉树 boolean isCompleteTree(Node root); //判断整棵树是不是完全二叉树 public static boolean isCompleteTree(Node root) { if (root == null) { return true;//空树,返回true } boolean isFirst = true; //是否是第一次遍历 //针对树进行层序遍历 Queuequeue = new LinkedList<>(); queue.offer(root); //根节点入队 while (!queue.isEmpty()) { Node node = queue.poll(); //队首出队 if(isFirst){ //进入一阶段 if(node.left==null&&node.right==null){ //没有子树,进入二阶段 isFirst=false; }else if(node.left!=null&&node.right==null){ //只有左子树, isFirst=false; //左子树入队列 queue.offer(node.left); }else if(node.left==null&&node.right!=null){ //只有右子树。返回false return false; }else{ queue.offer(node.left); queue.offer(node.right); } }else{ //进入二阶段 if(node.left!=null||node.right!=null){ //有子树,返回false return false; } //没有子树,继续遍历下一个节点,由于没有子树,所以不用入队 } } return true; //如果遍历完了,则是完全二叉树 } 1.要求每个节点必须要有两个子树 a)没有子树,进入二阶段 b)只有左子树,进入二阶段 c)只有右子树,判定false 2.要求每个节点必须没有子树
三:总结
本片博客讲解了树、二叉树的概念、以及实现基本操作和遍历。








浙公网安备 33010602011771号