1.介绍
1.1定义
二叉查找树(Binary Search Tree),是一种基础数据结构,支持多种动态集合的操作,search,insert,delete,minimum和maxmum
1.2二叉树的性质:
- 要么是一个空树
- 要么具备以下性质
(1)若左子树非空,则左子树上的所有节点的关键字值均小于根节点的关键字值;
(2)若右子树非空,则右子树上的所有节点的关键字值均大于根节点的关键字值;
(3)左右子树也分别是一个二叉查找树
2.二叉查找树的java实现
2.1 二叉查找树节点的定义
public class BSTree<T extends Comparable<T>> {
private BSTNode<T> mRoot; // 根结点
public class BSTNode<T extends Comparable<T>> {
T key; // 关键字(键值)
BSTNode<T> left; // 左孩子
BSTNode<T> right; // 右孩子
BSTNode<T> parent; // 父结点
public BSTNode(T key, BSTNode<T> parent, BSTNode<T> left, BSTNode<T> right) {
this.key = key;
this.parent = parent;
this.left = left;
this.right = right;
}
}
......
}
BSTNode包含二叉查找树的几个基本信息:
(01) key – 它是关键字,是用来对二叉查找树的节点进行排序的。
(02) left – 它指向当前节点的左孩子。
(03) right – 它指向当前节点的右孩子。
(04) parent – 它指向当前节点的父结点。
2.2二叉树的三种遍历
前序遍历,中序遍历,后序遍历
2.2.1 前序遍历
若二叉树非空,则执行以下操作:
(01) 访问根结点;
(02) 先序遍历左子树;
(03) 先序遍历右子树。
前序遍历代码
private void preOrder(BSTNode<T> tree) {
if(tree != null) {
System.out.print(tree.key+" ");
preOrder(tree.left);
preOrder(tree.right);
}
}
public void preOrder() {
preOrder(mRoot);
}
2.2.2 中序遍历
若二叉树非空,则执行以下操作:
(01) 中序遍历左子树;
(02) 访问根结点;
(03) 中序遍历右子树。
中序遍历代码:
private void inOrder(BSTNode<T> tree) {
if(tree != null) {
inOrder(tree.left);
System.out.print(tree.key+" ");
inOrder(tree.right);
}
}
public void inOrder() {
inOrder(mRoot);
}
2.2.3后序遍历
若二叉树非空,则执行以下操作:
(01) 后序遍历左子树;
(02) 后序遍历右子树;
(03) 访问根结点。
后序遍历代码:
private void postOrder(BSTNode<T> tree) {
if(tree != null)
{
postOrder(tree.left);
postOrder(tree.right);
System.out.print(tree.key+" ");
}
}
public void postOrder() {
postOrder(mRoot);
}
3.查找
3.1递归版本
/*
* (递归实现)查找"二叉树x"中键值为key的节点
*/
private BSTNode<T> search(BSTNode<T> x, T key) {
if (x==null)
return x;
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;
}
public BSTNode<T> search(T key) {
return search(mRoot, key);
}
3.2非递归版本
/*
* (非递归实现)查找"二叉树x"中键值为key的节点
*/
private BSTNode<T> iterativeSearch(BSTNode<T> x, T key) {
while (x!=null) {
int cmp = key.compareTo(x.key);
if (cmp < 0)
x = x.left;
else if (cmp > 0)
x = x.right;
else
return x;
}
return x;
}
public BSTNode<T> iterativeSearch(T key) {
return iterativeSearch(mRoot, key);
}
4.Minimum和Maximum
4.1Minimum
/*
* 查找最小结点:返回tree为根结点的二叉树的最小结点。
*/
private BSTNode<T> minimum(BSTNode<T> tree) {
if (tree == null)
return null;
while(tree.left != null)
tree = tree.left;
return tree;
}
public T minimum() {
BSTNode<T> p = minimum(mRoot);
if (p != null)
return p.key;
return null;
}
4.2 Maximum
/*
* 查找最大结点:返回tree为根结点的二叉树的最大结点。
*/
private BSTNode<T> maximum(BSTNode<T> tree) {
if (tree == null)
return null;
while(tree.right != null)
tree = tree.right;
return tree;
}
public T maximum() {
BSTNode<T> p = maximum(mRoot);
if (p != null)
return p.key;
return null;
}
5.插入
思路: 先循环比较,找到插入的地方,然后再判断是根节点还是作左叶子还是又叶子
/*
* 将结点插入到二叉树中
*
* 参数说明:
* tree 二叉树的
* z 插入的结点
*/
private void insert(BSTree<T> bst, BSTNode<T> z) {
int cmp;
BSTNode<T> y = null;
BSTNode<T> x = bst.mRoot;
// 查找z的插入位置
while (x != null) {
y = x;
cmp = z.key.compareTo(x.key);
if (cmp < 0)
x = x.left;
else
x = x.right;
}
z.parent = y;
if (y==null)
bst.mRoot = z;
else {
cmp = z.key.compareTo(y.key);
if (cmp < 0)
y.left = z;
else
y.right = z;
}
}
/*
* 新建结点(key),并将其插入到二叉树中
*
* 参数说明:
* tree 二叉树的根结点
* key 插入结点的键值
*/
public void insert(T key) {
BSTNode<T> z=new BSTNode<T>(key,null,null,null);
// 如果新建结点失败,则返回。
if (z != null)
insert(this, z);
}
6.前驱后继
节点的前驱: 是该节点的左子树中的最大节点。
节点的后继: 是该节点的右子树中的最小节点。
6.1前驱
/*
* 找结点(x)的前驱结点。即,查找"二叉树中数据值小于该结点"的"最大结点"。
*/
public BSTNode<T> predecessor(BSTNode<T> x) {
// 如果x存在左孩子,则"x的前驱结点"为 "以其左孩子为根的子树的最大结点"。
if (x.left != null)
return maximum(x.left);
// 如果x没有左孩子。则x有以下两种可能:
// (01) x是"一个右孩子",则"x的前驱结点"为 "它的父结点"。
// (01) x是"一个左孩子",则查找"x的最低的父结点,并且该父结点要具有右孩子",找到的这个"最低的父结点"就是"x的前驱结点"。
BSTNode<T> y = x.parent;
while ((y!=null) && (x==y.left)) {
x = y;
y = y.parent;
}
return y;
}
6.2 后继
/*
* 找结点(x)的后继结点。即,查找"二叉树中数据值大于该结点"的"最小结点"。
*/
public BSTNode<T> successor(BSTNode<T> x) {
// 如果x存在右孩子,则"x的后继结点"为 "以其右孩子为根的子树的最小结点"。
if (x.right != null)
return minimum(x.right);
// 如果x没有右孩子。则x有以下两种可能:
// (01) x是"一个左孩子",则"x的后继结点"为 "它的父结点"。
// (02) x是"一个右孩子",则查找"x的最低的父结点,并且该父结点要具有左孩子",找到的这个"最低的父结点"就是"x的后继结点"。
BSTNode<T> y = x.parent;
while ((y!=null) && (x==y.right)) {
x = y;
y = y.parent;
}
return y;
}
7. 删除(星标!!!!!)
删除操作需要判断的情况是比较复杂的。 分为三种情况:
- 没有子节点
针对这种情况,直接删除即可。
//这里忽略了父节点不存在的情况,最后会巧妙的处理这种情况
if(node.parent.left == node){
node.parent.left = null;
} else {
node.parent.right = null;
}
- 只有左节点或只有右节点
这种情况,左节点或者右节点来顶替这个节点
//先找到子节点,不需要管他是左是右
BSTNode<T> child = null;
if(node.left != null){
child = node.left;
} else {
child = node.right;
}
//这里忽略了父节点不存在的情况,最后会巧妙的处理这种情况
//将父节点和子节点建立关系
if(node.parent.left == node){
node.parent.left = child;
} else {
node.parent.right = child;
}
child.parent = node.parent;
- 左右节点都有
用后继节点来顶替这个节点
//获取当前节点的后继结点
Node<T> successor = successor(node);
//转移值
node.key = successor.key;
//后续变成删除 successor,就变成了前两种情况
//在图示例子中,就是第一种没有子节点的情况
node = successor;
总的代码:
将三种情况融合。先处理左右节点都有的情况。处理完之后,剩下的两种情况可以合并处理。
- 先看node有没有左右节点,有的话直接赋值给定义好的child节点。
- 然后再判断child是不是空的,不是的话,交接一下,node的父节点赋值给child的父节点。空的话,有妙用。
- 最后,判断node有没有父节点,没有的话,child节点直接就是根节点。有的话,则判断node是左节点还是有节点,交接一下。(此处的交接就看出2中child为空的妙用了,为空在3中的交接处理的是没有左右节点的这种情况,用空给抹去了node)
private BSTNode<T> delete(BSTNode<T> node) {
//第 3 种情况,如果同时存在左右子节点
if (node.left != null && node.right != null){
//获取后继结点
BSTNode<T> successor = successor(node);
//转移后继结点值到当前节点
node.key = successor.key;
//把要删除的当前节点设置为后继结点
node = successor;
}
//经过前一步处理,下面只有前两种情况,只能是一个节点或者没有节点
//不管是否有子节点,都获取子节点
BSTNode<T> child;
if (node.left != null)
child = node.left;
else
child = node.right;
//如果 child != null,就说明是有一个节点的情况
if (child != null)
//将子节点和父节点关联上
child.parent = node.parent;
//如果当前节点没有父节点(后继情况到这儿时一定有父节点)
//说明要删除的就是根节点
if (node.parent == null)
//根节点设置为子节点
//按照前面逻辑,根只有一个或者没有节点,所以直接赋值 child 即可
mRoot = child;
else if (node == node.parent.left)//存在父节点,并且当前节点是左节点时
//将父节点的左节点设置为 child
node.parent.left = child;
else//右节点时
//将父节点的右节点设置为 child
node.parent.right = child;
//返回被删除的节点
return node;
}
//删除指定的值
public void delete(T key) {
//获取要删除的节点
BSTNode<T> node = search(mRoot, key);
//如果存在就删除
if (node != null)
delete(node);
}