概念
二分搜索树也称为BST(Binary Search Tree)。 在JDK中,BST一般没有重复元素。
二分搜索树的最大特点在于节点值上:
左子树的值 < 根节点的值 < 右子树的值
元素的插入
在二分搜索树中,插入的新元素最终都放在了叶子节点。
在插入的过程中,不断和根节点比大小;
若新插入的值小于根节点,左树插入;
若新插入的值大于根节点的值,在右树插入;
直到碰到空节点。
private TreeNode add(TreeNode root,int val){
//base case
if(root ==null){
TreeNode node = new TreeNode(val);
size ++;
return node;
} else if (val< root.val) {
//在左子树中插入
root.left= add(root.left,val);
return root;
}
//在右子树中插入
root.right = add(root.right,val);
return root;
}
查找最大值节点
由于BST的特殊结构,所以最大值节点就是BST的最右侧节点。
不断向右子树遍历,碰到第一个
root.right == null
的节点就是最大值节点。
//在以root为根的二分搜索树中找到最大值的节点,并返回该节点
private TreeNode findMax(TreeNode root) {
if(root.right == null){
return root;
}
//继续在右子树中找
return findMax(root.right);
}
查找最小值节点
同理,最小值就是BST的最左侧的节点。
不断向左子树遍历,碰到第一个
root.left== null
的节点就是最小值节点。
//在以root为根的二分搜索树中找到最小值的节点,并返回该节点
private TreeNode findMin(TreeNode root) {
if(root.left == null){
//此时root就是最小值
return root;
}
//继续在左子树中找
return findMin(root.left);
}
查找任意值节点
普通的二分树在进行元素查找时,通常是先看根节点,然后在左子树中找,然后在右子树中找,比较盲目。由于二分搜索树的数据结构特殊,所以在二分搜素树中查找任意元素本质就是一个二分查找。
如果
val == root.val
,找到该元素;
如果val < root.val
,只在左子树中寻找;
如果val > root.val
,只在右子树中寻找;
因此二分搜索树查找一个元素的时间复杂度为logn
。
//判断在以root为根的二分搜索树中是否存在值为val的节点
private boolean contains(TreeNode root, int val) {
if(root == null){
return false;
}
if(root.val== val){
return true;
}else if(val <root.val){
return contains(root.left,val);
}
return contains(root.right,val);
}
删除最大值节点
//删除以root为根的BST中的最大值节点,返回删除后的根节点
private TreeNode removeMax(TreeNode root) {
if(root.right == null){
//此时root就是最大值节点
TreeNode left = root.left;
//断开最大值节点
root.left = root = null;
size--;
return left;
}
//继续在右子树中删除
root.right = removeMax(root.right);
return root;
}
删除最小值节点
//删除以root为根的BST中的最小值节点,返回删除后的根节点
private TreeNode removeMin(TreeNode root) {
if(root.left == null){
//此时root就是最小值节点
TreeNode right = root.right;
//断开最小值节点
root.right = root = null;
size--;
return right;
}
//继续在左子树中删除
root.left = removeMin(root.left);
return root;
}
删除任意值节点
删除任意值的节点比只删除最大值或最小值要复杂一些。
如果val < root.val,就在左子树中进行删除;
如果val > root.val ,就在右子树中进行删除;
如果前面两条都不成立,那么此时要删除的就是根节点,这里又有三种情况:
(1)若root.left == null,相当于前面介绍的删除最小值节点;
(2)若root.right == null,相当于前面介绍的删除最大值节点;
(3)如果都不满足,那表示待删除节点的左右子树都不为空,此时需要找到一个可代替的后继节点,保证删除后依然满足二分搜索树。
注意,上图中①和②的顺序一定不能换,因为若先拼接了左子树,再拼接右子树中删除最小值(后继节点)后剩下的树时,此时删除的最小值节点就不再是后继节点了。
//删除BST中的任意节点,返回删除后的树根节点
private TreeNode remove(TreeNode root, int val) {
//base case
if(root==null){
//此时没有值为val的节点
return null;
} else if (val <root.val) {
//在左子树中删除
return remove(root.left,val);
}else if(val> root.val){
//在右子树中删除
return remove(root.right,val);
}else{
//此时树根就是待删除的节点
if(root.left == null){
//相当于删除最小值节点
TreeNode right = root.right;
root.right = root = null;
size --;
return right;
} else if (root.right == null) {
//类似删除最大值,返回左树树根
TreeNode left = root.left;
root.left = root = null;
size --;
return left;
}
//此时左右子树都不为空,找到可替代的节点(后继节点)
//找到右子树的最小值最为后继节点
TreeNode successor = findMin(root.right);
//先将右子树中去掉最小值剩下的树拼接到后继节点的右边
successor.right = removeMin(root.right);
//再将根节点的左子树拼接到后继节点的右边
successor.left = root.right;
//断开待删除的根节点
root.right = root.left = root = null;
return successor;
}
}
toSting方法
//打印二分搜索树时,将当前节点的值和层次拼接到sb对象中
private void generateBSTString(TreeNode root, int depth, StringBuilder sb) {
//base case
if(root == null){
//树为空,只拼接层次
sb.append(generateDepthString(depth)).append("NULL\n");
return;
}
sb.append(generateDepthString(depth)).append(root.val).append("\n");
//继续处理左子树
generateBSTString(root.left,depth + 1,sb);
//处理右子树
generateBSTString(root.right,depth + 1,sb);
}
//打印当前节点的深度
//深度+1,多加一个“--”
private String generateDepthString(int depth) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < depth; i++) {
sb.append("--");
}
return sb.toString();
}
二分搜索树转为双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。
思路:
推广:左右子树有N个节点
1:先将左子树拉平,将左子树转换成双向链表。
2:将左半链表的和根节点拼接,也就是将根节点尾插到作伴链表的尾部。
3:再转换右子树为双向链表
4:再将根节点和右半链表拼接
注:在这个过程中一定要时刻注意空指针的问题,凡是能".
"的,一定不能让其空指针。
实现
public class Offer36_BST2LoopLinkList {
public TreeNode Convert(TreeNode pRootOfTree) {
//base case
if(pRootOfTree == null){
return null;
}
//1、先将左子树转为双向链表,返回转换后的链表头节点
TreeNode head = Convert(pRootOfTree.left);
//找到左半链表的尾节点
TreeNode tail = head;
while (tail != null){
if(tail.right == null){
//此时tail就是尾节点
break;
}
tail = tail.right;
}
//此时tail走到了左半链表的尾部
//2、将左半链表和根节点拼接
//此时tail有可能为空,先保证不会空指针
if(tail != null){
tail.right = pRootOfTree;
pRootOfTree.left= tail;
}
//3、转换右子树和根节点拼接
TreeNode right = Convert(pRootOfTree.right);
//拼接根节点和有半链表
if(right != null){
right.left = pRootOfTree;
pRootOfTree.right = right;
}
//若head为空,返回proot,若不为null,返回head
return head == null ? pRootOfTree:head;
}
}
继续加油努力!!!