二叉搜索树
1.介绍
二叉搜索树也叫二叉排序树或者,二叉查找树,他最重要的特点就是:左子节点比父节点小,右子节点比父节点大,如果使用中序遍历的话,可以得到一个有序序列
2.性质
- 二叉搜索树的左子树,右子树都是二叉搜索树
- 二叉搜索树是一棵二叉树
- 非空左子树的所有键值小于其根结点的键值。
- 非空右子树的所有键值大于其根结点的键值。
- 没有键值相等的节点。
- 二叉搜索树的平均深度是O(logN)一般不用担心栈空间被用尽
3.主要操作
1.二叉搜索树构建
二叉搜索树的构建其实可以看看一个个节点从空树一步一步递归插入的过程,在插入过程中,要判断左右节点,保证左子节点<父节点<右子节点
/**
* 构造二叉查找树
* @param x
* @param t
* @return
*/
public Node<AnyType> insert(AnyType x,Node<AnyType> t){
if(t == null)
return new Node<AnyType>(x, null, null);
int result = x.compareTo(t.element);
if(result > 0)
t.right = insert(x, t.right);
else if(result < 0)
t.left = insert(x, t.left);
else {
//dosomething
}
return t;
}
public static void main(String[] args) {
System.out.println("*******构造二叉查找树*******");
BinarySearchTree<Integer> binarySearchTree = new BinarySearchTree<>();
binarySearchTree.root = new Node<Integer>(12);
Node<Integer> node1 = binarySearchTree.insert(10, binarySearchTree.root);
Node<Integer> node2 = binarySearchTree.insert(13,binarySearchTree.root );
binarySearchTree.insert(11,node1);
binarySearchTree.insert(15, node2);
}
2.查找指定节点
在节点的查找中,我们要利用好二叉搜索树的性质:左子节点<父节点<右子节点,利用这个性质,比较要查找的值跟树的根节点比较,比根节点小则递归与根节点的左子节点比较,一直到找到指定元素为止
/**
* 查找指定节点
* @param data
* @param root
* @return
*/
public Node<Integer> getNode(AnyType data,Node<Integer> root){
if(root == null) {
return null;
}
int result = data.compareTo((AnyType) root.getElement());
if(result < 0)
return getNode(data, root.left);
else if(result > 0)
return getNode(data, root.right);
else
return root;
}
3.最值查找
最大节点—最大元素一定是在树的最右分枝的端结点上
/**
* 最大节点
* @param root
* @return
*/
public Node<Integer> findMax(Node<Integer> root){
Node<Integer> current = root;
Node<Integer> result = null;
while(current != null) {
result = current;
current = findMax(current.right);
}
return result;
}
最小节点— 最小元素一定是在树的最左分枝的端结点上
/**
* 最小节点
* @param root
* @return
*/
public Node<Integer> findMin(Node<Integer> root){
Node<Integer> current = root;
Node<Integer> result = null;
while(current != null) {
result = current;
current = current.left;
}
return result;
}
4.判断二叉查找树中是否包含指定节点
/**
* 判断二叉查找树中是否包含指定节点
* @param x
* @param node
* @return
*/
public boolean contains(AnyType x,Node node) {
if(node == null) {
throw new RuntimeException();
}
int result = x.compareTo((AnyType) node.element);
if(result<0) {
contains(x, node.left);
}else if(result>0) {
contains(x, node.right);
}else {
return true;
}
return false;
}
5.删除节点
删除节点有三种情况
- 删除的节点是叶子节点-----只需要删除该节点和父节点的关系
- 删除的节点有一个子节点-----需要断两个关系,然后建立父节点和子节点的关系
- 删除的节点有两个子节点 ---- 在右子树中找出最小的节点填充删除的节点,在删除节点的右子树中删除最小元素,即刚刚填充的元素
当我们删除 30 节点的时候,整个中序遍历的结果中,从 32 开始都往前移动了一位。32 是 30 的后继节点,就是比 30 大的节点中最小的节点。当某个节点存在右节点时,后继结点就是右节点中的最小值,由于左侧节点总比右侧节点和父节点小,所以后继节点一定没有左节点。从这一个特点就能看出来,后继结点有可能存在右节点,也有可能没有任何节点。后继结点还有一个特点,就是他比 30 的左节点大,比 30 所有的右节点都小,因此删除 30 的时候,可以直接将后继结点 32 的值(key)转移到 30 节点上,然后删除后继结点 32。由于后继结点最多只有一个子节点,因此删除后继节点时,就变成了 3 种情况中的前两种。
/**
* 删除指定节点
* @param element
* @param t
* @return
*/
public Node<Integer> remove(AnyType element , Node<Integer> root){
Node<Integer> temp;
//没有要删除的元素,因为这是空树
if(root == null) {
return null;
}
int result = element.compareTo((AnyType) root.element);
if(result<0) {
//左子树递归删除
root.left = remove(element, root.left);
}else if (result>0) {
//右子树递归删除
root.right = remove(element, root.right);
}else {
//找到要删除的元素
//要删除元素有两个节点的情况
if (root.left!=null && root.right!=null) {
//在右子树中找出最小的节点填充删除的节点
//root.element = findMin(root.right).element;
temp= findMin(root.right);
//在删除节点的右子树中删除最小元素,即刚刚填充的元素
root.right = remove((AnyType)root.element, root.right);
}else {
//单节点情况
temp = root;
//root = (root.left != null)?root.left:root.right;
if(root.left == null) {
root = root.right;
}else if (root.right == null) {
root = root.left;
}
temp = null;
}
}
return root;
}
4.二叉搜索树的优点:
- 二叉搜索树的最大优点就是,即使在最坏的情况下也允许你以O(H) [H:二叉搜索树高度]即O(logN)来进行插入和删除还有查找操作
- 如果你想有序地存储数据或者需要同时执行搜索、插入、删除等多步操作,二叉搜索树这个数据结构是一个很好的选择。