目录
1. 树的概念
1.1 概念
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
1.2 数的一些重要概念
①. 度 : 该节点的子树个数
②. 叶子结点或终端结点:度为0的结点称为叶结点
③. 树的高度或深度:树中结点的最大层次
④. 非终端结点或分支结点:度不为0的结点
⑤. 孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点
2.二叉树
2.1 二叉树的概念:
一棵二叉树是结点的一个有限集合,该集合:
1. 或者为空
2. 或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
上图就是一个二叉树
2.2 俩种特殊的二叉树
1.完全二叉树
简而言之就是向左靠齐 没有缺失
2.满二叉树
每层的结点数都达到最大值
2.3 二叉树的性质
1,非空二叉树的第 i 层 有2^(i - 1)的节点
2,深度为k的二叉树 最大节点数是 2^k - 1
3,若叶节点个数为 n0 那么 n0 = n2 + 1
4,有n个节点的完全二叉树的深度k 为 log(n + 1)向上取整(log底为2)利用性质2推导
1,一个具有767个节点的完全二叉树,其叶子节点个数为()
A 383
B 384
C 385
D 386
答案:完全二叉树 没有度为1的节点 那么767 = n0 + n2 又因为 n0 = n2 + 1;
所以 答案是 B
2,.一棵完全二叉树的节点数为531个,那么这棵树的高度为( )
A 11
B 10
C 8
D 12
利用性质4 答案可知是
2.4 二叉树的存储
二叉树的存储结构分为:顺序存储和类似于链表的链式存储。
我们先学习链式存储 且使用的是孩子表示法
2.5 二叉树的基本操作
先建立一个二叉树
代码如下:
利用静态内部类 给树定义 creatTree是建立一个二叉树
public class MyTree {
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;
}
}
2.5.2 二叉树的遍历
二叉树遍历可以大致分为: 前序遍历 中序遍历 后序遍历 层序遍历
我们先来学习前中后:
NLR:前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点--->根的左子树--->根的右子树。
LNR:中序遍历(Inorder Traversal)——根的左子树--->根节点--->根的右子树。
LRN:后序遍历(Postorder Traversal)——根的左子树--->根的右子树--->根节点。
大家可以通过根结点的访问顺序来记忆
层序遍历就是从上到下 从左到右 依次遍历!
我们来通过代码来实现此遍历:
//前序遍历 利用了遍历思想
List<Character> cur = new ArrayList<>();
public List<Character> preoderTraversal(TreeNode root){
if(root == null){
return null;
}
cur.add(root.val);
preoderTraversal(root.left);
preoderTraversal(root.right);
return cur;
}
在此基础上我们进阶一下,利用子问题思想来解决 前序遍历
public List<Character> preoderTraversal1(TreeNode root){
List<Character> ret = new ArrayList<>();
if(root == null){
return ret;
}
ret.add(root.val);
List<Character> leftTree = preoderTraversal1(root.left);
ret.addAll(leftTree);
List<Character> rightTree = preoderTraversal1(root.right);
ret.addAll(rightTree);
return ret;
}
我们通过画图来理解一下这个子问题的思想
这俩种思想我们大致了解了,那么我们再通过求叶子节点来训练这俩种思想
public static int size = 0;
//遍历思想
public int getLeafNodeCount(TreeNode root){
if(root == null){
return 0;
}
if(root.left == null && root.right == null){
size ++ ;
}
getLeafNodeCount(root.left);
getLeafNodeCount(root.right);
return size;
}
这是遍历思想,将每一个节点全部遍历一遍 来size++
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) ;
}
子问题思想,将每个树分为左右子树
再看这一题 求k层的节点个数
我们先讲思路,如果想求k层的节点,我们就可以通过每次递归将k-1,直到k == 1的时候再return1
代码如下:
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);
}
再整一个~
求树的高度:
这题我们可以利用子问题思想,将左右子树的节点个数进行比较,return较大的数
代码如下
public int getHeight(TreeNode root){
if(root == null){
return 0;
}
int leftSize = getHeight(root.left);
int rightSize = getHeight(root.right);
return leftSize > rightSize ? leftSize + 1 : rightSize + 1;
}
这道题需要注意的是 不能在return语句中使用递归,因为会发生重复递归,在做oj题的时候可能会出现超时问题!
检测value值是否存在
public TreeNode find(TreeNode root,int val){
if(root == null){
return null;
}
if(root.val == val){
return root;
}
TreeNode ret1 = find(root.left,val);
if(ret1 != null){
return ret1;
}
TreeNode ret2 = find(root.right,val);
if(ret2 != null){
return ret2;
}
return null;
}
先讲思路:判断俩个二叉树是否相同,先判断 是否都是空树,都是空树的话 返回true,接着判断俩个树是否结构都相同?真返回true,假返回false。到下一步就来判断其val值是否相同!
代码如下:
public boolean isSameTree(TreeNode p, TreeNode q){
if(p == null && q == null){
return true;
}
if(p != null && q == null || p == null && q != null){
return false;
}
if(p.val != q.val){
return false;
}
return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
}
思路:我们依旧可以利用子问题思路,先将其分为左子树与右子树,再分别判断是否相等。
public boolean isSameTree(TreeNode p, TreeNode q){
if(p == null && q == null){
return true;
}
if(p != null && q == null || p == null && q != null){
return false;
}
if(p.val != q.val){
return false;
}
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;
}
思路:将每一层的子树翻转
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;
}
这一道题比较简单哈~
先讲思路:先判断根节点是否平衡,在判断左右子树是否平衡
但这里有缺陷,时间复杂度是O(N^2)
public int maxDepth(TreeNode root) {
if(root == null){
return 0;
}
int leftTree = maxDepth(root.left);
int rightTree = maxDepth(root.right);
return leftTree > rightTree ?
leftTree + 1 :
rightTree + 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);
}
我们来写个时间复杂度是O(N) 的代码:
简而言之 在遍历的时候就判断是否平衡
public int maxDepth2(TreeNode root) {
if (root == null) {
return 0;
}
int leftH = maxDepth2(root.left);
int rightH = maxDepth2(root.right);
if(Math.abs(leftH - rightH) <= 1 && leftH >= 0 && rightH >= 0){
return Math.max(leftH,rightH) + 1;
}else{
return -1;
}
}
public boolean isBalanced2(TreeNode root) {
if(root == null){
return true;
}
return maxDepth2(root) >= 0;
}
思路:子问题思想
public boolean isSymmetric(TreeNode root) {
if(root == null){
return true;
}
return isSymmetricChild(root.left,root.right);
}
private boolean isSymmetricChild(TreeNode leftTree,TreeNode rightTree){
if(leftTree == null && rightTree == null){
return true;
}
if(leftTree == null){
return false;
}
if(leftTree != null && rightTree == null ||
leftTree != null && rightTree == null){
return false;
}
if(leftTree.val != rightTree.val){
return false;
}
return isSymmetricChild(leftTree.left,rightTree.right) &&
isSymmetricChild(leftTree.right,rightTree.left);
}
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> list = new ArrayList<>();
if(root == null){
return list;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()){
List<Integer> tmp = new LinkedList<>();
int size = queue.size();
while(size > 0) {
TreeNode cur = queue.poll();
size -- ;
tmp.add(cur.val);
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
}
list.add(tmp);
}
return list;
}
思路:
第一次先将root放到queue中
list用于返回 tmp用于记录当前层的元素
然后通过记录队列queue的size 每次tmp记录-- 来实现当前层的元素记录!
判断一个树是否是完全二叉树?
思路:这道题思路有点难,其关键在于其思想:无论cur的左右节点是否为空 都放入队列!
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();
queue.offer(cur.left);
queue.offer(cur.right);
if(cur == null){
break;
}
}
while(!queue.isEmpty()){
TreeNode cur = queue.poll();
if(cur != null){
return false;
}
}
return true;
}
该题意是通过前序遍历,来建立一个二叉树,并将其打印
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 注意 hasNext 和 hasNextLine 的区别
while (in.hasNextLine()) { // 注意 while 处理多个 case
String str = in.nextLine();
TreeNode root = creatTree(str);
inorder(root);
}
}
static class TreeNode {
public char val ;
public TreeNode left ;
public TreeNode right;
public TreeNode(char val) {
this.val = val;
}
}
private static int i = 0;
public static TreeNode creatTree(String str){
TreeNode root = null;
if(str == null){
return null;
}
char ch = str.charAt(i);
if(ch != '#'){
root = new TreeNode(ch);
i++;
root.left = creatTree(str);
root.right = creatTree(str);
}else{
i++;
}
return root;
}
private static void inorder(TreeNode root){
if(root == null){
return;
}
inorder(root.left);
System.out.print(root.val + " ");
inorder(root.right);
}
236. 二叉树的最近公共祖先 - 力扣(LeetCode)
在getPath()函数中的思路是这样的:
先判断root,subRoot是否是空的,然后先建立一个栈,将第一个root放进栈中,在判断root是否是目标subRoot,再进行递归,这里的递归意思是如果是返回的是ture就代表,找到了目标subRoot,如果找到了进入if的语句return true;如果左右俩个都没找到,就将该节点pop出去,return false!
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || p == null || q == null){
return null;
}
Stack<TreeNode> s1 = new Stack<>();
Stack<TreeNode> s2 = new Stack<>();
getPath(root,p,s1);
getPath(root,q,s2);
int size1 = s1.size();
int size2 = s2.size();
if(size1 > size2){
int size = size1 - size2;
while(size > 0){
s1.pop();
size--;
}
}else{
int size = size2 - size1;
while(size > 0){
s2.pop();
size--;
}
}
while(!s1.empty()){
if(s1.peek() == s2.peek()){
break;
}else{
s1.pop();
s2.pop();
}
}
return s1.peek();
}
private boolean getPath(TreeNode root, TreeNode subRoot, Stack<TreeNode> stack){
if(root == null || subRoot == null){
return false;
}
stack.push(root);
if(root == subRoot){
return true;
}
if(getPath(root.left,subRoot,stack)){
return true;
}
if(getPath(root.right,subRoot,stack)){
return true;
}
stack.pop();
return false;
}
第二种思路:
将其分为三种情况:
1.p或q有一个在root上,直接返回root
2.p q 在俩侧,递归俩次,返回root
3.在一侧,再一次递归,如果找到了,那么就返回第一次的TreeNode
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || p == null || q == null){
return null;
}
if(root == p || root == q){
return root;
}
TreeNode leftTree = lowestCommonAncestor(root.left,p,q);
TreeNode rightTree = lowestCommonAncestor(root.right,p,q);
if(leftTree != null && rightTree != null){
return root;
}
TreeNode Tree = null;
if(leftTree == null && rightTree != null){
Tree = lowestCommonAncestor(rightTree,p,q);
}
if(rightTree == null && leftTree != null){
Tree = lowestCommonAncestor(leftTree,p,q);
}
return Tree;
}
二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com)
TreeNode prev = null;
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null){
return null;
}
convertChile(pRootOfTree);
//这个时候已经连起来了,下一步要找头节点
TreeNode head = null;
while(pRootOfTree.left != null){
pRootOfTree = pRootOfTree.left;
}
head = pRootOfTree;
return head;
}
public void convertChile(TreeNode root){
if(root == null){
return;
}
convertChile(root.left);
root.left = prev;
if(prev != null){
prev.right = root;
}
prev = root;
convertChile(root.right);
}