二叉搜索树
概念:
二叉搜索树又称二叉排序树,他或是一颗空树,或是具有以下性质的一颗树
- 若它的左子树不为空,则左子树上所有节点的值都小于根结点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也都分别是二叉搜索树
- 二叉搜索树中不存在值相同的两个节点
接下来完成二叉搜索树的常见操作
搜索树的结构:
static class Node{
public int key;
public int value;
public Node left;
public Node right;
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
相关操作的完成过程写在代码的注释中:
public class BinarySearchTree {
static class Node{
public int key;
public int value;
public Node left;
public Node right;
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
private Node root = null;
//查找元素操作
public Node find(int key) {
//先让cur指向根结点
Node cur = root;
//开始循环 当cur指向空时就跳出
while(cur != null) {
//如果比较值小于节点值则向节点的左子树寻找
if(key < cur.key) {
cur = cur.left;
//如果比较值大于节点值则向节点的右子树寻找
}else if (key > cur.key) {
cur = cur.right;
//走到这里就说明val == cur.key
}else{
return cur;
}
}
//运行到这里时就说明没有找到相关值,则返回空
return null;
}
//插入元素操作
//进行插入操作时,需要查找到待插入位置,并且记录此此节点的父结点
public Node insert(int key,int value) {
//如果当前树是空树,那么就让root指向新的节点
if(root == null) {
root = new Node(key,value);
return root;
}
//此时利用cur找到应插入的位置
//利用prev记录cur的父结点
Node cur = root ;
Node prev = null;
//开始寻找应插入的位置
while(cur != null) {
//如果将插入的值小于当前节点的值,则向当前节点的左节点寻找
if(key < cur.key) {
prev = cur;
cur = cur.left;
//如果将插入的值大于当前节点的值,则向当前节点的右节点寻找
}else if( key > cur.key) {
prev = cur;
cur = cur.right;
}else {
//找到了key相同的元素
//此时处理情况有很多种
//可以直接使插入失败
//也可以把当前节点的value改成新的value
cur.value = value;
return cur;
}
}
//当循环结束时,cur 就为 null了
//经过上面的查找一定能找到合适的位置
//要把新节点插到prev的下面
Node newNode = new Node(key,value);
if(newNode.value < prev.value) {
prev.left = newNode;
}else {
prev.right = newNode;
}
return newNode;
}
//删除元素操作
public void remove(int key) {
//先找到要删除的节点,同时记录该节点的父结点位置
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{
//走到这里代表已经找到要删除的位置,进入辅助方法
removeNode(cur,parent);
return;
}
}
return ;
}
//删除元素辅助方法
private void removeNode(Node cur, Node parent) {
//搜索树的删除操作需要考虑的要素较多
//1.要删除的节点没有左子树
//2.要删除的节点没有右子树
//3.要删的节点左右子树都存在
if(cur.left == null) {
//如果要删除的节点没有左子树
if(cur == root) {
//如果要删除的节点为根结点
//因为此时的根结点没有左子树
cur = cur.right;
}else if(cur == parent.left){
//如果此时要删除的节点为父节点的左子树
parent.left = cur.right;
}else if(cur == parent.right) {
//如果此时要删除的节点为父结点的右子树
parent.right = cur.right;
}
}else if(cur.right == null) {
//如果要删除的节点没有右子树
if(cur == root) {
cur = cur.left;
}else if (cur == parent.left) {
parent.left = cur.left;
}else {
parent.right = cur.left;
}
}else{
//如果要删除的节点左右子树都存在
//那么找到当前节点右子树的最左侧的节点
//找到的节点之比当前节点的值大,而比当前节点右子树中其他节点的值窦骁
Node scapeGoat = cur.right;
Node scapeGoatParent = cur;
//下面这个条件是判断寻找的节点存在左子树,如果不存在,则当前节点就是最左侧的节点
while(scapeGoat.left != null){
scapeGoatParent = scapeGoat;
scapeGoat = scapeGoat.left;
}
//将当前节点的key和value更新为scapeGoat的相关值
cur.key = scapeGoat.key;
cur.value = scapeGoat.value;
//然后删除scapeGoat节点
if(scapeGoat == scapeGoatParent.left) {
scapeGoatParent.left = scapeGoat.right;
}else{
scapeGoatParent.right = scapeGoat.right;
}
}
}
}
总结:
二叉树的基本特性是:针对树上的任意节点,其左子树的所有节点都小于根结点,右子树的所有节点都大于根结点,并且一般不允许存在两个相同的值。
插入、查找、删除操作的时间复杂度是O(N),如果考虑到平衡性(AVL,红黑树)则为O(LogN)。