算法与数据结构之树形结构的相关知识,简单易懂。

来源:互联网 发布:类似sketch windows 编辑:程序博客网 时间:2024/05/17 23:26

一、    二叉查找树之Java的实现

1)  概要

在前面分别介绍了"二叉查找树的相关理论知识,然后给出了二叉查找树的C和C++实现版本"。这一章写一写二叉查找树的Java实现版本。

目录

1. 二叉树查找树

2. 二叉查找树的Java实现

3. 二叉查找树的Java测试程序

2)  二叉查找树简介

二叉查找树(Binary Search Tree),又被称为二叉搜索树。

它是特殊的二叉树:对于二叉树,假设x为二叉树中的任意一个结点,x节点包含关键字key,节点x的key值记为key[x]。如果y是x的左子树中的一个结点,则key[y] <= key[x];如果y是x的右子树的一个结点,则key[y]>= key[x]。那么,这棵树就是二叉查找树。如下图所示:

在二叉查找树中:

(01) 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;

(02) 任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

(03) 任意节点的左、右子树也分别为二叉查找树。

(04) 没有键值相等的节点(noduplicate nodes)。

3)  二叉查找树的Java实现

1. 二叉查找树节点的定义

public class BSTree<T extendsComparable<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;

       }

    }

   ......

}

BSTree是二叉树,它保护了二叉树的根节点mRoot;mRoot是BSTNode类型,而BSTNode是二叉查找树的节点,它是BSTree的内部类。BSTNode包含二叉查找树的几个基本信息:

(01) key -- 它是关键字,是用来对二叉查找树的节点进行排序的。

(02) left -- 它指向当前节点的左孩子。

(03) right -- 它指向当前节点的右孩子。

(04) parent -- 它指向当前节点的父结点。

2 遍历

这里讲解前序遍历、中序遍历、后序遍历3种方式。

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 中序遍历

若二叉树非空,则执行以下操作:

(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.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);

}

看看下面这颗树的各种遍历方式:

对于上面的二叉树而言,

(01) 前序遍历结果: 3 1 25 4 6

(02) 中序遍历结果: 1 2 34 5 6

(03) 后序遍历结果: 2 1 46 5 3

3. 查找

递归版本的代码

/*

 * (递归实现)查找"二叉树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(Tkey) {

   return iterativeSearch(mRoot, key);

}

4. 最大值和最小值

查找最大值的代码

/*

 * 查找最大结点:返回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;

}

5. 前驱和后继

节点的前驱:是该节点的左子树中的最大节点。

节点的后继:是该节点的右子树中的最小节点。

查找前驱节点的代码

/*

 * 找结点(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;

}

6. 插入

插入节点的代码

/*

 * 将结点插入到二叉树中

 *

 * 参数说明:

 *    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);

}

注:本文实现的二叉查找树是允许插入相同键值的节点的。若想禁止二叉查找树中插入相同键值的节点,可以参考"二叉查找树(一)之图文解析 和 C语言的实现"中的插入函数进行修改。

7. 删除

删除节点的代码

/*

 * 删除结点(z),并返回被删除的结点

 *

 * 参数说明:

 *    bst 二叉树

 *     z 删除的结点

 */

private BSTNode<T>remove(BSTree<T> bst, BSTNode<T> z) {

   BSTNode<T> x=null;

   BSTNode<T> y=null;

   if ((z.left == null) || (z.right == null) )

       y = z;

   else

       y = successor(z);

   if (y.left != null)

       x = y.left;

   else

       x = y.right;

   if (x != null)

       x.parent = y.parent;

   if (y.parent == null)

       bst.mRoot = x;

   else if (y == y.parent.left)

       y.parent.left = x;

   else

       y.parent.right = x;

   if (y != z)

       z.key = y.key;

    return y;

}

/*

 * 删除结点(z),并返回被删除的结点

 *

 * 参数说明:

 *    tree 二叉树的根结点

 *     z 删除的结点

 */

public void remove(T key) {

   BSTNode<T> z, node;

 

   if ((z = search(mRoot, key)) != null)

       if ( (node = remove(this, z)) != null)

           node = null;

}

8. 打印

打印二叉查找树的代码

/*

 * 打印"二叉查找树"

 *

 *key        -- 节点的键值

 *direction  --  0,表示该节点是根节点;

 *              -1,表示该节点是它的父结点的左孩子;

 *                1,表示该节点是它的父结点的右孩子。

 */

private void print(BSTNode<T> tree, Tkey, int direction) {

   if(tree != null) {

       if(direction==0)    // tree是根节点

           System.out.printf("%2d is root\n", tree.key);

       else                // tree是分支节点

           System.out.printf("%2d is %2d's %6s child\n", tree.key, key,direction==1?"right" : "left");

       print(tree.left, tree.key, -1);

       print(tree.right,tree.key,  1);

    }

}

public void print() {

   if (mRoot != null)

       print(mRoot, mRoot.key, 0);

}

9. 销毁

销毁二叉查找树的代码

/*

 * 销毁二叉树

 */

private void destroy(BSTNode<T> tree){

   if (tree==null)

       return ;

   if (tree.left != null)

       destroy(tree.left);

   if (tree.right != null)

       destroy(tree.right);

   tree=null;

}

public void clear() {

   destroy(mRoot);

   mRoot = null;

}

4)  二叉查找树的C++测试程序(BSTreeTest.java)

/**

 *Java 语言: 二叉查找树

 *

 *@author skywang

 *@date 2013/11/07

 */

public class BSTreeTest {

   private static final int arr[] = {1,5,4,3,2,6};

   public static void main(String[] args) {

       int i, ilen;

       BSTree<Integer> tree=new BSTree<Integer>();

       System.out.print("== 依次添加: ");

       ilen = arr.length;

       for(i=0; i<ilen; i++) {

           System.out.print(arr[i]+" ");

           tree.insert(arr[i]);

       }

       System.out.print("\n== 前序遍历: ");

       tree.preOrder();

       System.out.print("\n== 中序遍历: ");

       tree.inOrder();

       System.out.print("\n== 后序遍历: ");

       tree.postOrder();

       System.out.println();

       System.out.println("== 最小值: "+tree.minimum());

       System.out.println("== 最大值: "+tree.maximum());

       System.out.println("== 树的详细信息: ");

       tree.print();

       System.out.print("\n== 删除根节点: "+arr[3]);

       tree.remove(arr[3]);

       System.out.print("\n== 中序遍历: ");

       tree.inOrder();

       System.out.println();

       // 销毁二叉树

       tree.clear();

    }

}

在二叉查找树的Java实现中,使用了泛型,也就意味着支持任意类型; 但是该类型必须要实现Comparable接口。

二叉查找树的Java测试程序

上面的BSTreeTest.java是二叉查找树树的测试程序,运行结果如下:

== 依次添加: 1 5 4 3 26

== 前序遍历: 1 5 4 3 26

== 中序遍历: 1 2 3 4 56

== 后序遍历: 2 3 4 6 51

== 最小值: 1

== 最大值: 6

== 树的详细信息:

 1 isroot

 5is  1's right child

 4is  5's  left child

 3is  4's  left child

 2is  3's  left child

 6is  5's right child

== 删除根节点: 3

== 中序遍历: 1 2 4 5 6

下面对测试程序的流程进行分析!

(01) 新建"二叉查找树"root。

(02) 向二叉查找树中依次插入1,5,4,3,2,6。如下图所示:

(03) 遍历和查找

插入1,5,4,3,2,6之后,得到的二叉查找树如下:

前序遍历结果: 1 5 4 3 2 6

中序遍历结果: 1 2 3 4 5 6

后序遍历结果: 2 3 4 6 5 1

最小值是1,而最大值是6。

(04) 删除节点4。如下图所示:

(05) 重新遍历该二叉查找树。

中序遍历结果: 1 2 4 5 6

二、    AVL树之Java的实现

1)  概要

本章介绍AVL树的Java实现。内容包括:

1. AVL树的介绍

2. AVL树的Java实现

3. AVL树的Java测试程序

2)  AVL树的介绍

AVL树是高度平衡的而二叉树。它的特点是:AVL树中任何节点的两个子树的高度最大差别为1。

上面的两张图片,左边的是AVL树,它的任何节点的两个子树的高度差别都<=1;而右边的不是AVL树,因为7的两颗子树的高度相差为2(以2为根节点的树的高度是3,而以8为根节点的树的高度是1)。

3)  AVL树的Java实现

1. 节点

1.1 节点定义

public class AVLTree<T extendsComparable<T>> {

   private AVLTreeNode<T> mRoot;   // 根结点

   // AVL树的节点(内部类)

   class AVLTreeNode<T extends Comparable<T>> {

       T key;                // 关键字(键值)

       int height;         // 高度

       AVLTreeNode<T> left;    // 左孩子

       AVLTreeNode<T> right;    // 右孩子

        public AVLTreeNode(T key,AVLTreeNode<T> left, AVLTreeNode<T> right) {

           this.key = key;

           this.left = left;

           this.right = right;

           this.height = 0;

       }

    }

   ......

}

AVLTree是AVL树对应的类,而AVLTreeNode是AVL树节点,它是AVLTree的内部类。AVLTree包含了AVL树的根节点,AVL树的基本操作也定义在AVL树中。AVLTreeNode包括的几个组成对象:

(01) key -- 是关键字,是用来对AVL树的节点进行排序的。

(02) left -- 是左孩子。

(03) right -- 是右孩子。

(04) height -- 是高度。

1.2 树的高度

/*

 * 获取树的高度

 */

private int height(AVLTreeNode<T>tree) {

   if (tree != null)

       return tree.height;

 

   return 0;

}

public int height() {

   return height(mRoot);

}

关于高度,有的地方将"空二叉树的高度是-1",而本文采用维基百科上的定义:树的高度为最大层次。即空的二叉树的高度是0,非空树的高度等于它的最大层次(根的层次为1,根的子节点为第2层,依次类推)。

1.3 比较大小

/*

 * 比较两个值的大小

 */

private int max(int a, int b) {

   return a>b ? a : b;

}

2. 旋转

如果在AVL树中进行插入或删除节点后,可能导致AVL树失去平衡。这种失去平衡的可以概括为4种姿态:LL(左左),LR(左右),RR(右右)和RL(右左)。下面给出它们的示意图:

上图中的4棵树都是"失去平衡的AVL树",从左往右的情况依次是:LL、LR、RL、RR。除了上面的情况之外,还有其它的失去平衡的AVL树,如下图:

上面的两张图都是为了便于理解,而列举的关于"失去平衡的AVL树"的例子。总的来说,AVL树失去平衡时的情况一定是LL、LR、RL、RR这4种之一,它们都由各自的定义:

(1) LL:LeftLeft,也称为"左左"。插入或删除一个节点后,根节点的左子树的左子树还有非空子节点,导致"根的左子树的高度"比"根的右子树的高度"大2,导致AVL树失去了平衡。

     例如,在上面LL情况中,由于"根节点(8)的左子树(4)的左子树(2)还有非空子节点",而"根节点(8)的右子树(12)没有子节点";导致"根节点(8)的左子树(4)高度"比"根节点(8)的右子树(12)"高2。

(2) LR:LeftRight,也称为"左右"。插入或删除一个节点后,根节点的左子树的右子树还有非空子节点,导致"根的左子树的高度"比"根的右子树的高度"大2,导致AVL树失去了平衡。

     例如,在上面LR情况中,由于"根节点(8)的左子树(4)的左子树(6)还有非空子节点",而"根节点(8)的右子树(12)没有子节点";导致"根节点(8)的左子树(4)高度"比"根节点(8)的右子树(12)"高2。

(3) RL:RightLeft,称为"右左"。插入或删除一个节点后,根节点的右子树的左子树还有非空子节点,导致"根的右子树的高度"比"根的左子树的高度"大2,导致AVL树失去了平衡。

     例如,在上面RL情况中,由于"根节点(8)的右子树(12)的左子树(10)还有非空子节点",而"根节点(8)的左子树(4)没有子节点";导致"根节点(8)的右子树(12)高度"比"根节点(8)的左子树(4)"高2。

(4) RR:RightRight,称为"右右"。插入或删除一个节点后,根节点的右子树的右子树还有非空子节点,导致"根的右子树的高度"比"根的左子树的高度"大2,导致AVL树失去了平衡。

     例如,在上面RR情况中,由于"根节点(8)的右子树(12)的右子树(14)还有非空子节点",而"根节点(8)的左子树(4)没有子节点";导致"根节点(8)的右子树(12)高度"比"根节点(8)的左子树(4)"高2。

如果在AVL树中进行插入或删除节点后,可能导致AVL树失去平衡。AVL失去平衡之后,可以通过旋转使其恢复平衡,下面分别介绍"LL(左左),LR(左右),RR(右右)和RL(右左)"这4种情况对应的旋转方法。

2.1 LL的旋转

LL失去平衡的情况,可以通过一次旋转让AVL树恢复平衡。如下图:

图中左边是旋转之前的树,右边是旋转之后的树。从中可以发现,旋转之后的树又变成了AVL树,而且该旋转只需要一次即可完成。

对于LL旋转,你可以这样理解为:LL旋转是围绕"失去平衡的AVL根节点"进行的,也就是节点k2;而且由于是LL情况,即左左情况,就用手抓着"左孩子,即k1"使劲摇。将k1变成根节点,k2变成k1的右子树,"k1的右子树"变成"k2的左子树"。

LL的旋转代码

/*

 * LL:左左对应的情况(左单旋转)。

 *

 * 返回值:旋转后的根节点

 */

private AVLTreeNode<T>leftLeftRotation(AVLTreeNode<T> k2) {

   AVLTreeNode<T> k1;

   k1 = k2.left;

   k2.left = k1.right;

   k1.right = k2;

   k2.height = max( height(k2.left), height(k2.right)) + 1;

   k1.height = max( height(k1.left), k2.height) + 1;

   return k1;

}

2.2 RR的旋转

理解了LL之后,RR就相当容易理解了。RR是与LL对称的情况!RR恢复平衡的旋转方法如下:

图中左边是旋转之前的树,右边是旋转之后的树。RR旋转也只需要一次即可完成。

RR的旋转代码

/*

 * RR:右右对应的情况(右单旋转)。

 *

 * 返回值:旋转后的根节点

 */

private AVLTreeNode<T>rightRightRotation(AVLTreeNode<T> k1) {

   AVLTreeNode<T> k2;

   k2 = k1.right;

   k1.right = k2.left;

   k2.left = k1;

   k1.height = max( height(k1.left), height(k1.right)) + 1;

   k2.height = max( height(k2.right), k1.height) + 1;

   return k2;

}

2.3 LR的旋转

LR失去平衡的情况,需要经过两次旋转才能让AVL树恢复平衡。如下图:

第一次旋转是围绕"k1"进行的"RR旋转",第二次是围绕"k3"进行的"LL旋转"。

LR的旋转代码

/*

 * LR:左右对应的情况(左双旋转)。

 *

 * 返回值:旋转后的根节点

 */

private AVLTreeNode<T>leftRightRotation(AVLTreeNode<T> k3) {

   k3.left = rightRightRotation(k3.left);

   return leftLeftRotation(k3);

}

2.4 RL的旋转

RL是与LR的对称情况!RL恢复平衡的旋转方法如下:

第一次旋转是围绕"k3"进行的"LL旋转",第二次是围绕"k1"进行的"RR旋转"。

RL的旋转代码

/*

 * RL:右左对应的情况(右双旋转)。

 *

 * 返回值:旋转后的根节点

 */

private AVLTreeNode<T>rightLeftRotation(AVLTreeNode<T> k1) {

   k1.right = leftLeftRotation(k1.right);

 

   return rightRightRotation(k1);

}

3. 插入

插入节点的代码

/*

 * 将结点插入到AVL树中,并返回根节点

 *

 * 参数说明:

 *    tree AVL树的根结点

 *    key 插入的结点的键值

 * 返回值:

 *     根节点

 */

private AVLTreeNode<T> insert(AVLTreeNode<T>tree, T key) {

   if (tree == null) {

       // 新建节点

       tree = new AVLTreeNode<T>(key, null, null);

       if (tree==null) {

           System.out.println("ERROR: create avltree node failed!");

           return null;

       }

    }else {

       int cmp = key.compareTo(tree.key);

 

          if (cmp < 0) {    // 应该将key插入到"tree的左子树"的情况

           tree.left = insert(tree.left, key);

           // 插入节点后,若AVL树失去平衡,则进行相应的调节。

           if (height(tree.left) - height(tree.right) == 2) {

                if(key.compareTo(tree.left.key) < 0)

                    tree =leftLeftRotation(tree);

                else

                    tree =leftRightRotation(tree);

           }

       } else if (cmp > 0) {    // 应该将key插入到"tree的右子树"的情况

           tree.right = insert(tree.right, key);

           // 插入节点后,若AVL树失去平衡,则进行相应的调节。

           if (height(tree.right) - height(tree.left) == 2) {

                if(key.compareTo(tree.right.key) > 0)

                    tree =rightRightRotation(tree);

                else

                    tree =rightLeftRotation(tree);

           }

       } else {    // cmp==0

           System.out.println("添加失败:不允许添加相同的节点!");

       }

    }

   tree.height = max( height(tree.left), height(tree.right)) + 1;

   return tree;

}

public void insert(T key) {

   mRoot = insert(mRoot, key);

}

4. 删除

删除节点的代码

/*

 * 删除结点(z),返回根节点

 *

 * 参数说明:

 *    tree AVL树的根结点

 *     z 待删除的结点

 * 返回值:

 *     根节点

 */

private AVLTreeNode<T>remove(AVLTreeNode<T> tree, AVLTreeNode<T> z) {

   // 根为空 或者 没有要删除的节点,直接返回null。

   if (tree==null || z==null)

       return null;

   int cmp = z.key.compareTo(tree.key);

   if (cmp < 0) {        // 待删除的节点在"tree的左子树"中

       tree.left = remove(tree.left, z);

       // 删除节点后,若AVL树失去平衡,则进行相应的调节。

       if (height(tree.right) - height(tree.left) == 2) {

           AVLTreeNode<T> r = tree.right;

           if (height(r.left) > height(r.right))

                tree = rightLeftRotation(tree);

           else

                tree =rightRightRotation(tree);

       }

    }else if (cmp > 0) {    // 待删除的节点在"tree的右子树"中

       tree.right = remove(tree.right, z);

       // 删除节点后,若AVL树失去平衡,则进行相应的调节。

       if (height(tree.left) - height(tree.right) == 2) {

           AVLTreeNode<T> l = tree.left;

           if (height(l.right) > height(l.left))

                tree = leftRightRotation(tree);

           else

                tree = leftLeftRotation(tree);

       }

    }else {    // tree是对应要删除的节点。

       // tree的左右孩子都非空

       if ((tree.left!=null) && (tree.right!=null)) {

           if (height(tree.left) > height(tree.right)) {

                // 如果tree的左子树比右子树高;

                // 则(01)找出tree的左子树中的最大节点

                //  (02)将该最大节点的值赋值给tree。

                //   (03)删除该最大节点。

                // 这类似于用"tree的左子树中最大节点"做"tree"的替身;

                // 采用这种方式的好处是:删除"tree的左子树中最大节点"之后,AVL树仍然是平衡的。

                AVLTreeNode<T> max =maximum(tree.left);

                tree.key = max.key;

                tree.left = remove(tree.left,max);

           } else {

                // 如果tree的左子树不比右子树高(即它们相等,或右子树比左子树高1)

                // 则(01)找出tree的右子树中的最小节点

                //   (02)将该最小节点的值赋值给tree。

               //   (03)删除该最小节点。

                // 这类似于用"tree的右子树中最小节点"做"tree"的替身;

                // 采用这种方式的好处是:删除"tree的右子树中最小节点"之后,AVL树仍然是平衡的。

                AVLTreeNode<T> min =maximum(tree.right);

                tree.key = min.key;

                tree.right = remove(tree.right,min);

           }

       } else {

           AVLTreeNode<T> tmp = tree;

           tree = (tree.left!=null) ? tree.left : tree.right;

           tmp = null;

       }

    }

   return tree;

}

public void remove(T key) {

   AVLTreeNode<T> z;

 

   if ((z = search(mRoot, key)) != null)

       mRoot = remove(mRoot, z);

}

4)  完整的实现代码

AVL树的实现文件(AVRTree.java)

/**

 *Java 语言: AVL树

 *

 *@author skywang

 *@date 2013/11/07

 */

 

public class AVLTree<T extendsComparable<T>> {

   private AVLTreeNode<T> mRoot;   // 根结点

   // AVL树的节点(内部类)

   class AVLTreeNode<T extends Comparable<T>> {

       T key;                // 关键字(键值)

       int height;         // 高度

       AVLTreeNode<T> left;    // 左孩子

       AVLTreeNode<T> right;    // 右孩子

       public AVLTreeNode(T key, AVLTreeNode<T> left,AVLTreeNode<T> right) {

           this.key = key;

           this.left = left;

           this.right = right;

           this.height = 0;

       }

    }

   // 构造函数

   public AVLTree() {

       mRoot = null;

    }

   /*

    * 获取树的高度

    */

   private int height(AVLTreeNode<T> tree) {

       if (tree != null)

           return tree.height;

 

       return 0;

    }

   public int height() {

       return height(mRoot);

    }

   /*

    * 比较两个值的大小

    */

   private int max(int a, int b) {

       return a>b ? a : b;

    }

   /*

    * 前序遍历"AVL树"

    */

   private void preOrder(AVLTreeNode<T> tree) {

       if(tree != null) {

           System.out.print(tree.key+" ");

            preOrder(tree.left);

           preOrder(tree.right);

       }

    }

   public void preOrder() {

       preOrder(mRoot);

    }

   /*

    * 中序遍历"AVL树"

    */

   private void inOrder(AVLTreeNode<T> tree) {

       if(tree != null)

       {

           inOrder(tree.left);

           System.out.print(tree.key+" ");

           inOrder(tree.right);

       }

    }

   public void inOrder() {

       inOrder(mRoot);

    }

   /*

    * 后序遍历"AVL树"

    */

   private void postOrder(AVLTreeNode<T> tree) {

       if(tree != null) {

           postOrder(tree.left);

           postOrder(tree.right);

           System.out.print(tree.key+" ");

       }

    }

   public void postOrder() {

       postOrder(mRoot);

    }

   /*

    * (递归实现)查找"AVL树x"中键值为key的节点

    */

   private AVLTreeNode<T> search(AVLTreeNode<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 AVLTreeNode<T> search(T key) {

       return search(mRoot, key);

    }

   /*

    * (非递归实现)查找"AVL树x"中键值为key的节点

    */

   private AVLTreeNode<T> iterativeSearch(AVLTreeNode<T> x, Tkey) {

       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 AVLTreeNode<T> iterativeSearch(T key) {

       return iterativeSearch(mRoot, key);

    }

   /*

    * 查找最小结点:返回tree为根结点的AVL树的最小结点。

    */

   private AVLTreeNode<T> minimum(AVLTreeNode<T> tree) {

       if (tree == null)

           return null;

 

       while(tree.left != null)

           tree = tree.left;

       return tree;

    }

   public T minimum() {

       AVLTreeNode<T> p = minimum(mRoot);

       if (p != null)

           return p.key;

       return null;

    }

   /*

    * 查找最大结点:返回tree为根结点的AVL树的最大结点。

    */

   private AVLTreeNode<T> maximum(AVLTreeNode<T> tree) {

       if (tree == null)

           return null;

 

       while(tree.right != null)

           tree = tree.right;

       return tree;

    }

   public T maximum() {

       AVLTreeNode<T> p = maximum(mRoot);

       if (p != null)

           return p.key;

 

       return null;

    }

   /*

    * LL:左左对应的情况(左单旋转)。

    *

    * 返回值:旋转后的根节点

    */

   private AVLTreeNode<T> leftLeftRotation(AVLTreeNode<T> k2) {

       AVLTreeNode<T> k1;

       k1 = k2.left;

       k2.left = k1.right;

       k1.right = k2;

       k2.height = max( height(k2.left), height(k2.right)) + 1;

       k1.height = max( height(k1.left), k2.height) + 1;

       return k1;

    }

   /*

    * RR:右右对应的情况(右单旋转)。

    *

    * 返回值:旋转后的根节点

    */

   private AVLTreeNode<T> rightRightRotation(AVLTreeNode<T> k1){

       AVLTreeNode<T> k2;

       k2 = k1.right;

       k1.right = k2.left;

       k2.left = k1;

       k1.height = max( height(k1.left), height(k1.right)) + 1;

       k2.height = max( height(k2.right), k1.height) + 1;

       return k2;

    }

   /*

    * LR:左右对应的情况(左双旋转)。

    *

    * 返回值:旋转后的根节点

    */

   private AVLTreeNode<T> leftRightRotation(AVLTreeNode<T> k3){

       k3.left = rightRightRotation(k3.left);

       return leftLeftRotation(k3);

    }

   /*

    * RL:右左对应的情况(右双旋转)。

    *

    * 返回值:旋转后的根节点

    */

   private AVLTreeNode<T> rightLeftRotation(AVLTreeNode<T> k1){

       k1.right = leftLeftRotation(k1.right);

       return rightRightRotation(k1);

    }

   /*

    * 将结点插入到AVL树中,并返回根节点

    *

    * 参数说明:

    *     tree AVL树的根结点

    *     key 插入的结点的键值

    * 返回值:

    *     根节点

    */

   private AVLTreeNode<T> insert(AVLTreeNode<T> tree, T key) {

       if (tree == null) {

           // 新建节点

           tree = new AVLTreeNode<T>(key, null, null);

           if (tree==null) {

                System.out.println("ERROR:create avltree node failed!");

                return null;

           }

       } else {

           int cmp = key.compareTo(tree.key);

 

               if (cmp < 0) {    // 应该将key插入到"tree的左子树"的情况

                tree.left = insert(tree.left,key);

                // 插入节点后,若AVL树失去平衡,则进行相应的调节。

                if (height(tree.left) -height(tree.right) == 2) {

                    if(key.compareTo(tree.left.key) < 0)

                        tree =leftLeftRotation(tree);

                    else

                        tree =leftRightRotation(tree);

                }

           } else if (cmp > 0) {    // 应该将key插入到"tree的右子树"的情况

                tree.right = insert(tree.right,key);

                // 插入节点后,若AVL树失去平衡,则进行相应的调节。

                if (height(tree.right) -height(tree.left) == 2) {

                    if(key.compareTo(tree.right.key) > 0)

                        tree =rightRightRotation(tree);

                    else

                        tree =rightLeftRotation(tree);

               }

           } else {    // cmp==0

                System.out.println("添加失败:不允许添加相同的节点!");

           }

       }

       tree.height = max( height(tree.left), height(tree.right)) + 1;

       return tree;

    }

   public void insert(T key) {

       mRoot = insert(mRoot, key);

    }

   /*

    * 删除结点(z),返回根节点

    *

    * 参数说明:

    *     tree AVL树的根结点

    *     z 待删除的结点

    * 返回值:

    *     根节点

    */

   private AVLTreeNode<T> remove(AVLTreeNode<T> tree,AVLTreeNode<T> z) {

       // 根为空 或者 没有要删除的节点,直接返回null。

       if (tree==null || z==null)

           return null;

       int cmp = z.key.compareTo(tree.key);

       if (cmp < 0) {        // 待删除的节点在"tree的左子树"中

           tree.left = remove(tree.left, z);

           // 删除节点后,若AVL树失去平衡,则进行相应的调节。

           if (height(tree.right) - height(tree.left) == 2) {

                AVLTreeNode<T> r =  tree.right;

                if (height(r.left) >height(r.right))

                    tree =rightLeftRotation(tree);

                else

                    tree =rightRightRotation(tree);

           }

       } else if (cmp > 0) {    // 待删除的节点在"tree的右子树"中

           tree.right = remove(tree.right, z);

           // 删除节点后,若AVL树失去平衡,则进行相应的调节。

           if (height(tree.left) - height(tree.right) == 2) {

                AVLTreeNode<T> l =  tree.left;

                if (height(l.right) >height(l.left))

                    tree =leftRightRotation(tree);

                else

                    tree =leftLeftRotation(tree);

           }

       } else {    // tree是对应要删除的节点。

           // tree的左右孩子都非空

           if ((tree.left!=null) && (tree.right!=null)) {

                if (height(tree.left) >height(tree.right)) {

                    // 如果tree的左子树比右子树高;

                    // 则(01)找出tree的左子树中的最大节点

                    //   (02)将该最大节点的值赋值给tree。

                    //   (03)删除该最大节点。

                    // 这类似于用"tree的左子树中最大节点"做"tree"的替身;

                    // 采用这种方式的好处是:删除"tree的左子树中最大节点"之后,AVL树仍然是平衡的。

                   AVLTreeNode<T>max = maximum(tree.left);

                    tree.key = max.key;

                    tree.left =remove(tree.left, max);

                } else {

                    // 如果tree的左子树不比右子树高(即它们相等,或右子树比左子树高1)

                    // 则(01)找出tree的右子树中的最小节点

                    //   (02)将该最小节点的值赋值给tree。

                    //   (03)删除该最小节点。

                    // 这类似于用"tree的右子树中最小节点"做"tree"的替身;

                    // 采用这种方式的好处是:删除"tree的右子树中最小节点"之后,AVL树仍然是平衡的。

                    AVLTreeNode<T> min =maximum(tree.right);

                    tree.key = min.key;

                    tree.right =remove(tree.right, min);

                }

           } else {

                AVLTreeNode<T> tmp =tree;

                tree = (tree.left!=null) ?tree.left : tree.right;

                tmp = null;

           }

       }

       return tree;

    }

   public void remove(T key) {

       AVLTreeNode<T> z;

       if ((z = search(mRoot, key)) != null)

           mRoot = remove(mRoot, z);

    }

   /*

    * 销毁AVL树

    */

   private void destroy(AVLTreeNode<T> tree) {

       if (tree==null)

           return ;

       if (tree.left != null)

           destroy(tree.left);

       if (tree.right != null)

           destroy(tree.right);

       tree = null;

    }

   public void destroy() {

       destroy(mRoot);

    }

   /*

    * 打印"二叉查找树"

    *

    * key        -- 节点的键值

    * direction  --  0,表示该节点是根节点;

    *               -1,表示该节点是它的父结点的左孩子;

    *                1,表示该节点是它的父结点的右孩子。

    */

   private void print(AVLTreeNode<T> tree, T key, int direction) {

       if(tree != null) {

           if(direction==0)    // tree是根节点

                System.out.printf("%2d isroot\n", tree.key, key);

           else                // tree是分支节点

                System.out.printf("%2d is%2d's %6s child\n", tree.key, key, direction==1?"right" :"left");

           print(tree.left, tree.key, -1);

           print(tree.right,tree.key,  1);

       }

    }

   public void print() {

       if (mRoot != null)

           print(mRoot, mRoot.key, 0);

    }

}

AVL树的测试程序(AVLTreeTest.java)

/**

 *Java 语言: AVL树

 *

 *@author skywang

 *@date 2013/11/07

 */

public class AVLTreeTest {

   private static int arr[]= {3,2,1,4,5,6,7,16,15,14,13,12,11,10,8,9};

   public static void main(String[] args) {

       int i;

       AVLTree<Integer> tree = new AVLTree<Integer>();

       System.out.printf("== 依次添加: ");

       for(i=0; i<arr.length; i++) {

           System.out.printf("%d ", arr[i]);

           tree.insert(arr[i]);

       }

       System.out.printf("\n== 前序遍历: ");

       tree.preOrder();

       System.out.printf("\n== 中序遍历: ");

       tree.inOrder();

       System.out.printf("\n== 后序遍历: ");

       tree.postOrder();

       System.out.printf("\n");

       System.out.printf("== 高度: %d\n",tree.height());

       System.out.printf("== 最小值: %d\n",tree.minimum());

       System.out.printf("== 最大值: %d\n",tree.maximum());

       System.out.printf("== 树的详细信息: \n");

       tree.print();

       i = 8;

       System.out.printf("\n== 删除根节点: %d",i);

       tree.remove(i);

       System.out.printf("\n== 高度: %d",tree.height());

       System.out.printf("\n== 中序遍历: ");

       tree.inOrder();

        System.out.printf("\n== 树的详细信息: \n");

       tree.print();

       // 销毁二叉树

       tree.destroy();

    }

}

AVL树的Java测试程序

AVL树的测试程序运行结果如下:

== 依次添加: 3 2 1 4 56 7 16 15 14 13 12 11 10 8 9

== 前序遍历: 7 4 2 1 36 5 13 11 9 8 10 12 15 14 16

== 中序遍历: 1 2 3 4 56 7 8 9 10 11 12 13 14 15 16

== 后序遍历: 1 3 2 5 64 8 10 9 12 11 14 16 15 13 7

== 高度: 5

== 最小值: 1

== 最大值: 16

== 树的详细信息:

 7 isroot

 4is  7's  left child

 2is  4's  left child

 1is  2's  left child

 3is  2's right child

 6is  4's right child

 5is  6's  left child

13 is 7's  right child

11 is 13's  left child

 9 is11's   left child

 8is  9's  left child

10 is 9's  right child

12 is 11's right child

15 is 13's right child

14 is 15's  left child

16 is 15's right child

== 删除根节点: 8

== 高度: 5

== 中序遍历: 1 2 3 4 56 7 9 10 11 12 13 14 15 16

== 树的详细信息:

 7 isroot

 4is  7's  left child

 2is  4's  left child

 1is  2's  left child

 3is  2's right child

 6is  4's right child

 5is  6's  left child

13 is 7's  right child

11 is 13's  left child

 9 is11's   left child

10 is 9's  right child

12 is 11's right child

15 is 13's right child

14 is 15's  left child

16 is 15's right child

5)  总结分析

下面,我们对测试程序的流程进行分析!

1. 新建AVL树

2. 依次添加"3,2,1,4,5,6,7,16,15,14,13,12,11,10,8,9"到AVL树中。

2.01 添加3,2

添加3,2都不会破坏AVL树的平衡性。

2.02 添加1

添加1之后,AVL树失去平衡(LL),此时需要对AVL树进行旋转(LL旋转)。旋转过程如下:

2.03 添加4

添加4不会破坏AVL树的平衡性。

2.04 添加5

添加5之后,AVL树失去平衡(RR),此时需要对AVL树进行旋转(RR旋转)。旋转过程如下:

2.05 添加6

添加6之后,AVL树失去平衡(RR),此时需要对AVL树进行旋转(RR旋转)。旋转过程如下:

2.06 添加7

添加7之后,AVL树失去平衡(RR),此时需要对AVL树进行旋转(RR旋转)。旋转过程如下:

2.07 添加16

添加16不会破坏AVL树的平衡性。

2.08 添加15

添加15之后,AVL树失去平衡(RR),此时需要对AVL树进行旋转(RR旋转)。旋转过程如下:

2.09 添加14

添加14之后,AVL树失去平衡(RL),此时需要对AVL树进行旋转(RL旋转)。旋转过程如下:

2.10 添加13

添加13之后,AVL树失去平衡(RR),此时需要对AVL树进行旋转(RR旋转)。旋转过程如下:

2.11 添加12

添加12之后,AVL树失去平衡(LL),此时需要对AVL树进行旋转(LL旋转)。旋转过程如下:

2.12 添加11

添加11之后,AVL树失去平衡(LL),此时需要对AVL树进行旋转(LL旋转)。旋转过程如下:

2.13 添加10

添加10之后,AVL树失去平衡(LL),此时需要对AVL树进行旋转(LL旋转)。旋转过程如下:

2.14 添加8

添加8不会破坏AVL树的平衡性。

2.15 添加9

但是添加9之后,AVL树失去平衡(LR),此时需要对AVL树进行旋转(LR旋转)。旋转过程如下:

3. 打印树的信息

输出下面树的信息:

前序遍历: 7 4 2 1 3 6 5 13 11 9 8 10 12 15 1416

中序遍历: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 1516

后序遍历: 1 3 2 5 6 4 8 10 9 12 11 14 16 15 137

高度: 5

最小值: 1

最大值: 16

4. 删除节点8

删除操作并不会造成AVL树的不平衡。

删除节点8之后,再打印该AVL树的信息。

高度: 5

中序遍历: 1 2 3 4 5 6 7 9 10 11 12 13 14 15 16

三、    伸展树之Java的实现

1)  概要

前面分别通过C和C++实现了伸展树,本章给出伸展树的Java版本。基本算法和原理都与前两章一样。

1. 伸展树的介绍

2. 伸展树的Java实现(完整源码)

3. 伸展树的Java测试程序

2)  伸展树的介绍

伸展树(Splay Tree)是特殊的二叉查找树。

它的特殊是指,它除了本身是棵二叉查找树之外,它还具备一个特点: 当某个节点被访问时,伸展树会通过旋转使该节点成为树根。这样做的好处是,下次要访问该节点时,能够迅速的访问到该节点。

伸展树的Java实现

1. 基本定义

public class SplayTree<T extendsComparable<T>> {

   private SplayTreeNode<T> mRoot;   // 根结点

   public class SplayTreeNode<T extends Comparable<T>> {

       T key;                // 关键字(键值)

       SplayTreeNode<T> left;    //左孩子

       SplayTreeNode<T> right;   // 右孩子

       public SplayTreeNode() {

           this.left = null;

           this.right = null;

       }

       public SplayTreeNode(T key, SplayTreeNode<T> left,SplayTreeNode<T> right) {

           this.key = key;

           this.left = left;

           this.right = right;

       }

    }

       ...

}

SplayTree是伸展树,而SplayTreeNode是伸展树节点。在此,我将SplayTreeNode定义为SplayTree的内部类。在伸展树SplayTree中包含了伸展树的根节点mRoot。SplayTreeNode包括的几个组成元素:

(01) key -- 是关键字,是用来对伸展树的节点进行排序的。

(02) left -- 是左孩子。

(03) right -- 是右孩子。

2. 旋转

旋转是伸展树中需要重点关注的,它的代码如下:

/*

 * 旋转key对应的节点为根节点,并返回根节点。

 *

 * 注意:

 *   (a):伸展树中存在"键值为key的节点"。

 *         将"键值为key的节点"旋转为根节点。

 *   (b):伸展树中不存在"键值为key的节点",并且key < tree.key。

 *     b-1 "键值为key的节点"的前驱节点存在的话,将"键值为key的节点"的前驱节点旋转为根节点。

 *     b-2 "键值为key的节点"的前驱节点存在的话,则意味着,key比树中任何键值都小,那么此时,将最小节点旋转为根节点。

 *   (c):伸展树中不存在"键值为key的节点",并且key > tree.key。

 *     c-1 "键值为key的节点"的后继节点存在的话,将"键值为key的节点"的后继节点旋转为根节点。

 *     c-2 "键值为key的节点"的后继节点不存在的话,则意味着,key比树中任何键值都大,那么此时,将最大节点旋转为根节点。

 */

private SplayTreeNode<T>splay(SplayTreeNode<T> tree, T key) {

   if (tree == null)

       return tree;

   SplayTreeNode<T> N = new SplayTreeNode<T>();

    SplayTreeNode<T>l = N;

   SplayTreeNode<T> r = N;

   SplayTreeNode<T> c;

   for (;;) {

       int cmp = key.compareTo(tree.key);

       if (cmp < 0) {

           if (tree.left == null)

                break;

           if (key.compareTo(tree.left.key) < 0) {

                c = tree.left;                           /* rotate right */

                tree.left = c.right;

                c.right = tree;

                tree = c;

                if (tree.left == null)

                    break;

           }

           r.left = tree;                               /* link right */

           r = tree;

           tree = tree.left;

       } else if (cmp > 0) {

           if (tree.right == null)

                break;

           if (key.compareTo(tree.right.key) > 0) {

                c = tree.right;                          /* rotate left */

                tree.right = c.left;

                c.left = tree;

                tree = c;

                if (tree.right == null)

                    break;

           }

 

           l.right = tree;                              /* link left */

           l = tree;

           tree = tree.right;

       } else {

           break;

       }

    }

   l.right = tree.left;                                /* assemble */

   r.left = tree.right;

   tree.left = N.right;

   tree.right = N.left;

   return tree;

}

public void splay(T key) {

   mRoot = splay(mRoot, key);

}

上面的代码的作用:将"键值为key的节点"旋转为根节点,并返回根节点。它的处理情况共包括:

(a):伸展树中存在"键值为key的节点"。

       将"键值为key的节点"旋转为根节点。

(b):伸展树中不存在"键值为key的节点",并且key< tree->key。

       b-1) "键值为key的节点"的前驱节点存在的话,将"键值为key的节点"的前驱节点旋转为根节点。

       b-2) "键值为key的节点"的前驱节点存在的话,则意味着,key比树中任何键值都小,那么此时,将最小节点旋转为根节点。

(c):伸展树中不存在"键值为key的节点",并且key> tree->key。

       c-1) "键值为key的节点"的后继节点存在的话,将"键值为key的节点"的后继节点旋转为根节点。

       c-2) "键值为key的节点"的后继节点不存在的话,则意味着,key比树中任何键值都大,那么此时,将最大节点旋转为根节点。

下面列举个例子分别对a进行说明。

在下面的伸展树中查找10,,共包括"右旋" --> "右链接" --> "组合"这3步。

01, 右旋

对应代码中的"rotate right"部分

02, 右链接

对应代码中的"link right"部分

03. 组合

对应代码中的"assemble"部分

提示:如果在上面的伸展树中查找"70",则正好与"示例1"对称,而对应的操作则分别是"rotate left", "link left"和"assemble"。

其它的情况,例如"查找15是b-1的情况,查找5是b-2的情况"等等,这些都比较简单,大家可以自己分析。

3. 插入

插入代码

/*

* 将结点插入到伸展树中,并返回根节点

 *

 * 参数说明:

 *    tree 伸展树的

 *     z 插入的结点

 */

private SplayTreeNode<T> insert(SplayTreeNode<T>tree, SplayTreeNode<T> z) {

   int cmp;

   SplayTreeNode<T> y = null;

   SplayTreeNode<T> x = tree;

   // 查找z的插入位置

   while (x != null) {

       y = x;

       cmp = z.key.compareTo(x.key);

       if (cmp < 0)

           x = x.left;

       else if (cmp > 0)

           x = x.right;

       else {

           System.out.printf("不允许插入相同节点(%d)!\n",z.key);

           z=null;

           return tree;

       }

    }

   if (y==null)

       tree = z;

   else {

       cmp = z.key.compareTo(y.key);

       if (cmp < 0)

           y.left = z;

       else

           y.right = z;

    }

   return tree;

}

public void insert(T key) {

   SplayTreeNode<T> z=new SplayTreeNode<T>(key,null,null);

   // 如果新建结点失败,则返回。

    if ((z=newSplayTreeNode<T>(key,null,null)) == null)

       return ;

   // 插入节点

   mRoot = insert(mRoot, z);

   // 将节点(key)旋转为根节点

   mRoot = splay(mRoot, key);

}

insert(key)是提供给外部的接口,它的作用是新建节点(节点的键值为key),并将节点插入到伸展树中;然后,将该节点旋转为根节点。

insert(tree, z)是内部接口,它的作用是将节点z插入到tree中。insert(tree,z)在将z插入到tree中时,仅仅只将tree当作是一棵二叉查找树,而且不允许插入相同节点。

4. 删除

删除代码

/*

 * 删除结点(z),并返回被删除的结点

 *

 * 参数说明:

 *    bst 伸展树

 *     z 删除的结点

 */

private SplayTreeNode<T>remove(SplayTreeNode<T> tree, T key) {

   SplayTreeNode<T> x;

   if (tree == null)

       return null;

   // 查找键值为key的节点,找不到的话直接返回。

   if (search(tree, key) == null)

       return tree;

   // 将key对应的节点旋转为根节点。

   tree = splay(tree, key);

   if (tree.left != null) {

       // 将"tree的前驱节点"旋转为根节点

       x = splay(tree.left, key);

       // 移除tree节点

       x.right = tree.right;

    }

   else

       x = tree.right;

   tree = null;

   return x;

}

public void remove(T key) {

   mRoot = remove(mRoot, key);

}

remove(key)是外部接口,remove(tree,key)是内部接口。

remove(tree, key)的作用是:删除伸展树中键值为key的节点。

它会先在伸展树中查找键值为key的节点。若没有找到的话,则直接返回。若找到的话,则将该节点旋转为根节点,然后再删除该节点。

关于"前序遍历"、"中序遍历"、"后序遍历"、"最大值"、"最小值"、"查找"、"打印伸展树"、"销毁伸展树"等接口就不再单独介绍了,Please RTFSC(Read The Fucking Source Code)!这些接口,与前面介绍的"二叉查找树"、"AVL树"的相关接口都是类似的。

3)  伸展树的Java实现(完整源码)

伸展树的实现文件(SplayTree.java)

/**

 *Java 语言: 伸展树

 *

 *@author skywang

 *@date 2014/02/03

 */

 

public class SplayTree<T extendsComparable<T>> {

   private SplayTreeNode<T> mRoot;   // 根结点

   public class SplayTreeNode<T extends Comparable<T>> {

       T key;                // 关键字(键值)

       SplayTreeNode<T> left;    //左孩子

       SplayTreeNode<T> right;   // 右孩子

       public SplayTreeNode() {

           this.left = null;

           this.right = null;

       }

       public SplayTreeNode(T key, SplayTreeNode<T> left,SplayTreeNode<T> right) {

           this.key = key;

           this.left = left;

           this.right = right;

       }

    }

   public SplayTree() {

       mRoot=null;

    }

   /*

    * 前序遍历"伸展树"

    */

   private void preOrder(SplayTreeNode<T> tree) {

       if(tree != null) {

           System.out.print(tree.key+" ");

           preOrder(tree.left);

           preOrder(tree.right);

       }

    }

   public void preOrder() {

       preOrder(mRoot);

    }

   /*

    * 中序遍历"伸展树"

    */

   private void inOrder(SplayTreeNode<T> tree) {

       if(tree != null) {

           inOrder(tree.left);

           System.out.print(tree.key+" ");

           inOrder(tree.right);

       }

    }

   public void inOrder() {

       inOrder(mRoot);

    }

   /*

    * 后序遍历"伸展树"

    */

   private void postOrder(SplayTreeNode<T> tree) {

       if(tree != null)

       {

           postOrder(tree.left);

           postOrder(tree.right);

           System.out.print(tree.key+" ");

       }

    }

   public void postOrder() {

       postOrder(mRoot);

    }

   /*

    * (递归实现)查找"伸展树x"中键值为key的节点

    */

   private SplayTreeNode<T> search(SplayTreeNode<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 SplayTreeNode<T> search(T key) {

       return search(mRoot, key);

    }

   /*

    * (非递归实现)查找"伸展树x"中键值为key的节点

    */

   private SplayTreeNode<T> iterativeSearch(SplayTreeNode<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 SplayTreeNode<T> iterativeSearch(T key) {

       return iterativeSearch(mRoot, key);

    }

   /*

    * 查找最小结点:返回tree为根结点的伸展树的最小结点。

    */

   private SplayTreeNode<T> minimum(SplayTreeNode<T> tree) {

       if (tree == null)

           return null;

       while(tree.left != null)

           tree = tree.left;

       return tree;

    }

   public T minimum() {

       SplayTreeNode<T> p = minimum(mRoot);

       if (p != null)

           return p.key;

       return null;

    }

   /*

    * 查找最大结点:返回tree为根结点的伸展树的最大结点。

    */

   private SplayTreeNode<T> maximum(SplayTreeNode<T> tree) {

       if (tree == null)

           return null;

       while(tree.right != null)

           tree = tree.right;

       return tree;

    }

   public T maximum() {

       SplayTreeNode<T> p = maximum(mRoot);

       if (p != null)

           return p.key;

       return null;

    }

   /*

    * 旋转key对应的节点为根节点,并返回根节点。

    *

    * 注意:

    *   (a):伸展树中存在"键值为key的节点"。

    *          将"键值为key的节点"旋转为根节点。

    *   (b):伸展树中不存在"键值为key的节点",并且key < tree.key。

    *      b-1 "键值为key的节点"的前驱节点存在的话,将"键值为key的节点"的前驱节点旋转为根节点。

    *      b-2 "键值为key的节点"的前驱节点存在的话,则意味着,key比树中任何键值都小,那么此时,将最小节点旋转为根节点。

    *   (c):伸展树中不存在"键值为key的节点",并且key > tree.key。

    *      c-1 "键值为key的节点"的后继节点存在的话,将"键值为key的节点"的后继节点旋转为根节点。

    *      c-2 "键值为key的节点"的后继节点不存在的话,则意味着,key比树中任何键值都大,那么此时,将最大节点旋转为根节点。

    */

   private SplayTreeNode<T> splay(SplayTreeNode<T> tree, T key){

       if (tree == null)

           return tree;

       SplayTreeNode<T> N = new SplayTreeNode<T>();

       SplayTreeNode<T> l = N;

       SplayTreeNode<T> r = N;

       SplayTreeNode<T> c;

       for (;;) {

           int cmp = key.compareTo(tree.key);

           if (cmp < 0) {

               if (tree.left == null)

                    break;

                if(key.compareTo(tree.left.key) < 0) {

                    c = tree.left;                           /* rotate right */

                    tree.left = c.right;

                    c.right = tree;

                    tree = c;

                    if (tree.left == null)

                        break;

                }

                r.left = tree;                               /* link right */

                r = tree;

                tree = tree.left;

           } else if (cmp > 0) {

                if (tree.right == null)

                    break;

                if(key.compareTo(tree.right.key) > 0) {

                    c = tree.right;                          /* rotate left */

                    tree.right = c.left;

                    c.left = tree;

                    tree = c;

                    if (tree.right == null)

                        break;

                }

                l.right = tree;                              /* link left */

                l = tree;

                tree = tree.right;

           } else {

                break;

           }

       }

       l.right = tree.left;                                /* assemble */

       r.left = tree.right;

        tree.left = N.right;

       tree.right = N.left;

       return tree;

    }

   public void splay(T key) {

       mRoot = splay(mRoot, key);

    }

   /*

    * 将结点插入到伸展树中,并返回根节点

    *

    * 参数说明:

    *     tree 伸展树的

    *     z 插入的结点

    */

   private SplayTreeNode<T> insert(SplayTreeNode<T> tree,SplayTreeNode<T> z) {

       int cmp;

       SplayTreeNode<T> y = null;

       SplayTreeNode<T> x = tree;

       // 查找z的插入位置

       while (x != null) {

           y = x;

           cmp = z.key.compareTo(x.key);

           if (cmp < 0)

                x = x.left;

           else if (cmp > 0)

                x = x.right;

           else {

                System.out.printf("不允许插入相同节点(%d)!\n", z.key);

                z=null;

                return tree;

           }

       }

       if (y==null)

           tree = z;

       else {

           cmp = z.key.compareTo(y.key);

           if (cmp < 0)

                y.left = z;

           else

                y.right = z;

       }

       return tree;

    }

   public void insert(T key) {

       SplayTreeNode<T> z=new SplayTreeNode<T>(key,null,null);

       // 如果新建结点失败,则返回。

       if ((z=new SplayTreeNode<T>(key,null,null)) == null)

           return ;

       // 插入节点

       mRoot = insert(mRoot, z);

       // 将节点(key)旋转为根节点

       mRoot = splay(mRoot, key);

    }

   /*

    * 删除结点(z),并返回被删除的结点

    *

    * 参数说明:

    *     bst 伸展树

    *     z 删除的结点

    */

   private SplayTreeNode<T> remove(SplayTreeNode<T> tree, Tkey) {

       SplayTreeNode<T> x;

       if (tree == null)

           return null;

       // 查找键值为key的节点,找不到的话直接返回。

       if (search(tree, key) == null)

           return tree;

       // 将key对应的节点旋转为根节点。

       tree = splay(tree, key);

       if (tree.left != null) {

           // 将"tree的前驱节点"旋转为根节点

           x = splay(tree.left, key);

           // 移除tree节点

           x.right = tree.right;

       }

       else

           x = tree.right;

       tree = null;

       return x;

    }

   public void remove(T key) {

       mRoot = remove(mRoot, key);

    }

   /*

    * 销毁伸展树

    */

   private void destroy(SplayTreeNode<T> tree) {

       if (tree==null)

           return ;

       if (tree.left != null)

           destroy(tree.left);

       if (tree.right != null)

           destroy(tree.right);

       tree=null;

    }

   public void clear() {

       destroy(mRoot);

       mRoot = null;

    }

   /*

    * 打印"伸展树"

    *

    * key        -- 节点的键值

    * direction  --  0,表示该节点是根节点;

    *               -1,表示该节点是它的父结点的左孩子;

    *                1,表示该节点是它的父结点的右孩子。

    */

   private void print(SplayTreeNode<T> tree, T key, int direction) {

       if(tree != null) {

           if(direction==0)    // tree是根节点

                System.out.printf("%2d isroot\n", tree.key);

           else                // tree是分支节点

                System.out.printf("%2d is%2d's %6s child\n", tree.key, key, direction==1?"right" :"left");

           print(tree.left, tree.key, -1);

           print(tree.right,tree.key,  1);

       }

    }

   public void print() {

       if (mRoot != null)

           print(mRoot, mRoot.key, 0);

    }

}

伸展树的测试程序(SplayTreeTest.java)

/**

 *Java 语言: 伸展树

 *

 *@author skywang

 *@date 2014/02/03

 */

public class SplayTreeTest {

   private static final int arr[] = {10,50,40,30,20,60};

   public static void main(String[] args) {

       int i, ilen;

       SplayTree<Integer> tree=new SplayTree<Integer>();

       System.out.print("== 依次添加: ");

       ilen = arr.length;

       for(i=0; i<ilen; i++) {

           System.out.print(arr[i]+" ");

           tree.insert(arr[i]);

       }

       System.out.print("\n== 前序遍历: ");

       tree.preOrder();

        System.out.print("\n== 中序遍历: ");

       tree.inOrder();

       System.out.print("\n== 后序遍历: ");

       tree.postOrder();

       System.out.println();

       System.out.println("== 最小值: "+tree.minimum());

       System.out.println("== 最大值: "+ tree.maximum());

       System.out.println("== 树的详细信息: ");

       tree.print();

       i = 30;

       System.out.printf("\n== 旋转节点(%d)为根节点\n", i);

       tree.splay(i);

       System.out.printf("== 树的详细信息: \n");

       tree.print();

       // 销毁二叉树

       tree.clear();

    }

}

在二叉查找树的Java实现中,使用了泛型,也就意味着它支持任意类型;但是该类型必须要实现Comparable接口。

伸展树的Java测试程序

伸展树的测试程序运行结果如下:

== 依次添加: 10 50 4030 20 60

== 前序遍历: 60 30 2010 50 40

== 中序遍历: 10 20 3040 50 60

== 后序遍历: 10 20 4050 30 60

== 最小值: 10

== 最大值: 60

== 树的详细信息:

60 is root

30 is 60's  left child

20 is 30's  left child

10 is 20's  left child

50 is 30's right child

40 is 50's  left child

== 旋转节点(30)为根节点

== 树的详细信息:

30 is root

20 is 30's  left child

10 is 20's  left child

60 is 30's right child

50 is 60's  left child

40 is 50's  left child

4)  总结分析

测试程序的主要流程是:新建伸展树,然后向伸展树中依次插入10,50,40,30,20,60。插入完毕这些数据之后,伸展树的节点是60;此时,再旋转节点,使得30成为根节点。

依次插入10,50,40,30,20,60示意图如下:

将30旋转为根节点的示意图如下:

四、    红黑树之原理和算法详细介绍

1)  概要

目录

1 红黑树的介绍

2 红黑树的应用

3 红黑树的时间复杂度和相关证明

4 红黑树的基本操作(一) 左旋和右旋

5 红黑树的基本操作(二) 添加

6 红黑树的基本操作(三) 删除

2)  R-BTree简介

R-B Tree,全称是Red-BlackTree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。

红黑树的特性:

(1)每个节点或者是黑色,或者是红色。

(2)根节点是黑色。

(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]

(4)如果一个节点是红色的,则它的子节点必须是黑色的。

(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

注意:

(01) 特性(3)中的叶子节点,是只为空(NIL或null)的节点。

(02) 特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。

红黑树示意图如下:

3)  红黑树的应用

红黑树的应用比较广泛,主要是用它来存储有序的数据,它的时间复杂度是O(lgn),效率非常之高。

例如,Java集合中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的。

4)  红黑树的时间复杂度和相关证明

红黑树的时间复杂度为: O(lgn)

下面通过“数学归纳法”对红黑树的时间复杂度进行证明。

定理:一棵含有n个节点的红黑树的高度至多为2log(n+1).

证明:

   "一棵含有n个节点的红黑树的高度至多为2log(n+1)"的逆否命题是 "高度为h的红黑树,它的包含的内节点个数至少为 2h/2-1个"。

    我们只需要证明逆否命题,即可证明原命题为真;即只需证明 "高度为h的红黑树,它的包含的内节点个数至少为 2h/2-1个"。

    从某个节点x出发(不包括该节点)到达一个叶节点的任意一条路径上,黑色节点的个数称为该节点的黑高度(x'sblack height),记为bh(x)。关于bh(x)有两点需要说明:

    第1点:根据红黑树的"特性(5),即从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点"可知,从节点x出发到达的所有的叶节点具有相同数目的黑节点。这也就意味着,bh(x)的值是唯一的!

    第2点:根据红黑色的"特性(4),即如果一个节点是红色的,则它的子节点必须是黑色的"可知,从节点x出发达到叶节点"所经历的黑节点数目">= "所经历的红节点的数目"。假设x是根节点,则可以得出结论"bh(x) >= h/2"。进而,我们只需证明 "高度为h的红黑树,它的包含的黑节点个数至少为 2bh(x)-1个"即可。

    到这里,我们将需要证明的定理已经由

"一棵含有n个节点的红黑树的高度至多为2log(n+1)"

    转变成只需要证明

"高度为h的红黑树,它的包含的内节点个数至少为 2bh(x)-1个"。

下面通过"数学归纳法"开始论证高度为h的红黑树,它的包含的内节点个数至少为 2bh(x)-1个"。

(01) 当树的高度h=0时,

    内节点个数是0,bh(x) 为0,2bh(x)-1 也为 0。显然,原命题成立。

(02) 当h>0,且树的高度为 h-1 时,它包含的节点个数至少为 2bh(x)-1-1。这个是根据(01)推断出来的!

    下面,由树的高度为 h-1 的已知条件推出“树的高度为 h 时,它所包含的节点树为 2bh(x)-1”。

    当树的高度为 h 时,

    对于节点x(x为根节点),其黑高度为bh(x)。

    对于节点x的左右子树,它们黑高度为 bh(x) 或者 bh(x)-1。

    根据(02)的已知条件,我们已知 "x的左右子树,即高度为 h-1 的节点,它包含的节点至少为 2bh(x)-1-1 个";

    所以,节点x所包含的节点至少为 ( 2bh(x)-1-1 ) + ( 2bh(x)-1-1 ) +1 = 2^bh(x)-1。即节点x所包含的节点至少为2bh(x)-1。

    因此,原命题成立。

    由(01)、(02)得出,"高度为h的红黑树,它的包含的内节点个数至少为 2^bh(x)-1个"。

    因此,“一棵含有n个节点的红黑树的高度至多为2log(n+1)”。

红黑树的基本操作(一) 左旋和右旋

红黑树的基本操作是添加、删除。在对红黑树进行添加或删除之后,都会用到旋转方法。为什么呢?道理很简单,添加或删除红黑树中的节点之后,红黑树就发生了变化,可能不满足红黑树的5条性质,也就不再是一颗红黑树了,而是一颗普通的树。而通过旋转,可以使这颗树重新成为红黑树。简单点说,旋转的目的是让树保持红黑树的特性。

旋转包括两种:左旋 和 右旋。下面分别对它们进行介绍。

5)  1. 左旋

对x进行左旋,意味着"将x变成一个左节点"。

左旋的伪代码《算法导论》:参考上面的示意图和下面的伪代码,理解“红黑树T的节点x进行左旋”是如何进行的。

LEFT-ROTATE(T, x) 

 y ← right[x]            // 前提:这里假设x的右孩子为y。下面开始正式操作

 right[x] ←left[y]      // 将 “y的左孩子” 设为 “x的右孩子”,即 将β设为x的右孩子

 p[left[y]] ← x          // 将 “x”设为 “y的左孩子的父亲”,即 将β的父亲设为x

 p[y]← p[x]            // 将 “x的父亲” 设为 “y的父亲”

 ifp[x] = nil[T]      

 thenroot[T] ← y                 // 情况1:如果“x的父亲” 是空节点,则将y设为根节点

 elseif x = left[p[x]] 

          then left[p[x]] ← y    // 情况2:如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”

          else right[p[x]] ← y   // 情况3:(x是它父节点的右孩子) 将y设为“x的父节点的右孩子”

 left[y] ← x             // 将 “x”设为 “y的左孩子”

 p[x]← y               // 将 “x的父节点” 设为 “y”

理解左旋之后,看看下面一个更鲜明的例子。你可以先不看右边的结果,自己尝试一下。

6)  2. 右旋

对x进行左旋,意味着"将x变成一个左节点"。

右旋的伪代码《算法导论》:参考上面的示意图和下面的伪代码,理解“红黑树T的节点y进行右旋”是如何进行的。

RIGHT-ROTATE(T, y) 

x ← left[y]             // 前提:这里假设y的左孩子为x。下面开始正式操作

left[y] ←right[x]      // 将 “x的右孩子” 设为 “y的左孩子”,即 将β设为y的左孩子

p[right[x]] ←y         // 将 “y”设为 “x的右孩子的父亲”,即 将β的父亲设为y

p[x] ← p[y]             // 将 “y的父亲”设为 “x的父亲”

if p[y] = nil[T]      

then root[T] ←x                 // 情况1:如果 “y的父亲” 是空节点,则将x设为根节点

else if y = right[p[y]] 

        then right[p[y]] ← x   // 情况2:如果 y是它父节点的右孩子,则将x设为“y的父节点的左孩子”

        else left[p[y]] ← x    // 情况3:(y是它父节点的左孩子) 将x设为“y的父节点的左孩子”

right[x] ← y            // 将 “y”设为 “x的右孩子”

p[y] ← x                // 将 “y的父节点”设为 “x”

理解右旋之后,看看下面一个更鲜明的例子。你可以先不看右边的结果,自己尝试一下。

旋转总结:

(01) 左旋 和 右旋 是相对的两个概念,原理类似。理解一个也就理解了另一个。

(02) 下面谈谈如何区分 左旋 和 右旋。

在实际应用中,若没有彻底理解 左旋 和 右旋,可能会将它们混淆。下面谈谈我对如何区分 左旋 和 右旋 的理解。

7)  3. 区分 左旋 和 右旋

仔细观察上面"左旋"和"右旋"的示意图。我们能清晰的发现,它们是对称的。无论是左旋还是右旋,被旋转的树,在旋转前是二叉查找树,并且旋转之后仍然是一颗二叉查找树。

左旋示例图(以x为节点进行左旋):

                               z

  x                          /                 

  /\      --(左旋)-->       x

 y  z                      /

                           y

对x进行左旋,意味着,将“x的右孩子”设为“x的父亲节点”;即,将 x变成了一个左节点(x成了为z的左孩子)!。因此,左旋中的“左”,意味着“被旋转的节点将变成一个左节点”。

右旋示例图(以x为节点进行右旋):

                               y

  x                           \                

  /\      --(右旋)-->           x

 y  z                            \

                                   z

对x进行右旋,意味着,将“x的左孩子”设为“x的父亲节点”;即,将 x变成了一个右节点(x成了为y的右孩子)! 因此,右旋中的“右”,意味着“被旋转的节点将变成一个右节点”。

8)  红黑树的基本操作(二) 添加

将一个节点插入到红黑树中,需要执行哪些步骤呢?首先,将红黑树当作一颗二叉查找树,将节点插入;然后,将节点着色为红色;最后,通过旋转和重新着色等方法来修正该树,使之重新成为一颗红黑树。详细描述如下:

第一步: 将红黑树当作一颗二叉查找树,将节点插入。

      红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。此外,无论是左旋还是右旋,若旋转之前这棵树是二叉查找树,旋转之后它一定还是二叉查找树。这也就意味着,任何的旋转和重新着色操作,都不会改变它仍然是一颗二叉查找树的事实。

      好吧?那接下来,我们就来想方设法的旋转以及重新着色,使这颗树重新成为红黑树!

第二步:将插入的节点着色为"红色"。

      为什么着色成红色,而不是黑色呢?为什么呢?在回答之前,我们需要重新温习一下红黑树的特性:

(1) 每个节点或者是黑色,或者是红色。

(2) 根节点是黑色。

(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]

(4) 如果一个节点是红色的,则它的子节点必须是黑色的。

(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

      将插入的节点着色为红色,不会违背"特性(5)"!少违背一条特性,就意味着我们需要处理的情况越少。接下来,就要努力的让这棵树满足其它性质即可;满足了的话,它就又是一颗红黑树了。o(∩∩)o...哈哈

第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。

      第二步中,将插入节点着色为"红色"之后,不会违背"特性(5)"。那它到底会违背哪些特性呢?

      对于"特性(1)",显然不会违背了。因为我们已经将它涂成红色了。

      对于"特性(2)",显然也不会违背。在第一步中,我们是将红黑树当作二叉查找树,然后执行的插入操作。而根据二叉查找数的特点,插入操作不会改变根节点。所以,根节点仍然是黑色。

      对于"特性(3)",显然不会违背了。这里的叶子节点是指的空叶子节点,插入非空节点并不会对它们造成影响。

      对于"特性(4)",是有可能违背的!

      那接下来,想办法使之"满足特性(4)",就可以将树重新构造成红黑树了。

下面看看代码到底是怎样实现这三步的。

添加操作的伪代码《算法导论》

RB-INSERT(T, z) 

y ← nil[T]                        // 新建节点“y”,将y设为空节点。

x ← root[T]                       // 设“红黑树T”的根节点为“x”

while x ≠nil[T]                  // 找出要插入的节点“z”在二叉树T中的位置“y”

   doy ← x                     

     if key[z] < key[x] 

        then x ← left[x] 

        else x ← right[x] 

p[z] ← y                          // 设置 “z的父亲” 为 “y”

if y = nil[T]                    

 then root[T] ← z               // 情况1:若y是空节点,则将z设为根

 else if key[z] < key[y]       

         then left[y] ← z       // 情况2:若“z所包含的值” < “y所包含的值”,则将z设为“y的左孩子”

         else right[y] ← z      // 情况3:(“z所包含的值” >= “y所包含的值”)将z设为“y的右孩子”

left[z] ←nil[T]                  // z的左孩子设为空

right[z] ←nil[T]                 // z的右孩子设为空。至此,已经完成将“节点z插入到二叉树”中了。

color[z] ← RED                    // 将z着色为“红色”

RB-INSERT-FIXUP(T, z)             // 通过RB-INSERT-FIXUP对红黑树的节点进行颜色修改以及旋转,让树T仍然是一颗红黑树

结合伪代码以及为代码上面的说明,先理解RB-INSERT。理解了RB-INSERT之后,我们接着对 RB-INSERT-FIXUP的伪代码进行说明。

添加修正操作的伪代码《算法导论》

RB-INSERT-FIXUP(T, z)

while color[p[z]] = RED                                                 // 若“当前节点(z)的父节点是红色”,则进行以下处理。

   do if p[z] = left[p[p[z]]]                                           // 若“z的父节点”是“z的祖父节点的左孩子”,则进行以下处理。

         then y ← right[p[p[z]]]                                        // 将y设置为“z的叔叔节点(z的祖父节点的右孩子)”

               if color[y] = RED                                         //Case 1条件:叔叔是红色

                  then color[p[z]] ← BLACK                    ?Case 1   //  (01) 将“父节点”设为黑色。

                       color[y] ← BLACK                       ?Case 1   //  (02) 将“叔叔节点”设为黑色。

                       color[p[p[z]]] ← RED                   ? Case1   // (03) 将“祖父节点”设为“红色”。

                       z ← p[p[z]]                           ? Case 1   //  (04) 将“祖父节点”设为“当前节点”(红色节点)

                  else if z = right[p[z]]                                // Case 2条件:叔叔是黑色,且当前节点是右孩子

                          then z ← p[z]                       ? Case 2   // (01) 将“父节点”作为“新的当前节点”。

                               LEFT-ROTATE(T,z)              ? Case 2   // (02) 以“新的当前节点”为支点进行左旋。

                          color[p[z]] ← BLACK                 ? Case3   // Case 3条件:叔叔是黑色,且当前节点是左孩子。(01) 将“父节点”设为“黑色”。

                          color[p[p[z]]] ← RED                ? Case3   // (02) 将“祖父节点”设为“红色”。

                          RIGHT-ROTATE(T,p[p[z]])            ? Case 3   // (03) 以“祖父节点”为支点进行右旋。

      else (same as then clause with "right" and "left"exchanged)      // 若“z的父节点”是“z的祖父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。

color[root[T]] ←BLACK

根据被插入节点的父节点的情况,可以将"当节点z被着色为红色节点,并插入二叉树"划分为三种情况来处理。

① 情况说明:被插入的节点是根节点。

    处理方法:直接把此节点涂为黑色。

② 情况说明:被插入的节点的父节点是黑色。

    处理方法:什么也不需要做。节点被插入后,仍然是红黑树。

③ 情况说明:被插入的节点的父节点是红色。

    处理方法:那么,该情况与红黑树的“特性(5)”相冲突。这种情况下,被插入节点是一定存在非空祖父节点的;进一步的讲,被插入节点也一定存在叔叔节点(即使叔叔节点为空,我们也视之为存在,空节点本身就是黑色节点)。理解这点之后,我们依据"叔叔节点的情况",将这种情况进一步划分为3种情况(Case)。

    现象说明   处理策略

Case 1     当前节点的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。    

(01) 将“父节点”设为黑色。

(02) 将“叔叔节点”设为黑色。

(03) 将“祖父节点”设为“红色”。

(04) 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。

Case 2     当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子

(01) 将“父节点”作为“新的当前节点”。

(02) 以“新的当前节点”为支点进行左旋。

Case 3     当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子

(01) 将“父节点”设为“黑色”。

(02) 将“祖父节点”设为“红色”。

(03) 以“祖父节点”为支点进行右旋。

上面三种情况(Case)处理问题的核心思路都是:将红色的节点移到根节点;然后,将根节点设为黑色。下面对它们详细进行介绍。

1. (Case 1)叔叔是红色

1.1 现象说明

当前节点(即,被插入节点)的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。

1.2 处理策略

(01) 将“父节点”设为黑色。

(02) 将“叔叔节点”设为黑色。

(03) 将“祖父节点”设为“红色”。

(04) 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。

    下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比)

    “当前节点”和“父节点”都是红色,违背“特性(4)”。所以,将“父节点”设置“黑色”以解决这个问题。

    但是,将“父节点”由“红色”变成“黑色”之后,违背了“特性(5)”:因为,包含“父节点”的分支的黑色节点的总数增加了1。  解决这个问题的办法是:将“祖父节点”由“黑色”变成红色,同时,将“叔叔节点”由“红色”变成“黑色”。关于这里,说明几点:第一,为什么“祖父节点”之前是黑色?这个应该很容易想明白,因为在变换操作之前,该树是红黑树,“父节点”是红色,那么“祖父节点”一定是黑色。第二,为什么将“祖父节点”由“黑色”变成红色,同时,将“叔叔节点”由“红色”变成“黑色”;能解决“包含‘父节点’的分支的黑色节点的总数增加了1”的问题。这个道理也很简单。“包含‘父节点’的分支的黑色节点的总数增加了1”同时也意味着 “包含‘祖父节点’的分支的黑色节点的总数增加了1”,既然这样,我们通过将“祖父节点”由“黑色”变成“红色”以解决“包含‘祖父节点’的分支的黑色节点的总数增加了1”的问题; 但是,这样处理之后又会引起另一个问题“包含‘叔叔’节点的分支的黑色节点的总数减少了1”,现在我们已知“叔叔节点”是“红色”,将“叔叔节点”设为“黑色”就能解决这个问题。 所以,将“祖父节点”由“黑色”变成红色,同时,将“叔叔节点”由“红色”变成“黑色”;就解决了该问题。

    按照上面的步骤处理之后:当前节点、父节点、叔叔节点之间都不会违背红黑树特性,但祖父节点却不一定。若此时,祖父节点是根节点,直接将祖父节点设为“黑色”,那就完全解决这个问题了;若祖父节点不是根节点,那我们需要将“祖父节点”设为“新的当前节点”,接着对“新的当前节点”进行分析。

1.3 示意图

2. (Case 2)叔叔是黑色,且当前节点是右孩子

2.1 现象说明

当前节点(即,被插入节点)的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子

2.2 处理策略

(01) 将“父节点”作为“新的当前节点”。

(02) 以“新的当前节点”为支点进行左旋。

     下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比)

     首先,将“父节点”作为“新的当前节点”;接着,以“新的当前节点”为支点进行左旋。 为了便于理解,我们先说明第(02)步,再说明第(01)步;为了便于说明,我们设置“父节点”的代号为F(Father),“当前节点”的代号为S(Son)。

为什么要“以F为支点进行左旋”呢?根据已知条件可知:S是F的右孩子。而之前我们说过,我们处理红黑树的核心思想:将红色的节点移到根节点;然后,将根节点设为黑色。既然是“将红色的节点移到根节点”,那就是说要不断的将破坏红黑树特性的红色节点上移(即向根方向移动)。 而S又是一个右孩子,因此,我们可以通过“左旋”来将S上移!

     按照上面的步骤(以F为支点进行左旋)处理之后:若S变成了根节点,那么直接将其设为“黑色”,就完全解决问题了;若S不是根节点,那我们需要执行步骤(01),即“将F设为‘新的当前节点’”。那为什么不继续以S为新的当前节点继续处理,而需要以F为新的当前节点来进行处理呢?这是因为“左旋”之后,F变成了S的“子节点”,即S变成了F的父节点;而我们处理问题的时候,需要从下至上(由叶到根)方向进行处理;也就是说,必须先解决“孩子”的问题,再解决“父亲”的问题;所以,我们执行步骤(01):将“父节点”作为“新的当前节点”。

2.2 示意图

3. (Case 3)叔叔是黑色,且当前节点是左孩子

3.1 现象说明

当前节点(即,被插入节点)的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子

3.2 处理策略

(01) 将“父节点”设为“黑色”。

(02) 将“祖父节点”设为“红色”。

(03) 以“祖父节点”为支点进行右旋。

     下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比)

     为了便于说明,我们设置“当前节点”为S(Original Son),“兄弟节点”为B(Brother),“叔叔节点”为U(Uncle),“父节点”为F(Father),祖父节点为G(Grand-Father)。

     S和F都是红色,违背了红黑树的“特性(4)”,我们可以将F由“红色”变为“黑色”,就解决了“违背‘特性(4)’”的问题;但却引起了其它问题:违背特性(5),因为将F由红色改为黑色之后,所有经过F的分支的黑色节点的个数增加了1。那我们如何解决“所有经过F的分支的黑色节点的个数增加了1”的问题呢? 我们可以通过“将G由黑色变成红色”,同时“以G为支点进行右旋”来解决。

2.3 示意图

提示:上面的进行Case 3处理之后,再将节点"120"当作当前节点,就变成了Case 2的情况。

9)  红黑树的基本操作(三) 删除

将红黑树内的某一个节点删除。需要执行的操作依次是:首先,将红黑树当作一颗二叉查找树,将该节点从二叉查找树中删除;然后,通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。详细描述如下:

第一步:将红黑树当作一颗二叉查找树,将节点删除。

      这和"删除常规二叉查找树中删除节点的方法是一样的"。分3种情况:

      ① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。

      ② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。

      ③ 被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。在这里,后继节点相当于替身,在将后继节点的内容复制给"被删除节点"之后,再将后继节点删除。这样就巧妙的将问题转换为"删除后继节点"的情况了,下面就考虑后继节点。 在"被删除节点"有两个非空子节点的情况下,它的后继节点不可能是双子非空。既然"的后继节点"不可能双子都非空,就意味着"该节点的后继节点"要么没有儿子,要么只有一个儿子。若没有儿子,则按"情况① "进行处理;若只有一个儿子,则按"情况② "进行处理。

第二步:通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。

      因为"第一步"中删除节点之后,可能会违背红黑树的特性。所以需要通过"旋转和重新着色"来修正该树,使之重新成为一棵红黑树。

删除操作的伪代码《算法导论》

RB-DELETE(T, z)

if left[z] = nil[T] or right[z] =nil[T]        

  then y ← z                                  // 若“z的左孩子” 或 “z的右孩子”为空,则将“z”赋值给 “y”;

  else y ← TREE-SUCCESSOR(z)                  // 否则,将“z的后继节点”赋值给 “y”。

if left[y] ≠ nil[T]

   thenx ← left[y]                            // 若“y的左孩子” 不为空,则将“y的左孩子” 赋值给 “x”;

  else x ← right[y]                           // 否则,“y的右孩子” 赋值给 “x”。

p[x] ← p[y]                                    // 将“y的父节点” 设置为 “x的父节点”

if p[y] = nil[T]                              

  then root[T] ← x                            // 情况1:若“y的父节点” 为空,则设置“x”为 “根节点”。

  else if y = left[p[y]]                   

          then left[p[y]] ← x                 // 情况2:若“y是它父节点的左孩子”,则设置“x” 为 “y的父节点的左孩子”

          else right[p[y]] ← x                // 情况3:若“y是它父节点的右孩子”,则设置“x” 为 “y的父节点的右孩子”

if y ≠ z                                   

  then key[z] ← key[y]                        // 若“y的值” 赋值给 “z”。注意:这里只拷贝z的值给y,而没有拷贝z的颜色!!!

       copy y's satellite data into z        

if color[y] = BLACK                           

  then RB-DELETE-FIXUP(T, x)                  // 若“y为黑节点”,则调用

return y

结合伪代码以及为代码上面的说明,先理解RB-DELETE。理解了RB-DELETE之后,接着对 RB-DELETE-FIXUP的伪代码进行说明

RB-DELETE-FIXUP(T, x)

while x ≠ root[T]and color[x] = BLACK 

   do if x = left[p[x]]     

         then w ← right[p[x]]                                             //若 “x”是“它父节点的左孩子”,则设置 “w”为“x的叔叔”(即x为它父节点的右孩子)                                         

               if color[w] = RED                                           //Case 1: x是“黑+黑”节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。

                  then color[w] ← BLACK                       ?  Case 1   //  (01) 将x的兄弟节点设为“黑色”。

                       color[p[x]] ← RED                       ? Case 1   //   (02) 将x的父节点设为“红色”。

                       LEFT-ROTATE(T,p[x])                    ?  Case 1  //   (03) 对x的父节点进行左旋。

                       w ← right[p[x]]                        ?  Case 1   //  (04) 左旋后,重新设置x的兄弟节点。

               if color[left[w]] = BLACK andcolor[right[w]] = BLACK       // Case 2:x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。

                  then color[w] ← RED                         ?  Case 2   //  (01) 将x的兄弟节点设为“红色”。

                       x ←  p[x]                               ? Case 2   //   (02) 设置“x的父节点”为“新的x节点”。

                  else if color[right[w]] =BLACK                          // Case 3:x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。

                          then color[left[w]] ← BLACK          ?  Case 3  //   (01) 将x兄弟节点的左孩子设为“黑色”。

                               color[w] ← RED                  ?  Case 3  //   (02) 将x兄弟节点设为“红色”。

                               RIGHT-ROTATE(T,w)              ?  Case 3  //   (03) 对x的兄弟节点进行右旋。

                               w ← right[p[x]]                ?  Case 3   //  (04) 右旋后,重新设置x的兄弟节点。

                        color[w] ← color[p[x]]                ?  Case 4   // Case 4: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的。(01) 将x父节点颜色 赋值给 x的兄弟节点。

                       color[p[x]] ← BLACK                   ?  Case 4   //  (02) 将x父节点设为“黑色”。

                        color[right[w]] ← BLACK                ?  Case 4  //   (03) 将x兄弟节点的右子节设为“黑色”。

                        LEFT-ROTATE(T,p[x])                   ?  Case 4  //   (04) 对x的父节点进行左旋。

                        x ← root[T]                           ?  Case 4   //  (05) 设置“x”为“根节点”。

      else (same as then clause with "right" and "left"exchanged)        // 若 “x”是“它父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。

color[x] ← BLACK

下面对删除函数进行分析。在分析之前,我们再次温习一下红黑树的几个特性:

(1) 每个节点或者是黑色,或者是红色。

(2) 根节点是黑色。

(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]

(4) 如果一个节点是红色的,则它的子节点必须是黑色的。

(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

     前面我们将"删除红黑树中的节点"大致分为两步,在第一步中"将红黑树当作一颗二叉查找树,将节点删除"后,可能违反"特性(2)、(4)、(5)"三个特性。第二步需要解决上面的三个问题,进而保持红黑树的全部特性。

     为了便于分析,我们假设"x包含一个额外的黑色"(x原本的颜色还存在),这样就不会违反"特性(5)"。为什么呢?

     通过RB-DELETE算法,我们知道:删除节点y之后,x占据了原来节点y的位置。 既然删除y(y是黑色),意味着减少一个黑色节点;那么,再在该位置上增加一个黑色即可。这样,当我们假设"x包含一个额外的黑色",就正好弥补了"删除y所丢失的黑色节点",也就不会违反"特性(5)"。 因此,假设"x包含一个额外的黑色"(x原本的颜色还存在),这样就不会违反"特性(5)"。

     现在,x不仅包含它原本的颜色属性,x还包含一个额外的黑色。即x的颜色属性是"红+黑"或"黑+黑",它违反了"特性(1)"。

     现在,我们面临的问题,由解决"违反了特性(2)、(4)、(5)三个特性"转换成了"解决违反特性(1)、(2)、(4)三个特性"。RB-DELETE-FIXUP需要做的就是通过算法恢复红黑树的特性(1)、(2)、(4)。RB-DELETE-FIXUP的思想是:将x所包含的额外的黑色不断沿树上移(向根方向移动),直到出现下面的姿态:

a) x指向一个"红+黑"节点。此时,将x设为一个"黑"节点即可。

b) x指向根。此时,将x设为一个"黑"节点即可。

c) 非前面两种姿态。

将上面的姿态,可以概括为3种情况。

① 情况说明:x是“红+黑”节点。

    处理方法:直接把x设为黑色,结束。此时红黑树性质全部恢复。

② 情况说明:x是“黑+黑”节点,且x是根。

    处理方法:什么都不做,结束。此时红黑树性质全部恢复。

③ 情况说明:x是“黑+黑”节点,且x不是根。

    处理方法:这种情况又可以划分为4种子情况。这4种子情况如下表所示:

    现象说明   处理策略

Case 1     x是"黑+黑"节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。    

(01) 将x的兄弟节点设为“黑色”。

(02) 将x的父节点设为“红色”。

(03) 对x的父节点进行左旋。

(04) 左旋后,重新设置x的兄弟节点。

Case 2     x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。   

(01) 将x的兄弟节点设为“红色”。

(02) 设置“x的父节点”为“新的x节点”。

Case 3     x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。    

(01) 将x兄弟节点的左孩子设为“黑色”。

(02) 将x兄弟节点设为“红色”。

(03) 对x的兄弟节点进行右旋。

(04) 右旋后,重新设置x的兄弟节点。

Case 4     x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的,x的兄弟节点的左孩子任意颜色。   

(01) 将x父节点颜色赋值给 x的兄弟节点。

(02) 将x父节点设为“黑色”。

(03) 将x兄弟节点的右子节设为“黑色”。

(04) 对x的父节点进行左旋。

(05) 设置“x”为“根节点”。

1. (Case 1)x是"黑+黑"节点,x的兄弟节点是红色

1.1 现象说明

x是"黑+黑"节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。

1.2 处理策略

(01) 将x的兄弟节点设为“黑色”。

(02) 将x的父节点设为“红色”。

(03) 对x的父节点进行左旋。

(04) 左旋后,重新设置x的兄弟节点。

     下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比)

     这样做的目的是将“Case 1”转换为“Case2”、“Case 3”或“Case 4”,从而进行进一步的处理。对x的父节点进行左旋;左旋后,为了保持红黑树特性,就需要在左旋前“将x的兄弟节点设为黑色”,同时“将x的父节点设为红色”;左旋后,由于x的兄弟节点发生了变化,需要更新x的兄弟节点,从而进行后续处理。

1.3 示意图

2. (Case 2) x是"黑+黑"节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色

2.1 现象说明

x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。

2.2 处理策略

(01) 将x的兄弟节点设为“红色”。

(02) 设置“x的父节点”为“新的x节点”。

     下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比)

     这个情况的处理思想:是将“x中多余的一个黑色属性上移(往根方向移动)”。 x是“黑+黑”节点,我们将x由“黑+黑”节点变成 “黑”节点,多余的一个“黑”属性移到x的父节点中,即x的父节点多出了一个黑属性(若x的父节点原先是“黑”,则此时变成了“黑+黑”;若x的父节点原先时“红”,则此时变成了“红+黑”)。 此时,需要注意的是:所有经过x的分支中黑节点个数没变化;但是,所有经过x的兄弟节点的分支中黑色节点的个数增加了1(因为x的父节点多了一个黑色属性)!为了解决这个问题,我们需要将“所有经过x的兄弟节点的分支中黑色节点的个数减1”即可,那么就可以通过“将x的兄弟节点由黑色变成红色”来实现。

     经过上面的步骤(将x的兄弟节点设为红色),多余的一个颜色属性(黑色)已经跑到x的父节点中。我们需要将x的父节点设为“新的x节点”进行处理。若“新的x节点”是“黑+红”,直接将“新的x节点”设为黑色,即可完全解决该问题;若“新的x节点”是“黑+黑”,则需要对“新的x节点”进行进一步处理。

2.3 示意图

3. (Case 3)x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的

3.1 现象说明

x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。

3.2 处理策略

(01) 将x兄弟节点的左孩子设为“黑色”。

(02) 将x兄弟节点设为“红色”。

(03) 对x的兄弟节点进行右旋。

(04) 右旋后,重新设置x的兄弟节点。

      下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比)

      我们处理“Case 3”的目的是为了将“Case3”进行转换,转换成“Case 4”,从而进行进一步的处理。转换的方式是对x的兄弟节点进行右旋;为了保证右旋后,它仍然是红黑树,就需要在右旋前“将x的兄弟节点的左孩子设为黑色”,同时“将x的兄弟节点设为红色”;右旋后,由于x的兄弟节点发生了变化,需要更新x的兄弟节点,从而进行后续处理。

3.3 示意图

4. (Case 4)x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的,x的兄弟节点的左孩子任意颜色

4.1 现象说明

x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的,x的兄弟节点的左孩子任意颜色。

4.2 处理策略

(01) 将x父节点颜色赋值给 x的兄弟节点。

(02) 将x父节点设为“黑色”。

(03) 将x兄弟节点的右子节设为“黑色”。

(04) 对x的父节点进行左旋。

(05) 设置“x”为“根节点”。

     下面谈谈为什么要这样处理。(建议理解的时候,通过下面的图进行对比)

     我们处理“Case 4”的目的是:去掉x中额外的黑色,将x变成单独的黑色。处理的方式是“:进行颜色修改,然后对x的父节点进行左旋。下面,我们来分析是如何实现的。

     为了便于说明,我们设置“当前节点”为S(Original Son),“兄弟节点”为B(Brother),“兄弟节点的左孩子”为BLS(Brother's LeftSon),“兄弟节点的右孩子”为BRS(Brother's Right Son),“父节点”为F(Father)。

     我们要对F进行左旋。但在左旋前,我们需要调换F和B的颜色,并设置BRS为黑色。为什么需要这里处理呢?因为左旋后,F和BLS是父子关系,而我们已知BL是红色,如果F是红色,则违背了“特性(4)”;为了解决这一问题,我们将“F设置为黑色”。 但是,F设置为黑色之后,为了保证满足“特性(5)”,即为了保证左旋之后:

     第一,“同时经过根节点和S的分支的黑色节点个数不变”。

            若满足“第一”,只需要S丢弃它多余的颜色即可。因为S的颜色是“黑+黑”,而左旋后“同时经过根节点和S的分支的黑色节点个数”增加了1;现在,只需将S由“黑+黑”变成单独的“黑”节点,即可满足“第一”。

     第二,“同时经过根节点和BLS的分支的黑色节点数不变”。

            若满足“第二”,只需要将“F的原始颜色”赋值给B即可。之前,我们已经将“F设置为黑色”(即,将B的颜色"黑色",赋值给了F)。至此,我们算是调换了F和B的颜色。

     第三,“同时经过根节点和BRS的分支的黑色节点数不变”。

            在“第二”已经满足的情况下,若要满足“第三”,只需要将BRS设置为“黑色”即可。

经过,上面的处理之后。红黑树的特性全部得到的满足!接着,我们将x设为根节点,就可以跳出while循环(参考伪代码);即完成了全部处理。

至此,我们就完成了Case 4的处理。理解Case 4的核心,是了解如何“去掉当前节点额外的黑色”。

4.3 示意图

OK!至此,红黑树的理论知识差不多讲完了。后续再更新红黑树的实现代码!

五、    红黑树之Java的实现

1)  概要

前面分别介绍红黑树的理论知识、红黑树的C语言和C++的实现。本章介绍红黑树的Java实现,若读者对红黑树的理论知识不熟悉,建立先学习红黑树的理论知识,再来学习本章。还是那句老话,红黑树的C/C++/Java实现,原理一样,择其一了解即可。

目录

1. 红黑树的介绍

2. 红黑树的Java实现(代码说明)

3. 红黑树的Java实现(完整源码)

4. 红黑树的Java测试程序

2)  红黑树的介绍

红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树。

红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。

除了具备该特性之外,红黑树还包括许多额外的信息。

红黑树的每个节点上都有存储位表示节点的颜色,颜色是红(Red)或黑(Black)。

红黑树的特性:

(1) 每个节点或者是黑色,或者是红色。

(2) 根节点是黑色。

(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]

(4) 如果一个节点是红色的,则它的子节点必须是黑色的。

(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

关于它的特性,需要注意的是:

第一,特性(3)中的叶子节点,是只为空(NIL或null)的节点。

第二,特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。

红黑树示意图如下:

3)  红黑树的Java实现(代码说明)

红黑树的基本操作是添加、删除和旋转。在对红黑树进行添加或删除后,会用到旋转方法。为什么呢?道理很简单,添加或删除红黑树中的节点之后,红黑树就发生了变化,可能不满足红黑树的5条性质,也就不再是一颗红黑树了,而是一颗普通的树。而通过旋转,可以使这颗树重新成为红黑树。简单点说,旋转的目的是让树保持红黑树的特性。

旋转包括两种:左旋 和 右旋。下面分别对红黑树的基本操作进行介绍。

1. 基本定义

public class RBTree<T extendsComparable<T>> {

   private RBTNode<T> mRoot;   // 根结点

   private static final boolean RED  = false;

   private static final boolean BLACK = true;

   public class RBTNode<T extends Comparable<T>> {

       boolean color;        // 颜色

       T key;                // 关键字(键值)

       RBTNode<T> left;    // 左孩子

       RBTNode<T> right;    // 右孩子

       RBTNode<T> parent;    // 父结点

       public RBTNode(T key, boolean color, RBTNode<T> parent,RBTNode<T> left, RBTNode<T> right) {

           this.key = key;

           this.color = color;

           this.parent = parent;

           this.left = left;

           this.right = right;

       }

    }

   ...

}

RBTree是红黑树对应的类,RBTNode是红黑树的节点类。在RBTree中包含了根节点mRoot和红黑树的相关API。

注意:在实现红黑树API的过程中,我重载了许多函数。重载的原因,一是因为有的API是内部接口,有的是外部接口;二是为了让结构更加清晰。

2. 左旋

对x进行左旋,意味着"将x变成一个左节点"。

左旋的实现代码(Java语言)

/*

 * 对红黑树的节点(x)进行左旋转

 *

 * 左旋示意图(对节点x进行左旋):

 *      px                              px

 *    /                               /

 *   x                              y               

 *  /  \      --(左旋)-.           / \                #

 * lx   y                          x  ry    

 *    /   \                       /  \

 *   ly   ry                     lx  ly 

 *

 *

 */

private void leftRotate(RBTNode<T> x){

   // 设置x的右孩子为y

   RBTNode<T> y = x.right;

   // 将 “y的左孩子” 设为 “x的右孩子”;

   // 如果y的左孩子非空,将 “x”设为 “y的左孩子的父亲”

   x.right = y.left;

   if (y.left != null)

       y.left.parent = x;

   // 将 “x的父亲” 设为 “y的父亲”

   y.parent = x.parent;

   if (x.parent == null) {

       this.mRoot = y;            // 如果“x的父亲” 是空节点,则将y设为根节点

    }else {

       if (x.parent.left == x)

           x.parent.left = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”

       else

           x.parent.right = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”

    }

   // 将 “x” 设为 “y的左孩子”

   y.left = x;

   // 将 “x的父节点” 设为 “y”

   x.parent = y;

}

3. 右旋

对y进行左旋,意味着"将y变成一个右节点"。

右旋的实现代码(Java语言)

/*

 * 对红黑树的节点(y)进行右旋转

 *

 * 右旋示意图(对节点y进行左旋):

 *           py                              py

 *          /                                /

 *         y                               x                 

 *         / \      --(右旋)-.            /  \                     #

 *       x   ry                           lx   y 

 *      / \                                  / \                   #

 *     lx  rx                                rx  ry

 *

 */

private void rightRotate(RBTNode<T>y) {

   // 设置x是当前节点的左孩子。

   RBTNode<T> x = y.left;

   // 将 “x的右孩子” 设为 “y的左孩子”;

   // 如果"x的右孩子"不为空的话,将“y” 设为 “x的右孩子的父亲”

   y.left = x.right;

   if (x.right != null)

       x.right.parent = y;

   // 将 “y的父亲” 设为 “x的父亲”

   x.parent = y.parent;

   if (y.parent == null) {

       this.mRoot = x;            // 如果“y的父亲” 是空节点,则将x设为根节点

    }else {

       if (y == y.parent.right)

           y.parent.right = x;    // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”

       else

           y.parent.left = x;    // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”

    }

   // 将 “y” 设为 “x的右孩子”

   x.right = y;

   // 将 “y的父节点” 设为 “x”

   y.parent = x;

}

4)  4. 添加操作

将一个节点插入到红黑树中,需要执行哪些步骤呢?首先,将红黑树当作一颗二叉查找树,将节点插入;然后,将节点着色为红色;最后,通过"旋转和重新着色"等一系列操作来修正该树,使之重新成为一颗红黑树。详细描述如下:

第一步: 将红黑树当作一颗二叉查找树,将节点插入。

      红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。此外,无论是左旋还是右旋,若旋转之前这棵树是二叉查找树,旋转之后它一定还是二叉查找树。这也就意味着,任何的旋转和重新着色操作,都不会改变它仍然是一颗二叉查找树的事实。

好吧?那接下来,我们就来想方设法的旋转以及重新着色,使这颗树重新成为红黑树!

第二步:将插入的节点着色为"红色"。

      为什么着色成红色,而不是黑色呢?为什么呢?在回答之前,我们需要重新温习一下红黑树的特性:

(1) 每个节点或者是黑色,或者是红色。

(2) 根节点是黑色。

(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]

(4) 如果一个节点是红色的,则它的子节点必须是黑色的。

(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

     将插入的节点着色为红色,不会违背"特性(5)"!少违背一条特性,就意味着我们需要处理的情况越少。接下来,就要努力的让这棵树满足其它性质即可;满足了的话,它就又是一颗红黑树了。o(∩∩)o...哈哈

第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。

      第二步中,将插入节点着色为"红色"之后,不会违背"特性(5)"。那它到底会违背哪些特性呢?

      对于"特性(1)",显然不会违背了。因为我们已经将它涂成红色了。

      对于"特性(2)",显然也不会违背。在第一步中,我们是将红黑树当作二叉查找树,然后执行的插入操作。而根据二叉查找数的特点,插入操作不会改变根节点。所以,根节点仍然是黑色。

      对于"特性(3)",显然不会违背了。这里的叶子节点是指的空叶子节点,插入非空节点并不会对它们造成影响。

      对于"特性(4)",是有可能违背的!

      那接下来,想办法使之"满足特性(4)",就可以将树重新构造成红黑树了。

5)  添加操作的实现代码(Java语言)

/*

 * 将结点插入到红黑树中

 *

 * 参数说明:

 *    node 插入的结点       // 对应《算法导论》中的node

 */

private void insert(RBTNode<T> node){

   int cmp;

   RBTNode<T> y = null;

   RBTNode<T> x = this.mRoot;

   // 1. 将红黑树当作一颗二叉查找树,将节点添加到二叉查找树中。

   while (x != null) {

       y = x;

       cmp = node.key.compareTo(x.key);

       if (cmp < 0)

           x = x.left;

       else

           x = x.right;

    }

   node.parent = y;

   if (y!=null) {

       cmp = node.key.compareTo(y.key);

       if (cmp < 0)

           y.left = node;

       else

           y.right = node;

    }else {

       this.mRoot = node;

    }

   // 2. 设置节点的颜色为红色

   node.color = RED;

   // 3. 将它重新修正为一颗二叉查找树

   insertFixUp(node);

}

/*

 * 新建结点(key),并将其插入到红黑树中

 *

 * 参数说明:

 *    key 插入结点的键值

 */

public void insert(T key) {

   RBTNode<T> node=new RBTNode<T>(key,BLACK,null,null,null);

 

   // 如果新建结点失败,则返回。

   if (node != null)

       insert(node);

}

内部接口 -- insert(node)的作用是将"node"节点插入到红黑树中。

外部接口 -- insert(key)的作用是将"key"添加到红黑树中。

6)  添加修正操作的实现代码(Java语言)

/*

 * 红黑树插入修正函数

 *

 * 在向红黑树中插入节点之后(失去平衡),再调用该函数;

 * 目的是将它重新塑造成一颗红黑树。

 *

 * 参数说明:

 *    node 插入的结点       // 对应《算法导论》中的z

 */

private void insertFixUp(RBTNode<T>node) {

   RBTNode<T> parent, gparent;

   // 若“父节点存在,并且父节点的颜色是红色”

   while (((parent = parentOf(node))!=null) && isRed(parent)) {

       gparent = parentOf(parent);

       //若“父节点”是“祖父节点的左孩子”

       if (parent == gparent.left) {

           // Case 1条件:叔叔节点是红色

           RBTNode<T> uncle = gparent.right;

           if ((uncle!=null) && isRed(uncle)) {

               setBlack(uncle);

                setBlack(parent);

                setRed(gparent);

                node = gparent;

                continue;

           }

           // Case 2条件:叔叔是黑色,且当前节点是右孩子

           if (parent.right == node) {

               RBTNode<T> tmp;

                leftRotate(parent);

                tmp = parent;

                parent = node;

                node = tmp;

           }

           // Case 3条件:叔叔是黑色,且当前节点是左孩子。

           setBlack(parent);

           setRed(gparent);

           rightRotate(gparent);

       } else {    //若“z的父节点”是“z的祖父节点的右孩子”

           // Case 1条件:叔叔节点是红色

           RBTNode<T> uncle = gparent.left;

           if ((uncle!=null) && isRed(uncle)) {

                setBlack(uncle);

                setBlack(parent);

                setRed(gparent);

                node = gparent;

                continue;

           }

           // Case 2条件:叔叔是黑色,且当前节点是左孩子

           if (parent.left == node) {

                RBTNode<T> tmp;

                rightRotate(parent);

                tmp = parent;

                parent = node;

                node = tmp;

           }

           // Case 3条件:叔叔是黑色,且当前节点是右孩子。

           setBlack(parent);

           setRed(gparent);

           leftRotate(gparent);

       }

    }

   // 将根节点设为黑色

   setBlack(this.mRoot);

}

insertFixUp(node)的作用是对应"上面所讲的第三步"。它是一个内部接口。

7)  5. 删除操作

将红黑树内的某一个节点删除。需要执行的操作依次是:首先,将红黑树当作一颗二叉查找树,将该节点从二叉查找树中删除;然后,通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。详细描述如下:

第一步:将红黑树当作一颗二叉查找树,将节点删除。

      这和"删除常规二叉查找树中删除节点的方法是一样的"。分3种情况:

① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。

② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。

③ 被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。在这里,后继节点相当于替身,在将后继节点的内容复制给"被删除节点"之后,再将后继节点删除。这样就巧妙的将问题转换为"删除后继节点"的情况了,下面就考虑后继节点。 在"被删除节点"有两个非空子节点的情况下,它的后继节点不可能是双子非空。既然"的后继节点"不可能双子都非空,就意味着"该节点的后继节点"要么没有儿子,要么只有一个儿子。若没有儿子,则按"情况① "进行处理;若只有一个儿子,则按"情况② "进行处理。

第二步:通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。

       因为"第一步"中删除节点之后,可能会违背红黑树的特性。所以需要通过"旋转和重新着色"来修正该树,使之重新成为一棵红黑树。

8)  删除操作的实现代码(Java语言)

/*

 * 删除结点(node),并返回被删除的结点

 *

 * 参数说明:

 *    node 删除的结点

 */

private void remove(RBTNode<T> node){

   RBTNode<T> child, parent;

   boolean color;

   // 被删除节点的"左右孩子都不为空"的情况。

   if ( (node.left!=null) && (node.right!=null) ) {

       // 被删节点的后继节点。(称为"取代节点")

       // 用它来取代"被删节点"的位置,然后再将"被删节点"去掉。

       RBTNode<T> replace = node;

       // 获取后继节点

       replace = replace.right;

       while (replace.left != null)

           replace = replace.left;

       // "node节点"不是根节点(只有根节点不存在父节点)

       if (parentOf(node)!=null) {

           if (parentOf(node).left == node)

                parentOf(node).left = replace;

           else

                parentOf(node).right = replace;

       } else {

           // "node节点"是根节点,更新根节点。

           this.mRoot = replace;

       }

       // child是"取代节点"的右孩子,也是需要"调整的节点"。

       // "取代节点"肯定不存在左孩子!因为它是一个后继节点。

       child = replace.right;

       parent = parentOf(replace);

        // 保存"取代节点"的颜色

       color = colorOf(replace);

       // "被删除节点"是"它的后继节点的父节点"

       if (parent == node) {

           parent = replace;

       } else {

           // child不为空

           if (child!=null)

                setParent(child, parent);

           parent.left = child;

           replace.right = node.right;

           setParent(node.right, replace);

       }

       replace.parent = node.parent;

       replace.color = node.color;

       replace.left = node.left;

       node.left.parent = replace;

       if (color == BLACK)

           removeFixUp(child, parent);

       node = null;

       return ;

    }

   if (node.left !=null) {

       child = node.left;

    }else {

       child = node.right;

    }

   parent = node.parent;

   // 保存"取代节点"的颜色

   color = node.color;

   if (child!=null)

       child.parent = parent;

   // "node节点"不是根节点

   if (parent!=null) {

       if (parent.left == node)

           parent.left = child;

       else

           parent.right = child;

    }else {

       this.mRoot = child;

    }

   if (color == BLACK)

       removeFixUp(child, parent);

   node = null;

}

/*

 * 删除结点(z),并返回被删除的结点

 *

 * 参数说明:

 *    tree 红黑树的根结点

 *     z 删除的结点

 */

public void remove(T key) {

   RBTNode<T> node;

   if ((node = search(mRoot, key)) != null)

       remove(node);

}

内部接口 -- remove(node)的作用是将"node"节点插入到红黑树中。

外部接口 -- remove(key)删除红黑树中键值为key的节点。

9)  删除修正操作的实现代码(Java语言)

/*

 * 红黑树删除修正函数

 *

 * 在从红黑树中删除插入节点之后(红黑树失去平衡),再调用该函数;

 * 目的是将它重新塑造成一颗红黑树。

 *

 * 参数说明:

 *    node 待修正的节点

 */

private void removeFixUp(RBTNode<T>node, RBTNode<T> parent) {

   RBTNode<T> other;

   while ((node==null || isBlack(node)) && (node != this.mRoot)) {

       if (parent.left == node) {

           other = parent.right;

           if (isRed(other)) {

                // Case 1: x的兄弟w是红色的 

                setBlack(other);

                setRed(parent);

                leftRotate(parent);

                other = parent.right;

           }

           if ((other.left==null || isBlack(other.left)) &&

                (other.right==null ||isBlack(other.right))) {

                // Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的 

                setRed(other);

                node = parent;

                parent = parentOf(node);

           } else {

                if (other.right==null ||isBlack(other.right)) {

                    // Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。 

                    setBlack(other.left);

                    setRed(other);

                   rightRotate(other);

                    other = parent.right;

                }

                // Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。

                setColor(other,colorOf(parent));

                setBlack(parent);

                setBlack(other.right);

                leftRotate(parent);

                node = this.mRoot;

                break;

           }

       } else {

           other = parent.left;

           if (isRed(other)) {

                // Case 1: x的兄弟w是红色的 

                setBlack(other);

                setRed(parent);

                rightRotate(parent);

                other = parent.left;

           }

           if ((other.left==null || isBlack(other.left)) &&

                (other.right==null ||isBlack(other.right))) {

                // Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的 

                setRed(other);

                node = parent;

                parent = parentOf(node);

           } else {

                if (other.left==null || isBlack(other.left)){

                    // Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。 

                    setBlack(other.right);

                    setRed(other);

                    leftRotate(other);

                    other = parent.left;

                }

                // Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。

                setColor(other,colorOf(parent));

                setBlack(parent);

                setBlack(other.left);

                rightRotate(parent);

                node = this.mRoot;

                break;

           }

       }

    }

   if (node!=null)

       setBlack(node);

}

removeFixup(node, parent)是对应"上面所讲的第三步"。它是一个内部接口。

10)      红黑树的Java实现(完整源码)

下面是红黑树实现的完整代码和相应的测试程序。

(1) 除了上面所说的"左旋"、"右旋"、"添加"、"删除"等基本操作之后,还实现了"遍历"、"查找"、"打印"、"最小值"、"最大值"、"创建"、"销毁"等接口。

(2) 函数接口大多分为内部接口和外部接口。内部接口是private函数,外部接口则是public函数。

(3) 测试代码中提供了"插入"和"删除"动作的检测开关。默认是关闭的,打开方法可以参考"代码中的说明"。建议在打开开关后,在草稿上自己动手绘制一下红黑树。

红黑树的实现文件(RBTree.java)

/**

 *Java 语言: 红黑树

 *

 *@author skywang

 *@date 2013/11/07

 */

 

public class RBTree<T extendsComparable<T>> {

   private RBTNode<T> mRoot;   // 根结点

   private static final boolean RED  = false;

   private static final boolean BLACK = true;

   public class RBTNode<T extends Comparable<T>> {

       boolean color;        // 颜色

       T key;                // 关键字(键值)

       RBTNode<T> left;    // 左孩子

       RBTNode<T> right;    // 右孩子

       RBTNode<T> parent;    // 父结点

       public RBTNode(T key, boolean color, RBTNode<T> parent,RBTNode<T> left, RBTNode<T> right) {

           this.key = key;

           this.color = color;

           this.parent = parent;

           this.left = left;

           this.right = right;

       }

       public T getKey() {

           return key;

       }

       public String toString() {

           return ""+key+(this.color==RED?"(R)":"B");

       }

    }

   public RBTree() {

       mRoot=null;

    }

   private RBTNode<T> parentOf(RBTNode<T> node) {

       return node!=null ? node.parent : null;

    }

   private boolean colorOf(RBTNode<T> node) {

       return node!=null ? node.color : BLACK;

    }

   private boolean isRed(RBTNode<T> node) {

       return ((node!=null)&&(node.color==RED)) ? true : false;

    }

   private boolean isBlack(RBTNode<T> node) {

       return !isRed(node);

    }

   private void setBlack(RBTNode<T> node) {

       if (node!=null)

           node.color = BLACK;

    }

   private void setRed(RBTNode<T> node) {

       if (node!=null)

           node.color = RED;

    }

    private void setParent(RBTNode<T> node,RBTNode<T> parent) {

       if (node!=null)

           node.parent = parent;

    }

   private void setColor(RBTNode<T> node, boolean color) {

       if (node!=null)

           node.color = color;

    }

   /*

     * 前序遍历"红黑树"

    */

   private void preOrder(RBTNode<T> tree) {

       if(tree != null) {

           System.out.print(tree.key+" ");

           preOrder(tree.left);

           preOrder(tree.right);

       }

    }

   public void preOrder() {

       preOrder(mRoot);

    }

   /*

    * 中序遍历"红黑树"

    */

   private void inOrder(RBTNode<T> tree) {

       if(tree != null) {

           inOrder(tree.left);

           System.out.print(tree.key+" ");

           inOrder(tree.right);

       }

    }

   public void inOrder() {

       inOrder(mRoot);

    }

   /*

    * 后序遍历"红黑树"

    */

   private void postOrder(RBTNode<T> tree) {

       if(tree != null)

       {

           postOrder(tree.left);

           postOrder(tree.right);

           System.out.print(tree.key+" ");

       }

    }

   public void postOrder() {

       postOrder(mRoot);

    }

   /*

    * (递归实现)查找"红黑树x"中键值为key的节点

    */

   private RBTNode<T> search(RBTNode<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 RBTNode<T> search(T key) {

       return search(mRoot, key);

    }

   /*

    * (非递归实现)查找"红黑树x"中键值为key的节点

    */

   private RBTNode<T> iterativeSearch(RBTNode<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 RBTNode<T> iterativeSearch(T key) {

       return iterativeSearch(mRoot, key);

    }

   /*

    * 查找最小结点:返回tree为根结点的红黑树的最小结点。

    */

   private RBTNode<T> minimum(RBTNode<T> tree) {

       if (tree == null)

           return null;

       while(tree.left != null)

           tree = tree.left;

       return tree;

    }

   public T minimum() {

       RBTNode<T> p = minimum(mRoot);

       if (p != null)

           return p.key;

       return null;

    }

   /*

    * 查找最大结点:返回tree为根结点的红黑树的最大结点。

    */

   private RBTNode<T> maximum(RBTNode<T> tree) {

       if (tree == null)

           return null;

       while(tree.right != null)

           tree = tree.right;

       return tree;

    }

   public T maximum() {

       RBTNode<T> p = maximum(mRoot);

       if (p != null)

           return p.key;

       return null;

    }

   /*

    * 找结点(x)的后继结点。即,查找"红黑树中数据值大于该结点"的"最小结点"。

    */

   public RBTNode<T> successor(RBTNode<T> x) {

       // 如果x存在右孩子,则"x的后继结点"为 "以其右孩子为根的子树的最小结点"。

       if (x.right != null)

           return minimum(x.right);

        // 如果x没有右孩子。则x有以下两种可能:

       // (01) x是"一个左孩子",则"x的后继结点"为"它的父结点"。

       // (02) x是"一个右孩子",则查找"x的最低的父结点,并且该父结点要具有左孩子",找到的这个"最低的父结点"就是"x的后继结点"。

       RBTNode<T> y = x.parent;

       while ((y!=null) && (x==y.right)) {

           x = y;

           y = y.parent;

       }

       return y;

    }

   /*

    * 找结点(x)的前驱结点。即,查找"红黑树中数据值小于该结点"的"最大结点"。

    */

   public RBTNode<T> predecessor(RBTNode<T> x) {

       // 如果x存在左孩子,则"x的前驱结点"为 "以其左孩子为根的子树的最大结点"。

       if (x.left != null)

           return maximum(x.left);

       // 如果x没有左孩子。则x有以下两种可能:

       // (01) x是"一个右孩子",则"x的前驱结点"为"它的父结点"。

       // (01) x是"一个左孩子",则查找"x的最低的父结点,并且该父结点要具有右孩子",找到的这个"最低的父结点"就是"x的前驱结点"。

       RBTNode<T> y = x.parent;

       while ((y!=null) && (x==y.left)) {

           x = y;

           y = y.parent;

       }

       return y;

    }

   /*

    * 对红黑树的节点(x)进行左旋转

    *

    * 左旋示意图(对节点x进行左旋):

    *      px                              px

    *     /                               /

    *    x                               y               

    *   /  \     --(左旋)-.           / \                #

    *  lx   y                          x  ry    

    *     /   \                       /  \

    *    ly   ry                     lx  ly 

    *

    *

    */

   private void leftRotate(RBTNode<T> x) {

       // 设置x的右孩子为y

       RBTNode<T> y = x.right;

       // 将 “y的左孩子” 设为 “x的右孩子”;

       // 如果y的左孩子非空,将 “x”设为 “y的左孩子的父亲”

       x.right = y.left;

       if (y.left != null)

           y.left.parent = x;

       // 将 “x的父亲” 设为 “y的父亲”

       y.parent = x.parent;

       if (x.parent == null) {

           this.mRoot = y;            // 如果“x的父亲” 是空节点,则将y设为根节点

       } else {

           if (x.parent.left == x)

                x.parent.left = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”

           else

                x.parent.right = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”

       }

       // 将 “x” 设为 “y的左孩子”

       y.left = x;

       // 将 “x的父节点” 设为 “y”

       x.parent = y;

    }

   /*

    * 对红黑树的节点(y)进行右旋转

    *

    * 右旋示意图(对节点y进行左旋):

    *            py                               py

    *           /                                /

    *          y                                x                 

    *         /  \     --(右旋)-.            / \                     #

    *        x   ry                           lx   y 

    *       / \                                   / \                   #

    *      lx  rx                                rx  ry

    *

    */

   private void rightRotate(RBTNode<T> y) {

       // 设置x是当前节点的左孩子。

       RBTNode<T> x = y.left;

       // 将 “x的右孩子” 设为 “y的左孩子”;

       // 如果"x的右孩子"不为空的话,将“y” 设为 “x的右孩子的父亲”

       y.left = x.right;

       if (x.right != null)

           x.right.parent = y;

       // 将 “y的父亲” 设为 “x的父亲”

       x.parent = y.parent;

       if (y.parent == null) {

           this.mRoot = x;            // 如果“y的父亲” 是空节点,则将x设为根节点

       } else {

           if (y == y.parent.right)

                y.parent.right = x;    // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”

           else

                y.parent.left = x;    // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”

       }

       // 将 “y” 设为 “x的右孩子”

       x.right = y;

       // 将 “y的父节点” 设为 “x”

       y.parent = x;

    }

   /*

    * 红黑树插入修正函数

    *

    * 在向红黑树中插入节点之后(失去平衡),再调用该函数;

    * 目的是将它重新塑造成一颗红黑树。

    *

    * 参数说明:

    *     node 插入的结点        // 对应《算法导论》中的z

    */

   private void insertFixUp(RBTNode<T> node) {

       RBTNode<T> parent, gparent;

       // 若“父节点存在,并且父节点的颜色是红色”

       while (((parent = parentOf(node))!=null) && isRed(parent)) {

           gparent = parentOf(parent);

           //若“父节点”是“祖父节点的左孩子”

           if (parent == gparent.left) {

                // Case 1条件:叔叔节点是红色

                RBTNode<T> uncle =gparent.right;

                if ((uncle!=null) &&isRed(uncle)) {

                    setBlack(uncle);

                    setBlack(parent);

                    setRed(gparent);

                    node = gparent;

                    continue;

                }

                // Case 2条件:叔叔是黑色,且当前节点是右孩子

                if (parent.right == node) {

                    RBTNode<T> tmp;

                    leftRotate(parent);

                    tmp = parent;

                   parent = node;

                    node = tmp;

                }

                // Case 3条件:叔叔是黑色,且当前节点是左孩子。

                setBlack(parent);

                setRed(gparent);

                rightRotate(gparent);

           } else {    //若“z的父节点”是“z的祖父节点的右孩子”

                // Case 1条件:叔叔节点是红色

                RBTNode<T> uncle =gparent.left;

                if ((uncle!=null) &&isRed(uncle)) {

                    setBlack(uncle);

                    setBlack(parent);

                    setRed(gparent);

                    node = gparent;

                    continue;

                }

                // Case 2条件:叔叔是黑色,且当前节点是左孩子

                if (parent.left == node) {

                    RBTNode<T> tmp;

                    rightRotate(parent);

                    tmp = parent;

                    parent = node;

                    node = tmp;

                }

                // Case 3条件:叔叔是黑色,且当前节点是右孩子。

                setBlack(parent);

                setRed(gparent);

                leftRotate(gparent);

           }

       }

       // 将根节点设为黑色

       setBlack(this.mRoot);

    }

   /*

    * 将结点插入到红黑树中

    *

    * 参数说明:

    *     node 插入的结点        // 对应《算法导论》中的node

    */

   private void insert(RBTNode<T> node) {

       int cmp;

       RBTNode<T> y = null;

       RBTNode<T> x = this.mRoot;

       // 1. 将红黑树当作一颗二叉查找树,将节点添加到二叉查找树中。

       while (x != null) {

           y = x;

           cmp = node.key.compareTo(x.key);

           if (cmp < 0)

                x = x.left;

           else

                x = x.right;

       }

       node.parent = y;

       if (y!=null) {

           cmp = node.key.compareTo(y.key);

           if (cmp < 0)

                y.left = node;

           else

                y.right = node;

       } else {

           this.mRoot = node;

       }

       // 2. 设置节点的颜色为红色

       node.color = RED;

       // 3. 将它重新修正为一颗二叉查找树

       insertFixUp(node);

    }

   /*

    * 新建结点(key),并将其插入到红黑树中

    *

    * 参数说明:

    *     key 插入结点的键值

    */

   public void insert(T key) {

       RBTNode<T> node=new RBTNode<T>(key,BLACK,null,null,null);

       // 如果新建结点失败,则返回。

       if (node != null)

           insert(node);

    }

   /*

    * 红黑树删除修正函数

    *

    * 在从红黑树中删除插入节点之后(红黑树失去平衡),再调用该函数;

    * 目的是将它重新塑造成一颗红黑树。

    *

    * 参数说明:

    *     node 待修正的节点

    */

   private void removeFixUp(RBTNode<T> node, RBTNode<T> parent){

       RBTNode<T> other;

       while ((node==null || isBlack(node)) && (node != this.mRoot)) {

           if (parent.left == node) {

                other = parent.right;

                if (isRed(other)) {

                    // Case 1: x的兄弟w是红色的 

                    setBlack(other);

                    setRed(parent);

                    leftRotate(parent);

                    other = parent.right;

                }

                if ((other.left==null ||isBlack(other.left)) &&

                    (other.right==null ||isBlack(other.right))) {

                    // Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的 

                    setRed(other);

                    node = parent;

                    parent = parentOf(node);

                } else {

                    if (other.right==null ||isBlack(other.right)) {

                        // Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。 

                        setBlack(other.left);

                        setRed(other);

                        rightRotate(other);

                        other = parent.right;

                    }

                    // Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。

                    setColor(other,colorOf(parent));

                    setBlack(parent);

                    setBlack(other.right);

                    leftRotate(parent);

                   node = this.mRoot;

                    break;

                }

           } else {

                other = parent.left;

                if (isRed(other)) {

                    // Case 1: x的兄弟w是红色的 

                    setBlack(other);

                   setRed(parent);

                    rightRotate(parent);

                    other = parent.left;

                }

                if ((other.left==null ||isBlack(other.left)) &&

                    (other.right==null ||isBlack(other.right))) {

                    // Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的 

                    setRed(other);

                    node = parent;

                    parent = parentOf(node);

                } else {

                    if (other.left==null ||isBlack(other.left)) {

                        // Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。 

                        setBlack(other.right);

                        setRed(other);

                        leftRotate(other);

                        other = parent.left;

                    }

                    // Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。

                    setColor(other,colorOf(parent));

                    setBlack(parent);

                    setBlack(other.left);

                    rightRotate(parent);

                    node = this.mRoot;

                    break;

                }

           }

       }

       if (node!=null)

           setBlack(node);

    }

   /*

    * 删除结点(node),并返回被删除的结点

    *

    * 参数说明:

    *     node 删除的结点

    */

   private void remove(RBTNode<T> node) {

       RBTNode<T> child, parent;

       boolean color;

       // 被删除节点的"左右孩子都不为空"的情况。

       if ( (node.left!=null) && (node.right!=null) ) {

           // 被删节点的后继节点。(称为"取代节点")

           // 用它来取代"被删节点"的位置,然后再将"被删节点"去掉。

           RBTNode<T> replace = node;

           // 获取后继节点

           replace = replace.right;

           while (replace.left != null)

                replace = replace.left;

           // "node节点"不是根节点(只有根节点不存在父节点)

           if (parentOf(node)!=null) {

                if (parentOf(node).left ==node)

                    parentOf(node).left =replace;

                else

                    parentOf(node).right =replace;

           } else {

                // "node节点"是根节点,更新根节点。

                this.mRoot = replace;

           }

           // child是"取代节点"的右孩子,也是需要"调整的节点"。

           // "取代节点"肯定不存在左孩子!因为它是一个后继节点。

           child = replace.right;

           parent = parentOf(replace);

           // 保存"取代节点"的颜色

           color = colorOf(replace);

           // "被删除节点"是"它的后继节点的父节点"

           if (parent == node) {

                parent = replace;

           } else {

                // child不为空

                if (child!=null)

                    setParent(child, parent);

                parent.left = child;

 

                replace.right = node.right;

                setParent(node.right, replace);

           }

           replace.parent = node.parent;

           replace.color = node.color;

           replace.left = node.left;

           node.left.parent = replace;

           if (color == BLACK)

                removeFixUp(child, parent);

           node = null;

           return ;

       }

       if (node.left !=null) {

           child = node.left;

       } else {

           child = node.right;

       }

       parent = node.parent;

       // 保存"取代节点"的颜色

       color = node.color;

       if (child!=null)

           child.parent = parent;

       // "node节点"不是根节点

       if (parent!=null) {

           if (parent.left == node)

                parent.left = child;

           else

                parent.right = child;

       } else {

           this.mRoot = child;

       }

       if (color == BLACK)

           removeFixUp(child, parent);

       node = null;

    }

   /*

    * 删除结点(z),并返回被删除的结点

    *

    * 参数说明:

    *     tree 红黑树的根结点

    *     z 删除的结点

    */

   public void remove(T key) {

       RBTNode<T> node;

 

       if ((node = search(mRoot, key)) != null)

           remove(node);

    }

   /*

    * 销毁红黑树

    */

   private void destroy(RBTNode<T> tree) {

       if (tree==null)

           return ;

       if (tree.left != null)

           destroy(tree.left);

       if (tree.right != null)

           destroy(tree.right);

       tree=null;

    }

   public void clear() {

       destroy(mRoot);

       mRoot = null;

    }

   /*

    * 打印"红黑树"

    *

    * key        -- 节点的键值

    * direction  --  0,表示该节点是根节点;

    *               -1,表示该节点是它的父结点的左孩子;

    *                1,表示该节点是它的父结点的右孩子。

    */

   private void print(RBTNode<T> tree, T key, int direction) {

       if(tree != null) {

           if(direction==0)    // tree是根节点

                System.out.printf("%2d(B)is root\n", tree.key);

           else                // tree是分支节点

                System.out.printf("%2d(%s)is %2d's %6s child\n", tree.key, isRed(tree)?"R":"B",key, direction==1?"right" : "left");

           print(tree.left, tree.key,-1);

           print(tree.right,tree.key,  1);

       }

    }

   public void print() {

       if (mRoot != null)

           print(mRoot, mRoot.key, 0);

    }

}

红黑树的测试文件(RBTreeTest.java)

/**

 *Java 语言: 二叉查找树

 *

 *@author skywang

 *@date 2013/11/07

 */

public class RBTreeTest {

   private static final int a[] = {10, 40, 30, 60, 90, 70, 20, 50, 80};

   private static final boolean mDebugInsert = false;    // "插入"动作的检测开关(false,关闭;true,打开)

   private static final boolean mDebugDelete = false;    // "删除"动作的检测开关(false,关闭;true,打开)

   public static void main(String[] args) {

       int i, ilen = a.length;

       RBTree<Integer> tree=new RBTree<Integer>();

       System.out.printf("== 原始数据: ");

       for(i=0; i<ilen; i++)

           System.out.printf("%d ", a[i]);

       System.out.printf("\n");

       for(i=0; i<ilen; i++) {

           tree.insert(a[i]);

           // 设置mDebugInsert=true,测试"添加函数"

           if (mDebugInsert) {

                System.out.printf("== 添加节点: %d\n", a[i]);

                System.out.printf("== 树的详细信息: \n");

                tree.print();

               System.out.printf("\n");

           }

       }

       System.out.printf("== 前序遍历: ");

       tree.preOrder();

       System.out.printf("\n== 中序遍历: ");

       tree.inOrder();

       System.out.printf("\n== 后序遍历: ");

       tree.postOrder();

       System.out.printf("\n");

       System.out.printf("== 最小值: %s\n",tree.minimum());

       System.out.printf("== 最大值: %s\n",tree.maximum());

       System.out.printf("== 树的详细信息: \n");

       tree.print();

       System.out.printf("\n");

       // 设置mDebugDelete=true,测试"删除函数"

       if (mDebugDelete) {

           for(i=0; i<ilen; i++)

           {

                tree.remove(a[i]);

                System.out.printf("== 删除节点: %d\n", a[i]);

                System.out.printf("== 树的详细信息: \n");

                tree.print();

               System.out.printf("\n");

           }

       }

       // 销毁二叉树

       tree.clear();

    }

}

11)      红黑树的Java测试程序

前面已经给出了红黑树的测试代码(RBTreeTest.java),这里就不再重复说明。下面是测试程序的运行结果:

== 原始数据: 10 40 3060 90 70 20 50 80

== 前序遍历: 30 10 2060 40 50 80 70 90

== 中序遍历: 10 20 3040 50 60 70 80 90

== 后序遍历: 20 10 5040 70 90 80 60 30

== 最小值: 10

== 最大值: 90

== 树的详细信息:

30(B) is root

10(B) is 30's   left child

20(R) is 10's  right child

60(R) is 30's  right child

40(B) is 60's   left child

50(R) is 40's  right child

80(B) is 60's  right child

70(R) is 80's   left child

90(R) is 80's  right child

六、    哈夫曼树之Java详解

1)  概要

1. 哈夫曼树的介绍

2. 哈夫曼树的图文解析

3. 哈夫曼树的基本操作

4. 哈夫曼树的完整源码

2)  哈夫曼树的介绍

Huffman Tree,中文名是哈夫曼树或霍夫曼树,它是最优二叉树。

定义:给定n个权值作为n个叶子结点,构造一棵二叉树,若树的带权路径长度达到最小,则这棵树被称为哈夫曼树。这个定义里面涉及到了几个陌生的概念,下面就是一颗哈夫曼树,我们来看图解答。

(01) 路径和路径长度

    定义:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。

    例子:100和80的路径长度是1,50和30的路径长度是2,20和10的路径长度是3。

(02) 结点的权及带权路径长度

    定义:若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。

    例子:节点20的路径长度是3,它的带权路径长度= 路径长度 * 权 = 3 * 20 = 60。

(03) 树的带权路径长度

    定义:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。

    例子:示例中,树的WPL= 1*100 + 2*80 + 3*20 + 3*10 = 100 + 160 + 60 + 30 = 350。

比较下面两棵树

上面的两棵树都是以{10, 20, 50, 100}为叶子节点的树。

    左边的树WPL=2*10 + 2*20 + 2*50 + 2*100 = 360

    右边的树WPL=350

左边的树WPL > 右边的树的WPL。你也可以计算除上面两种示例之外的情况,但实际上右边的树就是{10,20,50,100}对应的哈夫曼树。至此,应该堆哈夫曼树的概念有了一定的了解了,下面看看如何去构造一棵哈夫曼树。

3)  哈夫曼树的图文解析

假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,哈夫曼树的构造规则为:

   1. 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);

   2. 在森林中选出根结点的权值最小的两棵树进行合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;

   3. 从森林中删除选取的两棵树,并将新树加入森林;

   4. 重复(02)、(03)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

以{5,6,7,8,15}为例,来构造一棵哈夫曼树。

第1步:创建森林,森林包括5棵树,这5棵树的权值分别是5,6,7,8,15。

第2步:在森林中,选择根节点权值最小的两棵树(5和6)来进行合并,将它们作为一颗新树的左右孩子(谁左谁右无关紧要,这里,我们选择较小的作为左孩子),并且新树的权值是左右孩子的权值之和。即,新树的权值是11。 然后,将"树5"和"树6"从森林中删除,并将新的树(树11)添加到森林中。

第3步:在森林中,选择根节点权值最小的两棵树(7和8)来进行合并。得到的新树的权值是15。然后,将"树7"和"树8"从森林中删除,并将新的树(树15)添加到森林中。

第4步:在森林中,选择根节点权值最小的两棵树(11和15)来进行合并。得到的新树的权值是26。 然后,将"树11"和"树15"从森林中删除,并将新的树(树26)添加到森林中。

第5步:在森林中,选择根节点权值最小的两棵树(15和26)来进行合并。得到的新树的权值是41。 然后,将"树15"和"树26"从森林中删除,并将新的树(树41)添加到森林中。

此时,森林中只有一棵树(树41)。这棵树就是我们需要的哈夫曼树!

4)  哈夫曼树的基本操作

哈夫曼树的重点是如何构造哈夫曼树。本文构造哈夫曼时,用到了以前介绍过的"(二叉堆)最小堆"。下面对哈夫曼树进行讲解。

1. 基本定义

public class HuffmanNode implementsComparable, Cloneable {

   protected int key;              //权值

   protected HuffmanNode left;     //左孩子

   protected HuffmanNode right;    //右孩子

   protected HuffmanNode parent;   //父结点

   protected HuffmanNode(int key, HuffmanNode left, HuffmanNode right,HuffmanNode parent) {

       this.key = key;

       this.left = left;

       this.right = right;

       this.parent = parent;

    }

   @Override

   public Object clone() {

       Object obj=null;

       try {

           obj = (HuffmanNode)super.clone();//Object 中的clone()识别出你要复制的是哪一个对象。   

       } catch(CloneNotSupportedException e) {

           System.out.println(e.toString());

       }

       return obj;   

    }

   @Override

   public int compareTo(Object obj) {

       return this.key - ((HuffmanNode)obj).key;

    }

}

HuffmanNode是哈夫曼树的节点类。

public class Huffman {

   private HuffmanNode mRoot;  // 根结点

   ...

}

Huffman是哈夫曼树对应的类,它包含了哈夫曼树的根节点和哈夫曼树的相关操作。

2. 构造哈夫曼树

/*

 * 创建Huffman树

 *

 *@param 权值数组

 */

public Huffman(int a[]) {

   HuffmanNode parent = null;

   MinHeap heap;

   // 建立数组a对应的最小堆

   heap = new MinHeap(a);

   for(int i=0; i<a.length-1; i++) {  

       HuffmanNode left = heap.dumpFromMinimum();  // 最小节点是左孩子

        HuffmanNode right = heap.dumpFromMinimum();// 其次才是右孩子

       // 新建parent节点,左右孩子分别是left/right;

       // parent的大小是左右孩子之和

       parent = new HuffmanNode(left.key+right.key, left, right, null);

       left.parent = parent;

       right.parent = parent;

       // 将parent节点数据拷贝到"最小堆"中

       heap.insert(parent);

    }

   mRoot = parent;

   // 销毁最小堆

   heap.destroy();

}

首先创建最小堆,然后进入for循环。

每次循环时:

   (01) 首先,将最小堆中的最小节点拷贝一份并赋值给left,然后重塑最小堆(将最小节点和后面的节点交换位置,接着将"交换位置后的最小节点"之前的全部元素重新构造成最小堆);

   (02) 接着,再将最小堆中的最小节点拷贝一份并将其赋值right,然后再次重塑最小堆;

   (03) 然后,新建节点parent,并将它作为left和right的父节点;

   (04) 接着,将parent的数据复制给最小堆中的指定节点。

在二叉堆中已经介绍过堆,这里就不再对堆的代码进行说明了。若有疑问,直接参考后文的源码。其它的相关代码,也Please RTFSC(Read The Fucking Source Code)!

5)  哈夫曼树的完整源码

哈夫曼树的源码共包括4个文件。

1. 哈夫曼树的节点类(HuffmanNode.java)

/**

 *Huffman节点类(Huffman.java的辅助类)

 *

 *@author skywang

 *@date 2014/03/27

 */

public class HuffmanNode implementsComparable, Cloneable {

    protectedint key;              // 权值

    protectedHuffmanNode left;     // 左孩子

    protectedHuffmanNode right;    // 右孩子

    protectedHuffmanNode parent;   // 父结点

    protectedHuffmanNode(int key, HuffmanNode left, HuffmanNode right, HuffmanNode parent) {

       this.key= key;

       this.left= left;

       this.right= right;

       this.parent= parent;

    }

    @Override

    publicObject clone() {

       Objectobj=null;

       try{

           obj= (HuffmanNode)super.clone();//Object 中的clone()识别出你要复制的是哪一个对象。   

       }catch(CloneNotSupportedException e) {

           System.out.println(e.toString());

       }

       returnobj;   

    }

    @Override

    publicint compareTo(Object obj) {

       returnthis.key - ((HuffmanNode)obj).key;

    }

}

2. 哈夫曼树的实现文件(Huffman.java)

/**

 *Huffman树

 *

 *@author skywang

 *@date 2014/03/27

 */

import java.util.List;

import java.util.ArrayList;

import java.util.Collections;

public class Huffman {

    privateHuffmanNode mRoot;  // 根结点

    /*

     * 创建Huffman树

     *

     * @param 权值数组

     */

    publicHuffman(int a[]) {

       HuffmanNode parent = null;

       MinHeapheap;

       //建立数组a对应的最小堆

       heap= new MinHeap(a);

       for(inti=0; i<a.length-1; i++) {  

           HuffmanNodeleft = heap.dumpFromMinimum();  // 最小节点是左孩子

          HuffmanNode right =heap.dumpFromMinimum(); // 其次才是右孩子

           //新建parent节点,左右孩子分别是left/right;

           //parent的大小是左右孩子之和

           parent= new HuffmanNode(left.key+right.key, left, right, null);

           left.parent = parent;

           right.parent= parent;

           //将parent节点数据拷贝到"最小堆"中

           heap.insert(parent);

       }

       mRoot= parent;

       //销毁最小堆

       heap.destroy();

    }

    /*

     * 前序遍历"Huffman树"

     */

    privatevoid preOrder(HuffmanNode tree) {

       if(tree!= null) {

           System.out.print(tree.key+"");

           preOrder(tree.left);

           preOrder(tree.right);

       }

    }

    publicvoid preOrder() {

       preOrder(mRoot);

    }

    /*

     * 中序遍历"Huffman树"

     */

    privatevoid inOrder(HuffmanNode tree) {

       if(tree!= null) {

           inOrder(tree.left);

           System.out.print(tree.key+"");

           inOrder(tree.right);

       }

    }

    publicvoid inOrder() {

       inOrder(mRoot);

    }

    /*

     * 后序遍历"Huffman树"

     */

    privatevoid postOrder(HuffmanNode tree) {

       if(tree!= null)

       {

           postOrder(tree.left);

           postOrder(tree.right);

           System.out.print(tree.key+"");

       }

    }

    publicvoid postOrder() {

       postOrder(mRoot);

    }

    /*

     * 销毁Huffman树

     */

    privatevoid destroy(HuffmanNode tree) {

       if(tree==null)

           return;

       if(tree.left != null)

           destroy(tree.left);

       if(tree.right != null)

           destroy(tree.right);

       tree=null;

    }

    publicvoid destroy() {

       destroy(mRoot);

       mRoot= null;

    }

    /*

     * 打印"Huffman树"

     *

     * key       -- 节点的键值

     * direction --  0,表示该节点是根节点;

     *              -1,表示该节点是它的父结点的左孩子;

     *                1,表示该节点是它的父结点的右孩子。

     */

    privatevoid print(HuffmanNode tree, int key, int direction) {

       if(tree!= null) {

           if(direction==0)  // tree是根节点

              System.out.printf("%2dis root\n", tree.key);

           else              // tree是分支节点

              System.out.printf("%2dis %2d's %6s child\n", tree.key, key, direction==1?"right" :"left");

           print(tree.left,tree.key, -1);

           print(tree.right,tree.key,  1);

       }

    }

    publicvoid print() {

       if(mRoot != null)

           print(mRoot,mRoot.key, 0);

    }

}

3. 哈夫曼树对应的最小堆(MinHeap.java)

/**

 * 最小堆(Huffman.java的辅助类)

 *

 *@author skywang

 *@date 2014/03/27

 */

import java.util.ArrayList;

import java.util.List;

public class MinHeap {

    privateList<HuffmanNode> mHeap;       // 存放堆的数组

    /*

     * 创建最小堆

     *

     * 参数说明:

     *     a-- 数据所在的数组

     */

    protectedMinHeap(int a[]) {

       mHeap= new ArrayList<HuffmanNode>();

       //初始化数组

       for(inti=0; i<a.length; i++) {

           HuffmanNode node = new HuffmanNode(a[i],null, null, null);

           mHeap.add(node);

       }

       //从(size/2-1) --> 0逐次遍历。遍历之后,得到的数组实际上是一个最小堆。

       for(int i = a.length / 2 - 1; i >= 0; i--)

           filterdown(i,a.length-1);

    }

    /*

     * 最小堆的向下调整算法

     *

     * 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。

     *

     * 参数说明:

     *    start -- 被下调节点的起始位置(一般为0,表示从第1个开始)

     *    end   -- 截至范围(一般为数组中最后一个元素的索引)

     */

    protectedvoid filterdown(int start, int end) {

       intc = start;        // 当前(current)节点的位置

       intl = 2*c + 1; // 左(left)孩子的位置

       HuffmanNodetmp = mHeap.get(c); // 当前(current)节点

       while(l<= end) {

           //"l"是左孩子,"l+1"是右孩子

           if(l< end && (mHeap.get(l).compareTo(mHeap.get(l+1))>0))

              l++;       // 左右两孩子中选择较小者,即mHeap[l+1]

           intcmp = tmp.compareTo(mHeap.get(l));

           if(cmp<= 0)

              break;     //调整结束

           else{

              mHeap.set(c,mHeap.get(l));

              c= l;

              l= 2*l + 1;  

           }      

       }  

       mHeap.set(c,tmp);

    }

    /*

     * 最小堆的向上调整算法(从start开始向上直到0,调整堆)

     *

     * 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。

     *

     * 参数说明:

     *    start -- 被上调节点的起始位置(一般为数组中最后一个元素的索引)

     */

    protectedvoid filterup(int start) {

       intc = start;           // 当前节点(current)的位置

       intp = (c-1)/2;     // 父(parent)结点的位置

       HuffmanNodetmp = mHeap.get(c); // 当前(current)节点

       while(c> 0) {

           intcmp = mHeap.get(p).compareTo(tmp);

           if(cmp<= 0)

              break;

           else{

              mHeap.set(c,mHeap.get(p));

              c= p;

              p= (p-1)/2;  

           }      

       }

       mHeap.set(c,tmp);

    }

    /*

     * 将node插入到二叉堆中

     */

    protectedvoid insert(HuffmanNode node) {

       intsize = mHeap.size();

       mHeap.add(node);  // 将"数组"插在表尾

       filterup(size);      // 向上调整堆

    }

    /*

     * 交换两个HuffmanNode节点的全部数据

     */

    privatevoid swapNode(int i, int j) {

       HuffmanNodetmp = mHeap.get(i);

       mHeap.set(i,mHeap.get(j));

       mHeap.set(j,tmp);

    }

    /*

     * 新建一个节点,并将最小堆中最小节点的数据复制给该节点。

     * 然后除最小节点之外的数据重新构造成最小堆。

     *

     * 返回值:

     *     失败返回null。

     */

    protectedHuffmanNode dumpFromMinimum() {

       intsize = mHeap.size();

        // 如果"堆"已空,则返回

       if(size== 0)

           returnnull;

       //将"最小节点"克隆一份,将克隆得到的对象赋值给node

       HuffmanNodenode = (HuffmanNode)mHeap.get(0).clone();

       //交换"最小节点"和"最后一个节点"

       mHeap.set(0,mHeap.get(size-1));

       //删除最后的元素

       mHeap.remove(size-1);

       if(mHeap.size() > 1)

           filterdown(0,mHeap.size()-1);

       returnnode;

    }

    //销毁最小堆

    protectedvoid destroy() {

       mHeap.clear();

       mHeap= null;

    }

}

4. 哈夫曼树的测试程序(HuffmanTest.java)哈夫曼树之Java详解

/**

 *Huffman树的测试程序

 *

 *@author skywang

 *@date 2014/03/27

 */

public class HuffmanTest {

    privatestatic final int a[]= {5,6,8,7,15};

    publicstatic void main(String[] args) {

       inti;

       Huffmantree;

       System.out.print("==添加数组: ");

       for(i=0;i<a.length; i++)

           System.out.print(a[i]+"");

       //创建数组a对应的Huffman树

       tree= new Huffman(a);

       System.out.print("\n==前序遍历: ");

       tree.preOrder();

       System.out.print("\n==中序遍历: ");

       tree.inOrder();

       System.out.print("\n==后序遍历: ");

       tree.postOrder();

       System.out.println();

       System.out.println("==树的详细信息: ");

       tree.print();

       //销毁二叉树

       tree.destroy();

    }

}

仅供个人学习,如有抄袭请包容.....

0 0