算法导论 第19章 二项堆

二项树

    和其他树一样,二项树也是递归定义的。如下图,二项树B0只包含一个节点。二项树Bk是由两颗二项树Bk-1连接而成:其中一棵是另外一棵树的根的左孩子。下图展现了二项树B0到B4。算法导论 第19章 二项堆_第1张图片

二项树的性质

    对于二项树Bk,有如下性质:

    1、有2^k个节点;

    2、树高为k;

    3、在深度i处,恰好有个节点;

    4、根的度为k,大于任何其他节点的度,并且,其子女从左到右的度一次为k-1.k-2...2.1.0。子女i是子树Bi的根。


二项堆定义

    二项堆H又满足下面两个二项堆性质的二项树组成:

    1、每一棵二项树均满足最小堆性质,即给定的关键字大于或等于其父节点的关键字,这样的二项堆是最小堆,同理也可以定义最大二项堆;

    2、对于任意非负整数k,在H中至多有一颗度为k的树。

其他性质:

    1、对于一个含有n个节点的二项堆,最多含有lgn + 1(向下取整)棵二项树。

    2、包含n个节点二项堆,假设n的二进制形式为,若含有度为i的树,则bi = 1,否则为0;例如含有13个节点的树,其二进制位1101,表示包含节点数为8,4,1,即度为3,2,0的树。

二项堆的表示

    二项堆节点结构

template < typename K, typename V>
struct binomial_heap_node
{//二项树节点
	K key;//键
	V value;//值
	size_t degree = 0;//度
	binomial_heap_node *parent = nullptr;
	binomial_heap_node *leftchild = nullptr;
	binomial_heap_node *sibling = nullptr;
	binomial_heap_node(const K &k, const V &v) :key(k), value(v){}
};

   二项堆的结构,如下图:

算法导论 第19章 二项堆_第2张图片

可以看出,对于每一棵树的孩子,在水平方向上,是一个单链表,head指向的是最上层的链表。


二项堆的操作

1make-heap:创建一个空二项堆;

2insert:向堆中插入一个元素;

3minimum:返回最小堆的极小值;

4extract-min:返回堆极小值,并删掉它;

5union:合并两个堆;

6decrease:减少某个元素的键,并调整堆;

7erase:删除某一元素。


算法就不讨论了,算法导论上讲得很明白,直接给出代码,注释标明一切


#include
#include

#define EXTREMUM 0x7fffffff //极值,取极大值则最小堆,取极小值则最大堆
using namespace std;

template < typename K, typename V>
struct binomial_heap_node
{//二项树节点
	K key;//键
	V value;//值
	size_t degree = 0;//度
	binomial_heap_node *parent = nullptr;
	binomial_heap_node *leftchild = nullptr;
	binomial_heap_node *sibling = nullptr;
	binomial_heap_node(const K &k, const V &v) :key(k), value(v){}
};

template  >
class binomial_heap
{//二项堆,左孩子右兄弟方式存储
public:
	typedef binomial_heap_node		node;
	typedef binomial_heap					Bheap;
private:
	node *head;
	Compare compare;//键比较器,默认小于,为最小堆
	void heapLink(node *lhs, node *rhs)
	{//两棵二项树的链接
		lhs->parent = rhs;
		lhs->sibling = rhs->leftchild;
		rhs->leftchild = lhs;
		++rhs->degree;
	}
	void linkAtTail(node *&tail,node *curr)
	{//尾插法链二项树
		if (head == nullptr)
		{
			head = curr;
			tail = curr;
		}
		else
		{
			tail->sibling = curr;
			tail = tail->sibling;
		}
	}
	node* findPre(node *curr)const
	{//查找curr的前驱
		node *pre = nullptr;
		if (curr->parent == nullptr) pre = head;
		else if (curr->parent->leftchild == curr) return pre;
		else pre = curr->parent->leftchild;
		while (pre->sibling != curr)
			pre = pre->sibling;
		return pre;
	}
	void heapMerge(binomial_heap&);
	void postTraversal(node *)const;
	void destroy(node*);
	void reverse();
public:
	binomial_heap(node *h, Compare c = Compare()) :head(h), compare(c){}
	binomial_heap(Compare c = Compare()) :head(nullptr), compare(c){}
	void insert(const K&, const V&);
	node* minimum()const;
	pair extractMin();
	void BheapUnion(binomial_heap&);
	void decreaseKey(node*, const K&);
	void erase(node*);
	bool empty()const { return head == nullptr; }
	void print()const { postTraversal(head); }
	~binomial_heap(){ destroy(head); }
};

template 
void binomial_heap::insert(const K &k, const V &v)
{//插入元素
	node *curr = new node(k, v);
	if (head == nullptr) head = curr;//若为第一个节点
	else
	{//否则
		binomial_heap heap(curr,compare);
		BheapUnion(heap);
	}
}

template 
binomial_heap_node* binomial_heap::minimum()const
{//寻找最小/大元素,返回指针
	node *p_min = nullptr, *curr = head;
	K min = EXTREMUM;
	while (curr != nullptr)
	{
		if (compare(curr->key,min))
		{
			min = curr->key;
			p_min = curr;
		}
		curr = curr->sibling;
	}
	return p_min;
}

template 
pair binomial_heap::extractMin()
{//获得最小/大值,返回pair,即键值对
	node  *p_min = nullptr, *pre = nullptr, *curr = head;
	K min = head->key;//初始时head即为最值
	while (curr->sibling != nullptr)
	{//迭代,寻得最值,及其前驱
		if (compare(curr->sibling->key,min))
		{
			min = curr->sibling->key;
			pre = curr;
		}
		curr = curr->sibling;
	}
	if (pre == nullptr)
	{//若只有一个元素或者第一个元素即为所求
		p_min = head;
		head = p_min->sibling;
	}
	else
	{//否则
		p_min = pre->sibling;
		pre->sibling = p_min->sibling;
	}
	binomial_heap heap(p_min->leftchild,compare);//创建临时二项堆
	heap.reverse();//先逆置
	BheapUnion(heap);//再和原来的堆合并
	pair return_value = pair(p_min->key, p_min->value);//构造返回值
	delete p_min;//释放节点内存
	return return_value;
}

template 
void binomial_heap::BheapUnion(binomial_heap &rhs)
{//二项堆的合并
	heapMerge(rhs);//先将两条链合并,按度的非递减顺序
	if (head == nullptr) return;
	node *prev = nullptr, *curr = head, *next = head->sibling;
	while (next != nullptr)
	{//遍历每一个二项树的根
		if ((curr->degree != next->degree) || (next->sibling != nullptr && next->sibling->degree
			== curr->degree))
		{//若当前树和下一棵树度不等,或者有三颗二项树的根的度相等
			prev = curr;
			curr = next;
		}//否则当前仅有两棵树度相等
		else if (compare(curr->key,next->key))
		{//若当前树的根的key较小
			curr->sibling = next->sibling;
			heapLink(next, curr);//则将下一棵二项树链为其左孩子
		}
		else
		{//否则,相反
			if (prev == nullptr) head = next;//若当前二项堆最前面两棵树的度相等,则修改head
			else prev->sibling = curr->sibling;
			heapLink(curr, next);
			curr = next;
		}
		next = curr->sibling;
	}
}

template 
void binomial_heap::decreaseKey(node *p, const K &k)
{//减小某一节点的key,并调整堆,则该函数其实就是increaseKey
	if (!compare(k,p->key))
	{//若新值较大
		cout << "Error : greater key" << endl;
		return;
	}
	p->key = k;
	node *par = p->parent,*p_sib = p->sibling;
	while (par != nullptr && compare(p->key,par->key))
	{//自底向上调整
		//修改p和par的后继,即右兄弟
		p->sibling = par->sibling;
		par->sibling = p_sib;
		//如果p和par存在前驱,则修改它们前驱的右兄弟
		node *p_pre, *par_pre;
		if ((p_pre = findPre(p)) != nullptr)
			p_pre->sibling = par;
		if ((par_pre = findPre(par)) != nullptr)
			par_pre->sibling = p;

		if (par->parent != nullptr && par->parent->leftchild == par)
			par->parent->leftchild = p;
		
		node *p_child = p->leftchild;
		//设置p的孩子
		if (p->parent->leftchild == p)//如果p是其父亲par的左孩子,p的祖先其实就是par
			p->leftchild = par;
		else
		{//否则
			p->leftchild = par->leftchild;
			par->leftchild->parent = p;
		}
		p->parent = par->parent;//设置p的祖先
		par->parent = p;//设置par的祖先

		//设置par的孩子
		par->leftchild = p_child;
		if (p_child != nullptr)
			p_child->parent = par;
		std::swap(p->degree, par->degree);//交换度
		par = p->parent;
	}
}

template 
void binomial_heap::erase(node *p)
{//删除指定节点
	node *p_min = minimum();
	decreaseKey(p, p_min->key - 1);
	extractMin();
}

template 
void binomial_heap::heapMerge(binomial_heap &rhs)
{//按度非递减顺序合并两个堆
	if (rhs.empty()) return;//若至少有一堆空
	if (empty())
	{
		head = rhs.head;
		rhs.head = nullptr;
		return;
	}
	node *curr1 = head, *curr2 = rhs.head,*tail = nullptr;
	head = nullptr, rhs.head = nullptr;
	while (curr1 != nullptr && curr2 != nullptr)
	{//不断链接,直到有一堆为空
		if (curr1->degree <= curr2->degree)
		{
			linkAtTail(tail,curr1);//尾端链入
			curr1 = curr1->sibling;
		}
		else
		{
			linkAtTail(tail,curr2);
			curr2 = curr2->sibling;
		}
	}
	if (curr1 != nullptr) tail->sibling = curr1;//若本堆还有剩下
	else if (curr2 != nullptr) tail->sibling = curr2;//若另一堆还有剩下
	else tail->sibling = nullptr;//若均没有剩下
}

template 
void binomial_heap::destroy(node *H)
{//销毁堆
	node *curr = H;
	while (curr != nullptr)
	{//销毁每一棵二项树
		destroy(curr->leftchild);//递归销毁每一个子堆
		node *p = curr;
		curr = curr->sibling;
		delete p;//释放该树根
	}
}

template 
void binomial_heap::reverse()
{//逆置
	node *curr = head,*r;
	head = nullptr;
	while (curr != nullptr)
	{
		curr->parent = nullptr; r = curr;
		curr = curr->sibling;
		if (head == nullptr)
		{
			head = r;
			r->sibling = nullptr;
		}
		else
		{
			r->sibling = head;
			head = r;
		}
	}
}

template 
void binomial_heap::postTraversal(node *H)const
{//后序遍历二项堆
	node *curr = H;
	while (curr != nullptr)
	{
		postTraversal(curr->leftchild);//递归
		printf("key: %-6d value: %-6d degree: %-6d\n", curr->key, curr->value,curr->degree);
		curr = curr->sibling;
	}
}

int main()
{	
	binomial_heap bh;
	vector*> ptr(10);
	for (int i = 0; i != 10; i++)
		ptr[i] = bh.insert(i, 2 * i);
	for (size_t i = 0; i != ptr.size(); ++i)
		cout << ptr[i]->key << ' ';
	cout << endl;
	//binomial_heap_node *p = bh.minimum();
	for (size_t i = 0; i != ptr.size(); i += 2)
		bh.decreaseKey(ptr[i], ptr[i]->key - 2);
	for (size_t i = 0; i != ptr.size(); ++i)
		cout << ptr[i]->key << ' ';
	cout << endl;
	bh.print();
	cout << endl;
	while (!bh.empty())
	{
		cout << bh.minimum()->key << endl;
		bh.extractMin();
	}
	getchar();
	return 0;
}
 
  

 
  

习题 19.2-1 见程序代码

习题 19.2-5

    如果堆中存在关键字为无穷的节点,则会找不到该节点,即返回地址为空。将if中的‘<’改为‘<=’。

习题 19.2-6

    

BINOMIAL-HEAP-DELETE(H,x)
{
      y <- BINOMIAL-HEAP-MINIMUM();
      BINOMIAL-HEAP-DECREASE-KEY(H,x,key[y] - 1);
      BINOMIAL-HEAP-EXTRACT-MIN()
}


习题 19.2-7

    对于二项堆H,若存在度为k的二项树,则二进制数(设为x)的第k+1位为1.

1、插入一个节点,对应到x上就是,x = x + 1;

2、合并两个堆(设另一个堆对应地二进制数为y),就是,x = x + y。

如果发生进位,则说明存在相同度数的二项树,这样的树将会合并,生成度数增1的二项树。


习题 19.2-8

     根据19.2-7,设x = ,插入一个节点相当于x自增1,关键在于是否进位,若没有,则结束,否则不断进位,直到遇到0;对于二项堆即是,从左向右合并,初始时若没有度为0,即只有一个节点的二项树,则合并停止,否则合并,生成度增1的二项树,然后继续,直至没有遇到度相同的树。

习题19.2-9

    相当于将最上层链表逆置后,再实现每种操作,逆置时间为O(lgn),因而不会改变渐进时间。


思考题19-2

      采用二项堆实现的最小生成树算法





你可能感兴趣的:(算法和数据结构)