大小堆&&堆排序&&堆的应用

一.首先说一下堆的概念吧这里就不按照标准的概念来说了,就说说我理解的堆。

堆就是一个数组中的元素,参考着完全二叉树的这种数据结构存储在数组中,这样就是一个堆。注意:这里是参考,实际的存储还是在数组中,只不过数组中的存储顺序满足完全二叉树而已。说到堆离不开大小堆,下面继续介绍大小堆的概念。

1.最小堆:因为数组中的元素是参考完全二叉树的存储顺序存储的,所以小堆就是每一个双亲结点的值都<=左右孩子的值,堆顶的元素是最小值


2.最大堆:任何一个双亲结点的值都大于等于左右孩子的值,并且堆顶元素的值是最大值。


二.下面来说说如何创建一个堆,因为堆离不开最大堆和最小堆,所以我这里创建的堆是生成了自动生成了最小堆,首先给出代码,然后在给出代码解释:

#include<iostream>
#include<vector>
using namespace std;
template<class T>
class Heap//堆
{
public:
	Heap(T* array, size_t size)//构造函数
		:_size(size)
	{
		_array.resize(size);
		for (size_t i = 0; i < _size; i++)
		{
			_array[i] = array[i];
		}
		_AdjustDown((_size - 2) >> 1);
	}
	T top() const//返回堆顶的元素
	{
		T a = _top();
		return a;
	}
	void Push(const T& data)//给堆中插入元素,使用向上调整插入
	{
		_Push(data);
	}
	void Pop()//删除堆中的元素其实就是删除堆顶的元素
	{
		_Pop();
	}
private:
	void _AdjustDown(int parent)//从上往下调整,建立小堆
	{
		for (int i = parent; i >= 0; i--)
		{
			parent = i;
			size_t child = parent * 2 + 1;
			while (child<_size)
			{
				if (child + 1 < _size&&_array[child] > _array[child + 1])
					child += 1;
				if (/*child<_size&&*/_array[parent] > _array[child])
				{
					swap(_array[parent], _array[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
	}
	void _AdjustUp(size_t child)//向上调整,插入中使用
	{
		size_t parent = (child - 1) >> 1;
		while (child > 0)
		{
			if (_array[parent] > _array[child])
			{
				swap(_array[parent], _array[child]);
				child = parent;
				parent = (child - 1) >> 1;
			}
			else
				break;
		}
	}
	T _top() const
	{
		return _array[0];
	}
	void _Push(const T data)//堆中插入元素
	{
		_array.push_back(data);
		_size++;
		if (_array.size() > 1)
		{
			_AdjustUp(_size - 1);
		}
	}
	void _Pop()//删除堆顶元素
	{
		if (_array.empty())
			return;
		size_t last = _size - 1;
		swap(_array[0], _array[last]);
		_array.pop_back();
		_size--;
		if (_size > 1)
		{
			_AdjustUp(0);
		}
	}
private:
	vector<T> _array;
	size_t _size;
};
int main()
{
	int array[] = { 53, 17, 78, 9, 45, 65, 87, 23 };
	Heap<int> s(array, sizeof(array) / sizeof(*array));
	s.Push(1);
	s.Pop();
	cout << s.top() << endl;
	system("pause");
	return 0;
}
上面是整体所有的代码下面给出解析:

1.创建堆(自动生成最小堆)

Heap(T* array, size_t size)//构造函数
		:_size(size)
	{
		_array.resize(size);
		for (size_t i = 0; i < _size; i++)
		{
			_array[i] = array[i];
		}
		_AdjustDown((_size - 2) >> 1);
	}
这段代码就不需要我多说了吧,将数组中的元素保存在容器vector中,下面我来终点介绍这段代码:即得出最小堆的方法--->向下调整法
_AdjustDown((_size - 2) >> 1);


注意想要生成最小堆的方法是向下调整法,并不是说要生成最大堆就用向上调整法,想要生成最大堆,只需要将其中比较的顺序符号反过来就可以了。而向上调整法是给堆中插入元素时使用的。

2.下面我来说说向上调整法



3.好了关于堆最核心的两个算法已经讲完了,剩下的就是一下很简单的东西了,下面我来说说关于堆的删除即Pop()

void _Pop()//删除堆顶元素
	{
		if (_array.empty())
			return;
		size_t last = _size - 1;
		swap(_array[0], _array[last]);
		_array.pop_back();
		_size--;
		if (_size > 1)
		{
			_AdjustUp(0);
		}
	}
算法核心就是将堆顶元素和最后一个元素互换,然后剔除互换后的最后一个元素(即要删除的元素),在重新调整堆就好了。

到这里关于堆的一些问题就讲完了置于剩下的那几个功能都很简单自己去看就可以了。测试代码我在最开是的代码中也已经给出了自己去测试就可以了。

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////虽然上面 的堆已经说完了但是美中不足的是,每次我创建的总是最小堆,要是我想创建最大难道每次都要去将其中的符号改动吗?当然不需要,下面我就来说说升级版本的,这个版本中加入了仿函数,使一切看起就变得很简单了

1.首先说一下什么是仿函数

其实就是在你定义的类中重载(),实现一个具体的功能,然后这个类对象就可以当成函数调用了!

举个简单的例子:

template<class T>
class Add//定义具有加法功能的仿函数
{
public:
	int operator()(const T& a, const T& b)
	{
		int  sum;
		sum = a + b;
		return sum;
	}
};
template<class T>
class Mul//定义具有乘法功能的仿函数
{
public:
	int operator()(const T& a, const T& b)
	{
		int  sum;
		sum = a*b;
		return sum;
	}
};
template<class T,class CA>//在这里只是多了个参数而已
class Caculate
{
private:
	int a;
	int b;
public:
	Caculate(int x, int y)
		:a(x)
		, b(y){}
	CA s;//仿函数类的对象
	int jisuanqi()
	{
		return s(a,b);//把对象当成函数调用
	}
};
int main()
{
	Caculate<int,Add<int>> s(10, 10);//在这里你向进行什么运算取决于你传过去的是哪个对象
	cout << s.jisuanqi() << endl;//执行加法运算
	system("pause");
	return 0;
}
上面就是一个简单的仿函数的应用。如果有不理解的地方私我。

下面来说说把仿函数应用到堆中的改造方法,因为最大小堆的建立就是取决于如下语句

if (child + 1 < _size&&com(_array[child+1], _array[child]))
					child += 1;
				if (child<_size&&com(_array[child],_array[parent]))
				{
					swap(_array[parent], _array[child]);
					parent = child;
					child = parent * 2 + 1;
				}
所以我们可以将判断的if语句中的表达式交给仿函数去完成,我们在if里面只需要调用仿函数就可以了,所以产生如下的仿函数

template<class T>
class Less//建立小堆的仿函数
{
public:
	bool operator()(const T& left, const T& right)
	{
		return left < right;
	}
};
template<class T>
class Greater//建立大堆的仿函数
{
public:
	bool operator()(const T& left, const T& right)
	{
		return left >right;
	}
};
使用方法如下

下面给出全部代码:

#include<iostream>
#include<vector>
using namespace std;
//1.函数指针可以实现
//2.仿函数
template<class T>
class Less
{
public:
	bool operator()(const T& left, const T& right)
	{
		return left < right;
	}
};
template<class T>
class Greater
{
public:
	bool operator()(const T& left, const T& right)
	{
		return left >right;
	}
};
//这里创建的都是小堆
template<class T,class Compare=Less<int>>
class Heap//堆
{
public:
	Heap(T* array, size_t size)
		:_size(size)
	{
		_array.resize(size);
		for (size_t i = 0; i < _size; i++)
		{
			_array[i] = array[i];
		}
		_AdjustDown((_size - 2) >> 1);
	}
	T top() const//返回堆顶的元素
	{
		return _array[0];
	}
	void Push(const T& data)//给堆中插入元素,使用向上调整插入
	{
		//方法1,直接尾插
		_array.push_back(data);
		_size++;
		if (_array.size() > 1)
		{
			_AdjustUp(_size - 1);
		}
	}
	void Pop()//删除堆中的元素其实,就是删除堆顶的元素
	{
		if (_array.empty())
			return;
		size_t last = _size - 1;
		swap(_array[0], _array[last]);
		_array.pop_back();
		_size--;
		if (_size > 1)
		{
			_AdjustDown(0);
		}
	}
private:
	void _AdjustDown(int parent)//从上往下调整,建立小堆
	{
		for (int i = parent; i >= 0; i--)
		{
			parent = i;
			size_t child = parent * 2 + 1;
			while (child<_size)
			{
				Compare com;
				if (child + 1 < _size&&com(_array[child+1], _array[child]))
					child += 1;
				if (child<_size&&com(_array[child],_array[parent]))
				{
					swap(_array[parent], _array[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
	}
	void _AdjustUp(size_t child)//向上调整,插入中使用
	{
		size_t parent = (child - 1) >> 1;
		while (child>0)
		{
			if (Compare()(_array[child],_array[parent]))
			{
				swap(_array[parent], _array[child]);
				child = parent;
				parent = (child - 1) >> 1;
			}
			else
				break;
		}
	}
private:
	vector<T> _array;
	size_t _size;//缺点就是每次插入的需要调整
};
int main()
{
	int array[] = { 53, 17, 78, 9, 45, 65, 87, 23 };
	Heap<int,Greater<int>> s(array, sizeof(array) / sizeof(*array));
	/*s._downtoup();*/
	s.Push(1);
	s.Pop();
	cout << s.top() << endl;
	system("pause");
	return 0;
}
因为测试代码我也给出了,所以自行去测试。

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

关于堆的内容说完了,下面说说堆的应用

1.优先级队列

template<class T,class Compare>
class Priority
{
public:
	Priority(){}
	void Push(const T& data)
	{
		_ph.Push(data);
	}
	void Pop()
	{
		_ph.Pop();
	}
	T Top() const
	{
		return _hp.Top();
	}
	bool Empty()const 
	{
		return _hp.Empty();
	}
private:
	Heap<int, Compare> _hp;
};
2.对排序(递增排序)

1.首先建立调整最大堆的的函数

2.将你要排序的数组首先调整一次,生成堆

3.开始排序(交换堆顶和最后一个元素的位置,然后除去最后一个元素,继续调整,一次类推直到全部排序完)

下面给出代码

void Adjustheap(int *array, int size, int parent)//调整堆。调整出来的是大堆
{
	int child = parent * 2 + 1;

	while (child<size)
	{
		if (child+1<size&&array[child]<array[child + 1])
			child += 1;
		if (array[parent] < array[child])
		{
			swap(array[parent], array[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			return;//不需要调整直接结束
	}
}
void HeapSort(int *array, int size)
{
	//创建堆
	int root = (size - 2) >> 1;
	for (; root > 0; --root)
	{
		Adjustheap(array, size, root);//调整堆
	}
	//堆排序
	for (int i = 0; i < size-1;i++)
	{
		swap(array[0],array[size - i - 1]);
		Adjustheap(array, size - i-1, 0);
	}
}
int main()
{
	int array[] = { 5, 3, 2, 4, 1 };
	HeapSort(array, 5);
	system("pause");
	return 0;
}
排序后的数组

3.Topk问题:

即在海量的数据中查找最大或者最小的前 N个数据

1.假如要找前N个最大的数话,首先建立一个元素为N的小堆,然后开始遍历数据,如果数据比堆根大则将堆根pop出去,然后将此元素push进堆,然后继续调整,调整好以后的堆顶是最小的,以此类推,当数据全部扫描完成,则这个堆中的元素就是我们要的数据

2.假如要找前N个最小的数的话,使用最大堆,比堆顶元素小的话pop堆顶,push元素,然后调整堆,直到数据遍历完毕。

以上关于堆的一些内容就介绍完了,希望对大家对于堆的理解有帮助,不懂或出错的地方欢迎留言一起交流学习!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值