引言
二叉搜索树是将链表插入的灵活性和有序数组查找的高效性结合起来的重要数据结构,是学习红黑树和AVL树的基础。
二叉搜索树定义
二叉搜索树最重要的一个特征:每个节点都含有一个Comparable的键和值,该节点的键要大于左子树所有节点的键,而要小于右子树所有节点的键。二叉搜索树是递归定义的,所以一般二叉搜索树的相关题目都可以使用递归的思想解决。
在java中使用内部类定义节点,每个节点对象包含键Key和值Value,两条链接left和right,及节点个数size。
package com.wsy.binarytree;
/**
*
* @author wsy
* @param <Key>
* @param <Value>
*/
public class BinaryTree<Key extends Comparable<Key>, Value>
{
private Node root;
private class Node
{
private Key key;
private Value value;
private Node left, right;
private int size; //子节点树(包含自身)
public Node(Key key, Value value, int size)
{
this.key = key;
this.value = value;
this.size = size;
}
}
public int size()
{
return size(root);
}
private int size(Node x)
{
return x == null ? 0 : x.size;
}
public boolean isEmpty()
{
return size() == 0;
}
public boolean contains(Key key)
{
return key == null ? false : search(key) != null;
}
}
查找操作
根据二叉搜索树:左子树的节点都要小于根节点,而右子树的节点都要大于根节点这一特性遍历二叉搜索树,结果如下:
1、如果查找的键key小于根节点的key,则要查找的key如果存在则肯定在左子树,则继续递归左树。
2、如果查找的键key大于根节点key,则查找的key如果存在则肯定在右子树,则继续递归右树。
3、如果查找的key等于根节点key,则直接返回根节点。
/**
* 查找
* @author wsy
* @param key
* @return value
*/
public Value search(Key key)
{
return key == null ? null : search(root, key);
}
private Value search(Node x, Key key)
{
if(x == null)
{
return null;
}
else
{
int cmp = key.compareTo(x.key);
if(cmp < 0)
{
return search(x.left, key);
}
else if(cmp > 0)
{
return search(x.right, key);
}
else
{
return x.value;
}
}
}
插入操作
插入操作与上面的查找操作原理类似,找到插入节点的位置将新节点插入即可。插入操作都是顺序插入,不能在节点中间插入。插入操作的特殊之处是需要更新其父节点的size,父节点的父节点的size,直到根节点。根节点的创建也是在insert中完成的。
public void insert(Key key, Value value)
{
if(key == null)
{
throw new IllegalArgumentException("first argument to put() is null");
}
root = insert(root, key, value);
}
private Node insert(Node x, Key key, Value value)
{
if(x == null)
{
return new Node(key, value, 1);
}
else
{
int cmp = key.compareTo(x.key);
if(cmp < 0)
{
x.left = insert(x.left, key, value);
}
else if(cmp > 0)
{
x.right = insert(x.right, key, value);
}
else
{
x.value = value;
}
x.size = size(x.left) + size(x.right) + 1;
return x;
}
}
删除操作
我们先来实现两个比较简单的删除操作:删除树中的最大节点和最小节点。
最大节点:(1)如果存在右树的情况在最大节点比存在右树中,遍历找到最大节点删除即可。(2)不存在右树则最大节点为跟节点,返回左树即可。
public void deleteMax()
{
if(isEmpty())
{
throw new IllegalArgumentException("symbol table underflow.");
}
else
{
root = deleteMax(root);
}
}
private Node deleteMax(Node x)
{
if(x == null)
{
return null;
}
if(x.right == null)
{
return x.left;
}
else
{
x.right = deleteMax(x.right);
x.size = size(x.left) + size(x.right) + 1;
return x;
}
}
最小节点:(1)如果存在左树的情况在最小节点比存在左树中,遍历找到最小节点删除即可。(2)不存在左树则最小节点为跟节点,返回右树即可。
public void deleteMin()
{
if(isEmpty())
{
throw new IllegalArgumentException("symbol table underflow.");
}
else
{
root = deleteMin(root);
}
}
private Node deleteMin(Node x)
{
if(x == null)
{
return null;
}
if(x.left == null)
{
return x.right;
}
else
{
x.left = deleteMin(x.left);
x.size = size(x.left) + size(x.right) + 1;
return x;
}
}
删除指定节点,有三种情况:
(1)指定节点的左树为空,则直接返回右树来代替删除的节点。
(2)指定节点的右树为空,则直接返回左树来代替删除的节点。
(3)指定节点的左树和右树都不为空,则需要删除右树中最小的节点并返回来代替要删除的节点(由于右树中的最小节点大于指定节点及指定节点左树中的所有节点,而小于右树中其他节点,这么操作满足二叉搜索树的特性)。
//删除节点
public void delete(Key key)
{
if(key == null)
{
throw new IllegalArgumentException("argument to delete() is null.");
}
else
{
root = delete(root, key);
}
}
private Node delete(Node x, Key key)
{
if(x == null)
{
return null;
}
else
{
int cmp = key.compareTo(x.key);
if(cmp < 0)
{
x.left = delete(x.left, key);
}
else if(cmp > 0)
{
x.right = delete(x.right, key);
}
else
{
if(x.right == null)
{
return x.left;
}
else if(x.left == null)
{
return x.right;
}
else
{
Node tmp = x;
x = min(tmp.right);
x.right = deleteMin(x.right);
x.left = tmp.left;
}
}
x.size = size(x.left) + size(x.right) + 1;
return x;
}
}
//最小节点
public Key min()
{
Node x = min(root);
return x == null ? null : x.key;
}
private Node min(Node x)
{
if(x == null)
{
return null;
}
else
{
int t = size(x.left) + 1;
if(t > 1)
{
return min(x.left);
}
else
{
return x;
}
}
}
查询二叉搜索树中第k小的节点
由于二叉搜索树中左子树的节点都小于根节点,右子树的节点都大于根节点;因此首先判断根节点左子树的size,如果size > k,则第k小节点在左子树中;如果size < k,则第k小节点在右子树中,则将root.right作为根节点在右子树中查找第k - size(root.left) -1小节点;不管是哪种情况继续递归查找即可:
//查找第K小的的节点
public Key select(int k)
{
if(k < 0 || k > size())
{
throw new IllegalArgumentException("called select() with incalid argument");
}
else
{
Node x = select(root, k);
return x == null ? null : x.key;
}
}
private Node select(Node x, int k)
{
if(x == null)
{
return null;
}
else
{
int t = size(x.left) + 1;
if(t > k)
{
return select(x.left, k);
}
else if(t < k)
{
return select(x.right, k - t);
}
else
{
return x;
}
}
}
查询指定Key在二叉搜索树中的排名
//查询节点在树中第几小
public int rank(Key key)
{
if(key == null)
{
throw new IllegalArgumentException("argument to rank() is null.");
}
else
{
return rank(root, key);
}
}
private int rank(Node x, Key key)
{
if(x == null)
{
return 0;
}
else
{
int cmp = key.compareTo(x.key);
if(cmp < 0)
{
return rank(x.left, key);
}
else if(cmp > 0)
{
return size(x.left) + 1 + rank(x.right, key);
}
else
{
return size(x.left) + 1;
}
}
}
二叉搜索树反转
//反转二叉树
public void invertTree()
{
if(isEmpty())
{
System.out.println("binary tree is empty.");;
}
else
{
root = invertTree(root);
}
}
private Node invertTree(Node x)
{
if(x == null)
{
return null;
}
else
{
x.left = invertTree(x.left);
x.right = invertTree(x.right);
Node tmp = x.left;
x.left = x.right;
x.right = tmp;
return x;
}
}
遍历–仅提供中序遍历
//遍历
public void forEach()
{
if(root == null)
{
return;
}
else
{
forEach(root);
}
}
private void forEach(Node x)
{
if(x == null)
{
return;
}
else
{
forEach(x.left);
System.out.println("Key: " + x.key + "; Value: " + x.value);
forEach(x.right);
}
}