定义:当一棵二叉树满足任意一个节点的左子节点的值<自己的值<节点右子节点的值的时候,并且此二叉树的中序遍历为有序的时候,这棵二叉树被称为二叉搜索树,又称为二叉查找树。
有关二叉搜索树的一些概念:查找一个节点,时间复杂度为O(n),这是最坏的情况,单分支的情况是查找效率最低的情况,理想情况下面,时间复杂度为O(logN),N为节点个数。
给定一棵二叉搜索树,根据节点值大小排序所需时间复杂度是线性的
如图为一棵二叉搜索树:
查找某个节点:
例如此时需要查找11,需要一个cur节点指向根节点,当目标值大于cur的值的时候,指针往右走,当目标值小于当前值的时候,指针往左走,否则返回当前的指向。
代码实现:
static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int val) {
this.val = val;
}
}
public TreeNode root=null;
public TreeNode search(int val){
TreeNode cur=root;
while (cur!=null){
if(cur.val<val){
cur=cur.left;
}
else if(cur.val>val){
cur=cur.right;
}
else {
return cur;
}
}
return null;
}
二:新增节点
如果root为null,那么直接插入到根节点当中
其次,每个节点都会插入到叶子节点的位置
如图,这种情况,需要插入一个10:
首先,定义一个parent指向cur走过的上一个节点。
当cur为null时候,就可以填充值了。
当cur的值小于待插入的值的时候,parent指向cur,cur指向自己的右子节点。
当cur的值大于待插入的值的时候, parent指向cur,cur指向自己的左节点。
如图:此时parent指向11,cur也指向11,当cur继续往左走的时候,cur为NULL了,此时,由于parent指向了11,比10大,因此把10插入到11的右侧。
代码实现:
public void insert(int val){
if(root==null){
root=new TreeNode(val);
return;
}
TreeNode parent=null;
TreeNode cur=root;
while (cur!=null){
if(cur.val<val){
parent=cur;
cur=cur.right;
}
else if(cur.val>val){
parent=cur;
cur=cur.left;
}else {
throw new RuntimeException("不能插入相同数据");
}
}
TreeNode node=new TreeNode(val);
if(parent.val<val){
parent.right=node;
}else {
parent.left=node;
}
}
二叉搜索树的删除节点:3种情况:
A.待删除节点的左子节点为null:
①待删除节点为root时候,直接root=cur.right;
②待删除节点位于parent的左枝时候,
待删除节点的parent节点的左子区域指向待删除节点的右枝
parent.left=cur.right;
③待删除节点位于parent右枝的时候:
parent.right=cur.right;
B.待删除节点的右子树为NULL时候,这里就不画图了,直接写结论:
简单来说,就是替换parent的左枝,右枝的指向。
if(cur.right==null){
if(root==parent){
root=cur.left;
}else if(parent.left==cur){
parent.left=cur.left;
}else if(parent.right==cur){
parent.right=cur.left;
}
}
C、左右都不为NULL的情况
这种时候,就要从待删除节点的左枝或者右枝寻找,找到一个值(target),要么为左枝当中最大的值,要么为右枝当中最小的值:即:左枝最右边的子树或者右枝最左边的子树。
用这个值替换掉被删除的节点,同时删除target原来的值所在的节点。
如果选择右边最左的子树的情况的话,就要定义一个辅助指针指向cur,称这个指针为targetParent,那么target就是cur.right。此时,就需要target一路向左走,当target的左枝为NULL时候,说明找到了target。那么删除的话,要分target位于target的左枝或者右枝的情况来删除。
代码实现:
/**
* 删除值
* 待删除的值@param val
*/
public void remove(int val) {
if (root == null) {
throw new RuntimeException("树为空,无法删除");
}
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 {
//找到需要删除的节点了,执行需要删除的逻辑
removeTheNode(parent, cur);
return;
}
}
}
/**
* 删除对应的节点
* 父亲节点@param parent
* 当前节点@param cur
*/
private void removeTheNode(TreeNode parent, TreeNode cur) {
if (cur.left == null) {
if (cur == root) {
root = cur.right;
} else if (parent.left == cur) {
parent.left = cur.right;
} else if (parent.right == cur) {
parent.right = cur.right;
}
} else if (cur.right == null) {
if (cur == root) {
root = cur.left;
} else if (parent.right == cur) {
parent.right = cur.left;
} else if (parent.left == cur) {
parent.left = cur.left;
}
} else {
TreeNode targetParent = cur;
TreeNode target = cur.right;
while (target.left != null) {
targetParent = target;
target = target.left;
}
cur.val = target.val;
//当待删除节点位于父亲节点左枝:
if (target == targetParent.left) {
targetParent.left = target.right;
}
//当待删除节点位于父亲节点右枝
else if (target == targetParent.right) {
targetParent.right = target.right;
}
}
}
如果问起二叉搜索树的查找时间复杂度,那么就应当为O(N),因为i当二叉查找树为仅仅左枝/右枝存在的时候,就会退化为链表的结构,查找效率降低了许多,因此将会引入avl树,红黑树等等的来提升查找的效率。