文章目录
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();
}
}