斐波那契堆

介绍

    斐波那契堆支持二叉堆上的基本操作并且插入和增加key值的速度相对较快。它是由多颗树组合而成的,而每颗树的根结点组合成的链表叫根链表,是一个双向的循环链表。它有一个指向树中最小结点的指针以及保存了一个树中的结点数量。每个结点可以有n个孩子组合成的双向循环链表,但是只能指向其中的某一个孩子,每个结点的孩子数量称为degree;每个结点还有一个mark属性,用来表示自从此结点有孩子以及是否丢失过孩子。

和二叉堆的对比

    comparewithbinaryheap

comparewithbinaryheap

具体结构

    fibonacci

fibonacci

    以图形的形式表现了上述的描述,更加直观些。

实现

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;
}

    在测试代码当中有些内存没有释放,在项目中要注意。

    结果如下:fibonacci-result

fibonacci-result

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Z小偉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值