目录
1. 二叉搜索树概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
2.操作
要实现二叉树的各种操作(查找, 插入, 删除), 先构造出其基本框架:
public class BinarySearchTree {
static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int val) {
this.val = val;
}
}
public TreeNode root;
}
2.1 操作-查找
/**
* 判断某个节点是否存在
* 最好情况:完全二叉树 O(logN)
* 最坏情况:单分支的数 O(N)
* @param key
* @return
*/
public boolean search(int key) {
TreeNode cur = root;
while (cur != null) {
if(cur.val < key) {
cur = cur.right;
}else if(cur.val > key) {
cur = cur.left;
}else {
return true;
}
}
return false;
}
2.2 操作-插入
1. 如果树为空树,即根 == null,直接插入
2. 如果树不是空树,按照查找逻辑确定插入位置,插入新结点
/**
* 插入节点
* @param val
* @return
*/
public boolean insert(int val) {
//判断当前的树是否为空树
if(root == null) {
root = new TreeNode(val);
return true;
}
TreeNode cur = root;
TreeNode parent = null;
while (cur != null) {
if(cur.val < val) {
parent = cur;
cur = cur.right;
}else if(cur.val > val) {
parent = cur;
cur = cur.left;
}else {
//二叉搜索树中的节点的值不能重复
return false;
}
}
TreeNode node = new TreeNode(val);
if(parent.val > val) {
//插入到左边
parent.left = node;
}else{
//插入到右边
parent.right = node;
}
return true;
}
5.4 操作-删除(难点)
图解以上三种情况
1. cur.left == null
2. cur.right == null
3. cur.left!=null && cur.right!=null
/**
* 删除节点
* @param key 要删除的节点值
*/
public void remove(int key) {
TreeNode cur = root;
TreeNode parent = null;
while (cur != null) {
if(cur.val < key) {
parent = cur;
cur = cur.right;
}else if(cur.val > key) {
parent = cur;
cur = cur.left;
}else {
//开始删除
removeNode(cur,parent);
}
}
}
private void removeNode(TreeNode cur, TreeNode parent) {
if(cur.left == null) {
if(cur == root) {
root = cur.right;
}else if(cur == parent.left) {
parent.left = cur.right;
}else {
//cur == parent.right
parent.right = cur.right;
}
}else if(cur.right == null) {
if(cur == root) {
root = cur.left;
}else if(cur == parent.left) {
parent.left = cur.left;
}else {
//cur == parent.right
parent.right = cur.left;
}
}else {
//cur.left!=null && cur.right!=null
//从cur右子树中找最小的数据赋值给cur, 再将右子树中最小的数删掉
TreeNode targetParent = cur;
TreeNode target = cur.right;
while (target.left != null) {
targetParent = target;
target = target.left;
}
cur.val = target.val;
//删除 target
if(targetParent.left == target) {
targetParent.left = target.right;
}else {
targetParent.right = target.right;
}
}
}
完整代码:
public class BinarySearchTree {
static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int val) {
this.val = val;
}
}
public TreeNode root;
/**
* 判断某个节点是否存在
* 最好情况:完全二叉树 O(logN)
* 最坏情况:单分支的数 O(N)
* @param key
* @return
*/
public boolean search(int key) {
TreeNode cur = root;
while (cur != null) {
if(cur.val < key) {
cur = cur.right;
}else if(cur.val > key) {
cur = cur.left;
}else {
return true;
}
}
return false;
}
/**
* 插入节点
* @param val
* @return
*/
public boolean insert(int val) {
//判断当前的树是否为空树
if(root == null) {
root = new TreeNode(val);
return true;
}
TreeNode cur = root;
TreeNode parent = null;
while (cur != null) {
if(cur.val < val) {
parent = cur;
cur = cur.right;
}else if(cur.val > val) {
parent = cur;
cur = cur.left;
}else {
//二叉搜索树中的节点的值不能重复
return false;
}
}
TreeNode node = new TreeNode(val);
if(parent.val > val) {
//插入到左边
parent.left = node;
}else{
//插入到右边
parent.right = node;
}
return true;
}
/**
* 删除节点
* @param key 要删除的节点值
*/
public void remove(int key) {
TreeNode cur = root;
TreeNode parent = null;
while (cur != null) {
if(cur.val < key) {
parent = cur;
cur = cur.right;
}else if(cur.val > key) {
parent = cur;
cur = cur.left;
}else {
//开始删除
removeNode(cur,parent);
}
}
}
private void removeNode(TreeNode cur, TreeNode parent) {
if(cur.left == null) {
if(cur == root) {
root = cur.right;
}else if(cur == parent.left) {
parent.left = cur.right;
}else {
//cur == parent.right
parent.right = cur.right;
}
}else if(cur.right == null) {
if(cur == root) {
root = cur.left;
}else if(cur == parent.left) {
parent.left = cur.left;
}else {
//cur == parent.right
parent.right = cur.left;
}
}else {
//cur.left!=null && cur.right!=null
//从cur右子树中找最小的数据赋值给cur, 再将右子树中最小的数删掉
TreeNode targetParent = cur;
TreeNode target = cur.right;
while (target.left != null) {
targetParent = target;
target = target.left;
}
cur.val = target.val;
//删除 target
if(targetParent.left == target) {
targetParent.left = target.right;
}else {
targetParent.right = target.right;
}
}
}
}
3.性能分析
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度 的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:logN
最差情况下,二叉搜索树退化为单支树,其平均比较次数为:N/2
问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,都可以 是二叉搜索树的性能最佳?
和 java 类集的关系
TreeMap 和 TreeSet 即 java 中利用搜索树实现的 Map 和 Set;实际上用的是红黑树,而红黑树是一棵近似平衡的 二叉搜索树,即在二叉搜索树的基础之上 + 颜色以及红黑树性质验证.