二叉查找树简介
二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树。
定义
二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
(3)左、右子树也分别为二叉排序树;
查找
否则,若小于根结点的关键字值,递归查左子树。
若大于根结点的关键字值,递归查右子树。
若子树为空,查找不成功。
平均情况分析(在成功查找两种的情况下):
在一般情况下,设 P(n,i)为它的左子树的结点个数为 i 时的平均查找长度。如图的结点个数为 n = 6 且 i = 3; 则 P(n,i)= P(6, 3) = [ 1+ ( P(3) + 1) * 3 + ( P(2) + 1) * 2 ] / 6= [ 1+ ( 5/3 + 1) * 3 + ( 3/2 + 1) * 2 ] / 6
注意:这里 P(3)、P(2) 是具有 3 个结点、2 个结点的二叉分类树的平均查找长度。 在一般情况,P(i)为具有 i 个结点二叉分类树的平均查找长度。平均查找长度= 每个结点的深度的总和 / 总结点数
(上图应为左子树P(3),右子树P(2))
P(3) = (1+2+2)/ 3 = 5/3
P(2) = (1+2)/ 2 = 3/2∴ P(n,i)= [ 1+ ( P(i) + 1) * i + ( P(n-i-1) + 1) * (n-i-1) ] / n
∴ P(n)=
P(n,i)/ n <= 2(1+I/n)lnn
因为 2(1+I/n)lnn≈1.38logn 故P(n)=O(logn)
删除结点
在二叉排序树删去一个结点,分三种情况讨论:
-
若*p结点为叶子结点,即PL(左子树)和PR(右子树)均为空树。由于删去叶子结点不破坏整棵树的结构,则可以直接删除此子结点。
-
若*p结点只有左子树PL或右子树PR,此时只要令PL或PR直接成为其双亲结点*f的左子树(当*p是左子树)或右子树(当*p是右子树)即可,作此修改也不破坏二叉排序树的特性。
-
若*p结点的左子树和右子树均不空。在删去*p之后,为保持其它元素之间的相对位置不变,可按中序遍历保持有序进行调整,可以有两种做法:
其一是令*p的左子树为*f的左/右(依*p是*f的左子树还是右子树而定)子树,*s为*p左子树的最右下的结点,而*p的右子树为*s的右子树;
其二是令*p的直接前驱(或直接后继)替代*p,然后再从二叉排序树中删去它的直接前驱(或直接后继)-即让*f的左子树(如果有的话)成为*p左子树的最左下结点(如果有的话),再让*f成为*p的左右结点的父结点。
代码实现
using System;
public class BinarySearchTree <T>
{
/*
二叉查找树(Binary Search 1+Tree),又被称为二叉搜索树。
它是特殊 的二叉树:对于二叉树,假设x为二叉树中的任意一个结点,
x节点包含关键字key,节点x的key值记为key[x]。
如果y是x的左子树中的一个结点,则key[y] <= key[x];
如果y是x的右子树的一个结点,则key[y] >= key[x]。
那么,这棵树就是二叉查找树
在二叉查找树中:
若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
任意节点的左、右子树也分别为二叉查找树。
没有键值相等的节点(no duplicate nodes)。
***树由节点组成,每个节点(泛型)包含值和三个引用
*/
private BSTNode<T> mRoot; // 根结点
public class BSTNode<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;
}
}
/*
* 前序遍历
若二叉树非空,则执行以下操作:
(01) 访问根结点;
(02) 先序遍历左子树;
(03) 先序遍历右子树。
参数为节点,首先打印值,在按照根左右遍历
*/
private void preOrder(BSTNode<T> tree)
{
if (tree != null)
{
Console.WriteLine(tree.key + " ");
preOrder(tree.left);
preOrder(tree.right);
}
}
//无参数直接遍历根节点
public void preOrder()
{
preOrder(mRoot);
}
/*
中序遍历
若二叉树非空,则执行以下操作:
(01) 中序遍历左子树;
(02) 访问根结点;
(03) 中序遍历右子树。
递归遍历,直接遍历最左端的节点,在打印节点值
*
*/
private void inOrder(BSTNode<T> tree)
{
if (tree != null)
{
inOrder(tree.left);
Console.WriteLine(tree.key + " ");
inOrder(tree.right);
}
}
public void inOrder()
{
inOrder(mRoot);
}
/*
后序遍历
若二叉树非空,则执行以下操作:
(01) 后序遍历左子树;
(02) 后序遍历右子树;
(03) 访问根结点。
*/
private void postOrder(BSTNode<T> tree)
{
if (tree != null)
{
postOrder(tree.left);
postOrder(tree.right);
Console.WriteLine(tree.key + " ");
}
}
public void postOrder()
{
postOrder(mRoot);
}
/*
* (递归实现)查找"二叉树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);
}
/*
* (非递归实现)查找"二叉树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);
}
/*
* 查找最大结点:返回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;
}
/*
* 查找最小结点:返回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;
}
/*
* 找结点(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;
}
/*
* 找结点(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;
}
}