文章目录
一、树是什么?
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 处理多个 case
Cur 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;
}
总结
本篇文章介绍了有关数据结构中《二叉树》相关的内容,包括什么是树,什么是二叉树,二叉树的遍历,以及遍历的模拟实现,还有关于二叉树的例题,如果有什么不正确、不严谨的地方,还望指正,谢谢大家!