1. 程式人生 > >《演算法導論》第十九章——斐波那契堆

《演算法導論》第十九章——斐波那契堆

  雖然寫這個部落格主要目的是為了給我自己做一個思路記憶錄,但是如果你恰好點了進來,那麼先對你說一聲歡迎。我並不是什麼大觸,只是一個菜菜的學生,如果您發現了什麼錯誤或者您對於某些地方有更好的意見,非常歡迎您的斧正!

可合併堆

可合併堆是支援以下5種操作的一種資料結構,其中每個元素都有一個關鍵字:

MakeHeap():建立和返回一個新的不含任何元素的堆
Insert(H,x):將一個已填入關鍵字的元素x插入堆H中
Minimum(H):返回一個指向堆H中具有最小關鍵字的元素的指標
ExtractMin(H):從堆H中刪除具有最小關鍵字的元素,並返回一個指向該元素的指標
Union(H1,H2):建立並返回一個包含堆H1和堆H2中所有元素的堆。堆H1與堆H2則銷燬。

斐波那契堆還支援:

DecreaseKey(H,x,k):將堆H中的元素x的關鍵字賦予新值k。k不大於當前關鍵字。
Delete(H,x):從堆H中刪除元素x

19.1斐波那契堆結構

一個斐波那契堆是一系列具有最小堆序的有根樹的集合。
在這裡插入圖片描述

每個結點包含

x.p指向父結點的指標
x.child指向它某個孩子的指標
x.degree儲存孩子數目
x.mark指示上一次成為另一個結點的孩子後,是否失去過孩子
每個孩子y都有指標y.left和y.right,分別指向左右兄弟

x的所有孩子被連結成一個環形的雙向連結串列,成為x的孩子連結串列。(各兄弟出現順序任意)
孩子連結串列的優點:
1、可以在O(1)時間內從一個環形連結串列的任何位置插入一個結點或刪除一個結點
2、給定兩個連結串列,可以用O(1)時間把它們連線

所有樹的根都用left與right連線在一起,成為根連結串列
在這裡插入圖片描述
勢函式

t(H):H根連結串列中樹的數目
m(H):H中已經標記的結點
則勢函式:θ(H)=t(H)+2m(H)

19.2可合併操作堆

①建立一個新的斐波那契堆

H.min:斐波那契堆中最小的結點
H.n:斐波那契堆的結點數量

H.min初始化為NULL
H.n初始化為0

②插入一個結點
在這裡插入圖片描述

在這裡插入圖片描述

③尋找最小結點(沒有寫函式)
其實就是H.min

④兩個斐波那契堆的合併
簡單地合併H1與H2,並且把H.min更新為兩個中小的那個。

在這裡插入圖片描述

第3行函式Concatenate:

在這裡插入圖片描述

⑤抽取最小結點

這邊寫的有點複雜,我就精簡一下:
步驟一:去掉根結點,把它的所有孩子都放入根連結串列
步驟二:努力地把根連結串列中散落的數串在一起,使得斐波那契堆裡所有的樹都不一樣高!
步驟二的實現可能比較麻煩,具體的圖解我們可以參考一下skywang12345的部落格,我覺得他的圖畫的就十分不錯,也是在抽取最小結點那個地方。
附上鍊接:
https://www.cnblogs.com/skywang12345/p/3659069.html

在這裡插入圖片描述

在這裡插入圖片描述
Consolidate的作用原理:

這邊我對Consolidate想了很久,終於想通了它的原理:A的一個指向根結點的陣列。A[0]存的是degree=0的根,A[1]存的是degree=1的根…我們來看一下書中的例圖:這邊我們看到(c)中A[1]儲存了一棵高度為1的樹,接下來在(d)中A[2]儲存了一棵高度為2的數,(e)中A[0]儲存了一棵高度為0的數,但是到了(f)圖的時候,根⑦的高度也是0,我們看到在虛擬碼第7行中while(A[d]≠NIL),這裡就要進入迴圈了,於是我們把⑦與23合併在一起,變成了一棵高度為1的樹。然後把高度為0的地方A[0]重新置為NULL。(虛擬碼第12行)這個時候又起衝突了!這棵樹與我們原來儲存的高度為1,根為17的樹撞在了一起,於是我們又要合併這兩棵樹,變成了一棵高度為3的數。然後繼續判斷。
在這裡插入圖片描述

19.3關鍵字減值和刪除一個結點

⑥關鍵字減值
在這裡插入圖片描述

在這裡插入圖片描述

⑦刪除一個結點
在這裡插入圖片描述

好了接下來就是程式碼部分了。建議貼上到自己的編輯中執行(我不知道為什麼我的程式碼就是沒有彩色的,感覺影響觀看,可能 我比較菜吧!):

斐波那契堆.h

#pragma once
#define D 5

/*斐波那契堆結點*/
typedef struct FibNode
{
	int key;			/*關鍵字值*/
	int degree;			/*最大度數*/
	FibNode* left;		/*左兄弟*/
	FibNode* right;		/*右兄弟*/
	FibNode* parent;	/*父結點*/
	FibNode* child;		/*孩子結點*/
	bool mark;			/*是否被刪除第一個孩子*/
}FibNode;
/*斐波那契堆*/
typedef struct FibHeap
{
	FibNode* min;
	int n;/*結點個數*/
}FibHeap;

/*產生一個新的結點*/
FibNode* CreateFibNode(int data);
/*①新建一個斐波那契堆*/
FibHeap* MakeFibHeap();

/*插入函式,y是新節點*/
void InsertHeap(FibNode* x, FibNode* y);
/*②插入一個新的結點*/
void FibInsert(FibHeap* &H, FibNode* x);

/*合併函式*/
void Concatenate(FibNode* x, FibNode* y);
/*④兩個斐波那契堆的合併*/
FibHeap* FibHeapUnion(FibHeap* &H1, FibHeap* &H2);

/*抽取最小節點的輔助函式*/
void NodeRemove(FibNode* x);
/*抽取最小節點的輔助函式*/
void FibHeapLink(FibHeap* &H, FibNode* x, FibNode* y);/*y成為x的孩子*/
/*抽取最小節點的輔助函式*/
void Consolidate(FibHeap* &H);
/*⑤抽取最小結點*/
FibNode* FibHeapExtractMin(FibHeap* &H);

/*切斷*/
void NodeCut(FibHeap* &H, FibNode* x, FibNode* y);
/*級聯切斷*/
void CascadingCut(FibHeap* &H, FibNode* y);
/*⑥關鍵字減值*/
void FibHeapDecreaseKey(FibHeap* &H, FibNode* x, int k);

/*⑦刪除一個結點*/
void FibHeapDelete(FibHeap* &H, FibNode* x);

/*列印輔助*/
void Print_1(FibNode* x, FibNode* prev, int direction);
/*列印斐波那契堆*/
void FibPrint(FibHeap* &H);

/*測試程式*/
void TestFibHeap();

斐波那契堆.cpp

#include "斐波那契堆.h"
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
using namespace std;

/*產生一個新的結點*/
FibNode* CreateFibNode(int data)
{
	FibNode* fn = new FibNode();
	if (fn != NULL)
	{
		fn->parent = NULL;
		fn->child = NULL;
		fn->left = NULL;
		fn->right = NULL;
		fn->key = data;
		fn->mark = false;
	}
	else
	{
		cout << "開闢結點失敗!" << endl;
	}
	return fn;
}
/*①新建一個斐波那契堆*/
FibHeap* MakeFibHeap()
{
	FibHeap* H = new FibHeap();
	if (H == NULL)
		cout << "建立失敗!" << endl;
	H->min = NULL;
	H->n = 0;
	return H;
}
/*插入函式,y是新節點*/
void InsertHeap(FibNode* x, FibNode* y)
{
	y->left = x->left;
	y->right = x;
	y->right->left = y;
	y->left->right = y;
}
/*②插入一個新的結點*/
void FibInsert(FibHeap* &H, FibNode* x)
{
	if (!H->min)/*如果斐波那契堆是空的*/
	{
		x->left = x;
		x->right = x;
		H->min = x;
	}
	else
	{
		InsertHeap(H->min, x);
		if (x->key < H->min->key)/*有必要的話,更新H.min*/
			H->min = x;
	}
	H->n++;/*結點數量+1*/
}
/*合併函式*/
void Concatenate(FibNode* x, FibNode* y)
{
	FibNode* tmpx = x->left;
	FibNode* tmpy = y->left;
	x->left = y;
	tmpy->right = x;
	y->left = x;
	tmpx->right = y;
}
/*④兩個斐波那契堆的合併*/
FibHeap* FibHeapUnion(FibHeap* &H1, FibHeap* &H2)
{
	if (!H1)return H2;/*如果H1為空,就直接返回H2*/
	if (!H2)return H1;/*如果H2為空,就直接返回H1*/

	Concatenate(H1->min, H2->min);

	if (H2->min->key < H1->min->key)
		H1->min = H2->min;
	H1->n += H2->n;
	return H1;
}
/*抽取最小節點的輔助函式*/
void NodeRemove(FibNode* x)
{
	x->left->right = x->right;
	x->right->left = x->left;
}
/*抽取最小節點的輔助函式*/
void FibHeapLink(FibHeap* &H, FibNode* x, FibNode* y)/*y成為x的孩子*/
{
	NodeRemove(y);/*把y從根連結串列中移除*/
	if (!x->child)/*如果x沒有孩子*/
	{
		x->child = y;
		y->left = y;
		y->right = y;
	}
	else
	{
		InsertHeap(x->child, y);
	}
	y->parent = x;
	x->degree++;
	y->mark = false;
}
/*抽取最小節點的輔助函式*/
void Consolidate(FibHeap* &H)
{
	FibNode* A[D + 1] = { NULL };
	FibNode* sentinel = H->min->left;/*哨兵*/
	bool flag = true;
	
	for (FibNode* w = H->min, *next; flag; w = next)/*處理每個根結點*/
	{
		if (w == sentinel)/*只有一個結點*/
			flag = false;
		next = w->right;
		FibNode* x = w;
		int d = x->degree;

		while (A[d])
		{
			FibNode* y = A[d];
			if (x->key > y->key)/*查詢最小節點*/
			{
				FibNode* tmp = x;
				x = y;
				y = tmp;
			}
			FibHeapLink(H, x, y);
			A[d++] = NULL;
		}
		A[d] = x;
	}
	H->min = NULL;
	for (int i = 0; i <= D; i++)
	{
		if (A[i])
		{
			if (!H->min)/*根結點為空*/
			{
				A[i]->left = A[i]->right = A[i];
				H->min = A[i];
			}
			else
			{
				InsertHeap(H->min, A[i]);
				if (A[i]->key < H->min->key)
					H->min = A[i];
			}
		}
	}
}
/*⑤抽取最小結點*/
FibNode* FibHeapExtractMin(FibHeap* &H)
{
	FibNode* z = H->min;
	if (z)
	{
		bool flag = true;
		for (FibNode* x = z->child, *next; x&&flag; x = next)
		{
			next = x->right;
			if (next == z->right)/*只有根結點*/
				flag = false;
			InsertHeap(H->min, x);
			x->parent = NULL;
		}
		NodeRemove(z);
		if (z == z->right)
			H->min = NULL;
		else
		{
			H->min = z->right;
			Consolidate(H);
		}
		H->n--;
	}
	return z;
}
/*切斷*/
void NodeCut(FibHeap* &H, FibNode* x, FibNode* y)
{
	if (x == x->right)/*只有x一個孩子*/
		y->child = NULL;
	else
	{
		if (x == y->child)
			y->child = x->left;
		NodeRemove(x);
	}
	y->degree--;
	InsertHeap(H->min, x);/*把x插入根連結串列*/
	x->parent = NULL;
	x->mark = false;

}
/*級聯切斷*/
void CascadingCut(FibHeap* &H, FibNode* y)
{
	FibNode* z = y->parent;
	if (z)
	{
		if (!y->mark)
			y->mark = true;
		else
		{
			NodeCut(H, y, z);
			CascadingCut(H, y);
		}
	}
}
/*⑥關鍵字減值*/
void FibHeapDecreaseKey(FibHeap* &H, FibNode* x, int k)
{
	if (k > x->key)
	{
		cout << "數值錯誤";
		return;
	}
	x->key = k;
	FibNode* y = x->parent;
	if (y&&x->key < y->key)
	{
		NodeCut(H, x, y);
		CascadingCut(H, y);
	}
	if (x->key < H->min->key)
		H->min = x;
}
/*⑦刪除一個結點*/
void FibHeapDelete(FibHeap* &H, FibNode* x)
{
	FibHeapDecreaseKey(H, x, -100);
	FibHeapExtractMin(H);
}
/*列印輔助*/
void Print_1(FibNode* x, FibNode* prev, int direction)
{
	FibNode* start = x;
	if (x == NULL)return;
	do
	{
		if (direction == 1)
		{
			cout <<"  "<< x->key << "(" << x->degree << ")是" << prev->key << "的孩子" << endl;
		}
		else
		{
			cout <<"  "<< x->key << "(" << x->degree << ")是" << prev->key << "的後一個數" << endl;
		}
		if (x->child != NULL)
			Print_1(x->child, x, 1);
		prev = x;
		x = x->right;
		direction = 2;	
	} while (x != start);
}
/*列印斐波那契堆*/
void FibPrint(FibHeap* &H)
{
	int i = 0;
	FibNode* p;
	if (H->min == NULL)
		return;
	cout << "斐波那契堆:" << endl;
	p = H->min;
	do
	{
		i++;
		cout << i << "." << p->key << "(" << p->degree << ")是根" << endl;
		Print_1(p->child, p, 1);
		p = p->right;
	} while (p != H->min);
	cout << endl;
	cout << endl;
}

/*測試程式*/
void TestFibHeap()
{
	FibHeap* H= MakeFibHeap();
	FibNode* y = CreateFibNode(0);
	FibNode* z = CreateFibNode(0);
	for (int i = 0; i < 10; i++)
	{
		FibNode* x = CreateFibNode((i+1)*10);
		if (i == 5)
			y = x;
		if (i == 6)
			z = x;
		FibInsert(H, x);
	}
	cout << "①我們初始的斐波那契堆:" << endl;
	FibPrint(H);

	cout << "②抽取最小值後:" << endl;
	FibHeapExtractMin(H);
	FibPrint(H);

	cout << "③我們把60降到5" << endl;
	FibHeapDecreaseKey(H, y, 5);
	FibPrint(H);

	cout << "④刪除70後" << endl;
	FibHeapDelete(H, z);
	FibPrint(H);

	cout << "建立一個只有結點8的堆,合併兩個堆" << endl;
	FibHeap* H2 = MakeFibHeap();
	FibNode* w = CreateFibNode(8);
	FibInsert(H2, w);
	H2 = FibHeapUnion(H, H2);
	FibPrint(H2);
}

主函式

#include "斐波那契堆.h"
#include <stdio.h>

int main()
{
	TestFibHeap();
	getchar();
	getchar();
	return 0;
}

執行結果
在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述