文章目录
- 一、树是什么?
- 1.1 树的概念
- 1.2 树的基本术语
- 二、二叉树
- 2.1 二叉树的概念
- 2.2 特殊的二叉树
- 2.3 二叉树的性质
- 三、二叉树的遍历
- 3.1 深度优先遍历
- 3.1.1 前序遍历
- 3.1.2 中序遍历
- 3.1.3 后序遍历
- 3.2 广度优先遍历
- 3.2.1 层序遍历
- 四、二叉树的基本操作
- 4.1 获取树中节点的个数
- 4.2 获取叶子节点的个数
- 4.3 获取第K层节点的个数
- 4.4 获取二叉树的高度
- 4.5 检测值为value的元素是否存在
- 4.6 判断一棵树是不是完全二叉树
- 五、二叉树的例题
- 总结
一、树是什么?
1.1 树的概念
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
注意:树形结构中,子树之间不能有交集,否则就不是树形结构
1.2 树的基本术语
结点的度:一个结点含有子树的个数称为该结点的度; 如上图:A的度为2,B的度为3,D的度为0。
树的度:一棵树中,在所有结点中度的最大值称为树的度; 如上图:树的度为3。
叶子结点或终端结点:度为0的结点称为叶结点; 如上图:D,E,F,H节点为叶结点。
双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点,B是D、E、F的父节点。
孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点。
根结点:一棵树中,没有双亲结点的结点;如上图:A。
结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推。
树的高度或深度:树中结点的最大层次; 如上图:树的高度为4。
森林:由m(m>=0)棵互不相交的树组成的集合称为森林。
树有些像我们的文件存储,一个文件下有不同的文件目录,一层一层的。
二、二叉树
2.1 二叉树的概念
一棵二叉树是结点的一个有限集合,该集合:
- 树为空
- 由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
2.2 特殊的二叉树
满二叉树: 一棵二叉树,如果每层的结点数都达到最大值,则这棵二叉树就是满二叉树。如果一棵二叉树的层数为K,且结点总数是2^k - 1个 ,则它就是满二叉树。
完全二叉树: 完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
2.3 二叉树的性质
-
若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有 (i>0)个结点。
-
若规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是 (k>=0)。
-
对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1。
-
具有n个结点的完全二叉树的深度k为log2(n+1)上取整
-
对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i的结点有:
若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
若2i+1<n,左孩子序号:2i+1,否则无左孩子
若2i+2<n,右孩子序号:2i+2,否则无右孩子
二叉树的存储有两种,一个是链式存储一个顺序存储,根据之前的学习我们不难理解这两种存储的区别,我们这里是用链式存储,便于理解和学习,更加直观。
三、二叉树的遍历
遍历的实现运用的是递归的思想
public static class MyTreeNode{int val;MyTreeNode left;MyTreeNode right;public MyTreeNode(int val) {this.val = val;}}private MyTreeNode root;//简单组建出一个二叉树便于使用和理解public MyTreeNode createBinaryTree(){MyTreeNode node1 = new MyTreeNode(1);MyTreeNode node2 = new MyTreeNode(2);MyTreeNode node3 = new MyTreeNode(3);MyTreeNode node4 = new MyTreeNode(4);MyTreeNode node5 = new MyTreeNode(5);MyTreeNode node6 = new MyTreeNode(6);root = node1;node1.left = node2;node1.right = node3;node2.left = node4;node2.right = node5;node3.left = node6;return root;}
3.1 深度优先遍历
从树的某个初始节点开始,沿着一条路径尽可能深地探索下去,直到无法继续或者达到目标节点,然后回溯到上一个未完全探索的分支点,继续探索其他分支,直到遍历完所有节点。
3.1.1 前序遍历
访问的顺序为—根,左,右
在我们向下遍历树的时候,在遇到这个节点时就将其打印,然后再向左遍历,左边遍历完,再向右遍历。
void preOrder(MyTreeNode root){if(root == null){return;}System.out.print(root.val + " ");preOrder(root.left);preOrder(root.right);}
3.1.2 中序遍历
访问的顺序为—左,根,右
在我们向下遍历树的时候,在遇到这个节点时先不打印,然后向左遍历,左边遍历完,返回到这个节点时再打印然后向右遍历。
void inOrder(MyTreeNode root){if(root == null){return;}inOrder(root.left);System.out.print(root.val + " ");inOrder(root.right);}
3.1.3 后序遍历
访问的顺序为—左,右,根
在我们向下遍历树的时候,在遇到这个节点时先不打印,然后向左遍历,左边遍历完,然后向右遍历最后右边返回到父亲节点时再打印父亲节点。
void postOrder(MyTreeNode root){if(root == null){return;}postOrder(root.left);postOrder(root.right);System.out.print(root.val + " ");}
3.2 广度优先遍历
从树的某个初始节点开始,首先访问该节点的所有邻接节点(对于树来说就是子节点),然后再依次访问这些邻接节点的邻接节点,按照层次依次向外扩展,直到遍历完所有节点。
3.2.1 层序遍历
运用队列的知识,首先将根节点放入队列中,然后开始循环出队列的队头节点,出的同时打印它的val,并且判断是否有左右孩子,如果有就放入队列中。
void levelOrder(MyTreeNode root){if(root == null){return;}Queue<MyTreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()){MyTreeNode node = queue.poll();System.out.print(node.val+" ");if(node.left != null){queue.offer(node.left);}if(node.right != null){queue.offer(node.right);}}}
四、二叉树的基本操作
4.1 获取树中节点的个数
public void size(MyTreeNode root){if(root == null){return;}nodesize++;size(root.left);size(root.right);}
4.2 获取叶子节点的个数
// 获取叶子节点的个数public int getLeafNodeCount(MyTreeNode root){if (root == null){return 0;}if(root.right == null && root.left == null){return 1;}return getLeafNodeCount(root.left) + getLeafNodeCount(root.right);}
4.3 获取第K层节点的个数
// 获取第K层节点的个数public int getKLevelNodeCount(MyTreeNode 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.4 获取二叉树的高度
// 获取二叉树的高度int getHeight(MyTreeNode root){if(root == null){return 0;}return Math.max(getHeight(root.left), getHeight(root.right))+1;}
4.5 检测值为value的元素是否存在
// 检测值为value的元素是否存在MyTreeNode find(MyTreeNode root, int val){if(root == null){return null;}if(root.val == val){return root;}MyTreeNode left = find(root.left,val);if(left != null){return left;}MyTreeNode right = find(root.right,val);if(right != null){return right;}return null;}
4.6 判断一棵树是不是完全二叉树
boolean isCompleteTree(MyTreeNode root){if(root == null){return true;}Queue<MyTreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()){MyTreeNode node = queue.poll();if(node == null){break;}queue.offer(node.left);queue.offer(node.right);}while (!queue.isEmpty()){MyTreeNode node = queue.poll();if(node != null){return false;}}return true;}
五、二叉树的例题
例题1 检查两颗树是否相同
public boolean isSameTree(TreeNode p, TreeNode q) {if(p == null && q != null){return false;}if(p != null && q == null){return false;}if(p == null && q == null){return true;}if(p.val == q.val){isSameTree(p.left,q.left);return isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);}return false;}
例题2 另一颗树的子树
这题和上面那题类似,只是我们要遍历那棵被主树,然后判断对应子树是否和要判断的树相同
//检查两颗树是否相同public boolean isSameTree(MyTreeNode p, MyTreeNode q) {if(p == null && q != null){return false;}if(p != null && q == null){return false;}if(p == null){return true;}if(p.val == q.val){isSameTree(p.left,q.left);return isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);}return false;}public boolean isSubtree(MyTreeNode root, MyTreeNode subRoot) {if(root == null){return false;}if(subRoot == null){return true;}if(isSameTree(root,subRoot)){return true;}if(isSubtree(root.left,subRoot)){return true;}if(isSubtree(root.right,subRoot)){return true;}return false;}
例题3 翻转二叉树
public TreeNode invertTree(TreeNode root) {if(root == null){return null;}TreeNode tmp = root.left;root.left = root.right;root.right = tmp;invertTree(root.left);invertTree(root.right);return root;}
例题4 判断一颗二叉树是否是平衡二叉树。
public boolean isBalanced(MyTreeNode root) {if(root == null){return true;}if(Math.abs(getHeight(root.left) - getHeight(root.right)) > 1){return false;}if(isBalanced(root.left) && isBalanced(root.right)){return true;}return false;}
private int getHeight(MyTreeNode root){if(root == null){return 0;}return Math.max(getHeight(root.left), getHeight(root.right))+1;}
例题5 对称二叉树
public boolean isSymmetric(MyTreeNode root) {if(root == null){return true;}return isSymmetricLeftAndRight(root.left,root.right);}private boolean isSymmetricLeftAndRight(MyTreeNode rootLeftTree, MyTreeNode rootRightTree){if(rootLeftTree == null && rootRightTree != null){return false;}if(rootLeftTree != null && rootRightTree == null){return false;}if(rootLeftTree == null){return true;}if(rootLeftTree.val != rootRightTree.val){return false;}return isSymmetricLeftAndRight(rootLeftTree.left,rootRightTree.right)&&isSymmetricLeftAndRight(rootLeftTree.right,rootRightTree.left);}
例题6 二叉树的遍历
import java.util.Scanner;
import java.util.LinkedList;
import java.util.Queue;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
class TreeNode{char val;TreeNode left;TreeNode right;public TreeNode(char val) {this.val = val;}}
//这个类是为了确保字符串的下标,即在每次调用时,可以刷新i的值,
//同时可以让i的值在每次调用后正常使用,如果直接用public static int i的话main方法中
//while (in.hasNextLine()) { // 注意 while 处理多个 case
// }无法刷新i的值,相当于全局变量,会出现上一次我i到了5下标,这次还是从5下标开始的问题。
class Cur{int i;
}
public class Main {public static void main(String[] args) {Scanner in = new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseCur cur = new Cur();String str = in.nextLine();TreeNode treeNode = createTree(str,cur);inOrder(treeNode);}}private static TreeNode createTree(String str,Cur cur){TreeNode root = null;if(str.charAt(cur.i) != '#'){root = new TreeNode(str.charAt(cur.i));cur.i++;root.left = createTree(str,cur);root.right = createTree(str,cur);}else{cur.i++;}return root;}private static void inOrder(TreeNode root){if(root == null){return;}inOrder(root.left);System.out.print(root.val + " ");inOrder(root.right);}
}
例题7 二叉树的层序遍历
public List<List<Integer>> levelOrder2(MyTreeNode root) {List<List<Integer>> listout = new ArrayList();Queue<MyTreeNode> queue = new LinkedList();if(root == null){return listout;}queue.offer(root);while(!queue.isEmpty()){int size = queue.size();List<Integer> listin = new ArrayList();while(size != 0){MyTreeNode cur = queue.poll();listin.add(cur.val);if(cur.left != null){queue.offer(cur.left);}if(cur.right != null){queue.offer(cur.right);}size--;}listout.add(listin);}return listout;}
例题8 二叉树的最近公共祖先
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {if(root == null){return null;}if(root == p || root == q){return root;}TreeNode leftNode = lowestCommonAncestor(root.left,p,q);TreeNode rightNode = lowestCommonAncestor(root.right,p,q);if(leftNode == null && rightNode == null){return null;}if(leftNode == null){return rightNode;}if(rightNode == null){return leftNode;}return root;}
例题9 从前序与中序遍历序列构造二叉树
class Solution {int preIndex;//定义一个成员变量,在类中充当全局变量来记录前序遍历的位置public TreeNode buildTree(int[] preorder, int[] inorder) {return buildTreeMethod(preorder,inorder,0,inorder.length -1);}private TreeNode buildTreeMethod(int[] preorder,int[] inorder,int begin,int end){if(begin > end){//如果开始位置大于结束位置说明是空的return null;}TreeNode root = new TreeNode(preorder[preIndex]);//rootIndex是为了标中序遍历中对应的前序遍历的节点,从而分数组的左右部分int rootIndex = findTheNode(inorder,begin,end,preorder[preIndex]);preIndex++;//让前序遍历的数组往后走root.left = buildTreeMethod(preorder,inorder,begin,rootIndex-1);root.right = buildTreeMethod(preorder,inorder,rootIndex+1,end);return root;}private int findTheNode(int[] inorder,int begin,int end,int val){for(int i = begin;i <= end;i++ ){if(val == inorder[i]){return i;}}return -1;}
}
例题10 从中序与后序遍历序列构造二叉树
class Solution {public int postIndex;public TreeNode buildTree(int[] inorder, int[] postorder) {postIndex = postorder.length-1;return buildTreeMethod(inorder,postorder,0,inorder.length-1);}private TreeNode buildTreeMethod(int[] inorder, int[] postorder,int begin,int end){if(begin > end){return null;}TreeNode root = new TreeNode(postorder[postIndex]);int rootIndex = findTheNode(inorder,begin,end,postorder[postIndex]);postIndex--;root.right = buildTreeMethod(inorder,postorder,rootIndex + 1,end);root.left = buildTreeMethod(inorder,postorder,begin,rootIndex -1);return root;}private int findTheNode(int[] inorder,int begin,int end,int val){for(int i = begin ;i <= end;i++) {if(inorder[i] == val) {return i;}}return -1;}
}
例题11 根据二叉树创建字符串
class Solution {public String tree2str(TreeNode root) {if(root == null){return null;}StringBuffer stringBuffer = new StringBuffer();return tree2strMethod(root,stringBuffer).toString();}private StringBuffer tree2strMethod(TreeNode root,StringBuffer stringBuffer){stringBuffer.append(root.val);if(root.left != null){stringBuffer.append('(');tree2strMethod(root.left,stringBuffer);stringBuffer.append(')');}else{if(root.right != null){stringBuffer.append("()");}else{return stringBuffer;}}if(root.right != null){stringBuffer.append('(');tree2strMethod(root.right,stringBuffer);stringBuffer.append(')');}else{return stringBuffer;}return stringBuffer;}
}
这后面的三个题很简单,就是将前序后序中序遍历放入一个顺序表中
例题12 二叉树前序非递归遍历实现
List<Integer> list = new ArrayList<>();public List<Integer> preorderTraversal(TreeNode root) {if(root == null){return list;}list.add(root.val);preorderTraversal(root.left);preorderTraversal(root.right);return list;}
例题13 二叉树中序非递归遍历实现
class Solution {List<Integer> list = new ArrayList<>();public List<Integer> inorderTraversal(TreeNode root) {if(root == null){return list;}inorderTraversal(root.left);list.add(root.val);inorderTraversal(root.right);return list;}
}
例题14 二叉树后序非递归遍历实现
List<Integer> list = new ArrayList<>();public List<Integer> inorderTraversal(TreeNode root) {if(root == null){return list;}inorderTraversal(root.left);list.add(root.val);inorderTraversal(root.right);return list;}
总结
本篇文章介绍了有关数据结构中《二叉树》相关的内容,包括什么是树,什么是二叉树,二叉树的遍历,以及遍历的模拟实现,还有关于二叉树的例题,如果有什么不正确、不严谨的地方,还望指正,谢谢大家!