树及相关常见问题(二)

1. 二叉搜索树概念

  • 二叉树的一种变形
  • 对于BST中的任意一个节点,其左子节点值比它小,右子节点值比它大

在这里插入图片描述

  • BST的节点类型和普通二叉树一样
  • 对BST进行中序遍历可以得到一个升序序列

2. BST的一些操作

2.1 查找某个元素

public static Node find(Node root,Node target) {
		if(root==null)
			return null;
		if(root.val<target.val)
			return find(root.right, target);
		else if(root.val>target.val)
			return find(root.left, target);
		else if(root.val==target.val)
			return root;
		return null;
			
		
	}
public static Node find(Node root,Node target) {
		if(root==null)
			return null;
		while(root!=null) {
			if(root.val<target.val)
				root=root.right;
			else if(root.val>target.val)
				root=root.left;
			else if(root.val==target.val)
				return root;
		}
		return null;
	}

2.2 查找最小的元素

/*
	 * 一直沿着左子树找 找到最左边的节点,该节点没有左子节点
	 */
	public static Node findMin(Node root) {
		if(root==null)
			return null;
		if(root.left==null)
			return root;
		return findLeast(root.left);
	}
/*
	 * 一直沿着左子树找 找到最左边的节点,该节点没有左子节点
	 */
	public static Node findMin(Node root) {
		if(root==null)
			return null;
		while(root.left!=null) {
			
			root=root.left;
		}
		return root;
	}

2.2 查找最大的元素

/*
	 * 一直沿着右子树找 找到最右边的节点,该节点没有右子节点
	 */
	public static Node findMax(Node root) {
		if(root==null)
			return null;
		if(root.right==null)
			return root;
		return findMax(root.right);
	}
/*
	 * 一直沿着右子树找 找到最右边的节点,该节点没有右子节点
	 */
	public static Node findMax(Node root) {
		if(root==null)
			return null;
		while(root.right!=null) {
			
			root=root.right;
		}
		return root;
	}

2.4 寻找中序序列的前驱和后继

情形1:节点X的左右子节点都存在
对于BST中的一个节点X而言,中序序列中在该节点前面的是其左子树中的最大值,在该节点后面的是其右子树中的最小值
在这里插入图片描述

情形2:节点X的左子节点不存在(左子节点不存在只影响前驱)

X的前驱是其第一个左祖先节点
寻找过程:一直遍历X的父节点,直到遍历到的节点是某个节点Y的右子节点,则Y是前驱,如果找不到Y,说明X没有前驱,也即X是最小的
在这里插入图片描述

情形2:节点X的右子节点不存在(右子节点不存在只影响后继)

X的后继是其第一个右祖先节点
寻找过程:一直遍历X的父节点,直到遍历到的节点是某个节点Y的左子节点,则Y是后继,如果找不到Y,说明X没有后继,也即X是最大的
在这里插入图片描述

2.5 插入元素

 public TreeNode insertIntoBST(TreeNode root, int val) {
        if(root==null)
			return new TreeNode(val);
		if(root.val<val)
			root.right=insertIntoBST(root.right, val);
		else if(root.val>val)
			root.left=insertIntoBST(root.left, val);
		return root;
    }
//先找到一个适合插入的位置  注意要用一个pre保存待插入位置的父节点  
//之后设置父节点的左子节点或右子节点为新节点即可
public TreeNode insertIntoBST(TreeNode root, int val) {
        if(root==null)
			return new TreeNode(val);
		TreeNode pre=null;
        TreeNode newNode=new TreeNode(val);
        TreeNode tmp=root;
		while(tmp!=null) {
			pre=tmp;
			if(tmp.val<newNode.val)
				tmp=tmp.right;
			else if(tmp.val>newNode.val)
				tmp=tmp.left;
			
		}
		if(pre.left==null&&pre.val>newNode.val)
			pre.left=newNode;
		else if(pre.right==null&&pre.val<newNode.val)
			pre.right=newNode;
		return root;
    }

2.6 删除元素

情形1:被删除节点是叶子节点,直接删除,令待删除节点父节点的指针为null
在这里插入图片描述

情形二:被删除节点有一个子节点,将待删除节点的子节点返回给父节点
在这里插入图片描述

情形三:被删除节点有两个子节点,删除该节点,并用待删除节点左子树中的最大节点来代替被删除节点(也可以用待删除节点右子树中的最小节点来代替)
在这里插入图片描述

//存在左右子节点时 用左子树中的最大节点来替代待删除节点
class Solution {
    public TreeNode deleteNode(TreeNode root, int key) {
        if(root==null)
			return null;
		if(root.val<key)
			root.right=deleteNode(root.right, key);//去root的右子树中删除
		else if(root.val>key)
			root.left=deleteNode(root.left, key);//去root的左子树中删除
		else if(root.val==key) {
			if(root.left==null&&root.right==null) 
				return null;
			else if(root.left==null)
				return root.right;
			else if(root.right==null)
				return root.left;
			else {//左子节点和右子节点都存在
				TreeNode tmp=root;//保存指向target节点的引用
				root=getMax(tmp.left);//root指向待删除节点左子树中的最大节点X
				root.left=deleteMax(tmp.left);//X的左子树为去掉自己后的新子树
				root.right=tmp.right;//将原来target节点的右子树连接到X上
			}
		}
		return root;
    }
    /*
	 * 寻找以node为根节点的的树中的最大节点
	 */
	public TreeNode getMax(TreeNode node) {
		if(node==null)
			return null;
		while(node.right!=null)
			node=node.right;
		return node;
	}
	/*
	 * 删除以node为根节点的的树中的最大节点
	 */
	public TreeNode deleteMax(TreeNode node) {
		if(node.right==null)//没有右子节点 则node本身就是最小的
			return node.left;
		node.right=deleteMax(node.right);//递归删除
		return node;
	}
    
}
//存在左右子节点时 用右子树中的最小节点替代待删除节点
class Solution {
    public TreeNode deleteNode(TreeNode root, int key) {
        if(root==null)
			return null;
		if(root.val<key)
			root.right=deleteNode(root.right, key);//去root的右子树中删除
		else if(root.val>key)
			root.left=deleteNode(root.left, key);//去root的左子树中删除
		else if(root.val==key) {
			if(root.left==null&&root.right==null) 
				return null;
			else if(root.left==null)
				return root.right;
			else if(root.right==null)
				return root.left;
			else {//左子节点和右子节点都存在
				TreeNode tmp=root;//保存指向target节点的引用
				root=getMin(tmp.right);//root指向待删除节点右子树中的最小节点X
				root.right=deleteMin(tmp.right);//X的右子树为去掉自己后的新子树
				root.left=tmp.left;//将原来target节点的左子树连接到X上
			}
		}
		return root;
    }
    /*
	 * 寻找以node为根节点的的树中的最小节点
	 */
	public TreeNode getMin(TreeNode node) {
		if(node==null)
			return null;
		while(node.left!=null)
			node=node.left;
		return node;
	}
	/*
	 * 删除以node为根节点的的树中的最小节点
	 */
	public TreeNode deleteMin(TreeNode node) {
		if(node.left==null)//没有左子节点 则node本身就是最小的
			return node.right;
		node.left=deleteMin(node.left);//递归删除
		return node;
	}
    
}

在这里插入图片描述

2.7 验证二叉搜索树

class Solution {
    int pre;
    boolean flag=false;
    public boolean isValidBST(TreeNode root) {
        if(root==null)
            return true;
        if(!isValidBST(root.left))
            return false;
        if(flag&&root.val<=pre)//flag为true表示pre已经赋值过了
            return false;
        pre=root.val;
        flag=true;
        if(!isValidBST(root.right))
            return false;
        return true;
    }
}
//O(n)
//O(1)

2.8 二叉搜索树转化为循环双向链表

在这里插入图片描述
在这里插入图片描述

从图中可以看出,next指针的指向的节点形成的序列是升序的,因此可以采用中序遍历来处理:

class Solution {
    Node pre,head;
    public Node treeToDoublyList(Node root) {
        if(root==null)
            return null;
        dfs(root);
        head.left=pre;//pre最后指向最后一个节点
        pre.right=head;
        return head;
    }
    public void dfs(Node cur){
        if(cur==null)
            return;
        dfs(cur.left);
        if(pre!=null)//cur==null的话说明当前cur是第一个节点 第一个节点没有pre
            pre.right=cur;
        else
            head=cur;
        cur.left=pre;
        pre=cur;
        dfs(cur.right);
    }
}

2.9 有序数组转化为高度平衡的二叉搜索树

高度平衡:左右子树高度差小于1

public TreeNode sortedArrayToBST(int[] nums) {
        return build(nums,0,nums.length-1);
    }
    public TreeNode build(int[] nums,int left,int right){
        if(left>right){
            return null;
        }
        int mid=left+(right-left)/2;//选择中间位置(靠左)
        //left+(right-left+1)/2; 选择中间位置(靠右)
        TreeNode root=new TreeNode(nums[mid]);
        root.left=build(nums,left,mid-1);
        root.right=build(nums,mid+1,right);
        return root;
    }

2.10 求二叉搜索树中第K小的节点

中序遍历+计数

class Solution {
    int cnt=0;
    int ans;
    public int kthSmallest(TreeNode root, int k) {
        dfs(root,k);
        return ans;
    }
    public void dfs(TreeNode root,int k){
        if(root==null)
            return;
        dfs(root.left,k);
        cnt++;
        if(cnt==k){
            ans=root.val;
            return;
        }
        dfs(root.right,k);
    }
}

2.11 求二叉搜索树中第K大的节点

逆序中序遍历+计数

class Solution {
    int cnt=0;
    int ans;
    public int kthLargest(TreeNode root, int k) {
        dfs(root,k);
        return ans;
    }
    public void dfs(TreeNode root,int k){
        if(root==null)
            return;
        dfs(root.right,k);//先递归遍历右子树
        cnt++;
        if(cnt==k){
            ans=root.val;
            return;
        }
        dfs(root.left,k);
    }
}

2.12 修剪BST,使得修剪后的树中的节点处于给定的范围

class Solution {
    public TreeNode trimBST(TreeNode root, int low, int high) {
        if(root==null)
            return null;
        if(root.val<low)//当前节点小于下界  则修剪后的树一定出现在右子树中
            return trimBST(root.right,low,high);
        if(root.val>high)//当前节点大于上界  则修剪后的树一定出现在左子树中
            return trimBST(root.left,low,high);
        //当前root节点在范围内  则修剪它的左右子树
        root.left=trimBST(root.left,low,high);
        root.right=trimBST(root.right,low,high);
        return root;
    }
}

3. AVL树概念

  • 是一棵二叉搜索树
  • 对任意节点X,其左子树和右子树高度差不超过1

在这里插入图片描述

当向AVL树中插入一个节点时,可能会导致某个节点形成的子树不平衡,假设不平衡的子树的根节点是X,则有以下几种情况:

情况1:在X的左节点的左子树中插入新节点(左左情况)
在这里插入图片描述

解决:需要右旋转:
节点p 右旋时,会携带自己的右子树,向右旋转到q 的右子树位置,q 的右子树被抛弃,此时p 右旋后左子树正好空闲,将q 的右子树放在p 的左子树位置,旋转后的树根为q
在这里插入图片描述

情况2:在X的右节点的右子树中插入新节点(右右情况)
在这里插入图片描述
解决:需要左旋转:节点p 左旋时,携带自己的左子树,向左旋转到q 的左子树位置,q 的左子树被抛弃,此时p 左旋后右子树正好空闲,将q 的左子树放在p 的右子树位置,旋转后的树根为q
在这里插入图片描述

情况3:在X的左节点的右子树中插入新节点(左右情况)

节点8是X节点,新节点是7
解决:先左旋再右旋
在这里插入图片描述

情况4:在X的右节点的左子树中插入新节点(右左情况)
节点4是X节点,新节点是5
解决:先右旋再左旋

在这里插入图片描述

情况整理:
在这里插入图片描述

4. AVL树代码实现

package datastructure.tree;


public class AVLTree {
	private class Node{
		int data;//节点值
		int height;//某个节点形成子树的高度
		Node left;//左孩子指针
		Node right;//右孩子指针
		Node(int data){
			this.data=data;
			this.height=1;
		}
		
	}
	private Node root;//AVL树的根节点
	public void insert(int data) {//插入一个节点
		this.root=insert(this.root,data);
	}
	private Node insert(Node node,int item) {
		//1 2 3处代码类似于BST的操作
		if(node==null) {//找到了插入的位置  1
			Node add=new Node(item);
			return add;
		}
		if(node.data>item) {//2
			node.left=insert(node.left,item);
		}
		if(node.data<item) {//3
			node.right=insert(node.right,item);
		}
		
		//经过1 2 3 步 节点插入完成 下面需要判断是否平衡
		
		//更新以node为节点的子树的高度
		node.height=Math.max(height(node.left),height(node.right))+1;
		int bf=bf(node);//以node为节点的子树的左右子树的高度差
		
		//LL case 左左情况 
		//bf>1: node节点形成的子树左子树高
		//item<node.left.data: 在node左子节点的左子树中添加的
		if(bf>1&&item<node.left.data)
			return rightRotate(node);//需要右旋
		
		
		//RR case 右右情况 
		//bf<-1: node节点形成的子树右子树高
		//item<node.left.data: 在node右子节点的右子树中添加的
		if(bf<-1&&item>node.right.data)
			return leftRotate(node);//需要左旋
		
		
		//RL case 右左情况
		//bf<-1: node节点形成的子树右子树高
		//item<node.right.data: 在node右子节点的左子树中添加的
		if(bf<-1&&item<node.right.data) {
			node.right=rightRotate(node.right);//先右旋
			return leftRotate(node);//再左旋
		}
		
		//LR case 左右情况
		//bf>1: node节点形成的子树左子树高
		//item>node.left.data: 在node左子节点的右子树中添加的
		if(bf>1&&item>node.left.data) {
			node.left=leftRotate(node.left);//先左旋
			return rightRotate(node);//再右旋
		}
			
		return node;
	}
	//打印AVL树
	public void display() { 
		this.display(this.root);
		System.out.println(this.root.height);//输出高度
	}
	//打印AVL树
	private void display (Node node) {
		String str="";
		if(node.left!=null)
			str+=node.left.data+"=>";
		else
			str+="null=>";
		str+=node.data+"";
		if(node.right!=null)
			str+="<="+node.right.data;
		else
			str+="<=null";
		System.out.println(str);
		if(node.left!=null)
			display(node.left);
		if(node.right!=null)
			display(node.right);
	}
	private int height(Node node) {
		if(node==null) {
			return 0;
		}
		return node.height;//返回树高度
		
	}
	
	//获取node作为根节点的左右子树的高度差
	private int bf(Node node) {
		if(node==null)
			return 0;
		return height(node.left)-height(node.right);
	}
	
	/*
	 * 右旋的具体操作
	 * q是p的左子树
	 * 节点p右旋时,会携带自己的右子树,向右旋转到q的右子树位置,q 的右子树被抛弃,
	 * 此时p 右旋后左子树正好空闲,将q 的右子树放在p 的左子树位置,旋转后的树根为q
	 */
	private Node rightRotate(Node c) {
		//对节点c进行右旋  c相当于上面解释的q
		Node b=c.left;//b指向c的左子树  相当于上面解释的q
		Node T3=b.right;//保存q的右子树
		
		b.right=c;//p向右旋转到q的右子树位置
		c.left=T3;//q的右子树连接到p的左子树上
		c.height=Math.max(height(c.left),height(c.right))+1;
		b.height=Math.max(height(b.left),height(b.right))+1;
		return b;//b(q)成为旋转后的新根节点
		
	}
	/*
	 * 左旋的具体操作
	 * q是p的右子树
	 * 节点p左旋时,携带自己的左子树,向左旋转到q的左子树位置,q的左子树被抛弃,
	 * 此时p 左旋后右子树正好空闲,将q 的左子树放在p 的右子树位置,旋转后的树根为q
	 */
	private Node leftRotate(Node c) {
		//对节点c进行右旋  c相当于上面解释的q
		Node b=c.right;///b指向c的右子树  相当于上面解释的q
		Node T3=b.left;//保存q的左子树
		
		b.left=c;//p向右旋转到q的左子树位置
		c.right=T3;//q的左子树连接到p的右子树上
		c.height=Math.max(height(c.left),height(c.right))+1;//更新高度
		b.height=Math.max(height(b.left),height(b.right))+1;
		return b;//b(q)成为旋转后的新根节点
		
	}
	public static void main(String[] args) {
		AVLTree tree=new AVLTree();
		tree.insert(20);
		tree.insert(25);
		tree.insert(30);
		tree.insert(10);
		tree.insert(5);
		tree.insert(15);
		tree.insert(27);
		tree.insert(19);
		tree.insert(16);
		tree.display();
	}

}

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodePanda@GPF

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值