「左程云-算法与数据结构笔记」| P8 图

606 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第六天,点击查看活动详情。

最近在看左神的数据结构与算法,考虑到视频讲的内容和给出的资料的PDF有些出入,不方便去复习,打算出一个左神的数据结构与算法的笔记系列供大家复习,同时也可以加深自己对于这些知识的掌握,该系列以视频的集数为分隔,今天是第六篇:P8|图


一、二叉树相关概念


1、判断一棵树是否是搜索二叉树


1️⃣ 搜索二叉树

对于每一棵树来说,它的左树的结点都比它小,右树的结点都比它大

2️⃣ 判断方法

方法一

  • 中序打印:在打印的时候,先打印左节点,然后头结点,最后右结点
  • 而搜索二叉树的性质是:右树>头>左树
  • 因此判断方法就是:中序遍历,判断遍历的过程是否是升序的

方法二(此方法和下面的判断平衡二叉树的方法一致)

  • 一棵树是搜索二叉树的四个条件:
    • 它的左子树是搜索二叉树
    • 它的右子树是搜索二叉树
    • 左子树的最大值小于当前结点的值
    • 右子树的最小值大于当前结点的值
  • 因此在进行递归调用的时候,我们需要返回值有:
    • 当前树的最小值
    • 当前树的最大值
    • 该树是否是搜索二叉树

3️⃣ 实现代码

	public static boolean checkBST(Node head){
		if(head == null){
			return true;
		}
		boolean isLeftBst = checkBST(head.left);
		if(!isLeftBst){
			return false;
		}
		if(head.value <= preValue){
			return false;
		}else{
			preValue = head.value;
		}
		return checkBst(head.right);
	}

2、判断一棵树是否是完全二叉树


满二叉树的概念在讲堆排序的时候就讲到了,此处再提一下: 完全二叉树(Completed Binary Tree):所有的节点要么没有子节点、要么只有左孩子、或者有左孩子和右孩子,总之就是不能只有右孩子

1️⃣ 判断方法

  • 层序遍历,层序遍历在[[数据结构与算法 左程云 P7]]中有讲到,我们在做层序遍历的时候添加两个条件:
    • 任意一个接地那有右节点但是没有左节点的,返回false,这个很直观
    • 第二个条件是:当第一次出现了左右孩子不双全,即只有左节点的情况,那么后面遍历过程中所有的结点都应该是叶节点(没有孩子的结点)

image.png

2️⃣ 代码实现

	public static boolean isCBT(Node head) {  
	   if (head == null) {  
	      return true;  
	   }  
	   LinkedList<Node> queue = new LinkedList<>();  
	   boolean leaf = false;  
	   Node l = null;  
	   Node r = null;  
	   queue.add(head);  
	   while (!queue.isEmpty()) {  
	      head = queue.poll();  
	      l = head.left;  
	      r = head.right;  
	      if ((leaf && (l != null || r != null)) || (l == null && r != null)) {  
	         return false;  
	      }  
	      if (l != null) {  
	         queue.add(l);  
	      }  
	      if (r != null) {  
	         queue.add(r);  
	      } else {  
	         leaf = true;  
	      }  
	   }  
	   return true;  
	}

3、判断一棵树是否是满二叉树

  • 获取二叉树的最大深度 depth,就有多少层
  • 获取二叉树的总结点个数 count
  • 如果满足 2^depth - 1 == count,那么就是满二叉树

4、判断一棵树是否是平衡二叉树

1️⃣ 概念

平衡二叉树:对于任何一个子树来说,它的左树和右树的高度差不能超过一

2️⃣分析

  • 现要保证一棵子树是平衡二叉树,那么对于它来说,这个子树是平衡二叉树的条件是:
    • 它的左子树是平衡二叉树
    • 它的右子树是平衡二叉树
    • | 左子树的高度 - 右子树的高度 | ≤ 1
  • 因此在进行递归调用的时候,我们需要的条件有:
    • 该子树是否是平衡二叉树
    • 该子树的高度

3️⃣ 代码实现

	public static boolean isBalanced(Node head) {  
	   return process(head).isBalanced;  
	}  

	// 封装返回值的对象
	public static class ReturnType {  
	   public boolean isBalanced;  
	   public int height;  
	   public ReturnType(boolean isB, int hei) {  
	      isBalanced = isB;  
	      height = hei;  
	   }  
	}  
	  
	public static ReturnType process(Node x) {  
	   if (x == null) {  
	      return new ReturnType(true, 0);  
	   }  
	   ReturnType leftData = process(x.left);  
	   ReturnType rightData = process(x.right);  
	   // 当前树的高度 = 左子树和右子树最大高度 + 1️
	   int height = Math.max(leftData.height, rightData.height);
	   // 是平衡树的三个条件:
	   // ① 左子树是平衡树 
	   // ② 右子树是平衡树 
	   // ③ 两颗子树的高度差小于2
	   boolean isBalanced = leftData.isBalanced && rightData.isBalanced  
	         && Math.abs(leftData.height - rightData.height) < 2;  
	   return new ReturnType(isBalanced, height);  
	}️


二、公共祖先结点


题目:给定两个二叉树的结点node1node2,找到他们的最低公共祖先结点。

思路一

  • 还是使用树形DP,把所有父子关系存入HashMap,即HashMap.put(子,父)
  • 其实有了父与子的关系,即能通过子节点访问到父节点,要找最低公共祖先结点类似于两个非成环链表的公共结点了
  • 不过这里的方法是:把node1到根节点的所有结点放在HashSet中,然后node2往头结点走,每次遍历都去set中查看是否有该结点,第一个有的结点就是最低公共祖先结点

思路二

  • 这个逻辑有点不好理解,我们先看代码
	public static Node lowestAncestor(Node head, Node o1, Node o2) {  
	   if (head == null || head == o1 || head == o2) {  
	      return head;  
	   }  
	   Node left = lowestAncestor(head.left, o1, o2);  
	   Node right = lowestAncestor(head.right, o1, o2);  
	   if (left != null && right != null) {  
	      return head;  
	   }  
	   return left != null ? left : right;  
	}
  • 对于两个结点存在公共祖先的情况我们可以分为两种:
    • 第一种——node1node2的祖先/node2node1的祖先
    • 第二种——node1node2有公共祖先
  • 两种情况图解示例分析如下:

image.png

三、找到一个结点的后继结点


题目:现在有一种新的二叉树结点类型如下:

	public class Node{
		public int value;
		public Node left;
		public Node right;
		public Node parent;
		public Node(int val){
			value = val;
		};
	}

该结构比普通二叉树的节点结构多了一个指向父节点的parent指针 假设有一个Node类型的结点组成的二叉树,树中每个结点的parent指针都正确的指向自己的父节点,根节点的parent指向null 只给一个二叉树中的某个结点node,请返回node的后继结点 后继结点:在中序遍历的序列中,node的下一个结点

实现思路

先要找一个随机结点x的后继结点,存在的情况有以下几种:

1、该结点有右树

  • 它的后继结点是右树上的最左结点

2、该结点没有右树

  • 依次向上判断,当前结点是否是父节点的左子树,一直遍历到是为止,如果遍历结束都没有这种,那它的后继节点就是空

代码实现

	public static Node getSuccessorNode(Node node) {  
	   if (node == null) {  
	      return node;  
	   }  
	   if (node.right != null) {  
	      return getLeftMost(node.right);  
	   } else {  
	      Node parent = node.parent;  
	      while (parent != null && parent.left != node) {  
	         node = parent;  
	         parent = node.parent;  
	      }  
	      return parent;  
	   }  
	}  
	  
	public static Node getLeftMost(Node node) {  
	   if (node == null) {  
	      return node;  
	   }  
	   while (node.left != null) {  
	      node = node.left;  
	   }  
	   return node;  
	}

四、二叉树的序列化与反序列化


实现思路

  • 使用先序遍历/中序遍历/后序遍历
  • value 之间使用 分隔
  • null 使用 # 代替
  • 下面用先序遍历举个例子:

image.png

实现代码

	// 序列化
	public static String serialByPre(Node head) {  
	   if (head == null) {  
	      return "#!";  
	   }  
	   String res = head.value + "!";  
	   res += serialByPre(head.left);  
	   res += serialByPre(head.right);  
	   return res;  
	}

	// 反序列化
	public static Node reconByPreString(String preStr) {  
	   String[] values = preStr.split("!");  
	   Queue<String> queue = new LinkedList<String>();  
	   for (int i = 0; i != values.length; i++) {  
	      queue.offer(values[i]);  
	   }  
	   return reconPreOrder(queue);  
	}  
	  
	public static Node reconPreOrder(Queue<String> queue) {  
	   String value = queue.poll();  
	   if (value.equals("#")) {  
	      return null;  
	   }  
	   Node head = new Node(Integer.valueOf(value));  
	   head.left = reconPreOrder(queue);  
	   head.right = reconPreOrder(queue);  
	   return head;  
	}