持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第9天,点击查看活动详情
最近开始接触与学习数据结构与算法,一方面为了解决课内数据结构课解题思路不清晰的问题,另一方面也是听说左神的大名,故开始跟着左神一起学习数据结构与算法。同时以写博客的形式作为输出,也算是为了对所学的知识能掌握的更深吧
二叉树的相应判断
如何判断一颗二叉树是否是搜索二叉树
什么是搜索二叉树: 对于任何一个节点A来说,它的左节点的值必须小于A的值,它的右节点的值必须大于A的值,同时A节点的左右子树都必须是搜索二叉树,这样整个二叉树就是搜索二叉树
思路:中序遍历二叉树,如果该二叉树是搜索二叉树,那么在中序遍历时出来的结果应该是一个升序的结果,如果不是升序就不是搜索二叉树 (上图的二叉树中序遍历结果就是: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);
}
我们可以联系二叉树的递归代码:
我们进行判断升序操作时正是在递归head.left和head.right中间部分,method(head.left)和method(head.right)分别对应上面的isBST(head.left)和isBST(head.right)
因此我们可以达到中序遍历二叉树同时判断是否是升序的效果
如何判断一棵二叉树是完全二叉树
思路:使用根据深度一层一层遍历二叉树,即宽度优先遍历的思想,在遍历时需要对两种情况进行判断
- 任一节点,如果有右节点没有左节点的话,返回false,不是完全二叉树
- 在不违背(1)情况的条件下,如果遇到了某个节点左右子不全的情况(有左无右,无左无右),那么该结点接下来的所有结点都是叶子结点(没有子节点的节点)
代码实现:
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 那么这整个二叉树就是平衡二叉树
思路:如果是平衡二叉树的话,那么对于树中的任意一个节点x来说,它都需要满足上述的三个条件,那么对于每一个节点x,我们需要从它的两个子树中获取到3个信息来判断是否满足这三个条件
- 节点x的左子树是否是平衡二叉树isBalanced(没有节点默认为true)
- 节点x的右子树是否是平衡二叉树isBalanced(没有节点默认为true)
- 节点x的左子树和右子树的高度height 所以我们可以封装一个returnType(其中包含isBalanced和height)来返回给上一级调用递归函数的对象。
而递归方法(假设名字为process)就是:
- 对于传入方法的节点head,将它的左节点head.left传入process,得到leftReturnType;将它的右节点head.right传入process,得到rightReturnType;
- 判断leftReturnType和rightReturnType中的isBalanced是否为true(左右两个子树是否是二叉平衡树),任意一个是false那么整个树就不是平衡二叉树
- 判断leftReturnType和rightReturnType中height的高度差是否<=1,如果不是的话说明整个树不是平衡二叉树
- 如果满足平衡二叉树的三个条件的话返回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的位置有两种情况
思路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不断向上返回。
代码实现:
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;
}
}
二叉树序列化和反序列化
就是内存里的一棵树如何变成字符串形式,又如何从字符串形式变成内存里的树
先序序列化:将二叉树按照先序的方式序列化成字符串,字符串再按照先序的方式反序列化成二叉树
代码实现:
//序列化函数
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;
}