跟着左神学算法——数据结构与算法学习日记(六)

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

最近开始接触与学习数据结构与算法,一方面为了解决课内数据结构课解题思路不清晰的问题,另一方面也是听说左神的大名,故开始跟着左神一起学习数据结构与算法。同时以写博客的形式作为输出,也算是为了对所学的知识能掌握的更深吧

二叉树的相应判断

如何判断一颗二叉树是否是搜索二叉树

什么是搜索二叉树: 对于任何一个节点A来说,它的左节点的值必须小于A的值,它的右节点的值必须大于A的值,同时A节点的左右子树都必须是搜索二叉树,这样整个二叉树就是搜索二叉树

image.png

思路:中序遍历二叉树,如果该二叉树是搜索二叉树,那么在中序遍历时出来的结果应该是一个升序的结果,如果不是升序就不是搜索二叉树 (上图的二叉树中序遍历结果就是:0 1 2 3 4 5 6 8 9)

代码实现:

public static int preValue = Integer.MIN_VALUE;//用来记录中序遍历的上一个节点的值

public static boolean isBST(Node head){//Binary Search Tree -> BST
	if(head == null) return true;//传进来了null,这个null的父节点是叶子节点

	boolean isLeftBST = isBST(head.left);
	if(!isLeftBST){
		//当前head节点的左子树不是搜索二叉树
		return false;
	}

	if(head.value<=preValue){
		return false;//不符合升序,不是搜索二叉树
	}else{
		preValue = head.value;
	}

	return isBST(head.right);
}

我们可以联系二叉树的递归代码:

image.png 我们进行判断升序操作时正是在递归head.left和head.right中间部分,method(head.left)和method(head.right)分别对应上面的isBST(head.left)和isBST(head.right) 因此我们可以达到中序遍历二叉树同时判断是否是升序的效果

如何判断一棵二叉树是完全二叉树

思路:使用根据深度一层一层遍历二叉树,即宽度优先遍历的思想,在遍历时需要对两种情况进行判断

  1. 任一节点,如果有右节点没有左节点的话,返回false,不是完全二叉树
  2. 在不违背(1)情况的条件下,如果遇到了某个节点左右子不全的情况(有左无右,无左无右),那么该结点接下来的所有结点都是叶子结点(没有子节点的节点)

image.png

代码实现:

public static boolean isCBT(Node head){
	if(head == null) return ture;
	QUeue<Node> queue = new LinkedList<>();
	//leaf变量用来记录是否遇到过左右孩子不全的节点
	boolean leaf = false;
	Node l = null;//左孩子
	Node r = null;//右孩子
	queue.add(head);

	while(queue.isEmpty()){
		head = queue.poll();
		l = head.lrft;
		r = head.right;
		if(
			(leaf && (l!=null || r != null))
			//已经遇到过左右孩子不全的情况可是当前节点不是叶子节点
			||
			(r!=null && l==null)
			//该结点有右节点,没有左节点,不是完全二叉树
		) return false;

		if(r!=null){
			queue.add(r);
		}
		if(l!=null){
			queue.add(l);
		}
		if(l==null || r==null){
			leaf = true;
		}
	}
	return true;
}

如何判断一颗二叉树是否是满二叉树

什么是满二叉树:每一层的节点都是满的,若是要判断满二叉树,只需要得到二叉树的节点数量Nodes和深度k,判断符不符合公式:Nodes = 2^k-1即可

采用递归思想: 对于每一个节点,都向它的左右子树获取两个信息,深度k和节点数Nodes,最后返回给主函数二叉树的深度k和节点数量Nodes

代码实现:

public static class Info{
	public int Nodes;
	public int k;

	public static class(int Nodes , int k){
		this.Nodes = Nodes;
		this.k = k;
	}
}

public static Info process(Node x){
	if(x == null) return new Info(0,0);

	Info leftInfo = process(x.left);
	Info rightInfo = process(x.right);

	int k = Math.max(leftInfo.k,rightInfo.k)+1;
	int Nodes = leftInfo.Nodes + rightInfo.Nodes+1;

	return new Info(Nodes,k);
}

如何判断一颗二叉树是否是平衡二叉树

什么是平衡二叉树:对于任何一个节点来说,如果它满足如下三个条件:

  1. 它的左子树是平衡二叉树
  2. 它的右子数是平衡二叉树
  3. 它的左右子树的高度差<=1 那么这整个二叉树就是平衡二叉树

思路:如果是平衡二叉树的话,那么对于树中的任意一个节点x来说,它都需要满足上述的三个条件,那么对于每一个节点x,我们需要从它的两个子树中获取到3个信息来判断是否满足这三个条件

  1. 节点x的左子树是否是平衡二叉树isBalanced(没有节点默认为true)
  2. 节点x的右子树是否是平衡二叉树isBalanced(没有节点默认为true)
  3. 节点x的左子树和右子树的高度height 所以我们可以封装一个returnType(其中包含isBalanced和height)来返回给上一级调用递归函数的对象。

而递归方法(假设名字为process)就是:

  1. 对于传入方法的节点head,将它的左节点head.left传入process,得到leftReturnType;将它的右节点head.right传入process,得到rightReturnType;
  2. 判断leftReturnType和rightReturnType中的isBalanced是否为true(左右两个子树是否是二叉平衡树),任意一个是false那么整个树就不是平衡二叉树
  3. 判断leftReturnType和rightReturnType中height的高度差是否<=1,如果不是的话说明整个树不是平衡二叉树
  4. 如果满足平衡二叉树的三个条件的话返回true

代码实现:

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 leftReturnType = process(x.left);
	ReturnType rightReturnType = process(x.right);

	int height =
	Math.max(leftReturnType.height,rightReturnType.height)+1;
		
	boolean isBalanced = leftReturnType.isBalanced &&
	rightReturnType.isBalanced && 
	Math.abs(leftReturnType.height - rightReturnType.height )<2;

	return ReturnType(isBalanced , height );
}

//主函数直接把根节点传入即可
public static boolean isBBT(Node head){
	return process(head);
}

二叉树的公共祖先问题

最低公共祖先

题目:给定两个二叉树的节点node1和node2,找到他们的最低公共祖先节点

什么是最低公共祖先节点:两个节点往上回溯,两条路线第一次汇聚的点就是最低公共祖先节点

node1和node2的位置有两种情况

image.png

思路1:首先实现节点向上回溯的功能,从根节点开始遍历二叉树,在遍历的过程中使用一个HashMap记录每一个节点和它对应的父节点,根节点的父节点默认为它本身,然后从o1开始(o2也行)根据HashMap向上回溯,直到回到根节点为止。在这个过程中使用一个Set集合记录下回溯的路程上经过的节点。 然后从o2开始根据HashMap向上回溯,每向上回溯一次就判断一下当前到达的节点是否存在于Set集合中,如果存在则当前到达的节点就是最低公共祖先。

思路2:从head节点开始从下递归遍历,如果遇到了O1和O2,递归函数process把O1和O2返回给上一级递归函数,如果没遇到O1或者O2,就返回null给上一级递归函数。 O1和O2一定要在同一层递归函数里返回给上一级递归函数,上一级递归函数判断两个返回结果是否是O1和O2,如果是的话,说明上一级递归函数里的head就是O1和O2的最低公共祖先,将head不断向上返回。

image.png

代码实现:

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;
}

二叉树的后继节点问题

在二叉树中找到一个节点的后继节点

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

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的下一个节点叫做node的后继节点,上一个节点叫做node的前驱节点

思路:对于二叉树的任何一个节点x来说,如果它有右树,它的后继节点一定是右数上的最左节点;如果它没有右节点,设它的父节点为parent,如果parent是parent的父节点parent.parent的左孩子的话,那么parent.parent就是x的后继节点。

代码实现:

public static Node getSuccessorNode(Node node){
	if(node == null) return node;

	if(node.right != null){
		//有右子树,直接遍历到右子树的最左节点
		while(node.left!=null){
			node = node.left;
		}
		return node;
	}else{
		Node parent = node.parent;
		while(parent!=null || parnet.left != node){
			node = parent;
			parent = node.parent;
		}
		return parent;
	}

}

二叉树序列化和反序列化

就是内存里的一棵树如何变成字符串形式,又如何从字符串形式变成内存里的树

先序序列化:将二叉树按照先序的方式序列化成字符串,字符串再按照先序的方式反序列化成二叉树

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.add(values[i]);
	}

	return reconPreOrder(queue);
}

public static Node reconPreOrder(Queue<String> queue){
	String value = queue.poll();

	if(value.equals("#")){
		return null;//遇到#说明节点是当前节点是null
	}
	
	Node head = new Node(Integer.valueOf(value));
	head.left = reconPreOrder(queue);
	head.right = reconPreOrder(queue);
	return head;
}