我们先来看看现实生活中树的概念.下面就是我们现实中树的定义如下:
无论多高多大的树,那也是从小到大、由根到叶、一点点成长起来的.俗话说十
年树木、 年树人,可一棵大树又何止是十年这样容易一一哈哈,说到哪里去了,我
们现在不是在上生物谍,而是要讲 种新的数据结构 一树。
这里来解释树的概念,究竟什么是树呢?用文字来描述
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
图片如下:
树形结构中,子树之间不能有交集,否则就不是树形结构.
树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,实际中树有很多种表示方式,如:双亲表示法,
孩子表示法、孩子双亲表示法、孩子兄弟表示法等等。我们这里就简单的了解其中最常用的孩子兄弟表示法。
static class TreeNode {
public char val;//数据域
public TreeNode left;//左孩子的引用
public TreeNode right;//右孩子的引用
public TreeNode(char val) {
this.val = val;
}
}
一棵二叉树是结点的一个有限集合,该集合:
这是两种特殊的二叉树
满二叉树是一种特殊的二叉树,它的每个节点都有0个或2个子节点,且所有叶节点都在同一层上。满二叉树的层数为h,节点总数为2^h-1。满二叉树是一种特殊的完全二叉树.
满二叉树的特点是结构非常规整,每层节点数都是满的,因此也叫做“完美二叉树”。
完全二叉树是一种二叉树,除了最后一层,其它层的节点数都是满的,最后一层的节点都靠左排列。
完全二叉树的特点是结构也比较规整,最后一层的节点可能不满,但是一定是从左到右依次排列的。
性质1:
在一棵二叉树中,第i层上最多有2^(i-1)个节点。
性质2:
在一棵二叉树中,深度为k,最多有2^k-1个节点.
性质3
在一棵二叉树中,如果叶子节点数为n0,度数为2的节点数为n2,则n0=n2+1。
下面列举一个场景,大家还是看图片
终端结点数其实就是叶子结点数,而一棵二叉树,除了叶子结点外,剩下的就是度为1或2的结点数了,我们设n为度是1的结点数。则树T结点总数n=no+n1+n2。
性质5:
二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,井且结点的存储位
置,也就是数组的下标要能体现结点之间的逻辑关系,比如~亲与孩 的关系,左右
兄弟的关系等。
大家可以来看一下,完全二叉树的存储结构
将这棵树,放到数组中,如下:
另外对于一 般的二叉树,尽管层序编号不能反映逻辑关系,但也可以进行编号,只不过,把不存在的结点设置为 ^ 而已.
具体过程如下:
将这棵树,放到数组中,如下:
既然顺序存储适用性不强,我们就要考虑链式存储结构。 二叉树每个结点最多有
两个孩子,所以为官设计一个数据域和两个指针域 较自然的想法, 我们称这样的
链表叫做二叉链表.
结构示意图,如图所示:
对于java来说我们采用的是内部类去实现.
代码如下:
public class TestBinaryTree {
static class TreeNode {
public char val;//数据域
public TreeNode left;//左孩子的引用
public TreeNode right;//右孩子的引用
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');
TreeNode H = new TreeNode('H');
A.left = B;
A.right = C;
B.left = D;
B.right = E;
C.left = F;
C.right = G;
E.right = H;
return A;
}
前序遍历的规则是先遍历根节点,再遍历左节点,然后遍历右节点,具体过程如下:
至于我们代码上的思路:
自然是采用递归,在采用递归之前,我们首先要明白递归的三个要点:
我对这三个问题的回答:
代码思路如下:
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+" ");
}
二叉树的层次遍历顾名思义按照次序依次遍历,具体怎么个遍历法,大家看动图操作
咋说呢,我们要手动去实现层次遍历,我们要借助另一个数据结构,就是队列,队列的特性大家都知道,就是先进先出.我们要利用这个特性帮我们完成层次遍历,我下面会给出层次遍历的思路:
具体代码如下:
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);
}
}
这里针对的遍历的非递归实现是前序遍历,中序遍历,后序遍历.
接下来我们将对这三种方式展开讨论
我接下来给出前序遍历的思路:
我们要利用一个数据结构栈的先进后出特性,来实现这个前序遍历.
具体思路如下:
具体操作步骤
具体代码如下:
public void preOrderNor(TreeNode root) {
if(root == null) {
return;
}
TreeNode cur = root;
Deque<TreeNode> stack = new ArrayDeque<>();
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
System.out.print(cur.val + " ");
cur = cur.left;
}
TreeNode top = stack.pop();
cur = top.right;
}
System.out.println();
}
中序非递归的思路如下:
具体代码思路如下:
public void inOrderNor(TreeNode root) {
if(root == null) {
return;
}
TreeNode cur = root;
Deque<TreeNode> stack = new ArrayDeque<>();
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode top = stack.pop();
System.out.print(top.val + " ");
cur = top.right;
}
System.out.println();
}
后序遍历非递思路如下:
具体的代码如下:
public void postOrderNor(TreeNode root) {
if(root == null) {
return;
}
TreeNode cur = root;
TreeNode prev = null;
Deque<TreeNode> stack = new ArrayDeque<>();
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode top = stack.peek();
if(top.right == null || top.right == prev) {
System.out.print(top.val+" ");
stack.pop();
prev = top;
}else {
cur = top.right;
}
}
System.out.println();
}
获取叶子节点的思路如下:
子问题思路:
什么是叶子节点?
左节点和右节点都为null就为叶子节点.
具体思路:
代码如下:
public int getLeafNodeCount(TreeNode root) {
if(root == null) {
return 0;
}
if(root.left == null && root.right == null){
return 1;
}
int leftSize = getLeafNodeCount(root.left);
int rightSize = getLeafNodeCount(root.right);
return leftSize+rightSize;
}
具体思路如下:
代码如下:
public int getKLevelNodeCount(TreeNode root,int k) {
if(root == null) {
return 0;
}
if(k == 1) {
return 1;
}
int leftSize = getKLevelNodeCount(root.left,k-1);
int rightSize = getKLevelNodeCount(root.right,k-1);
return leftSize+rightSize;
}
具体思路如下:
public int getHeight(TreeNode root) {
if(root == null) {
return 0;
}
return (getHeight(root.left) > getHeight(root.right)) ?
(getHeight(root.left)+1):(getHeight(root.right)+1);
}
具体思路入下:
TreeNode find(TreeNode root, int val) {
if(root == null) {
return null;
}
if(root.val == val) {
return root;
}
TreeNode leftTree = find(root.left,val);
if(leftTree != null) {
return leftTree;
}
TreeNode rightTree = find(root.right,val);
if(rightTree != null) {
return rightTree;
}
return null;//没有找到
}
什么是完全二叉树,大家可以先来看看下面的图示:
完全二叉树只有两种情况,情况一:就是满二叉树,
情况二:最后一层叶子节点没有满。
知道了什么是完全二叉树之后,我们需要去判断一课树是不是完全二叉树,具体的思路如下:
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 tmp = queue.poll();
if(tmp != null) {
return false;
}
}
return true;
}
OJ链接
大概就是
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
首先我们先来划分不相同和相同的一个情况
具体思路如下:
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p == null && q != null || p != null && q == null) {
return false;
}
//走到这里 要么都是空 要么都不是空
if(p == null && q == null) {
return true;
}
if(p.val != q.val) {
return false;
}
//p q都不空 且 值一样
return isSameTree(p.left,q.left)
&& isSameTree(p.right,q.right);
}
OJ链接
具体来说
给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。
二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。
代码如下:
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p == null && q != null || p != null && q == null) {
return false;
}
//走到这里 要么都是空 要么都不是空
if(p == null && q == null) {
return true;
}
if(p.val != q.val) {
return false;
}
//p q都不空 且 值一样
return isSameTree(p.left,q.left)
&& isSameTree(p.right,q.right);
}
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if(root == null || subRoot == null) {
return false;
}
if(isSameTree(root,subRoot)) return true;
if(isSubtree(root.left,subRoot)) return true;
if(isSubtree(root.right,subRoot)) return true;
return false;
}
OJ链接
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;
}
OJ链接
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。
具体解决思路:
1.先求左边高度
2.再求右边高度
3.求高度之差,进行比较绝对值绝对不超过1
具体代码:
public boolean isBalanced(TreeNode root) {
if(root == null) {
return true;
}
int leftH = maxDepth(root.left);
int rightH = maxDepth(root.right);
return Math.abs(leftH-rightH) < 2
&& isBalanced(root.left)
&& isBalanced(root.right);
}
//求高度
public int maxDepth(TreeNode root) {
if(root == null) {
return 0;
}
int leftHeight = maxDepth(root.left);
int rightHeight = maxDepth(root.right);
return (leftHeight > rightHeight) ?
(leftHeight+1):(rightHeight+1);
}
}
OJ链接
对称二叉树的结构如下:
我们的解题思路就是:
1.左子树的左树和右子树的右树是否相等
2.左子树的右树和右子树的左树是否相等
3.重复上面的俩个操作.
代码如下:
public boolean isSymmetric(TreeNode root) {
if(root == null) return true;
return isSymmetricChild(root.left,root.right);
}
public boolean isSymmetricChild(TreeNode leftTree,TreeNode rightTree) {
//情况1
if(leftTree != null && rightTree == null || leftTree == null && rightTree != null) {
return false;
}
//情况2
if(leftTree == null && rightTree == null) {
return true;
}
//走到这里说明都不为空了
if(leftTree.val != rightTree.val) {
return false;
}
return isSymmetricChild(leftTree.left,rightTree.right)
&& isSymmetricChild(leftTree.right,rightTree.left);
}
OJ链接
这道题的意思如下:
编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。
我们用具体的步骤去演示现在的内容.
具体的代码思路:
class TreeNode {
char val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(char val) {
this.val = val;
}
}
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
while (scan.hasNextLine()) {
String str = scan.nextLine();
TreeNode root = createTree(str);
inOrder(root);
}
}
public static int i = 0;
public static TreeNode createTree(String str) {
TreeNode root = null;
if (str.charAt(i) != '#') {
root = new TreeNode(str.charAt(i));
i++;
root.left = createTree(str);
root.right = createTree(str);
} else {
i++;
}
return root;
}
public static void inOrder(TreeNode root) {
if (root == null) {
return;
}
inOrder(root.left);
System.out.print(root.val + " ");
inOrder(root.right);
}
}
OJ链接
在介绍这个题目之前,我们还要了解一下,什么是二叉树的分层遍历
规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。如图所示,遍历的顺序为:ABCDEFGHI。
接下来我们先来实现最简单的分层遍历,再根据这个简单的分层遍历去思考在线oj的题目
具体思路如下:
具体代码如下:
public void levelOrder(TreeNode root) {
if(root == null) {
return;
}
Queue<TreeNode> queue = new LinkedList<>();
//1.根节点入队
queue.offer(root);
while (!queue.isEmpty()) {
//2.队头出队
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);
}
}
}
看了上面的题解之后,我们来解决上面的在线oj.
具体思路:
public List<List<Integer>> levelOrder2(TreeNode root) {
List<List<Integer>> list = new ArrayList<>();
if(root == null) {
return list;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
List<Integer> tmp = new ArrayList<>();
while (size != 0) {
TreeNode cur = queue.poll();
//System.out.print(cur.val + " ");
//tmp.add(cur.val);
size--;
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
}
list.add(tmp);
}
return list;
}
先来对问题进行一定的分析.
目前公共祖先出现的情况我们分为以下三种情况.
OJ链接
具体思路:
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null) {
return null;
}
//先判断根节点 是不是其中的一个
if(root == p || root == q) {
return root;
}
TreeNode leftRet = lowestCommonAncestor(root.left,p,q);
TreeNode rightRet = lowestCommonAncestor(root.right,p,q);
if(leftRet != null && rightRet != null) {
return root;
}else if(leftRet != null) {
return leftRet;
}else if( rightRet != null ){
return rightRet;
}
return null;
}
OJ链接
在写代码之前,我们先来看看,具体我们怎么通过分析得到构造的二叉树.
代码具体思路:
class Solution {
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
public int i = 0;
public TreeNode buildTree(int[] preorder, int[] inorder) {
return buildTreeChild(preorder,inorder,0,inorder.length-1);
}
public TreeNode buildTreeChild(int[] preorder, int[] inorder,
int inbegin,int inend) {
if(inbegin > inend) {
return null;
}
TreeNode root = new TreeNode(preorder[i]);
//找到当前根,在中序遍历的位置
int rootIndex = findIndex(inorder,inbegin,inend,preorder[i]);
i++;
root.left = buildTreeChild(preorder,inorder,inbegin,rootIndex-1);
root.right = buildTreeChild(preorder,inorder,rootIndex+1,inend);
return root;
}
private int findIndex( int[] inorder,int inbegin,int inend, int key) {
for(int i = inbegin;i <= inend; i++) {
if(inorder[i] == key) {
return i;
}
}
return -1;
}
OJ链接
在写代码之前,我们先来看看,具体我们怎么通过分析得到构造的二叉树.
具体思路:
具体代码:
class Solution {
public int i = 0;
public TreeNode buildTree(int[] inorder, int[] postorder) {
i = postorder.length-1;
return buildTreeChild(postorder,inorder,0,inorder.length-1);
}
public TreeNode buildTreeChild(int[] postorder, int[] inorder,
int inbegin,int inend) {
if(inbegin > inend) {
return null;
}
TreeNode root = new TreeNode(postorder[i]);
//找到当前根,在中序遍历的位置
int rootIndex = findIndex(inorder,inbegin,inend,postorder[i]);
i--;
root.right = buildTreeChild(postorder,inorder,rootIndex+1,inend);
root.left = buildTreeChild(postorder,inorder,inbegin,rootIndex-1);
return root;
}
private int findIndex( int[] inorder,int inbegin,int inend, int key) {
for(int i = inbegin;i <= inend; i++) {
if(inorder[i] == key) {
return i;
}
}
return -1;
}
}
}
我们这里
具体思路:
public String tree2str(TreeNode root) {
if(root == null ){
return null;
}
StringBuilder stringBuilder=new StringBuilder();
tree2strChilde(root,stringBuilder);
return stringBuilder.toString();
}
public void tree2strChilde(TreeNode t,StringBuilder stringBuilder) {
if(t == null) {
return;
}
stringBuilder.append(t.val);
if(t.left != null) {
stringBuilder.append("(");
tree2strChilde(t.left,stringBuilder);
stringBuilder.append(")");
}else {
//左边为空了
if(t.right != null) {
//右边不为空
stringBuilder.append("()");
}else {
//右边为空
return ;
}
}
if(t.right == null) {
return;
}else {
stringBuilder.append("(");
tree2strChilde(t.right,stringBuilder);
stringBuilder.append(")");
}
}
树是一种非线性数据结构,由节点和边组成。每个节点最多只有一个父节点,但可以有多个子节点。二叉树是树的一种特殊形式,每个节点最多只有两个子节点。树和二叉树在算法和数据结构中有广泛的应用,例如搜索、排序、数据库、编译器等。它们也可以用于解决各种实际问题,如计算机网络、生物学、图形学等。
扩展:
树和二叉树是计算机科学中非常重要的基础数据结构,它们有许多扩展形式和应用。例如:
平衡树:平衡树是一种特殊的二叉树,它可以保证树的高度始终在一个较小的范围内,从而提高了树的效率。常见的平衡树包括AVL树、红黑树等。
B树和B+树:B树和B+树是一种多叉树,它们被广泛应用于文件系统和数据库中,可以快速地进行插入、删除和查找等操作。
Trie树:Trie树是一种多叉树,它用于存储字符串集合,可以快速地进行字符串匹配和前缀搜索等操作。
堆:堆是一种特殊的树形数据结构,它满足堆序性质,可以用于排序和优先级队列等场景。常见的堆包括最大堆和最小堆等。
赫夫曼树:赫夫曼树是一种特殊的二叉树,它被广泛应用于数据压缩中,可以将数据压缩成更小的体积。
除此之外,树和二叉树还有许多其他的应用,如图形学中的空间分区、计算机网络中的路由算法、生物学中的系统演化等。因此,学习树和二叉树对于理解计算机科学中的许多问题具有重要意义。
期待我们后续的学习,小伙伴们!