一、 什么是二叉搜索树?
二叉搜索树又称二叉排序树或是二叉查找树,关于它的定义:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树
二、二叉搜索树查找value为key节点
二叉搜索树的查找是比较方便的,每次都能排除一半的元素。看图:
查找value为70的节点:
1、定义Node cur = root,让cur来遍历二叉树;
2、cur.val = 50 < 70,cur —> 右走,即cur = cur.right;
3、cur.val = 60 < 70,cur —> 右走,即cur = cur.right;
4、cur.val = 80 > 70,cur —> 左走,即cur = cur.left;
5、cur.val = 70 == 70,return true;
6、如果找的value为75,这个时候cur == null,不进入循环,说明没找到。return flase;
我们来代码实现一下:
public boolean findKey(int key) {
if (root == null) return false;
Node cur = root;
while (cur != null) {
if (cur.val == key){
return true;
}else if (cur.val < key) {
cur = cur.left;
}else {
cur = cur.right;
}
}
return false;
}
三、二叉搜索树增加value为key的节点
假如我们要增加value为25的节点:
增加后的效果应该是这样的,那该怎么增加呢?
我们任然时按照查找value为key的节点的思路来增加节点的:
1、先定义一个value为key = 25的新节点,即Node node = new Node(25);
2、如果当前树为空,将根节点root = node;
3、如果当前树不为空:
-
定义cur 遍历二叉树,定义 prev 为 cur 的父亲节点,即
Node cur = root;
Node prev= null; -
cur.val = 50 > 25,cur —> 左走,即cur = cur.left;
cur.val = 40 > 25,cur —> 左走,即cur = cur.left;
cur.val = 20 < 25,cur —> 右走,即cur = cur.right;
cur.val = 30 > 25,cur —> 左走,即cur = cur.left;
cur == null,退出循环。 -
判断prev.val = 30 > 25,所以25应该放在prev的左边,
即 prev.left = node; -
当插入的value 与树中的某个值相等时,直接return,二叉搜索树中的值都是不相等的。
代码实现:
public void insertKey(int key) {
Node node = new Node(key);
if (root == null) {
root = node;
return;
}
Node prev = null;
Node cur = root;
while (cur != null) {
if (key > cur.val) {
prev = cur;
cur = cur.right;
}else if (key < cur.val){
prev = cur;
cur = cur.left;
}else {
System.out.println("插入失败,已经存在了该value的节点");
return;
}
}
if (prev.val > key) {
prev.left = node;
}else {
prev.right = node;
}
}
四、二叉搜索树删除value为key的节点(替罪羊删除法)
同理,我们依然需要依靠二叉树的查找方法来找到需要删除的节点:
public void removeNode(int key) {
if (root == null) {
return;
}
Node cur = root;
Node prev = null;
while (cur != null) {
if (cur.val == key) {
remove(prev,cur);
//删除元素
return;
}else if(cur.val > key){
prev = cur;
cur = cur.left;
}else {
prev = cur;
cur = cur.right;
}
}
}
当我们找到需要删除的节点后,又需要分为几种情况:
-
cur.right == null
cur 是 root,则 root = cur.left
cur 不是 root,cur 是 prev.left,则 prev.left = cur.left
cur 不是 root,cur 是 prev.right,则 prev.right = cur.left -
cur.left == null
cur 是 root,则 root = cur.right
cur 不是 root,cur 是 prev.left,则 prev.left = cur.right
cur 不是 root,cur 是 prev.right,则 prev.right = cur.right -
cur.left != null && cur.right != null
现在我先用cur.right == null 来举例,先看图:
-
当我们需要删除value = 50节点时:
root.left == cur.left; -
当我们需要删除value为40的节点时:
cur.right == null,且 prev.left == cur;
所以我们可以:
prev.left == cur.left;
- 当我们需要删除value为35的节点时:
当cur.ring == null,且 prev.right == cur;
所以我们可以:
prev.right == cur.left;
同理,当cur.left == null 时,也是一样的分析;
下面我们来理解 cur.left != null && cur.right != null的情况(需要使用替罪羊删除法):
当我们需要删除 value = 80 的节点:
根据二叉搜索树的定义,也就是它的特点:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树
cur.left的value都小于cur.value,
而cur.right的value都大于cur.value。
我们可以在cur左边或者右边找一个value最接近80 的节点来进行删除,
并且在 cur 左边找的时候需要满足,找到的x.value 要 大于 cur.left 的 value;
在 cur 右边找的时候需要满足,找到的x.value 要 小于于 cur.right的 value
暂且命名这个节点为x。
我们在cur左边找这个x的时候,发现x一定在 cur.left的最右边;
在右边找这个x的时候,发现x一定在 cur.right 的最左边。
还有一种特殊情况:
当cur.right 没有最左边时
或者cur.left没有最右边时
我们需要进行判断,看代码。
当cur.right 没有最左边时
if (xp.left == x) {
//此时相当于cur.left == null cur == prev.left时
//删除cur节点 所以prev.left = cur.right
xp.left = x.right;
}else {
//当 cur.right 没有左节点时
xp.right = x.right;
}
当cur.right 没有最左边时
if (xp.right == x){
cur.val = x.val;
}else {
//当 cur.left 没有右节点时
xp.right = x.left;
}
找到x之后将x.value 赋值 给 cur.value ,对 x 节点进行删除,我们以在cur.right 的最左边找x为例:
public void remove(Node prev,Node cur) {
if (cur.right == null) {
if (cur == root) {
root = cur.left;
}else if (prev.left == cur) {
prev.left = cur.left;
}else {//prev.right == cur
prev.right = cur.left;
}
}else if (cur.left == null) {
if (cur == root) {
root = cur.right;
}else if (prev.right == cur) {
prev.right = cur.right;
}else {//prev.left == cur
prev.left = cur.right;
}
}else {
//cur左右都不为空时,
//cur右边的下一个节点的左边找最小值
Node xp = cur;
Node x = xp.right;
//在cur 右边找最小值 覆盖cur的值
while (x.left != null) {
xp = x;
x = x.left;
}
cur.val = t.val;
if (xp.left == x) {
//此时相当于cur.left == null cur == prev.left时 删除cur节点 所以prev.left = cur.right
xp.left = x.right;
}else {
//当 cur.right 没有左节点时
xp.right = x.right;
}
/*
//cur左边的下一个节点的最右边找最大值
Node xp = cur;
Node x = cur.left;
while (x.right != null) {
xp = x;
x = x.right;
}
if (xp.right == x){
cur.val = x.val;
}else {
//当 cur.left 没有右节点时
xp.right = x.left;
}*/
}
}
五、总结
二叉搜索树的增、删、查都是围绕着查来进行的,需要修改节点(增加,删除)时就需要定义前驱。
不像链表,在修改节点时前驱后驱都可以定义。因为二叉搜索树中 cur 连自己下一步要去哪里都不知道,后驱是没法定义的。
老规矩,小白的理解,如有错误请多多指正。感谢!