堆的实现(大小堆及 优先队列

一、堆的概念

堆数据结构是一种数组对象,它可以被视为一棵完全二叉树结构。

堆结构的二叉树存储是:

最大堆:每个父节点的都大于孩子节点。

最小堆:每个父节点的都小于孩子节点。

堆栈中的物体具有一个特性: 最后一个放入堆栈中的物体总是被最先拿出来, 这个特性通常称为后进先出(LIFO)队列。 堆栈中定义了一些操作。 两个最重要的是PUSH和POP。 PUSH操作在堆栈的顶部加入一 个元素。POP操作相反, 在堆栈顶部移去一个元素, 并将堆栈的大小减一。

在此,用vector容器来实现存储,vector容器是一个模板类,可以存放任何类型的对象(但必须是同一类对象)。vector对象可以在运行时高效地添加元素,并且vector中元素是连续存储的。当容量不够时,它能够自己去扩容。故我们在push数据时就不用考虑一些其他容量不足等等因素。

二、堆的实现

通过二叉树来实现堆的结构。

先实现一个compare,如果实现大小堆用对象调其对应类运算符“()”重载

template<class T>
struct Less
{
	bool operator()(const T& l, const T& r)
	{
		return l < r;
	}
};
template<class T>
struct Big
{
	bool operator()(const T& l, const T& r)
	{
		return l > r;
	}
};

先定义一个堆:

用模板的模板参数:

如:当 测试用例为:

int arr[] = { 12, 10, 43, 23, 22, 45, 67,9 };

Heap<int,Big>  N(arr, sizeof(arr)/sizeof(arr[0]));

当你给定compare为Big时它会按照大堆去排序

Heap<int,Less>  N(arr, sizeof(arr)/sizeof(arr[0]));

当你给定compare为Big时它会按照小堆去排序

template<class T , template<class>  class compare >  //模板的模板参数
class Heap
{

public:
	Heap()
	{}
	Heap(T* a, size_t size)
	{
		_a.reserve(size);
		for (size_t i = 0; i < size; ++i)
		{
			_a.push_back(a[i]);
		}
		//建堆

		for (int i = (_a.size() -2)/2; i >= 0; --i)
		{
			_AdjustDown(i);
		}
	    Disp(_a, size);
	}
	//Pop时,先将第一个与最后一个交换,(这样不至于打乱其他子堆的顺序),然后
	//删除最后一个,再让它下调重新调整顺序
	void Pop()       
	{
		size_t _size = _a.size();
		assert(_size > 0);
		swap(_a[0], _a[_size-1]);
		_a.pop_back();
		_size = _a.size();
		_AdjustDown(0);
		Disp(_a, _size);
	}
	//push一个数据后,让其上调,以调整顺序
	void Push(const T& x)
	{
		_a.push_back(x);
		size_t _size = _a.size();
		_AdjustUp(_size-1);
		Disp(_a, _size);
	}
	T& Top()
	{
		assert(!_a.empty());
	    return _a[0];
	}
	bool empty()
	{
		return _a.size() == 0;
	}
	void Disp(vector<T> a, size_t k)//打印
	{
		for (size_t j = 0; j < k; j++)
		{
			cout << a[j] << " ";
		}
		cout << endl;
	}

在建堆时,首先来定义一个下调函数_AdjustDown();用来调整已实现大小堆顺序。

实现思想:

1、找最后一个非叶子结点

2、如果当前结点的孩子结点左孩子大于右孩子,就让child指向最大孩子结点(在此必须满足存在右孩子)

3、如果当前结点小于孩子结点,就交换,下调,将孩子给父亲,孩子结点下移

4、不满足  就break;

	void _AdjustDown(size_t parent)    //     下调
	{
		size_t child = parent * 2 + 1;
		while (child < _a.size())
		{
			compare<T> _com;  
			if ( child + 1 < _a.size()&&_com(_a[child + 1], _a[child]) )
			{
				++child;
			}
			if (_com(_a[child],_a[parent]))
			{
				swap(_a[child], _a[parent]);
				parent = child;
				child = parent * 2 + 1;
			}
			else
			{
				break;
			}
		}
	}

再写一个上调函数_AdjustUp()当Push一个数时,让它上调,以调整整个堆的顺序。

实现思想:

1、上调,传当前结点,令当前节点为孩子结点,上一结点为父结点,

2、在这里不用考虑左右结点谁大谁小

3、如果孩子结点大于父亲结点,交换,上移

4、不满足  就break;

注意:在此不用考虑左右子数谁大谁小,上调是如果孩子结点比父结点大,那它肯定比兄弟结点大。

void _AdjustUp(size_t child)   //上调
	{
		compare<T> _com;
		size_t parent = (child - 1) / 2;
		while (child > 0)
		{
			if (_com(_a[child], _a[parent]))
			{
				swap(_a[child], _a[parent]);
				child = parent;
				parent = (child - 1) / 2;
			}
			else
			{
				break;
			}
		}
	}

三、优先队列

template<class T, template<class>  class compare = Big>//利用模板的模板参数
class PriorityQueue  //优先队列
{
protected:
	Heap<T, compare> _hP;
public:
	void _push(const T& x)
	{
		_hP.Push(x);
	}
	void Pop()
	{
		_hP.Pop();
	}
	T& Top()
	{
		return _hP.Top();
	}
};

测试用例:
PriorityQueue<int,Big> s;
	s._push(3);
	s._push(12);
	s._push(5);
	s._push(78);
	s._push(43);
	s._push(10);
	s._push(32);	

结果会以大堆形式实现为:

如果将测试用例改为:

PriorityQueue<int,Less> s;
	s._push(3);
	s._push(12);
	s._push(5);
	s._push(78);
	s._push(43);
	s._push(10);
	s._push(32);

结果会以小堆实现 为:

时间: 2024-10-30 21:30:24

堆的实现(大小堆及 优先队列的相关文章

[剑指offer] 41. 数据流中的中位数 (大小堆,优先队列)

对于海量数据与数据流,用最大堆,最小堆来管理. class Solution { public: /* * 1.定义一个规则:保证左边(大顶堆)和右边(小顶堆)个数相差不大于1,且大顶堆的数值都小于等于小顶堆的数 * 2.大小堆顶可以用优先序列实现 插入规则: 当插入数值小于左边的堆顶时候,就插入左边,否则插入右边堆.(注意初始为空时,插入不能比较) 调整使得满足个数差<=1: 正常时是只有两种情况:p=q或者p=q+1,由于每插一个值就会考虑调整,那么边界情况就是p=q+2或者p+1=q p=

结构之美——优先队列基本结构(四)——二叉堆、d堆、左式堆、斜堆

实现优先队列结构主要是通过堆完成,主要有:二叉堆.d堆.左式堆.斜堆.二项堆.斐波那契堆.pairing 堆等. 1. 二叉堆 1.1. 定义 完全二叉树,根最小. 存储时使用层序. 1.2. 操作 (1). insert(上滤) 插入末尾 26,不断向上比较,大于26则交换位置,小于则停止. (2). deleteMin(下滤) 提取末尾元素,放在堆顶,不断下滤: (3). 其他操作: 都是基于insert(上滤)与deleteMin(下滤)的操作. 减小元素:减小节点的值,上滤调整堆. 增大

【数据结构】用模版实现大小堆、实现优先级队列,以及堆排序

一.用模版实现大小堆 如果不用模版的话,写大小堆,就需要分别实现两次,但是应用模版的话问题就简单多了,我们只需要实现两个仿函数,Greater和Less就行了,仿函数就是用类实现一个()的重载就实现了仿函数.这个看下代码就能理解了.再设计参数的时候,需要把模版设计成模版的模版参数,因为要实现大小堆嘛!当我们实现好一个大堆或者小队的逻辑后只需要用模版接收的Greater或Less类定义一个变量,就能实现通用功能了. template<typename T> struct Less {     b

【Leetcode 大小堆、二分、BFPRT、二叉排序树、AVL】数据流的中位数(295)

题目 中位数是有序列表中间的数.如果列表长度是偶数,中位数则是中间两个数的平均值. 例如, [2,3,4]?的中位数是 3 [2,3] 的中位数是 (2 + 3) / 2 = 2.5 设计一个支持以下两种操作的数据结构: void addNum(int num) - 从数据流中添加一个整数到数据结构中. double findMedian() - 返回目前所有元素的中位数. 示例: addNum(1) addNum(2) findMedian() -> 1.5 addNum(3) findMed

升序堆和降序堆(优先队列) 洛谷1801

1 // 洛谷1801 2 // 一个升序堆,一个降序堆 3 // 降序堆维护序列的前i个最小值 4 // 插如元素的时候,如果x小于降序堆最大值,则替换,并将最大值插入升序堆:否则,直接插入升序堆 5 // 每次输出升序堆的最小值即可 6 7 8 #include <bits/stdc++.h> 9 using namespace std; 10 #define LL long long 11 typedef pair<int,int> pii; 12 const int inf

12、【堆】二项堆

一.二项树的介绍 二项树的定义 二项堆是二项树的集合.在了解二项堆之前,先对二项树进行介绍. 二项树是一种递归定义的有序树.它的递归定义如下: (1) 二项树B0只有一个结点: (2) 二项树Bk由两棵二项树B(k-1)组成的,其中一棵树是另一棵树根的最左孩子. 如下图所示: 上图的B0.B1.B2.B3.B4都是二项树.对比前面提到的二项树的定义:B0只有一个节点,B1由两个B0所组成,B2由两个B1所组成,B3由两个B2所组成,B4由两个B3所组成:而且,当两颗相同的二项树组成另一棵树时,其

堆之左式堆和斜堆

d-堆 类似于二叉堆,但是它有d个儿子,此时,d-堆比二叉堆要浅很多,因此插入操作更快了,但是相对的删除操作更耗时.因为,需要在d个儿子中找到最大的,但是很多算法中插入操作要远多于删除操作,因此,这种加速是现实的. 除了不能执行find去查找一般的元素外,两个堆的合并也很困难. 左式堆 左式堆可以有效的解决上面说的堆合并的问题.合并就涉及插入删除,很显然使用数组不合适,因此,左式堆使用指针来实现.左式堆和二叉堆的区别:左式堆是不平衡的.它两个重要属性:键值和零距离 零距离(英文名NPL,即Nul

堆之二叉堆

堆的定义 堆通常是一个可以被看做一棵树,它满足下列性质: 堆中任意节点的值总是不大于(不小于)其子节点的值: 堆总是一棵完全树. 将任意节点不大于其子节点的堆叫做最小堆或小根堆,而将任意节点不小于其子节点的堆叫做最大堆或大根堆.常见的堆有二叉堆.左倾堆.斜堆.二项堆.斐波那契堆等等. 二叉堆 堆有两种性质:结构性和堆序性 结构性:堆是一颗完全二叉树.若设完全二叉树的高度是h,则它的节点数是2^h到2^(h+1) - 1:则节点数为N的完全二叉树的高度O(logn). 完全二叉树中父子节点位置关系

【转载】java项目中经常碰到的内存溢出问题: java.lang.OutOfMemoryError: PermGen space, 堆内存和非堆内存,写的很好,理解很方便

Tomcat Xms Xmx PermSize MaxPermSize 区别 及 java.lang.OutOfMemoryError: PermGen space 解决 解决方案 在 catalina.bat 里的 蓝色代码前加入: 红色代码 rem ----- Execute The Requested Command --------------------------------------- set JAVA_OPTS=%JAVA_OPTS%-server -Xms800m -Xmx1