刚刚介绍完静态查找树查找算法总结(一),接下来谈谈一些常用的动态查找结构。其中包括最基本的二叉排序树(二叉查找树、二叉收索树)、二叉平衡树(AVL树)、红黑树、以及一些多路查找树(B+,B-树)。
二叉排序树 :
特点:
1、如果它的左子树不空,那么左子树上的所有结点值均小于它的根结点值;
2、如果它的右子树不空,那么右子树上的所有结点值均大于它的根结点值;
3、它的左右子树也分别为二叉查找树
如下如所示二叉查找树:
二叉查找树的插入和删除都非常的方便,很好的解决了折半查找添加删除所带来的问题。
那么它的效率又如何呢?
很显然,二叉查找树查找一个数据,不需要遍历全部的节点,查找效率确实提高了。但是,也有一个很严重的问题,我在a图中查找8需要比较5次,而在b图中查找8需要3次,更为严重的是,我的二叉查找树是c图,如果再查找8,那将会如何呢?很显然,整棵树就退化成了一个线性结构,此时再查找8,就和顺序查找没什么区别了。
时间复杂度分析:最坏的情况下和顺序查找相同,是O(N),最好的情况下和折半查找相同,是O(logN)。进过研究发现,在随机的情况下,二叉排序树的平均查找长度和logn是等数量级的。然而,在某些情况下(有人研究表明,这种概率大约占46.5%)尚需在构成二叉排序树的过程中进行”平衡化”处理,成为二叉平衡树。
这说明了一个问题,同样的一组数据集合,不同的添加顺序会导致二叉查找树的结构完全不一样,直接影响到了查找的效率。
二叉排序树的一些基本操作,包括,添加节点,删除节点,获取做大的节点,获取最小的节点,得到任意孩子节点的父节点等。
/**
* 向二叉排序树中添加节点
* @param root
* @param value
* @return 二叉排数树根节点
*/
public static TreeNode insertTreeNode(TreeNode root,int value){
if(root == null){
root = new TreeNode(value);
}else if(root.value>value){
root.left = insertTreeNode(root.left,value);
}else if(root.valuereturn root;
}
/**
* 在二叉排数树中查找,关键字为key的节点
* @param root
* @param key
* @return 二叉排数树中关键字为key的节点
*/
public static TreeNode serachTreeNode(TreeNode root,int key){
if(root == null){
return null;
}
if(key>root.value)
return serachTreeNode(root.right,key);
else if(keyreturn serachTreeNode(root.left,key);
else
return root;
}
//获取父节点
public static TreeNode getParentTreeNode (TreeNode root,int key){
Queue queue = new LinkedList();
if(root==null || root.value==key){
return null;
}
queue.add(root);
while(!queue.isEmpty()){
TreeNode current = queue.poll();
if(current.left!=null){
if(current.left.value==key)
return current;
else
queue.add(current.left);
}
if(current.right!=null){
if(current.right.value==key)
return current;
else
queue.add(current.right);
}
}
return null;
//获得关键字最大的节点
public static TreeNode getMaxTreeNode(TreeNode root){
if(root == null || root.right==null)
return root;
if(root.right != null){
root = getMaxTreeNode(root.right);
}
return root;
}
//获得关键字最小的节点
public static TreeNode getMinTreeNode(TreeNode root){
if(root == null || root.left==null)
return root;
if(root.left != null){
root = getMinTreeNode(root.left);
}
return root;
}
//打印排序二叉树
public static void printInOrder(TreeNode root){
if(root!=null){
printInOrder(root.left);
System.out.print(root.value+" ");
printInOrder(root.right);
}
}
最后关于二叉排序树的删除相对比较复杂,可以分下面三种情况讨论:
(1)需要删除的节点下并没有其他子节点,其为叶子节点。
(2)需要删除的节点下有一个子节点(左或右)。
(3)需要删除的节点下有两个子节点(既左右节点都存在)。
针对第一种情况:删除叶子节点,例如删除4,只需判断4是它的父节点的左孩子,还是右孩子。如图中,4则为其父节点3的右孩子,所以直接将3的右孩子置为空。
针对第二种情况:要删除的节点有一个孩子节点,若其为其父节点的右孩子,则让其父节点的右指针指向其孩子节点;若其为其父节点的左孩子,则让其父节点的左指针指向其孩子节点。
针对第三种情况:要删除的节点既有左孩子又有右孩子,例如节点2,则我们先在需要删除的节点的右子树中,找到一个最小的值(因为右子树中的节点的值一定大于根节点)4。然后,用找到的最小的值与需要删除的节点的值替换。然后,再将最小值的原节点进行删除.
public static void deleteTreeNode(TreeNode root,TreeNode pNode){
TreeNode parent = getParentTreeNode(root,pNode.value);
//该节点是叶子节点
if(pNode.left==null && pNode.right==null){
//该节点是父节点的左孩子
if(parent.left==pNode)
parent.left=null;
else
parent.right=null;
//该节点只有右孩子
}else if(pNode.left==null&&pNode.right!=null){
//该节点为其父节点的左孩子
if(pNode == parent.left ){
parent.left = pNode.right;
}else{
parent.right = pNode.right;
}
//该节点只有右孩子
}else if(pNode.left!=null&&pNode.right==null){
if(pNode ==parent.left ){
parent.left = pNode.left;
}else{
parent.right = pNode.left;
}
//该节点左右孩子都存在
}else if(pNode.left!=null&&pNode.right!=null){
/*
* 我们先在需要删除的节点的右子树中,找到一个最小的值(因为右子树中的节点的值一定大于根节点)。
* 然后,用找到的最小的值与需要删除的节点的值替换。然后,再将最小值的原节点进行删除
* */
TreeNode minNode=getMinTreeNode(pNode.right);
deleteTreeNode(root,minNode);
TreeNode pNpdeparent = getParentTreeNode(root,pNode.value);
if(pNpdeparent.left==pNode){
pNpdeparent.left=minNode;
minNode.left = pNode.left;
minNode.right = pNode.right;
}
else{
pNpdeparent.right=minNode;
minNode.left = pNode.left;
minNode.right = pNode.right;
}
}
}
最后附上有关二叉排序树的基本操作的代码:(可直接运行)
输入:两行,第一行为二叉树节点的个数,第二行为各个节点的值(不重复)
10
89 67 34 30 40 50 44 55 66 77
输出:逐步按输入删除节点(除根节点)后二叉排序树的中序遍历(有序)
30 34 40 44 50 55 66 67 77 89
30 34 40 44 50 55 66 77 89
30 40 44 50 55 66 77 89
40 44 50 55 66 77 89
44 50 55 66 77 89
44 55 66 77 89
55 66 77 89
66 77 89
77 89
89
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.LinkedList;
import java.util.Queue;
class TreeNode {
public int value;
public TreeNode left;
public TreeNode right;
public TreeNode(int value){
this.value = value;
this.left = this.right = null;
}
}
public class BinarySearchTree {
/**
* 向二叉排序树中添加节点
* @param root
* @param value
* @return 二叉排数树根节点
*/
public static TreeNode insertTreeNode(TreeNode root,int value){
if(root == null){
root = new TreeNode(value);
}else if(root.value>value){
root.left = insertTreeNode(root.left,value);
}else if(root.valuereturn root;
}
/**
* 在二叉排数树中查找,关键字为key的节点
* @param root
* @param key
* @return 二叉排数树中关键字为key的节点
*/
public static TreeNode serachTreeNode(TreeNode root,int key){
if(root == null){
return null;
}
if(key>root.value)
return serachTreeNode(root.right,key);
else if(keyreturn serachTreeNode(root.left,key);
else
return root;
}
public static void deleteTreeNode(TreeNode root,TreeNode pNode){
TreeNode parent = getParentTreeNode(root,pNode.value);
//该节点是叶子节点
if(pNode.left==null && pNode.right==null){
//该节点是父节点的左孩子
if(parent.left==pNode)
parent.left=null;
else
parent.right=null;
//该节点只有右孩子
}else if(pNode.left==null&&pNode.right!=null){
//该节点为其父节点的左孩子
if(pNode == parent.left ){
parent.left = pNode.right;
}else{
parent.right = pNode.right;
}
//该节点只有右孩子
}else if(pNode.left!=null&&pNode.right==null){
if(pNode ==parent.left ){
parent.left = pNode.left;
}else{
parent.right = pNode.left;
}
//该节点左右孩子都存在
}else if(pNode.left!=null&&pNode.right!=null){
/*
* 我们先在需要删除的节点的右子树中,找到一个最小的值(因为右子树中的节点的值一定大于根节点)。
* 然后,用找到的最小的值与需要删除的节点的值替换。然后,再将最小值的原节点进行删除
* */
TreeNode minNode=getMinTreeNode(pNode.right);
deleteTreeNode(root,minNode);
TreeNode pNpdeparent = getParentTreeNode(root,pNode.value);
if(pNpdeparent.left==pNode){
pNpdeparent.left=minNode;
minNode.left = pNode.left;
minNode.right = pNode.right;
}
else{
pNpdeparent.right=minNode;
minNode.left = pNode.left;
minNode.right = pNode.right;
}
}
}
//获取父节点
/*
public static TreeNode getParentTreeNode(TreeNode root,int key){
if(root!=null){
if(root.left!=null && root.right!=null){
if(root.left.value == key || root.right.value ==key){
return root;
}else{
TreeNode tempRoot= getParentTreeNode(root.left,key);
if(tempRoot!=null)
return tempRoot;
root = getParentTreeNode(root.right,key);
}
}else if(root.left==null && root.right!=null){
if(root.right.value ==key){
return root;
}else{
root = getParentTreeNode(root.right,key);
}
}else if(root.left!=null && root.right==null){
if(root.left.value ==key){
return root;
}else{
root = getParentTreeNode(root.left,key);
}
}
else {
return null;
}
}
return root;
}
*/
public static TreeNode getParentTreeNode (TreeNode root,int key){
Queue queue = new LinkedList();
if(root==null || root.value==key){
return null;
}
queue.add(root);
while(!queue.isEmpty()){
TreeNode current = queue.poll();
if(current.left!=null){
if(current.left.value==key)
return current;
else
queue.add(current.left);
}
if(current.right!=null){
if(current.right.value==key)
return current;
else
queue.add(current.right);
}
}
return null;
}
//获得关键字最大的节点
public static TreeNode getMaxTreeNode(TreeNode root){
if(root == null || root.right==null)
return root;
if(root.right != null){
root = getMaxTreeNode(root.right);
}
return root;
}
//获得关键字最小的节点
public static TreeNode getMinTreeNode(TreeNode root){
if(root == null || root.left==null)
return root;
if(root.left != null){
root = getMinTreeNode(root.left);
}
return root;
}
//打印排序二叉树
public static void printInOrder(TreeNode root){
if(root!=null){
printInOrder(root.left);
System.out.print(root.value+" ");
printInOrder(root.right);
}
}
public static void main(String[] args) throws IOException {
StreamTokenizer cin = new StreamTokenizer (new BufferedReader(new InputStreamReader(System.in)));
while(cin.nextToken()!=cin.TT_EOF){
int n =(int)cin.nval;
int []input = new int[n];
for(int i=0;iint)cin.nval;
}
TreeNode root = null;
for(int i=0;ifor(int i=1;i"\n");
printInOrder(root);
}
}
}
}