skywang12345

导航

 

 

概要

前面分别通过C和C++实现了二项堆,本章给出二项堆的Java版本。还是那句老话,三种实现的原理一样,择其一了解即可。

目录
1. 二项树的介绍
2. 二项堆的介绍
3. 二项堆的基本操作
4. 二项堆的Java实现(完整源码)
5. 二项堆的Java测试程序

转载请注明出处:http://www.cnblogs.com/skywang12345/p/3656098.html


更多内容:数据结构与算法系列 目录

(01) 二项堆(一)之 图文解析 和 C语言的实现
(02) 二项堆(二)之 C++的实现
(03) 二项堆(二)之 Java的实现

 

二项树的介绍

二项树的定义

二项堆是二项树的集合。在了解二项堆之前,先对二项树进行介绍。

二项树是一种递归定义的有序树。它的递归定义如下:
(01) 二项树B0只有一个结点;
(02) 二项树Bk由两棵二项树B(k-1)组成的,其中一棵树是另一棵树根的最左孩子。

如下图所示:

上图的B0、B1、B2、B3、B4都是二项树。对比前面提到的二项树的定义:B0只有一个节点,B1由两个B0所组成,B2由两个B1所组成,B3由两个B2所组成,B4由两个B3所组成;而且,当两颗相同的二项树组成另一棵树时,其中一棵树是另一棵树的最左孩子。

 

二项树的性质

二项树有以下性质:
[性质一] Bk共有2k个节点。
               如上图所示,B0有20=1节点,B1有21=2个节点,B2有22=4个节点,...

[性质二] Bk的高度为k。
               如上图所示,B0的高度为0,B1的高度为1,B2的高度为2,...

[性质三] Bk在深度i处恰好有C(k,i)个节点,其中i=0,1,2,...,k。
              C(k,i)是高中数学中阶乘元素,例如,C(10,3)=(10*9*8) / (3*2*1)=240
              B4中深度为0的节点C(4,0)=1
              B4中深度为1的节点C(4,1)= 4 / 1 = 4
              B4中深度为2的节点C(4,2)= (4*3) / (2*1) = 6
              B4中深度为3的节点C(4,3)= (4*3*2) / (3*2*1) = 4
              B4中深度为4的节点C(4,4)= (4*3*2*1) / (4*3*2*1) = 1
             合计得到B4的节点分布是(1,4,6,4,1)。

[性质四] 根的度数为k,它大于任何其它节点的度数。
              节点的度数是该结点拥有的子树的数目。

注意:树的高度和深度是相同的。关于树的高度的概念,《算法导论》中只有一个节点的树的高度是0,而"维基百科"中只有一个节点的树的高度是1。本文使用了《算法导论中》"树的高度和深度"的概念。

 

二项堆的介绍

二项堆和之前所讲的堆(二叉堆左倾堆斜堆)一样,也是用于实现优先队列的。二项堆是指满足以下性质的二项树的集合:
(01) 每棵二项树都满足最小堆性质。即,父节点的关键字 <= 它的孩子的关键字。
(02) 不能有两棵或以上的二项树具有相同的度数(包括度数为0)。换句话说,具有度数k的二项树有0个或1个。

上图就是一棵二项堆,它由二项树B0、B2和B3组成。对比二项堆的定义:(01)二项树B0、B2、B3都是最小堆;(02)二项堆不包含相同度数的二项树。

         二项堆的第(01)个性质保证了二项堆的最小节点就是某个二项树的根节点,第(02)个性质则说明结点数为n的二项堆最多只有log{n} + 1棵二项树。实际上,将包含n个节点的二项堆,表示成若干个2的指数和(或者转换成二进制),则每一个2个指数都对应一棵二项树。例如,13(二进制是1101)的2个指数和为13=23 + 22+ 20, 因此具有13个节点的二项堆由度数为3, 2, 0的三棵二项树组成。

 

二项堆的基本操作

二项堆是可合并堆,它的合并操作的复杂度是O(log n)。

1. 基本定义

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

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

    private class BinomialNode<T extends Comparable<T>> {
        T key;                // 关键字(键值)
        int degree;            // 度数
        BinomialNode<T> child;    // 左孩子
        BinomialNode<T> parent;    // 父节点
        BinomialNode<T> next;    // 兄弟节点

        public BinomialNode(T key) {
            this.key = key;
            this.degree = 0;
            this.child = null;
            this.parent = null;
            this.next = null;
        }

        public String toString() {
            return "key:"+key;
        }
    }
        
    ...
}

BinomialNode是二项堆的节点。它包括了关键字(key),用于比较节点大小;度数(degree),用来表示当前节点的度数;左孩子(child)、父节点(parent)以及兄弟节点(next)。
BinomialHeap是二项堆对应的类,它包括了二项堆的根节点mRoot以及二项堆的基本操作的定义。

下面是一棵二项堆的树形图和它对应的内存结构关系图。

 

2. 合并操作

合并操作是二项堆的重点,它的添加操作也是基于合并操作来实现的。合并两个二项堆,需要的步骤概括起来如下:
(01) 将两个二项堆的根链表合并成一个链表。合并后的新链表按照"节点的度数"单调递增排列。
(02) 将新链表中"根节点度数相同的二项树"连接起来,直到所有根节点度数都不相同。

下面,先看看合并操作的代码;然后再通过示意图对合并操作进行说明。
merge()代码(Java)

 1 /*
 2  * 将h1, h2中的根表合并成一个按度数递增的链表,返回合并后的根节点
 3  */
 4 private BinomialNode<T> merge(BinomialNode<T> h1, BinomialNode<T> h2) {
 5     if (h1 == null) return h2;
 6     if (h2 == null) return h1;
 7 
 8     // root是新堆的根,h3用来遍历h1和h3的。
 9     BinomialNode<T> pre_h3, h3, root=null;
10  
11     pre_h3 = null;
12     //整个while,h1, h2, pre_h3, h3都在往后顺移
13     while ((h1!=null) && (h2!=null)) {
14 
15         if (h1.degree <= h2.degree) {
16             h3 = h1;
17             h1 = h1.next;
18         } else {
19             h3 = h2;
20             h2 = h2.next;
21         }
22 
23         if (pre_h3 == null) {
24             pre_h3 = h3;
25             root = h3;
26         } else {
27             pre_h3.next = h3;
28             pre_h3 = h3;
29         }
30 
31         if (h1 != null) {
32             h3.next = h1;
33         } else {
34             h3.next = h2;
35         }
36     }
37     return root;
38 }
View Code

link()代码(Java)

1 /*
2  * 合并两个二项堆:将child合并到root中
3  */
4 private void link(BinomialNode<T> child, BinomialNode<T> root) {
5     child.parent = root;
6     child.next   = root.child;
7     root.child = child;
8     root.degree++;
9 }
View Code

合并操作代码(Java)

 1 /*
 2  * 合并二项堆:将h1, h2合并成一个堆,并返回合并后的堆
 3  */
 4 private BinomialNode<T> union(BinomialNode<T> h1, BinomialNode<T> h2) {
 5     BinomialNode<T> root;
 6 
 7     // 将h1, h2中的根表合并成一个按度数递增的链表root
 8     root = merge(h1, h2);
 9     if (root == null)
10         return null;
11  
12     BinomialNode<T> prev_x = null;
13     BinomialNode<T> x      = root;
14     BinomialNode<T> next_x = x.next;
15     while (next_x != null) {
16 
17         if (   (x.degree != next_x.degree) 
18             || ((next_x.next != null) && (next_x.degree == next_x.next.degree))) {
19             // Case 1: x.degree != next_x.degree
20             // Case 2: x.degree == next_x.degree == next_x.next.degree
21             prev_x = x;
22             x = next_x;
23         } else if (x.key.compareTo(next_x.key) <= 0) {
24             // Case 3: x.degree == next_x.degree != next_x.next.degree
25             //      && x.key    <= next_x.key
26             x.next = next_x.next;
27             link(next_x, x);
28         } else {
29             // Case 4: x.degree == next_x.degree != next_x.next.degree
30             //      && x.key    >  next_x.key
31             if (prev_x == null) {
32                 root = next_x;
33             } else {
34                 prev_x.next = next_x;
35             }
36             link(x, next_x);
37             x = next_x;
38         }
39         next_x = x.next;
40     }
41 
42     return root;
43 }
44 
45 /*
46  * 将二项堆other合并到当前堆中
47  */
48 public void union(BinomialHeap<T> other) {
49     if (other!=null && other.mRoot!=null)
50         mRoot = union(mRoot, other.mRoot);
51 }

合并函数combine(h1, h2)的作用是将h1和h2合并,并返回合并后的二项堆。在combine(h1, h2)中,涉及到了两个函数merge(h1, h2)和link(child, root)。
merge(h1, h2)就是我们前面所说的"两个二项堆的根链表合并成一个链表,合并后的新链表按照'节点的度数'单调递增排序"。
link(child, root)则是为了合并操作的辅助函数,它的作用是将"二项堆child的根节点"设为"二项堆root的左孩子",从而将child整合到root中去。

        在combine(h1, h2)中对h1和h2进行合并时;首先通过 merge(h1, h2) 将h1和h2的根链表合并成一个"按节点的度数单调递增"的链表;然后进入while循环,对合并得到的新链表进行遍历,将新链表中"根节点度数相同的二项树"连接起来,直到所有根节点度数都不相同为止。在将新联表中"根节点度数相同的二项树"连接起来时,可以将被连接的情况概括为4种。

x是根链表的当前节点,next_x是x的下一个(兄弟)节点。
Case 1: x->degree != next_x->degree
             即,"当前节点的度数"与"下一个节点的度数"相等时。此时,不需要执行任何操作,继续查看后面的节点。
Case 2x->degree == next_x->degree == next_x->next->degree
             即,"当前节点的度数"、"下一个节点的度数"和"下下一个节点的度数"都相等时。此时,暂时不执行任何操作,还是继续查看后面的节点。实际上,这里是将"下一个节点"和"下下一个节点"等到后面再进行整合连接。
Case 3x->degree == next_x->degree != next_x->next->degree
        && x->key <= next_x->key
             即,"当前节点的度数"与"下一个节点的度数"相等,并且"当前节点的键值"<="下一个节点的度数"。此时,将"下一个节点(对应的二项树)"作为"当前节点(对应的二项树)的左孩子"。
Case 4x->degree == next_x->degree != next_x->next->degree
        && x->key > next_x->key
             即,"当前节点的度数"与"下一个节点的度数"相等,并且"当前节点的键值">"下一个节点的度数"。此时,将"当前节点(对应的二项树)"作为"下一个节点(对应的二项树)的左孩子"。


下面通过示意图来对合并操作进行说明。

第1步:将两个二项堆的根链表合并成一个链表
          执行完第1步之后,得到的新链表中有许多度数相同的二项树。实际上,此时得到的是对应"Case 4"的情况,"树41"(根节点为41的二项树)和"树13"的度数相同,且"树41"的键值 > "树13"的键值。此时,将"树41"作为"树13"的左孩子。
第2步:合并"树41"和"树13"
         执行完第2步之后,得到的是对应"Case 3"的情况,"树13"和"树28"的度数相同,且"树13"的键值 < "树28"的键值。此时,将"树28"作为"树13"的左孩子。
第3步:合并"树13"和"树28"
         执行完第3步之后,得到的是对应"Case 2"的情况,"树13"、"树28"和"树7"这3棵树的度数都相同。此时,将x设为下一个节点。
第4步:将x和next_x往后移
         执行完第4步之后,得到的是对应"Case 3"的情况,"树7"和"树11"的度数相同,且"树7"的键值 < "树11"的键值。此时,将"树11"作为"树7"的左孩子。
第5步:合并"树7"和"树11"
         执行完第5步之后,得到的是对应"Case 4"的情况,"树7"和"树6"的度数相同,且"树7"的键值 > "树6"的键值。此时,将"树7"作为"树6"的左孩子。
第6步:合并"树7"和"树6"
         此时,合并操作完成!

PS. 合并操作的图文解析过程与"测试程序(Main.java)中的testUnion()函数"是对应的!

 

3. 插入操作

理解了"合并"操作之后,插入操作就相当简单了。插入操作可以看作是将"要插入的节点"和当前已有的堆进行合并。

插入操作代码(Java)

 1 /*
 2  * 新建key对应的节点,并将其插入到二项堆中。
 3  */
 4 public void insert(T key) {
 5     BinomialNode<T> node;
 6 
 7     // 禁止插入相同的键值
 8     if (contains(key)==true) {
 9         System.out.printf("insert failed: the key(%s) is existed already!\n", key);
10         return ;
11     }
12 
13     node = new BinomialNode<T>(key);
14     if (node==null)
15         return ;
16 
17     mRoot = union(mRoot, node);
18 }

在插入时,首先通过contains(key)查找键值为key的节点。存在的话,则直接返回;不存在的话,则新建BinomialNode对象node,然后将node和heap进行合并。


注意:我这里实现的二项堆是"进制插入相同节点的"!若你想允许插入相同键值的节点,则屏蔽掉插入操作中的contains(key)部分代码即可。

 

4. 删除操作

删除二项堆中的某个节点,需要的步骤概括起来如下:
(01) 将"该节点"交换到"它所在二项树"的根节点位置。方法是,从"该节点"不断向上(即向树根方向)"遍历,不断交换父节点和子节点的数据,直到被删除的键值到达树根位置。
(02) 将"该节点所在的二项树"从二项堆中移除;将该二项堆记为heap。
(03) 将"该节点所在的二项树"进行反转。反转的意思,就是将根的所有孩子独立出来,并将这些孩子整合成二项堆,将该二项堆记为child。
(04) 将child和heap进行合并操作。

下面,先看看删除操作的代码;再进行图文说明。

删除操作代码(Java)

 1 /* 
 2  * 删除节点:删除键值为key的节点
 3  */
 4 private BinomialNode<T> remove(BinomialNode<T> root, T key) {
 5     if (root==null)
 6         return root;
 7 
 8     BinomialNode<T> node;
 9 
10     // 查找键值为key的节点
11     if ((node = search(root, key)) == null)
12         return root;
13 
14     // 将被删除的节点的数据数据上移到它所在的二项树的根节点
15     BinomialNode<T> parent = node.parent;
16     while (parent != null) {
17         // 交换数据
18         T tmp = node.key;
19         node.key = parent.key;
20         parent.key = tmp;
21 
22         // 下一个父节点
23         node   = parent;
24         parent = node.parent;
25     }
26 
27     // 找到node的前一个根节点(prev)
28     BinomialNode<T> prev = null;
29     BinomialNode<T> pos  = root;
30     while (pos != node) {
31         prev = pos;
32         pos  = pos.next;
33     }
34     // 移除node节点
35     if (prev!=null)
36         prev.next = node.next;
37     else
38         root = node.next;
39 
40     root = union(root, reverse(node.child)); 
41 
42     // help GC
43     node = null;
44 
45     return root;
46 }
47 
48 public void remove(T key) {
49     mRoot = remove(mRoot, key);
50 }

remove(key)的作用是删除二项堆中键值为key的节点,并返回删除节点后的二项堆。


下面通过示意图来对删除操作进行说明(删除二项堆中的节点20)。

总的思想,就是将被"删除节点"从它所在的二项树中孤立出来,然后再对二项树进行相应的处理。


PS. 删除操作的图文解析过程与"测试程序(Main.java)中的testDelete()函数"是对应的!

 

5. 更新操作

更新二项堆中的某个节点,就是修改节点的值,它包括两部分分:"减少节点的值" 和 "增加节点的值" 。

更新操作代码(Java)

/* 
 * 更新二项堆的节点node的键值为key
 */
private void updateKey(BinomialNode<T> node, T key) {
    if (node == null)
        return ;

    int cmp = key.compareTo(node.key);
    if(cmp < 0)                            // key < node.key
        decreaseKey(node, key);
    else if(cmp > 0)                       // key > node.key
        increaseKey(node, key);
    else
        System.out.println("No need to update!!!");
}

/* 
 * 将二项堆中键值oldkey更新为newkey
 */
public void update(T oldkey, T newkey) {
    BinomialNode<T> node;

    node = search(mRoot, oldkey);
    if (node != null)
        updateKey(node, newkey);
}

 

5.1 减少节点的值

减少节点值的操作很简单:该节点一定位于一棵二项树中,减小"二项树"中某个节点的值后要保证"该二项树仍然是一个最小堆";因此,就需要我们不断的将该节点上调。

减少操作代码(Java)

 1 /* 
 2  * 减少关键字的值:将二项堆中的节点node的键值减小为key。
 3  */
 4 private void decreaseKey(BinomialNode<T> node, T key) {
 5     if(key.compareTo(node.key)>=0 || contains(key)==true) {
 6     System.out.println("decrease failed: the new key("+key+") is existed already, or is no smaller than current key("+node.key+")");
 7         return ;
 8     }
 9     node.key = key;
10  
11     BinomialNode<T> child, parent;
12     child = node;
13     parent = node.parent;
14     while(parent != null && child.key.compareTo(parent.key)<0) {
15         // 交换parent和child的数据
16         T tmp = parent.key;
17         parent.key = child.key;
18         child.key = tmp;
19 
20         child = parent;
21         parent = child.parent;
22     }
23 }

 

下面是减少操作的示意图(20->2)

减少操作的思想很简单,就是"保持被减节点所在二项树的最小堆性质"。

PS. 减少操作的图文解析过程与"测试程序(Main.java)中的testDecrease()函数"是对应的!

 

5.2 增加节点的值

增加节点值的操作也很简单。上面说过减少要将被减少的节点不断上调,从而保证"被减少节点所在的二项树"的最小堆性质;而增加操作则是将被增加节点不断的下调,从而保证"被增加节点所在的二项树"的最小堆性质。

增加操作代码(Java)

 1 /* 
 2  * 增加关键字的值:将二项堆中的节点node的键值增加为key。
 3  */
 4 private void increaseKey(BinomialNode<T> node, T key) {
 5     if(key.compareTo(node.key)<=0 || contains(key)==true) {
 6     System.out.println("increase failed: the new key("+key+") is existed already, or is no greater than current key("+node.key+")");
 7         return ;
 8     }
 9     node.key = key;
10 
11     BinomialNode<T> cur = node;
12     BinomialNode<T> child = cur.child;
13     while (child != null) {
14 
15         if(cur.key.compareTo(child.key) > 0) {
16             // 如果"当前节点" < "它的左孩子",
17             // 则在"它的孩子中(左孩子 和 左孩子的兄弟)"中,找出最小的节点;
18             // 然后将"最小节点的值" 和 "当前节点的值"进行互换
19             BinomialNode<T> least = child;    // least是child和它的兄弟中的最小节点
20             while(child.next != null) {
21                 if (least.key.compareTo(child.next.key) > 0)
22                     least = child.next;
23                 child = child.next;
24             }
25             // 交换最小节点和当前节点的值
26             T tmp = least.key;
27             least.key = cur.key;
28             cur.key = tmp;
29 
30             // 交换数据之后,再对"原最小节点"进行调整,使它满足最小堆的性质:父节点 <= 子节点
31             cur = least;
32             child = cur.child;
33         } else {
34             child = child.next;
35         }
36     }
37 }

 

下面是增加操作的示意图(6->60)

增加操作的思想很简单,"保持被增加点所在二项树的最小堆性质"。

PS. 增加操作的图文解析过程与"测试程序(Main.java)中的testIncrease()函数"是对应的!

 

注意:关于二项堆的"查找"、"更新"、"打印"等接口就不再单独介绍了,后文的源码中有给出它们的实现代码。有兴趣的话,Please RTFSC(Read The Fucking Source Code)!

 

二项堆的Java实现(完整源码)

二项堆的实现文件(BinomialHeap.java)

  1 /**
  2  * Java 语言: 二项堆
  3  *
  4  * @author skywang
  5  * @date 2014/04/03
  6  */
  7 
  8 public class BinomialHeap<T extends Comparable<T>> {
  9 
 10     private BinomialNode<T> mRoot;    // 根结点
 11 
 12     private class BinomialNode<T extends Comparable<T>> {
 13         T key;                // 关键字(键值)
 14         int degree;            // 度数
 15         BinomialNode<T> child;    // 左孩子
 16         BinomialNode<T> parent;    // 父节点
 17         BinomialNode<T> next;    // 兄弟节点
 18 
 19         public BinomialNode(T key) {
 20             this.key = key;
 21             this.degree = 0;
 22             this.child = null;
 23             this.parent = null;
 24             this.next = null;
 25         }
 26 
 27         public String toString() {
 28             return "key:"+key;
 29         }
 30     }
 31 
 32     public BinomialHeap() {
 33         mRoot = null;
 34     }
 35 
 36     /*
 37      * 获取二项堆中的最小节点的键值
 38      */
 39     public T minimum() {
 40         if (mRoot==null)
 41             return null;
 42 
 43         BinomialNode<T> x, prev_x;    // x是用来遍历的当前节点
 44         BinomialNode<T> y, prev_y;    // y是最小节点
 45 
 46         prev_x  = mRoot;
 47         x       = mRoot.next;
 48         prev_y  = null;
 49         y       = mRoot;
 50         // 找到最小节点
 51         while (x != null) {
 52             if (x.key.compareTo(y.key) < 0) {
 53                 y = x;
 54                 prev_y = prev_x;
 55             }
 56             prev_x = x;
 57             x = x.next;
 58         }
 59 
 60         return y.key;
 61     }
 62 
 63      
 64     /*
 65      * 合并两个二项堆:将child合并到root中
 66      */
 67     private void link(BinomialNode<T> child, BinomialNode<T> root) {
 68         child.parent = root;
 69         child.next   = root.child;
 70         root.child = child;
 71         root.degree++;
 72     }
 73 
 74     /*
 75      * 将h1, h2中的根表合并成一个按度数递增的链表,返回合并后的根节点
 76      */
 77     private BinomialNode<T> merge(BinomialNode<T> h1, BinomialNode<T> h2) {
 78         if (h1 == null) return h2;
 79         if (h2 == null) return h1;
 80 
 81         // root是新堆的根,h3用来遍历h1和h3的。
 82         BinomialNode<T> pre_h3, h3, root=null;
 83      
 84         pre_h3 = null;
 85         //整个while,h1, h2, pre_h3, h3都在往后顺移
 86         while ((h1!=null) && (h2!=null)) {
 87 
 88             if (h1.degree <= h2.degree) {
 89                 h3 = h1;
 90                 h1 = h1.next;
 91             } else {
 92                 h3 = h2;
 93                 h2 = h2.next;
 94             }
 95  
 96             if (pre_h3 == null) {
 97                 pre_h3 = h3;
 98                 root = h3;
 99             } else {
100                 pre_h3.next = h3;
101                 pre_h3 = h3;
102             }
103 
104             if (h1 != null) {
105                 h3.next = h1;
106             } else {
107                 h3.next = h2;
108             }
109         }
110         return root;
111     }
112 
113     /*
114      * 合并二项堆:将h1, h2合并成一个堆,并返回合并后的堆
115      */
116     private BinomialNode<T> union(BinomialNode<T> h1, BinomialNode<T> h2) {
117         BinomialNode<T> root;
118 
119         // 将h1, h2中的根表合并成一个按度数递增的链表root
120         root = merge(h1, h2);
121         if (root == null)
122             return null;
123      
124         BinomialNode<T> prev_x = null;
125         BinomialNode<T> x      = root;
126         BinomialNode<T> next_x = x.next;
127         while (next_x != null) {
128 
129             if (   (x.degree != next_x.degree) 
130                 || ((next_x.next != null) && (next_x.degree == next_x.next.degree))) {
131                 // Case 1: x.degree != next_x.degree
132                 // Case 2: x.degree == next_x.degree == next_x.next.degree
133                 prev_x = x;
134                 x = next_x;
135             } else if (x.key.compareTo(next_x.key) <= 0) {
136                 // Case 3: x.degree == next_x.degree != next_x.next.degree
137                 //      && x.key    <= next_x.key
138                 x.next = next_x.next;
139                 link(next_x, x);
140             } else {
141                 // Case 4: x.degree == next_x.degree != next_x.next.degree
142                 //      && x.key    >  next_x.key
143                 if (prev_x == null) {
144                     root = next_x;
145                 } else {
146                     prev_x.next = next_x;
147                 }
148                 link(x, next_x);
149                 x = next_x;
150             }
151             next_x = x.next;
152         }
153 
154         return root;
155     }
156 
157     /*
158      * 将二项堆other合并到当前堆中
159      */
160     public void union(BinomialHeap<T> other) {
161         if (other!=null && other.mRoot!=null)
162             mRoot = union(mRoot, other.mRoot);
163     }
164 
165     /*
166      * 新建key对应的节点,并将其插入到二项堆中。
167      */
168     public void insert(T key) {
169         BinomialNode<T> node;
170 
171         // 禁止插入相同的键值
172         if (contains(key)==true) {
173             System.out.printf("insert failed: the key(%s) is existed already!\n", key);
174             return ;
175         }
176 
177         node = new BinomialNode<T>(key);
178         if (node==null)
179             return ;
180 
181         mRoot = union(mRoot, node);
182     }
183 
184     /*
185      * 反转二项堆root,并返回反转后的根节点
186      */
187     private BinomialNode<T> reverse(BinomialNode<T> root) {
188         BinomialNode<T> next;
189         BinomialNode<T> tail = null;
190 
191         if (root==null)
192             return root;
193 
194         root.parent = null;
195         while (root.next!=null) {
196             next         = root.next;
197             root.next    = tail;
198             tail         = root;
199             root         = next;
200             root.parent  = null;
201         }
202         root.next = tail;
203 
204         return root;
205     }
206 
207     /*
208      * 移除二项堆root中的最小节点,并返回删除节点后的二项树
209      */
210     private BinomialNode<T> extractMinimum(BinomialNode<T> root) {
211         if (root==null)
212             return root;
213      
214         BinomialNode<T> x, prev_x;    // x是用来遍历的当前节点
215         BinomialNode<T> y, prev_y;    // y是最小节点
216      
217         prev_x  = root;
218         x       = root.next;
219         prev_y = null;
220         y      = root;
221         // 找到最小节点
222         while (x != null) {
223             if (x.key.compareTo(y.key) < 0) {
224                 y = x;
225                 prev_y = prev_x;
226             }
227             prev_x = x;
228             x = x.next;
229         }
230 
231         if (prev_y == null)    // root的根节点就是最小根节点
232             root = root.next;
233         else                // root的根节点不是最小根节点
234             prev_y.next = y.next;
235      
236         // 反转最小节点的左孩子,得到最小堆child;
237         // 这样,就使得最小节点所在二项树的孩子们都脱离出来成为一棵独立的二项树(不包括最小节点)
238         BinomialNode<T> child = reverse(y.child);
239         // 将"删除最小节点的二项堆child"和"root"进行合并。
240         root = union(root, child);
241 
242         // help GC
243         y = null;
244 
245         return root;
246     }
247 
248     public void extractMinimum() {
249         mRoot = extractMinimum(mRoot);
250     }
251 
252     /* 
253      * 减少关键字的值:将二项堆中的节点node的键值减小为key。
254      */
255     private void decreaseKey(BinomialNode<T> node, T key) {
256         if(key.compareTo(node.key)>=0 || contains(key)==true) {
257             System.out.println("decrease failed: the new key("+key+") is existed already, or is no smaller than current key("+node.key+")");
258             return ;
259         }
260         node.key = key;
261      
262         BinomialNode<T> child, parent;
263         child = node;
264         parent = node.parent;
265         while(parent != null && child.key.compareTo(parent.key)<0) {
266             // 交换parent和child的数据
267             T tmp = parent.key;
268             parent.key = child.key;
269             child.key = tmp;
270 
271             child = parent;
272             parent = child.parent;
273         }
274     }
275 
276     /* 
277      * 增加关键字的值:将二项堆中的节点node的键值增加为key。
278      */
279     private void increaseKey(BinomialNode<T> node, T key) {
280         if(key.compareTo(node.key)<=0 || contains(key)==true) {
281             System.out.println("increase failed: the new key("+key+") is existed already, or is no greater than current key("+node.key+")");
282             return ;
283         }
284         node.key = key;
285 
286         BinomialNode<T> cur = node;
287         BinomialNode<T> child = cur.child;
288         while (child != null) {
289 
290             if(cur.key.compareTo(child.key) > 0) {
291                 // 如果"当前节点" < "它的左孩子",
292                 // 则在"它的孩子中(左孩子 和 左孩子的兄弟)"中,找出最小的节点;
293                 // 然后将"最小节点的值" 和 "当前节点的值"进行互换
294                 BinomialNode<T> least = child;    // least是child和它的兄弟中的最小节点
295                 while(child.next != null) {
296                     if (least.key.compareTo(child.next.key) > 0)
297                         least = child.next;
298                     child = child.next;
299                 }
300                 // 交换最小节点和当前节点的值
301                 T tmp = least.key;
302                 least.key = cur.key;
303                 cur.key = tmp;
304 
305                 // 交换数据之后,再对"原最小节点"进行调整,使它满足最小堆的性质:父节点 <= 子节点
306                 cur = least;
307                 child = cur.child;
308             } else {
309                 child = child.next;
310             }
311         }
312     }
313 
314     /* 
315      * 更新二项堆的节点node的键值为key
316      */
317     private void updateKey(BinomialNode<T> node, T key) {
318         if (node == null)
319             return ;
320 
321         int cmp = key.compareTo(node.key);
322         if(cmp < 0)                            // key < node.key
323             decreaseKey(node, key);
324         else if(cmp > 0)                    // key > node.key
325             increaseKey(node, key);
326         else
327             System.out.println("No need to update!!!");
328     }
329 
330     /* 
331      * 将二项堆中键值oldkey更新为newkey
332      */
333     public void update(T oldkey, T newkey) {
334         BinomialNode<T> node;
335 
336         node = search(mRoot, oldkey);
337         if (node != null)
338             updateKey(node, newkey);
339     }
340 
341     /*
342      * 查找:在二项堆中查找键值为key的节点
343      */
344     private BinomialNode<T> search(BinomialNode<T> root, T key) {
345         BinomialNode<T> child;
346         BinomialNode<T> parent = root;
347 
348         parent = root;
349         while (parent != null) {
350             if (parent.key.compareTo(key) == 0)
351                 return parent;
352             else {
353                 if((child = search(parent.child, key)) != null)
354                     return child;
355                 parent = parent.next;
356             }
357         }
358 
359         return null;
360     }
361      
362     /*
363      * 二项堆中是否包含键值key
364      */
365     public boolean contains(T key) {
366         return search(mRoot, key)!=null ? true : false;
367     }
368 
369     /* 
370      * 删除节点:删除键值为key的节点
371      */
372     private BinomialNode<T> remove(BinomialNode<T> root, T key) {
373         if (root==null)
374             return root;
375 
376         BinomialNode<T> node;
377 
378         // 查找键值为key的节点
379         if ((node = search(root, key)) == null)
380             return root;
381 
382         // 将被删除的节点的数据数据上移到它所在的二项树的根节点
383         BinomialNode<T> parent = node.parent;
384         while (parent != null) {
385             // 交换数据
386             T tmp = node.key;
387             node.key = parent.key;
388             parent.key = tmp;
389 
390             // 下一个父节点
391             node   = parent;
392             parent = node.parent;
393         }
394 
395         // 找到node的前一个根节点(prev)
396         BinomialNode<T> prev = null;
397         BinomialNode<T> pos  = root;
398         while (pos != node) {
399             prev = pos;
400             pos  = pos.next;
401         }
402         // 移除node节点
403         if (prev!=null)
404             prev.next = node.next;
405         else
406             root = node.next;
407 
408         root = union(root, reverse(node.child)); 
409 
410         // help GC
411         node = null;
412 
413         return root;
414     }
415 
416     public void remove(T key) {
417         mRoot = remove(mRoot, key);
418     }
419 
420     /*
421      * 打印"二项堆"
422      *
423      * 参数说明:
424      *     node       -- 当前节点
425      *     prev       -- 当前节点的前一个节点(父节点or兄弟节点)
426      *     direction  --  1,表示当前节点是一个左孩子;
427      *                    2,表示当前节点是一个兄弟节点。
428      */
429     private void print(BinomialNode<T> node, BinomialNode<T> prev, int direction) {
430         while(node != null)
431         {
432             if(direction==1)    // node是根节点
433                 System.out.printf("\t%2d(%d) is %2d's child\n", node.key, node.degree, prev.key);
434             else                // node是分支节点
435                 System.out.printf("\t%2d(%d) is %2d's next\n", node.key, node.degree, prev.key);
436 
437             if (node.child != null)
438                 print(node.child, node, 1);
439 
440             // 兄弟节点
441             prev = node;
442             node = node.next;
443             direction = 2;
444         }
445     }
446 
447     public void print() {
448         if (mRoot == null)
449             return ;
450 
451         BinomialNode<T> p = mRoot;
452         System.out.printf("== 二项堆( ");
453         while (p != null) {
454             System.out.printf("B%d ", p.degree);
455             p = p.next;
456         }
457         System.out.printf(")的详细信息:\n");
458 
459         int i=0;
460         p = mRoot;
461         while (p != null) {
462             i++;
463             System.out.printf("%d. 二项树B%d: \n", i, p.degree);
464             System.out.printf("\t%2d(%d) is root\n", p.key, p.degree);
465 
466             print(p.child, p, 1);
467             p = p.next;
468         }
469         System.out.printf("\n");
470     }
471 }
View Code

二项堆的测试程序(Main.java)

  1 /**
  2  * Java 语言: 二项堆
  3  *
  4  * @author skywang
  5  * @date 2014/03/31
  6  */
  7 
  8 public class Main {
  9 
 10     private static final boolean DEBUG = false;
 11 
 12     // 共7个 = 1+2+4
 13     private static int a[] = {12,  7, 25, 15, 28, 33, 41};
 14     // 共13个 = 1+4+8
 15     private static int b[] = {18, 35, 20, 42,  9, 
 16                31, 23,  6, 48, 11, 
 17                24, 52, 13 };
 18 
 19 
 20     // 验证"二项堆的插入操作"
 21     public static void testInsert() {
 22         BinomialHeap<Integer> ha=new BinomialHeap<Integer>();
 23 
 24         // 二项堆ha
 25         System.out.printf("== 二项堆(ha)中依次添加: ");
 26         for(int i=0; i<a.length; i++) {
 27             System.out.printf("%d ", a[i]);
 28             ha.insert(a[i]);
 29         }
 30         System.out.printf("\n");
 31         System.out.printf("== 二项堆(ha)的详细信息: \n");
 32         ha.print();
 33     }
 34 
 35     // 验证"二项堆的合并操作"
 36     public static void testUnion() {
 37         BinomialHeap<Integer> ha=new BinomialHeap<Integer>();
 38         BinomialHeap<Integer> hb=new BinomialHeap<Integer>();
 39 
 40         // 二项堆ha
 41         System.out.printf("== 二项堆(ha)中依次添加: ");
 42         for(int i=0; i<a.length; i++) {
 43             System.out.printf("%d ", a[i]);
 44             ha.insert(a[i]);
 45         }
 46         System.out.printf("\n");
 47         System.out.printf("== 二项堆(ha)的详细信息: \n");
 48         ha.print();
 49 
 50         // 二项堆hb
 51         System.out.printf("== 二项堆(hb)中依次添加: ");
 52         for(int i=0; i<b.length; i++) {
 53             System.out.printf("%d ", b[i]);
 54             hb.insert(b[i]);
 55         }
 56         System.out.printf("\n");
 57         // 打印二项堆hb
 58         System.out.printf("== 二项堆(hb)的详细信息: \n");
 59         hb.print();
 60 
 61         // 将"二项堆hb"合并到"二项堆ha"中。
 62         ha.union(hb);
 63         // 打印二项堆ha的详细信息
 64         System.out.printf("== 合并ha和hb后的详细信息:\n");
 65         ha.print();
 66     }
 67 
 68     // 验证"二项堆的删除操作"
 69     public static void testDelete() {
 70         BinomialHeap<Integer> hb=new BinomialHeap<Integer>();
 71 
 72         // 二项堆hb
 73         System.out.printf("== 二项堆(hb)中依次添加: ");
 74         for(int i=0; i<b.length; i++) {
 75             System.out.printf("%d ", b[i]);
 76             hb.insert(b[i]);
 77         }
 78         System.out.printf("\n");
 79         // 打印二项堆hb
 80         System.out.printf("== 二项堆(hb)的详细信息: \n");
 81         hb.print();
 82 
 83         // 将"二项堆hb"合并到"二项堆ha"中。
 84         hb.remove(20);
 85         System.out.printf("== 删除节点20后的详细信息: \n");
 86         hb.print();
 87     }
 88 
 89     // 验证"二项堆的更新(减少)操作"
 90     public static void testDecrease() {
 91         BinomialHeap<Integer> hb=new BinomialHeap<Integer>();
 92 
 93         // 二项堆hb
 94         System.out.printf("== 二项堆(hb)中依次添加: ");
 95         for(int i=0; i<b.length; i++) {
 96             System.out.printf("%d ", b[i]);
 97             hb.insert(b[i]);
 98         }
 99         System.out.printf("\n");
100         // 打印二项堆hb
101         System.out.printf("== 二项堆(hb)的详细信息: \n");
102         hb.print();
103 
104         // 将节点20更新为2
105         hb.update(20, 2);
106         System.out.printf("== 更新节点20->2后的详细信息: \n");
107         hb.print();
108     }
109 
110     // 验证"二项堆的更新(减少)操作"
111     public static void testIncrease() {
112         BinomialHeap<Integer> hb=new BinomialHeap<Integer>();
113 
114         // 二项堆hb
115         System.out.printf("== 二项堆(hb)中依次添加: ");
116         for(int i=0; i<b.length; i++) {
117             System.out.printf("%d ", b[i]);
118             hb.insert(b[i]);
119         }
120         System.out.printf("\n");
121         // 打印二项堆hb
122         System.out.printf("== 二项堆(hb)的详细信息: \n");
123         hb.print();
124 
125         // 将节点6更新为60
126         hb.update(6, 60);
127         System.out.printf("== 更新节点6->60后的详细信息: \n");
128         hb.print();
129     }
130 
131     public static void main(String[] args) {
132         // 1. 验证"二项堆的插入操作"
133         testInsert();
134         // 2. 验证"二项堆的合并操作"
135         //testUnion();
136         // 3. 验证"二项堆的删除操作"
137         //testDelete();
138         // 4. 验证"二项堆的更新(减少)操作"
139         //testDecrease();
140         // 5. 验证"二项堆的更新(增加)操作"
141         //testIncrease();
142     }
143 }
View Code

 

二项堆的Java测试程序

二项堆的测试程序包括了五部分,分别是"插入"、"删除"、"增加"、"减少"、"合并"这5种功能的测试代码。默认是运行的"插入"功能代码,你可以根据自己的需要来对相应的功能进行验证!

下面是插入功能运行结果:

== 二项堆(ha)中依次添加: 12 7 25 15 28 33 41 
== 二项堆(ha)的详细信息: 
== 二项堆( B0 B1 B2 )的详细信息:
1. 二项树B0: 
    41(0) is root
2. 二项树B1: 
    28(1) is root
    33(0) is 28's child
3. 二项树B2: 
     7(2) is root
    15(1) is  7's child
    25(0) is 15's child
    12(0) is 15's next

 

posted on 2014-04-16 09:25  如果天空不死  阅读(4104)  评论(1编辑  收藏  举报