介绍
斐波那契堆支持二叉堆上的基本操作并且插入和增加key值的速度相对较快。它是由多颗树组合而成的,而每颗树的根结点组合成的链表叫根链表,是一个双向的循环链表。它有一个指向树中最小结点的指针以及保存了一个树中的结点数量。每个结点可以有n个孩子组合成的双向循环链表,但是只能指向其中的某一个孩子,每个结点的孩子数量称为degree;每个结点还有一个mark属性,用来表示自从此结点有孩子以及是否丢失过孩子。
和二叉堆的对比
具体结构
以图形的形式表现了上述的描述,更加直观些。
实现
typedef struct _Node
{
bool mark;
int key;
int degree;
struct _Node *parent;
struct _Node *child;
struct _Node *left;
struct _Node *right;
}Node;
typedef struct _Fabonacci
{
int number;
Node *min;
}Fabonacci;
Fabonacci *g_fabonacci = nullptr;
根据上面的描述以及图形展示出来的,定义finobacci heap的结构。基本都有提到,应该很容易理解了。
首先,我们先在斐波那契堆中插入结点:
void insertToRootList(Node *node)
{
if (nullptr == g_fabonacci->min)
{
/// node serve as min node
node->left = node->right = node;
g_fabonacci->min = node;
}else
{
/// insert node into root list
node->right = g_fabonacci->min->right;
node->left = g_fabonacci->min;
g_fabonacci->min->right->left = node;
g_fabonacci->min->right = node;
if (g_fabonacci->min->key > node->key)
{
g_fabonacci->min = node;
}
}
}
void insert(Node *node)
{
insertToRootList(node);
++ g_fabonacci->number;
}
从代码可以看出,插入结点是比较简单的。插入结点的时候,先要判断是否为空,可以根据min结点来判断。如果为空,则直接把此结点当成min结点。如果不为空,则直接在min结点的左右2边随便哪边插入即可,插入完成以后和当前的min值比较,小则替换掉当前min值,否则不做操作。最后增加斐波那契堆的结点数量。
接着就是访问最小值,这个很简单了:
Node* min()
{
return g_fabonacci->min;
}
直接返回min结点就可以了。
那如果是取出最小值呢,这就比较复杂了:
void link(Node *y, Node *x)
{
/// delete y from root list
y->left->right = y->right;
y->right->left = y->left;
/// make y a child of x
Node *p = x->child;
if (nullptr == p)
{
x->child = y;
y->right = y->left = y;
y->parent = x;
}else
{
y->left = p;
y->right = p->right;
p->right->left = y;
p->right = y;
y->parent = x;
}
++ x->degree;
y->mark = false;
}
void consolidate()
{
int maxDegree = static_cast<int>(log(g_fabonacci->number * 1.0) / log(2.0)) + 1;
Node **array = new Node* [maxDegree];
for (int i = 0; i < maxDegree; ++ i)
{
array[i] = nullptr;
}
Node *p = g_fabonacci->min;
while(nullptr != p)
{
Node *x = p;
int d = x->degree;
while(nullptr != array[d] && x != array[d])
{
Node *y = array[d];
if (x->key > y->key)
{
Node *p = x;
x = y;
y = p;
}
link(y, x);
array[d] = nullptr;
++ d;
}
if (x == array[d])
{
break;
}
array[d] = x;
p = x->right;
}
g_fabonacci->min = nullptr;
for (int i = 0; i < maxDegree; ++ i)
{
if (nullptr != array[i])
{
insertToRootList(array[i]);
}
}
}
Node* extractMin()
{
Node *z = g_fabonacci->min;
if (nullptr != z)
{
Node *minChild = z->child;
if (nullptr != minChild)
{
while (nullptr != minChild && minChild->parent == z)
{
minChild->parent = nullptr;
minChild = minChild->right;
}
z->child = nullptr;
/// concatenate two list, one is root list and another is the list of minimum child
unionToRootList(minChild);
}
/// delete the minimum node
g_fabonacci->min->right->left = g_fabonacci->min->left;
g_fabonacci->min->left->right = g_fabonacci->min->right;
/// reset the minimum node
if (z->right == z)
{
g_fabonacci->min = nullptr;
}else
{
g_fabonacci->min = z->right;
consolidate();
}
/// decrease the node number
-- g_fabonacci->number;
}
return z;
}
这里的做法是:
1. 把最小结点的孩子结点全部增加到根链表中
2. 把最小结点从根链表中删除
3. 取任意根结点为最小结点并校正斐波那契堆
3.1 定义一个最大degree的数组
3.2 根据根结点的degree存入数组当中,如果数组中已经有数据,则把当前结点和已有结点进行合并,把其中键值大的结点合并成键值小的结点的孩子,同时在根结点中删除它。
3.3 最后,根据数据中存储的结点重新组合成一个新的根链表,并且赋值最小min值。
删除结点比较简单,就是降低一个结点的键值为min,然后取出最小键值:
void cut(Node *x, Node *y)
{
if (x == x->left)
{
y->child = nullptr;
}else
{
x->left->right = x->right;
x->right->left = x->left;
}
-- y->degree;
insertToRootList(x);
x->parent = nullptr;
x->mark = false;
}
void cascadingCut(Node *y)
{
Node *z = y->parent;
if (nullptr != z)
{
if (false == y->mark)
{
y->mark = true;
}else
{
cut(y, z);
cascadingCut(z);
}
}
}
void decreaseKey(Node *x, int key)
{
assert(nullptr != x);
if (x->key > key)
{
return;
}
x->key = key;
Node *y = x->parent;
if (nullptr != y && x->key < y->key)
{
cut(x, y);
cascadingCut(y);
}
if (x->key < g_fabonacci->min->key)
{
g_fabonacci->min = x;
}
}
int getSentry()
{
return numeric_limits<int>::min();
}
void deleteNode(Node *x)
{
decreaseKey(x, getSentry());
extractMin();
}
所以,这里主要讲解下降低键值的操作:
1. 如果当前键值小于需要降低的值,则不做操作
2. 如果当前结点是根结点或者当前结点的值大于父结点的值,则不做操作
3. 否则,把当前结点从它的父结点的孩子当中删除,并且插入到根结点中去
4. 因为删除了结点的孩子,所以mark要被修改,并循环直到它的第一个孩子为止
因为斐波那契堆的根结点是双向链表,所以合并2个斐波那契堆是非常简单的操作:
void unionToRootList(Node *node)
{
assert(nullptr != node);
Node *p = g_fabonacci->min->right;
g_fabonacci->min->right = node;
node->left->right = p;
p->left = node->left;
node->left = g_fabonacci->min;
if (g_fabonacci->min->key > node->key)
{
g_fabonacci->min = node;
}
}
void unionFabonacci(Fabonacci *fabonacci)
{
unionToRootList(fabonacci->min);
g_fabonacci->number += fabonacci->number;
}
直接断开斐波那契堆的左右结点,并和另一个斐波那契堆进行双向链表的合并就ok了。
测试下代码:
#include <iostream>
#include <algorithm>
#include <assert.h>
using namespace std;
void init()
{
g_fabonacci = new Fabonacci;
g_fabonacci->number = 0;
g_fabonacci->min = nullptr;
}
void finit()
{
delete g_fabonacci;
g_fabonacci = nullptr;
}
Node* createNode(int key)
{
Node *node = new Node;
node->child = nullptr;
node->degree = 0;
node->left = nullptr;
node->right = nullptr;
node->parent = nullptr;
node->mark = false;
node->key = key;
return node;
}
int _tmain(int argc, _TCHAR* argv[])
{
init();
insert(createNode(4));
insert(createNode(5));
insert(createNode(6));
insert(createNode(1));
insert(createNode(2));
insert(createNode(3));
cout << "min key = " << g_fabonacci->min->key << endl;
Node *minNode = extractMin();
cout << "extrack min key = " << minNode->key << endl;
cout << "min key = " << g_fabonacci->min->key << endl;
minNode = extractMin();
cout << "extrack min key = " << minNode->key << endl;
cout << "min key = " << g_fabonacci->min->key << endl;
minNode = extractMin();
cout << "extrack min key = " << minNode->key << endl;
cout << "min key = " << g_fabonacci->min->key << endl;
Node *p = createNode(1);
insert(p);
cout <<"---------test delete start----------" << endl;
cout << "insert 1" << endl;
cout << "min key = " << g_fabonacci->min->key << endl;
deleteNode(p);
delete p;
cout << "delete 1" << endl;
cout << "min key = " << g_fabonacci->min->key << endl;
cout <<"---------test delete end----------" << endl;
minNode = extractMin();
cout << "extrack min key = " << minNode->key << endl;
cout << "min key = " << g_fabonacci->min->key << endl;
minNode = extractMin();
cout << "extrack min key = " << minNode->key << endl;
cout << "min key = " << g_fabonacci->min->key << endl;
minNode = extractMin();
cout << "extrack min key = " << minNode->key << endl;
finit();
return 0;
}
在测试代码当中有些内存没有释放,在项目中要注意。
结果如下: