二项堆就是二项树的集合,将二项树按照度的大小从左到右串联在一起。
二项树:一组固定的递归定义的树。
Bo 为只有一个节点的二项树;
Bn 是一颗n叉树,有n个孩子,从左到右分别为Bn-1、Bn-2....Bo。
二项堆:
性质:
1、二项堆中的每一棵二项树都满足最小堆的性质。
2、每一棵二项树的度都不相同。
typedef int Type;
// 节点
struct Node {
Node(Type key) :Key(key), Degree(0), Child(NULL), Next(NULL), Parent(NULL) {}
Type Key; // 键值
int Degree; // 度
Node* Child; // 孩子节点
Node* Next; // 兄弟节点
Node* Parent; // 父节点
};
// 二项堆
class BinomialHeap {
friend void Traverse(BinomialHeap &heap);
public:
Node* Search(Node* node, Type key); // 查找
bool Insert(Type key); // 插入
bool Delete(Type key); // 删除
BinomialHeap() :Root(NULL) {}
BinomialHeap(Type key) :Root(new Node(key)) {}
private:
Node* Root; // 堆根
};
合并堆:
1、先将俩个堆的二项树按照度的大小,小-->大 的顺序把根连接在一起。
2、将根连接之后,判断是否存在度数相同的二项树,若度数相同,则将根的键值大的二项树,作为另一二项树的孩子,重复操作。
连接俩堆树根:
// 连接堆树根
Node* Merge(Node* heap1, Node* heap2) {
Node* newHeap = NULL; // 新的堆根
Node* T = NULL;
while (heap1&&heap2) { // 直到一方为空
if (heap1->Degree < heap2->Degree) {
if (NULL == newHeap) {
newHeap = heap1;
T = newHeap;
}
else {
newHeap->Next = heap1;
T = T->Next;
}
heap1 = heap1->Next;
}
else {
if (NULL == newHeap) {
newHeap = heap2;
T = newHeap;
}
else {
newHeap->Next = heap2;
T = T->Next;
}
heap2 = heap2->Next;
}
}
if (heap1) T->Next = heap1;
if (heap2) T->Next = heap2;
return newHeap;
}
度相同的树合并:
// 将度相同的 C 作为 P 的孩子
void Link(Node* C, Node* P) {
C->Parent = P;
C->Next = P->Child;
P->Child = C;
++P->Degree; // P度+1
}
判定条件:
定义:当前节点 T;下一节点 N;前一节点Pre
1、若T的度不等于N的度。则不需要合并,T直接后移。
2、若T的度等于N的度且N的度等于N的下一节点的度(即连续三棵二项树的度相同)。也不需要调整,T直接后移。说明:T直接后移之后,下一次判断便可将后面那俩颗树合并,合并后度+1,使得原本连续三棵二项树的度相同的情况,变为了俩棵树且后一颗树比前一棵树的度高+1。
啥?问会不会T后移之后会不会仍出现连续三棵二项树的度相同? P:不可能!why?由二项堆性质2(每一棵二项树的度都不相同)可知,俩个堆的所有二项树根连接在一起后,初始情况最多只会出现连续俩棵树的度相同的情况。那连续三棵二项树的度相同的情况怎么来的呢?那当然是由前面俩个树合并之后使得度+1,刚好又等于它们的度时出现的。
3、T的度等于N的度且N的度不等于N的下一节点的度(只有连续俩棵二项树的度相同)。
3.1 T根的键值比N的键值大。则将T作为N的孩子。
3.2 T根的键值比N的键值小。则将N作为T的孩子。
//堆结合
Node* Combine(Node* heap1, Node* heap2) {
if (NULL == heap1) return heap2;
if (NULL == heap2) return heap1;
Node* newHeap = Merge(heap1, heap2);
Node* T = newHeap; // 记录起始位置
Node* Pre = NULL; // 前一堆根
Node* N = T->Next; // 下一堆根
while (N) {
if (T->Degree != N->Degree || (N->Next&&N->Degree == N->Next->Degree)) {
// 当前节点的度与下一节点的度不相等
// 当前节点、下一节点、下一节点的下节点的度都相等
Pre = T;
T = N;
N = N->Next;
}
else if (T->Key <= N->Key) { // 该条件决定出来的堆是大顶堆还是小顶堆
T->Next = N->Next;
Link(N, T); // N作为T的孩子
}
else {
if (NULL == Pre) newHeap = N;
else Pre->Next = N;
Link(T, N); // T作为N的孩子
T = N;
}
N = T->Next;
}
return newHeap;
}
插入:即俩堆合并
// 插入
bool BinomialHeap::Insert(Type key) {
Node* T = new Node(key);
if (T) {
Root = Combine(Root, T);
return true;
}
return false;
}
查找:(递归)
// 查找( 返回key 所在的节点 )
Node* BinomialHeap::Search(Node* node, Type key) {
Node* P = node;
Node* C = NULL;
while (P) {
if (P->Key == key) return P;
C = Search(node->Child, key);
if (C) return C; // 在子节点找到
P = P->Next;
}
return NULL;
}
删除:
1、找到删除键值所在的节点,将待删键值上提到所在二项树的根。
2、将该二项树根的孩子组成一个新的堆。即将孩子的度从原本的大到小连接变为小到大连接;并取消孩子的父节点置空。
3、将待删键值所在的树根从二项堆中删除。
4、将二项堆和孩子组成的新堆合并。
长图说明:
// 删除
bool BinomialHeap::Delete(Type key) {
Node* T = Search(Root, key); // 查找是否存在 键值key
if (NULL == T) return false;
Node* P = T->Parent;
// 将待删键值上移至堆根
while (P) {
Type tem = T->Key;
T->Key = P->Key;
P->Key = tem;
T = P;
P = T->Parent;
}
// 将待删键值所在节点的孩子 组成一个新堆 (反转)
// 反转:原本的孩子的度是从大到小 组成新堆时,变成从小到大
Node* C = T->Child;
Node* N = C->Next;
Node* Head = NULL; // 孩子组成新堆的头部
while (N) {
C->Parent = NULL; // 取消与 T节点的关系
C->Next = Head;
Head = C;
C = N;
N = C->Next;
}
C->Parent = NULL;
C->Next = Head;
Head = C;
// 找到 T节点的前一节点
Node* Pre = Root;
if (Root == T) Root = T->Next;
else {
while (Pre->Next != T) {
Pre = Pre->Next;
}
}
Pre->Next = Pre->Next->Next;
// 合并原堆 和 T的孩子组成的堆
Root = Combine(Root, Head);
return true;
}