堆的实现(建立大小堆、push、pop元素)

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

堆结构的二叉树存储:

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

建堆:由于堆被视为完全二叉树,故在h-1层找到第一个(从后往前找)非叶子结点,进行堆的下调

建大堆时,从下往上依次判断并调整堆,使该结点的左右子树都满足大堆

建小堆时,从下往上依次判断并调整堆,使该结点的左右子树都满足小堆

可见大堆的建立与小堆的建立方式类似,下面以大堆进行讨论。

利用vactor模板存储堆中元素

template<class T>
class Heap
{
public:
	Heap();
	Heap(const T* a, size_t size);
	void Push(const T& x);
	void Pop();
	T& GetTop();//访问堆顶元素
	bool Empty();//判空
	size_t Size();//堆元素个数
	void PrintHeap();
protected:
	void _AdjustDown(size_t Parent);//下调--建大堆(每个父结点都大于孩子结点)
	void _AdjustUp(size_t Child);//上调--建小堆(每个父结点都小于孩子结点)
private:
	vector<T> _a;
};

实现堆的建立

template<class T>
Heap<T>::Heap()
:_a(NULL)
{}
template<class T>
Heap<T>::Heap(const T* a, size_t size)
{
	assert(a);
	_a.reserve(size);//初始化_a(vector模板的使用)
	for (size_t i = 0; i < size; ++i)
	{
		_a.push_back(a[i]);
	}
	////堆的第一个非叶子结点的数组下标时((size-1)-1)/2(最后一个结点是size-1)
	for (int i = (int)(size - 2) / 2; i >= 0; --i)//不能定义为size_t(无符号)
	{
		_AdjustDown(i);
	}
	//建小堆,类似建大堆的方式,从下向上进行调整堆,使该结点处的左右子树都满足小堆
	//在进行调小堆时,也通过下调实现
}
//下调--建大堆/小堆
template<class T>
void Heap<T>::_AdjustDown(size_t Parent)
{
	size_t Child = Parent * 2 + 1;
	while (Child < _a.size())
	{//先进行左右结点的比较,使Child为较大的数的下标,然后与父亲结点进行比较,使较大的数据为父亲结点
		if (Child + 1 < _a.size() && _a[Child] < _a[Child + 1])//存在右结点再进行比较
		{
			++Child;
		}
		if (_a[Child] > _a[Parent])//如果子结点大于父亲结点就交换,否则就要跳出循环
		{
			swap(_a[Child], _a[Parent]);
			Parent = Child;
			Child = Parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
//在建立小堆时,只需要将比较条件进行改变就可以实现

在已经是大堆或小堆的堆中加入元素使堆仍为大堆,可通过该元素与它的父结点进行比较

ps:由于插入的元素在数组末尾,故需要通过上调进行比较实现堆的大堆或小堆

template<class T>
void Heap<T>::_AdjustUp(size_t Child)//上调
{
	size_t Parent = (Child - 1) / 2;//结点为Child的父亲结点为(Child-1)/2
	while (Child > 0)//当Child等于0时以到堆顶,终止循环
	{
		if (_a[Parent] < _a[Child])//直接进行父亲结点和子结点的比较
		{
			swap(_a[Child], _a[Parent]);
			Child = Parent;
			Parent = (Child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
template<class T>
void Heap<T>::Push(const T& x)//元素x入堆
{
	//_a.resize(_a.size() + 1);
	//_a[_a.size()-1] = x;
	_a.push_back(x);
	_AdjustUp(_a.size() - 1);
}

堆中pop元素,删除堆顶元素,使堆仍为大堆。

在已经是大堆或小堆的堆中删除堆顶元素,直接删除堆顶元素,造成无法进行大堆或小堆的实现,可通过将第一个元素与最后一个元素进行交换,然后删除最后一个元素,最后通过下调实现大堆或小堆

template<class T>
void Heap<T>::Pop()//出堆
{
	size_t size = _a.size();
	assert(size > 0);//断言堆非空
	swap(_a[0], _a[size - 1]);
	_a.pop_back();
	_AdjustDown(0);//从堆顶开始进行下调
}

实现堆的堆顶,判空及堆元素个数

template<class T>
T& Heap<T>::GetTop()//访问堆顶元素
{
	return _a[0];
}
template<class T>
bool Heap<T>::Empty()//判空
{
	return _a.size() == 0;
}
template<class T>
size_t Heap<T>::Size()//堆元素个数
{
	return _a.size();
}
template<class T>
void Heap<T>::PrintHeap()
{
	for (size_t i = 0; i < _a.size(); ++i)
	{
		cout << _a[i] << " ";
	}
	cout << endl;
}

测试用例

#include"Heap.hpp"
void Test4()
{
	int arr[] = { 10, 16, 18, 12, 11, 13, 15, 17, 14, 19};
	Heap<int> h(arr, sizeof(arr) / sizeof(arr[0]));
	h.PrintHeap();
	cout << "empty: " << h.Empty() << endl;
	cout << "size: " << h.Size() << endl;
	cout << "gettop: " << h.GetTop() << endl;
	h.Push(20);
	h.PrintHeap();
	h.Pop();
	h.PrintHeap();
}

如果对于上述说明还是不是很清楚,可自己亲手画图分析,存在不足之处请多多指教。

【vector】包含着一系列连续存储的元素, 其行为和数组类似。访问Vector中的任意元素或从末尾添加元素都可以在常量级时间复杂度内完成,而查找特定值的元素所处的位置或是在Vector中插入元素则是线性时间复杂度。

时间: 2024-10-05 03:15:22

堆的实现(建立大小堆、push、pop元素)的相关文章

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

一.堆的概念 堆数据结构是一种数组对象,它可以被视为一棵完全二叉树结构. 堆结构的二叉树存储是: 最大堆:每个父节点的都大于孩子节点. 最小堆:每个父节点的都小于孩子节点. 堆栈中的物体具有一个特性: 最后一个放入堆栈中的物体总是被最先拿出来, 这个特性通常称为后进先出(LIFO)队列. 堆栈中定义了一些操作. 两个最重要的是PUSH和POP. PUSH操作在堆栈的顶部加入一 个元素.POP操作相反, 在堆栈顶部移去一个元素, 并将堆栈的大小减一. 在此,用vector容器来实现存储,vecto

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

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

【数据结构】堆的实现(包括:默认成员函数,插元素push,删元素pop,访问根节点top,判空,大小)

在数据结构里,堆是一类很重要的结构.堆结构是一组数组对象,我们可以把它当作是一颗完全二叉树. 最大堆:堆里每一个父亲节点大于它的子女节点. 最小堆:堆里每一个父亲节点小于它的子女节点. 如图就是一个最大堆: 实现代码时我的测试序列是:int a[] = { 10, 11, 13, 12, 16, 18, 15, 17, 14, 19 }; 我们把它的图画出来,便于分析. 实现代码如下: 建立头文件heap.hpp #define _CRT_SECURE_NO_WARNINGS 1 #includ

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

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

用仿函数实现大小堆及堆排序

"2.h" #include<iostream> #include<vector> #include<assert.h> using namespace std; template<class T> struct Less { bool operator()(const T& left,const T& right) { return left<right; } }; template<class T>

【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

如何限制对象只能建立在堆上或者栈上(转载)

转载:http://blog.csdn.net/szchtx/article/details/12000867 在C++中,类的对象建立分为两种,一种是静态建立,如A a:另一种是动态建立,如A* ptr=new A:这两种方式是有区别的. 静态建立一个类对象,是由编译器为对象在栈空间中分配内存,是通过直接移动栈顶指针,挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象.使用这种方法,直接调用类的构造函数. 动态建立类对象,是使用new运算符将对象建立在堆空间中.这个过程分为两步,第

如何限制对象只能建立在堆上或者栈上

转自http://blog.csdn.net/szchtx/article/details/12000867# 在C++中,类的对象建立分为两种,一种是静态建立,如A a:另一种是动态建立,如A* ptr=new A:这两种方式是有区别的. 静态建立一个类对象,是由编译器为对象在栈空间中分配内存,是通过直接移动栈顶指针,挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象.使用这种方法,直接调用类的构造函数. 动态建立类对象,是使用new运算符将对象建立在堆空间中.这个过程分为两步,第

进程默认堆和额外创建的堆

在<Windows核心编程>第五版的第十八章 <堆> 中提到了进程默认堆和额外创建的堆.这在编程中是十分重要的知识,今天整理一下. 1,堆非常适合分配大量的小型数据.使用堆可以让程序员专心解决手头的问题,而不必理会分配粒度和页面边界之类的事情.因此堆是管理链表和数的最佳方式.但是堆进行内存分配和释放时的速度比其他方式都慢,而且无法对物理存储器的调拨和撤销调拨进行控制. 为了能适应各种硬件平台,如果程序员想要控制物理存储器的调拨和撤销调拨,就不应该使用堆,而应该使用虚拟内存(Virt