二叉搜索树:笔试一般不会考,但是面试一般总考。
二叉搜索树:是一个特殊的二叉树,它要求左子树的所有节点都比根节点值小,要求右子树的所有节点都比根节点值大。
二叉搜索树的最核心用途:是用来查找元素的,类似于二分查找,中序遍历二叉搜索树的话,得到的是一个有序序列。
二叉搜索树的查找过程:
根节点和要查找元素进行比较,根节点比待查找元素大,就去左子树中找;
根节点比待查找元素小,就去右子树中找;
依次再比较待查找元素和左右子树的根节点的值,再去递归的找左子树或右子树;
它避免了遍历整个树,比较高效。
二叉搜索树的时间复杂度:最坏情况:O(n),当前是一个通用的二叉搜索树,可能会出现极端情况导致查找效率较低,最好是O(logN)平衡二叉树的情况,比较平衡的情况。
解决方案是:采取更平衡的二叉搜索树:AVL树(绝对平衡) 红黑树(没那么平衡但是也说的过去,TreeSet和TreeMap就是基于红黑树实现的)
Map Set
TreeMap
~~~~~~
TreeSet
~~~~~~
基于二叉搜索树实现
HashMap
~~~~~~
HashSet
~~~~~~
基于哈希表
二叉搜索树的基本操作:
1:插入元素:和查找非常相似,需要先找到待插入元素的合适位置,元素都是插入到叶子节点上(为了插入方便)
2:查找元素:也很简单
3:删除元素:需要考虑很多种不同情况:
-
待删除元素是父节点的左子树,并且待删除元素左子树为空,右子树非空。方法:parent.left = cur.right
-
待删除元素是父节点的右子树,待删除元素的左子树为空,右子树为非空。方法:parent.right =
cur.right -
待删除元素是父节点的左子树,待删除元素左子树为非空,右子树为空。方法:parent.left = cur.left
-
待删除元素是父节点的左子树,待删除元素左子树为非空,右子树为空。方法:parent.left = cur.left;
-
待删除是父节点的左子树,待删除元素的左右子树均非空。
解决方法:把未知问题转换成已知问题:待删除元素是10,先找10这个节点右子树中的最小值(也可以找左子树中的最大值):把右子树中的最小值12赋值到10这个元素的位置,由于12是右子树的最小值,此时赋值之后,二叉搜索树仍然符合条件,接下来删除12这个节点就可以了,下面这个12要么没有子树,要么只有右子树(此时就转换成了第一个情况) -
待删除元素是父节点的右子树,待删除元素左右节点均非空。方法:此处的情况和紧挨着上面的情况完全相同,也是找到待删除元素右子树中的最小值(或者找待删除元素左子树中的最大值),把这个值赋值到待删除节点,再删除下面的刚才找到的最小值。
【注意】:不应该修改二叉搜索树中的key,value是允许修改的。如果实在需要改key,可以删掉之前的记录,重新插入个新的记录。
代码实现:
public class BinarySearchTree {
public static class Node {
int key;
Node left;
Node right;
public Node(int key) {
this.key = key;
}
}
private Node root = null;//这是根节点,root为null的时候表示这是一个空树。
public Node find(int key) {//查找key是否在树中存在,如果存在返回对应的Node
Node cur = root;
while (cur != null) {
if (key < cur.key) {
//就去左子树中找
cur = cur.left;
} else if (key > cur.key) {
//就去右子树中找
cur = cur.right;
} else {
//走到这一步就是相等,找着了
return cur;
}
}
//循环结束了,也没找到,说明key就不存在,返回null
return null;
}
public boolean insert(int key) {
/*二叉搜索树中不允许存在相同key的元素的,如果发现新插入的key重复了,那就插入失败,返回false
* 插入成功返回true*/
if (root == null) {
//当前如果为空树,直接让root指向key对应的新节点即可。
root = new Node(key);
return true;
}
//和查找类似,需要先找到合适的位置,再插入元素
Node cur = root;//先从根节点出发
Node parent = null;//让parent始终指向cur的父节点。
while (cur != null) {
if (key < cur.key) {//往左走
parent = cur;
cur = cur.left;
} else if(key > cur.key){//往右走
parent = cur;
cur = cur.right;
}else {
//到这里说明当前找到的某个元素和带插入元素的值相同,那么插入失败,返回false
/*如果当前树存的只是key,发现相同的key就会认为插入失败,如果当前树存放的是键值对,发现相同的key就修改值即可*/
return false;
}
}
//当循环结束走到这,cur就指向null,当前元素就要插入到parent的子树位置,但是具体要插入到parent的左子树
//还是右子树,就拿key和parent再比较一次就行了
if (key < parent.key) {
//插入到parent的左侧
parent.left = new Node(key);
} else {
parent.right = new Node(key);
}
return true;
}
/*删除成功返回true,删除失败返回false
* key在树中存在,就删除成功
* key在树中不存在,就删除失败*/
public boolean remove(int key) {
/*先找到要删除节点的位置,再进行具体的删除,找到这个待删除元素后,再去判定是a~f这六种情况中的哪一种情况*/
Node cur = root;
Node parent = null;
while (cur != null) {
if (key < cur.key) {
parent = cur;
cur = cur.left;
} else if (key > cur.key) {
parent = cur;
cur = cur.right;
} else {
//到这一步就是找到了要删除的元素,就是cur指向的节点
removeNode(parent,cur);//在这个方法中去判定a~f这六种情况并进行删除
return true;
}
}
return false;//到这一步说明当前的k,并没有被找到,直接返回false即可。
}
private void removeNode(Node parent, Node cur) {
if (cur.left == null) {
//1.要删除的元素没有左子树
if (cur == root) {
//1.1如果要删除的节点为root
root = cur.right;
} else if (cur == parent.left) {
//1.2 cur 是parent的左子树,对应画图板的情况 a
parent.left = cur.right;
} else {
//1.3 cur是parent的右子树,对应画图板的情况 b
parent.right = cur.right;
}
} else if (cur.right == null) {
//2.要删除的元素没有右子树
if (cur == root) {
//2.1如果要删除的节点是root
root = cur.left;
} else if (cur == parent.left) {
//2.2要删除节点是父节点的左子树,对应画图板的情况c
parent.left = cur.left;
} else {
//2.3要删除接点是父节点的右子树
parent.right = cur.left;
}
} else {
//3.当前要删除节点有两个子树,对应画图板的e 和 f
//(1)先找到右子树中的最小元素(替罪羊)
Node goatParent = cur;//goatParent为替罪羊节点的父节点
Node scapeGoat = cur.right;//替罪羊节点
while (scapeGoat.left != null) {
goatParent = scapeGoat;
scapeGoat = scapeGoat.left;
}
//(2)把刚才找到的替罪羊的值赋给待删除节点
cur.key = scapeGoat.key;
//(3)删除替罪羊节点
//替罪羊节点一定没有左子树(和情况a 和 b类似)
if (scapeGoat == goatParent.left) {
goatParent.left = scapeGoat.right;
} else {
goatParent.right = scapeGoat.right;
}
}
}
public void preOrder(Node root) {//先序遍历
if(root == null) {
return;
}
System.out.print(root.key + " ");
preOrder(root.left);
preOrder(root.right);
}
public void inOrder(Node root) {//中序遍历
if (root == null) {
return;
}
inOrder(root.left);
System.out.print(root.key + " ");
inOrder(root.right);
}
public static void main(String[] args) {
BinarySearchTree tree = new BinarySearchTree();
tree.insert(9);
tree.insert(5);
tree.insert(2);
tree.insert(3);
tree.insert(4);
tree.insert(6);
tree.insert(1);
//为了查看树的结构,打印出树先序和中序遍历结果
tree.preOrder(tree.root);
System.out.println();
tree.inOrder(tree.root);
System.out.println();
Node cur = tree.find(1);
System.out.println(cur.key);
System.out.println("========");
tree.remove(5);
tree.preOrder(tree.root);
System.out.println();
tree.inOrder(tree.root);
System.out.println();
}
}