二项堆(二)之 C++的实现

时间:2021-12-27 14:11:33

 

概要

上一章介绍了二项堆的基本概念,并通过C语言实现了二项堆。本章是二项堆的C++实现。

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

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


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

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

 

二项树的介绍

二项树的定义

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

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

如下图所示:

二项堆(二)之 C++的实现

上图的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个。

二项堆(二)之 C++的实现

上图就是一棵二项堆。它由二项树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的三棵二项树(即B0、B2和B3)组成。

 

二项堆的基本操作

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

1. 基本定义

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

BinomialNode(T value):key(value), degree(
0),
child(NULL),parent(NULL),next(NULL) {}
};

BinomialNode是二项堆的节点。它包括了关键字(key),用于比较节点大小;度数(degree),用来表示当前节点的度数;左孩子(child)、父节点(parent)以及兄弟节点(next)。

template <class T>
class BinomialHeap {
private:
BinomialNode
<T> *mRoot; // 根结点

public:
BinomialHeap();
~BinomialHeap();

// 新建key对应的节点,并将其插入到二项堆中
void insert(T key);
// 将二项堆中键值oldkey更新为newkey
void update(T oldkey, T newkey);
// 删除键值为key的节点
void remove(T key);
// 移除二项堆中的最小节点
void extractMinimum();

// 将other的二项堆合并到当前二项堆中
void combine(BinomialHeap<T>* other);

// 获取二项堆中的最小节点的键值
T minimum();
// 二项堆中是否包含键值key
bool contains(T key);
// 打印二项堆
void print();
private:

// 合并两个二项堆:将child合并到root中
void link(BinomialNode<T>* child, BinomialNode<T>* root);
// 将h1, h2中的根表合并成一个按度数递增的链表,返回合并后的根节点
BinomialNode<T>* merge(BinomialNode<T>* h1, BinomialNode<T>* h2);
// 合并二项堆:将h1, h2合并成一个堆,并返回合并后的堆
BinomialNode<T>* combine(BinomialNode<T>* h1, BinomialNode<T>* h2);
// 反转二项堆root,并返回反转后的根节点
BinomialNode<T>* reverse(BinomialNode<T>* root);
// 移除二项堆root中的最小节点,并返回删除节点后的二项树
BinomialNode<T>* extractMinimum(BinomialNode<T>* root);
// 删除节点:删除键值为key的节点,并返回删除节点后的二项树
BinomialNode<T>* remove(BinomialNode<T> *root, T key);
// 在二项树root中查找键值为key的节点
BinomialNode<T>* search(BinomialNode<T>* root, T key);

// 增加关键字的值:将二项堆中的节点node的键值增加为key。
void increaseKey(BinomialNode<T>* node, T key);
// 减少关键字的值:将二项堆中的节点node的键值减小为key
void decreaseKey(BinomialNode<T>* node, T key);
// 更新关键字的值:更新二项堆的节点node的键值为key
void updateKey(BinomialNode<T>* node, T key);

// 获取二项堆中的最小根节点
void minimum(BinomialNode<T>* root, BinomialNode<T> *&prev_y, BinomialNode<T> *&y);
// 打印二项堆
void print(BinomialNode<T>* node, BinomialNode<T>* prev, int direction);
};

BinomialHeap是二项堆对应的类,它包括了二项堆的根节点mRoot以及二项堆的基本操作的定义。

 

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

二项堆(二)之 C++的实现

 

2. 合并操作

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

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

二项堆(二)之 C++的实现二项堆(二)之 C++的实现
 1 /*
2 * 将h1, h2中的根表合并成一个按度数递增的链表,返回合并后的根节点
3 */
4 template <class T>
5 BinomialNode<T>* BinomialHeap<T>::merge(BinomialNode<T>* h1, BinomialNode<T>* h2)
6 {
7 BinomialNode<T>* root = NULL; //heap为指向新堆根结点
8 BinomialNode<T>** pos = &root;
9
10 while (h1 && h2)
11 {
12 if (h1->degree < h2->degree)
13 {
14 *pos = h1;
15 h1 = h1->next;
16 }
17 else
18 {
19 *pos = h2;
20 h2 = h2->next;
21 }
22 pos = &(*pos)->next;
23 }
24 if (h1)
25 *pos = h1;
26 else
27 *pos = h2;
28
29 return root;
30 }
View Code

link()代码(C++)

二项堆(二)之 C++的实现二项堆(二)之 C++的实现
 1 /*
2 * 合并两个二项堆:将child合并到root中
3 */
4 template <class T>
5 void BinomialHeap<T>::link(BinomialNode<T>* child, BinomialNode<T>* root)
6 {
7 child->parent = root;
8 child->next = root->child;
9 root->child = child;
10 root->degree++;
11 }
View Code

合并操作代码(C++)

/*
* 合并二项堆:将h1, h2合并成一个堆,并返回合并后的堆
*/
template
<class T>
BinomialNode
<T>* BinomialHeap<T>::combine(BinomialNode<T>* h1, BinomialNode<T>* h2)
{
BinomialNode
<T> *root;
BinomialNode
<T> *prev_x, *x, *next_x;

// 将h1, h2中的根表合并成一个按度数递增的链表root
root = merge(h1, h2);
if (root == NULL)
return NULL;

prev_x
= NULL;
x
= root;
next_x
= x->next;

while (next_x != NULL)
{
if ( (x->degree != next_x->degree)
|| ((next_x->next != NULL) && (next_x->degree == next_x->next->degree)))
{
// Case 1: x->degree != next_x->degree
// Case 2: x->degree == next_x->degree == next_x->next->degree
prev_x = x;
x
= next_x;
}
else if (x->key <= next_x->key)
{
// Case 3: x->degree == next_x->degree != next_x->next->degree
// && x->key <= next_x->key
x->next = next_x->next;
link(next_x, x);
}
else
{
// Case 4: x->degree == next_x->degree != next_x->next->degree
// && x->key > next_x->key
if (prev_x == NULL)
{
root
= next_x;
}
else
{
prev_x
->next = next_x;
}
link(x, next_x);
x
= next_x;
}
next_x
= x->next;
}

return root;
}

/*
* 将二项堆other合并到当前堆中
*/
template
<class T>
void BinomialHeap<T>::combine(BinomialHeap<T> *other)
{
if (other!=NULL && other->mRoot!=NULL)
mRoot
= combine(mRoot, other->mRoot);
}

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


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

二项堆(二)之 C++的实现

第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.cpp)中的testUnion()函数"是对应的!

 

3. 插入操作

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

插入操作代码(C++)

 1 /*
2 * 新建key对应的节点,并将其插入到二项堆中。
3 */
4 template <class T>
5 void BinomialHeap<T>::insert(T key)
6 {
7 BinomialNode<T>* node;
8
9 // 禁止插入相同的键值
10 if (contains(key))
11 {
12 cout << "Insert Error: the key (" << key << ") is existed already!" << endl;
13 return ;
14 }
15
16 node = new BinomialNode<T>(key);
17 if (node==NULL)
18 return ;
19
20 mRoot = combine(mRoot, node);
21 }

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

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

 

4. 删除操作

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

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

二项堆(二)之 C++的实现二项堆(二)之 C++的实现
 1 /*
2 * 反转二项堆root,并返回反转后的根节点
3 */
4 template <class T>
5 BinomialNode<T>* BinomialHeap<T>::reverse(BinomialNode<T>* root)
6 {
7 BinomialNode<T>* next;
8 BinomialNode<T>* tail = NULL;
9
10 if (!root)
11 return root;
12
13 root->parent = NULL;
14 while (root->next)
15 {
16 next = root->next;
17 root->next = tail;
18 tail = root;
19 root = next;
20 root->parent = NULL;
21 }
22 root->next = tail;
23
24 return root;
25 }
View Code

删除操作代码(C++)

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

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


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

二项堆(二)之 C++的实现

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

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

 

5. 更新操作

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

更新操作代码(C++)

二项堆(二)之 C++的实现二项堆(二)之 C++的实现
 1 /* 
2 * 更新二项堆的节点node的键值为key
3 */
4 template <class T>
5 void BinomialHeap<T>::updateKey(BinomialNode<T>* node, T key)
6 {
7 if (node == NULL)
8 return ;
9
10 if(key < node->key)
11 decreaseKey(node, key);
12 else if(key > node->key)
13 increaseKey(node, key);
14 else
15 cout <<"No need to update!!!" <<endl;
16 }
17
18 /*
19 * 将二项堆中键值oldkey更新为newkey
20 */
21 template <class T>
22 void BinomialHeap<T>::update(T oldkey, T newkey)
23 {
24 BinomialNode<T> *node;
25
26 node = search(mRoot, oldkey);
27 if (node != NULL)
28 updateKey(node, newkey);
29 }
View Code

 

5.1 减少节点的值

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

减少操作代码(C++)

 1 /* 
2 * 减少关键字的值:将二项堆中的节点node的键值减小为key。
3 */
4 template <class T>
5 void BinomialHeap<T>::decreaseKey(BinomialNode<T>* node, T key)
6 {
7 if(key>=node->key || contains(key))
8 {
9 cout << "decrease failed: the new key(" << key <<") is existed already, "
10 << "or is no smaller than current key(" << node->key <<")" << endl;
11 return ;
12 }
13 node->key = key;
14
15 BinomialNode<T> *child, *parent;
16 child = node;
17 parent = node->parent;
18 while(parent != NULL && child->key < parent->key)
19 {
20 swap(parent->key, child->key);
21 child = parent;
22 parent = child->parent;
23 }
24 }

 

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

二项堆(二)之 C++的实现

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

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

 

5.2 增加节点的值

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

增加操作代码(C++)

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

 

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

二项堆(二)之 C++的实现

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

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

 

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

 

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

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

二项堆(二)之 C++的实现二项堆(二)之 C++的实现
  1 /**
2 * C++: 二项堆
3 *
4 * @author skywang
5 * @date 2014/04/02
6 */
7
8 #ifndef _BINOMIAL_TREE_HPP_
9 #define _BINOMIAL_TREE_HPP_
10
11 #include <iomanip>
12 #include <iostream>
13 using namespace std;
14
15 template <class T>
16 class BinomialNode {
17 public:
18 T key; // 关键字(键值)
19 int degree; // 度数
20 BinomialNode<T> *child; // 左孩子
21 BinomialNode<T> *parent; // 父节点
22 BinomialNode<T> *next; // 兄弟节点
23
24 BinomialNode(T value):key(value), degree(0),
25 child(NULL),parent(NULL),next(NULL) {}
26 };
27
28 template <class T>
29 class BinomialHeap {
30 private:
31 BinomialNode<T> *mRoot; // 根结点
32
33 public:
34 BinomialHeap();
35 ~BinomialHeap();
36
37 // 新建key对应的节点,并将其插入到二项堆中
38 void insert(T key);
39 // 将二项堆中键值oldkey更新为newkey
40 void update(T oldkey, T newkey);
41 // 删除键值为key的节点
42 void remove(T key);
43 // 移除二项堆中的最小节点
44 void extractMinimum();
45
46 // 将other的二项堆合并到当前二项堆中
47 void combine(BinomialHeap<T>* other);
48
49 // 获取二项堆中的最小节点的键值
50 T minimum();
51 // 二项堆中是否包含键值key
52 bool contains(T key);
53 // 打印二项堆
54 void print();
55 private:
56
57 // 合并两个二项堆:将child合并到root中
58 void link(BinomialNode<T>* child, BinomialNode<T>* root);
59 // 将h1, h2中的根表合并成一个按度数递增的链表,返回合并后的根节点
60 BinomialNode<T>* merge(BinomialNode<T>* h1, BinomialNode<T>* h2);
61 // 合并二项堆:将h1, h2合并成一个堆,并返回合并后的堆
62 BinomialNode<T>* combine(BinomialNode<T>* h1, BinomialNode<T>* h2);
63 // 反转二项堆root,并返回反转后的根节点
64 BinomialNode<T>* reverse(BinomialNode<T>* root);
65 // 移除二项堆root中的最小节点,并返回删除节点后的二项树
66 BinomialNode<T>* extractMinimum(BinomialNode<T>* root);
67 // 删除节点:删除键值为key的节点,并返回删除节点后的二项树
68 BinomialNode<T>* remove(BinomialNode<T> *root, T key);
69 // 在二项树root中查找键值为key的节点
70 BinomialNode<T>* search(BinomialNode<T>* root, T key);
71
72 // 增加关键字的值:将二项堆中的节点node的键值增加为key。
73 void increaseKey(BinomialNode<T>* node, T key);
74 // 减少关键字的值:将二项堆中的节点node的键值减小为key
75 void decreaseKey(BinomialNode<T>* node, T key);
76 // 更新关键字的值:更新二项堆的节点node的键值为key
77 void updateKey(BinomialNode<T>* node, T key);
78
79 // 获取二项堆中的最小根节点
80 void minimum(BinomialNode<T>* root, BinomialNode<T> *&prev_y, BinomialNode<T> *&y);
81 // 打印二项堆
82 void print(BinomialNode<T>* node, BinomialNode<T>* prev, int direction);
83 };
84
85 /*
86 * 构造函数
87 */
88 template <class T>
89 BinomialHeap<T>::BinomialHeap():mRoot(NULL)
90 {
91 }
92
93 /*
94 * 析构函数
95 */
96 template <class T>
97 BinomialHeap<T>::~BinomialHeap()
98 {
99 }
100
101 /*
102 * 获取二项堆中的最小根节点
103 *
104 * 参数说明:
105 * root -- 二项堆
106 * prev_y -- [输出参数]最小根节点y的前一个根节点
107 * y -- [输出参数]最小根节点
108 */
109 template <class T>
110 void BinomialHeap<T>::minimum(BinomialNode<T>* root,
111 BinomialNode<T> *&prev_y, BinomialNode<T> *&y)
112 {
113 BinomialNode<T> *x, *prev_x; // x是用来遍历的当前节点
114
115 if (root==NULL)
116 return ;
117
118 prev_x = root;
119 x = root->next;
120 prev_y = NULL;
121 y = root;
122 // 找到最小节点
123 while (x != NULL) {
124 if (x->key < y->key) {
125 y = x;
126 prev_y = prev_x;
127 }
128 prev_x = x;
129 x = x->next;
130 }
131 }
132
133 /*
134 * 获取二项堆中的最小节点的键值
135 */
136 template <class T>
137 T BinomialHeap<T>::minimum()
138 {
139 BinomialNode<T> *prev_y, *y;
140
141 minimum(mRoot, prev_y, y);
142
143 return y->key;
144 }
145
146
147 /*
148 * 合并两个二项堆:将child合并到root中
149 */
150 template <class T>
151 void BinomialHeap<T>::link(BinomialNode<T>* child, BinomialNode<T>* root)
152 {
153 child->parent = root;
154 child->next = root->child;
155 root->child = child;
156 root->degree++;
157 }
158
159 /*
160 * 将h1, h2中的根表合并成一个按度数递增的链表,返回合并后的根节点
161 */
162 template <class T>
163 BinomialNode<T>* BinomialHeap<T>::merge(BinomialNode<T>* h1, BinomialNode<T>* h2)
164 {
165 BinomialNode<T>* root = NULL; //heap为指向新堆根结点
166 BinomialNode<T>** pos = &root;
167
168 while (h1 && h2)
169 {
170 if (h1->degree < h2->degree)
171 {
172 *pos = h1;
173 h1 = h1->next;
174 }
175 else
176 {
177 *pos = h2;
178 h2 = h2->next;
179 }
180 pos = &(*pos)->next;
181 }
182 if (h1)
183 *pos = h1;
184 else
185 *pos = h2;
186
187 return root;
188 }
189
190 /*
191 * 合并二项堆:将h1, h2合并成一个堆,并返回合并后的堆
192 */
193 template <class T>
194 BinomialNode<T>* BinomialHeap<T>::combine(BinomialNode<T>* h1, BinomialNode<T>* h2)
195 {
196 BinomialNode<T> *root;
197 BinomialNode<T> *prev_x, *x, *next_x;
198
199 // 将h1, h2中的根表合并成一个按度数递增的链表root
200 root = merge(h1, h2);
201 if (root == NULL)
202 return NULL;
203
204 prev_x = NULL;
205 x = root;
206 next_x = x->next;
207
208 while (next_x != NULL)
209 {
210 if ( (x->degree != next_x->degree)
211 || ((next_x->next != NULL) && (next_x->degree == next_x->next->degree)))
212 {
213 // Case 1: x->degree != next_x->degree
214 // Case 2: x->degree == next_x->degree == next_x->next->degree
215 prev_x = x;
216 x = next_x;
217 }
218 else if (x->key <= next_x->key)
219 {
220 // Case 3: x->degree == next_x->degree != next_x->next->degree
221 // && x->key <= next_x->key
222 x->next = next_x->next;
223 link(next_x, x);
224 }
225 else
226 {
227 // Case 4: x->degree == next_x->degree != next_x->next->degree
228 // && x->key > next_x->key
229 if (prev_x == NULL)
230 {
231 root = next_x;
232 }
233 else
234 {
235 prev_x->next = next_x;
236 }
237 link(x, next_x);
238 x = next_x;
239 }
240 next_x = x->next;
241 }
242
243 return root;
244 }
245
246 /*
247 * 将二项堆other合并到当前堆中
248 */
249 template <class T>
250 void BinomialHeap<T>::combine(BinomialHeap<T> *other)
251 {
252 if (other!=NULL && other->mRoot!=NULL)
253 mRoot = combine(mRoot, other->mRoot);
254 }
255
256 /*
257 * 新建key对应的节点,并将其插入到二项堆中。
258 */
259 template <class T>
260 void BinomialHeap<T>::insert(T key)
261 {
262 BinomialNode<T>* node;
263
264 // 禁止插入相同的键值
265 if (contains(key))
266 {
267 cout << "Insert Error: the key (" << key << ") is existed already!" << endl;
268 return ;
269 }
270
271 node = new BinomialNode<T>(key);
272 if (node==NULL)
273 return ;
274
275 mRoot = combine(mRoot, node);
276 }
277
278 /*
279 * 反转二项堆root,并返回反转后的根节点
280 */
281 template <class T>
282 BinomialNode<T>* BinomialHeap<T>::reverse(BinomialNode<T>* root)
283 {
284 BinomialNode<T>* next;
285 BinomialNode<T>* tail = NULL;
286
287 if (!root)
288 return root;
289
290 root->parent = NULL;
291 while (root->next)
292 {
293 next = root->next;
294 root->next = tail;
295 tail = root;
296 root = next;
297 root->parent = NULL;
298 }
299 root->next = tail;
300
301 return root;
302 }
303
304 /*
305 * 移除二项堆root中的最小节点,并返回删除节点后的二项树
306 */
307 template <class T>
308 BinomialNode<T>* BinomialHeap<T>::extractMinimum(BinomialNode<T>* root)
309 {
310 BinomialNode<T> *y, *prev_y; // y是最小节点
311
312 if (root==NULL)
313 return root;
314
315 // 找到"最小节点根y"和"它的前一个根节点prev_y"
316 minimum(root, prev_y, y);
317
318 if (prev_y == NULL) // root的根节点就是最小根节点
319 root = root->next;
320 else // root的根节点不是最小根节点
321 prev_y->next = y->next;
322
323 // 反转最小节点的左孩子,得到最小堆child;
324 // 这样,就使得最小节点所在二项树的孩子们都脱离出来成为一棵独立的二项树(不包括最小节点)
325 BinomialNode<T>* child = reverse(y->child);
326 // 将"删除最小节点的二项堆child"和"root"进行合并。
327 root = combine(root, child);
328
329 // 删除最小节点
330 delete y;
331
332 return root;
333 }
334
335 template <class T>
336 void BinomialHeap<T>::extractMinimum()
337 {
338 mRoot = extractMinimum(mRoot);
339 }
340
341 /*
342 * 减少关键字的值:将二项堆中的节点node的键值减小为key。
343 */
344 template <class T>
345 void BinomialHeap<T>::decreaseKey(BinomialNode<T>* node, T key)
346 {
347 if(key>=node->key || contains(key))
348 {
349 cout << "decrease failed: the new key(" << key <<") is existed already, "
350 << "or is no smaller than current key(" << node->key <<")" << endl;
351 return ;
352 }
353 node->key = key;
354
355 BinomialNode<T> *child, *parent;
356 child = node;
357 parent = node->parent;
358 while(parent != NULL && child->key < parent->key)
359 {
360 swap(parent->key, child->key);
361 child = parent;
362 parent = child->parent;
363 }
364 }
365
366 /*
367 * 增加关键字的值:将二项堆中的节点node的键值增加为key。
368 */
369 template <class T>
370 void BinomialHeap<T>::increaseKey(BinomialNode<T>* node, T key)
371 {
372 if(key<=node->key || contains(key))
373 {
374 cout << "decrease failed: the new key(" << key <<") is existed already, "
375 << "or is no greater than current key(" << node->key <<")" << endl;
376 return ;
377 }
378
379 node->key = key;
380
381 BinomialNode<T> *cur, *child, *least;
382 cur = node;
383 child = cur->child;
384 while (child != NULL)
385 {
386 if(cur->key > child->key)
387 {
388 // 如果"当前节点" < "它的左孩子",
389 // 则在"它的孩子中(左孩子 和 左孩子的兄弟)"中,找出最小的节点;
390 // 然后将"最小节点的值" 和 "当前节点的值"进行互换
391 least = child;
392 while(child->next != NULL)
393 {
394 if (least->key > child->next->key)
395 {
396 least = child->next;
397 }
398 child = child->next;
399 }
400 // 交换最小节点和当前节点的值
401 swap(least->key, cur->key);
402
403 // 交换数据之后,再对"原最小节点"进行调整,使它满足最小堆的性质:父节点 <= 子节点
404 cur = least;
405 child = cur->child;
406 }
407 else
408 {
409 child = child->next;
410 }
411 }
412 }
413
414 /*
415 * 更新二项堆的节点node的键值为key
416 */
417 template <class T>
418 void BinomialHeap<T>::updateKey(BinomialNode<T>* node, T key)
419 {
420 if (node == NULL)
421 return ;
422
423 if(key < node->key)
424 decreaseKey(node, key);
425 else if(key > node->key)
426 increaseKey(node, key);
427 else
428 cout <<"No need to update!!!" <<endl;
429 }
430
431 /*
432 * 将二项堆中键值oldkey更新为newkey
433 */
434 template <class T>
435 void BinomialHeap<T>::update(T oldkey, T newkey)
436 {
437 BinomialNode<T> *node;
438
439 node = search(mRoot, oldkey);
440 if (node != NULL)
441 updateKey(node, newkey);
442 }
443
444 /*
445 * 查找:在二项堆中查找键值为key的节点
446 */
447 template <class T>
448 BinomialNode<T>* BinomialHeap<T>::search(BinomialNode<T>* root, T key)
449 {
450 BinomialNode<T> *child;
451 BinomialNode<T> *parent = root;
452
453 parent = root;
454 while (parent != NULL)
455 {
456 if (parent->key == key)
457 return parent;
458 else
459 {
460 if((child = search(parent->child, key)) != NULL)
461 return child;
462 parent = parent->next;
463 }
464 }
465
466 return NULL;
467 }
468
469 /*
470 * 二项堆中是否包含键值key
471 */
472 template <class T>
473 bool BinomialHeap<T>::contains(T key)
474 {
475 return search(mRoot, key)!=NULL ? true : false;
476 }
477
478 /*
479 * 删除节点:删除键值为key的节点
480 */
481 template <class T>
482 BinomialNode<T>* BinomialHeap<T>::remove(BinomialNode<T>* root, T key)
483 {
484 BinomialNode<T> *node;
485 BinomialNode<T> *parent, *prev, *pos;
486
487 if (root==NULL)
488 return root;
489
490 // 查找键值为key的节点
491 if ((node = search(root, key)) == NULL)
492 return root;
493
494 // 将被删除的节点的数据数据上移到它所在的二项树的根节点
495 parent = node->parent;
496 while (parent != NULL)
497 {
498 // 交换数据
499 swap(node->key, parent->key);
500 // 下一个父节点
501 node = parent;
502 parent = node->parent;
503 }
504
505 // 找到node的前一个根节点(prev)
506 prev = NULL;
507 pos = root;
508 while (pos != node)
509 {
510 prev = pos;
511 pos = pos->next;
512 }
513 // 移除node节点
514 if (prev)
515 prev->next = node->next;
516 else
517 root = node->next;
518
519 root = combine(root, reverse(node->child));
520
521 delete node;
522
523 return root;
524 }
525
526 template <class T>
527 void BinomialHeap<T>::remove(T key)
528 {
529 mRoot = remove(mRoot, key);
530 }
531
532
533
534 /*
535 * 打印"二项堆"
536 *
537 * 参数说明:
538 * node -- 当前节点
539 * prev -- 当前节点的前一个节点(父节点or兄弟节点)
540 * direction -- 1,表示当前节点是一个左孩子;
541 * 2,表示当前节点是一个兄弟节点。
542 */
543 template <class T>
544 void BinomialHeap<T>::print(BinomialNode<T>* node, BinomialNode<T>* prev, int direction)
545 {
546 while(node != NULL)
547 {
548 if(direction==1) // node是根节点
549 cout << "\t" << setw(2) << node->key << "(" << node->degree << ") is "<< setw(2) << prev->key << "'s child" << endl;
550 else // node是分支节点
551 cout << "\t" << setw(2) << node->key << "(" << node->degree << ") is "<< setw(2) << prev->key << "'s next" << endl;
552
553 if (node->child != NULL)
554 print(node->child, node, 1);
555
556 // 兄弟节点
557 prev = node;
558 node = node->next;
559 direction = 2;
560 }
561 }
562
563 template <class T>
564 void BinomialHeap<T>::print()
565 {
566 BinomialNode<T> *p;
567 if (mRoot == NULL)
568 return ;
569
570 cout << "== 二项堆( ";
571 p = mRoot;
572 while (p != NULL)
573 {
574 cout << "B" << p->degree << " ";
575 p = p->next;
576 }
577 cout << ")的详细信息:" << endl;
578
579 int i=0;
580 p = mRoot;
581 while (p != NULL)
582 {
583 i++;
584 cout << i << ". 二项树B" << p->degree << ":" << endl;
585 cout << "\t" << setw(2) << p->key << "(" << p->degree << ") is root" << endl;
586
587 print(p->child, p, 1);
588 p = p->next;
589 }
590 cout << endl;
591 }
592 #endif
View Code

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

二项堆(二)之 C++的实现二项堆(二)之 C++的实现
  1 /**
2 * C 语言: 二项堆
3 *
4 * @author skywang
5 * @date 2014/04/02
6 */
7
8 #include <iostream>
9 #include "BinomialHeap.h"
10 using namespace std;
11
12 #define DEBUG 0
13
14 // 共7个 = 1+2+4
15 int a[] = {12, 7, 25, 15, 28,
16 33, 41};
17 // 共13个 = 1+4+8
18 int b[] = {18, 35, 20, 42, 9,
19 31, 23, 6, 48, 11,
20 24, 52, 13 };
21
22 // 验证"二项堆的插入操作"
23 void testInsert()
24 {
25 int i;
26 int alen=sizeof(a)/sizeof(a[0]);
27 BinomialHeap<int>* ha=new BinomialHeap<int>();
28
29 cout << "== 二项堆(ha)中依次添加: ";
30 for(i=0; i<alen; i++)
31 {
32 cout << a[i] <<" ";
33 ha->insert(a[i]);
34 }
35 cout << endl;
36 cout << "== 二项堆(ha)的详细信息: " << endl;
37 ha->print();
38 }
39
40 // 验证"二项堆的合并操作"
41 void testUnion()
42 {
43 int i;
44 int alen=sizeof(a)/sizeof(a[0]);
45 int blen=sizeof(b)/sizeof(b[0]);
46 BinomialHeap<int>* ha=new BinomialHeap<int>();
47 BinomialHeap<int>* hb=new BinomialHeap<int>();
48
49 cout << "== 二项堆(ha)中依次添加: ";
50 for(i=0; i<alen; i++)
51 {
52 cout << a[i] <<" ";
53 ha->insert(a[i]);
54 }
55 cout << endl;
56 cout << "== 二项堆(ha)的详细信息: " << endl;
57 ha->print();
58
59
60 cout << "== 二项堆(hb)中依次添加: ";
61 for(i=0; i<blen; i++)
62 {
63 cout << b[i] <<" ";
64 hb->insert(b[i]);
65 }
66 cout << endl;
67 cout << "== 二项堆(hb)的详细信息: " << endl;
68 hb->print();
69
70
71 // 将"二项堆hb"合并到"二项堆ha"中。
72 ha->combine(hb);
73 cout << "== 合并ha和hb后的详细信息: " << endl;
74 ha->print();
75 }
76
77 // 验证"二项堆的删除操作"
78 void testDelete()
79 {
80 int i;
81 int blen=sizeof(b)/sizeof(b[0]);
82 BinomialHeap<int>* hb=new BinomialHeap<int>();
83
84 cout << "== 二项堆(hb)中依次添加: ";
85 for(i=0; i<blen; i++)
86 {
87 cout << b[i] <<" ";
88 hb->insert(b[i]);
89 }
90 cout << endl;
91 cout << "== 二项堆(hb)的详细信息: " << endl;
92 hb->print();
93
94
95 // 将"二项堆hb"合并到"二项堆ha"中。
96 hb->remove(20);
97 cout << "== 删除节点20后的详细信息: " << endl;
98 hb->print();
99 }
100
101 // 验证"二项堆的更新(减少)操作"
102 void testDecrease()
103 {
104 int i;
105 int blen=sizeof(b)/sizeof(b[0]);
106 BinomialHeap<int>* hb=new BinomialHeap<int>();
107
108 cout << "== 二项堆(hb)中依次添加: ";
109 for(i=0; i<blen; i++)
110 {
111 cout << b[i] <<" ";
112 hb->insert(b[i]);
113 }
114 cout << endl;
115 cout << "== 二项堆(hb)的详细信息: " << endl;
116 hb->print();
117
118
119 // 将节点20更新为2
120 hb->update(20, 2);
121 cout << "== 更新节点20->2后的详细信息: " << endl;
122 hb->print();
123 }
124
125 // 验证"二项堆的更新(增加)操作"
126 void testIncrease()
127 {
128 int i;
129 int blen=sizeof(b)/sizeof(b[0]);
130 BinomialHeap<int>* hb=new BinomialHeap<int>();
131
132 cout << "== 二项堆(hb)中依次添加: ";
133 for(i=0; i<blen; i++)
134 {
135 cout << b[i] <<" ";
136 hb->insert(b[i]);
137 }
138 cout << endl;
139 cout << "== 二项堆(hb)的详细信息: " << endl;
140 hb->print();
141
142
143 // 将节点6更新为60
144 hb->update(6, 60);
145 cout << "== 更新节点6->60后的详细信息: " << endl;
146 hb->print();
147 }
148
149 int main()
150 {
151 // 1. 验证"二项堆的插入操作"
152 testInsert();
153 // 2. 验证"二项堆的合并操作"
154 //testUnion();
155 // 3. 验证"二项堆的删除操作"
156 //testDelete();
157 // 4. 验证"二项堆的更新(减少)操作"
158 //testDecrease();
159 // 5. 验证"二项堆的更新(增加)操作"
160 //testIncrease();
161
162 return 0;
163 }
View Code

 

二项堆的C++测试程序

二项堆的测试程序包括了五部分,分别是"插入"、"删除"、"增加"、"减少"、"合并"这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