二项堆(插入、删除)

二项堆就是二项树的集合,将二项树按照度的大小从左到右串联在一起。

二项树:一组固定的递归定义的树。

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值