应用1.优先级队列
优先级队列 是不同于先进先出队列的另一种队列。每次从队列中取出的是具有最高优先权的元素。
#pragma once
#include <cassert>
#include <iostream>
#include <cstdlib>
#include "Heap.h"
using namespace std;
template <class T , class Compare = Greater<T>>
class PriorityQueue
{
public:
PriorityQueue(T* array, size_t size)
:_h(array, size)
{}
void Push(const T& d)
{
_h.Push(d);
}
void Pop()
{
_h.Pop();
}
const T& Top()
{
return _h.GetTop();
}
protected:
Heap <T, Compare> _h;
};
void TestPriorityQueue()
{
int a[] = { 3, 8, 12, 2, 19, 11, 14, 13, 15, 10 };
PriorityQueue<int> a1(a, 10);
}
应用2.n个数中找出最大的前k个数
在有大量的数据时,找出最大的前k个数。此类问题可以用堆来解决。
首先,取出n个数据中的前k个数,创建一个有k个数据的小堆。
然后,把后面的数据依次和堆顶的数据比较,找出俩个数中大的数据放在堆顶,调用向下调整算法,保证这是一个小堆。这样就可以找出最大的前k个数。
找出最小的k个数的方法也类似。
注意:找最大数时建的是小堆,找最小数时建的是大堆。
#pragma once
#include <cassert>
#include <iostream>
#include <cstdlib>
using namespace std;
#define K 10
#define N 10000
template <class T>
//将根节点向下调整
void AdjustDown(T *top,int root)
{
assert(root < K);
size_t parent = root;
size_t child = parent * 2 + 1;
while (child < K)
{
if (child + 1 < K && top[child + 1] < top[child])
{
++child;
}
if (top[child] < top[parent] && child < K)
{
swap(top[parent], top[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
return;
}
}
}
template <class T>
void GetTopK(T *arr,T *top)
{
assert(K < N);
for (size_t i = 0; i < K; i++)//取出N个数据的前K个数
{
top[i] = arr[i];
}
for (int j = (K - 2) / 2; j >= 0; j--)//创建一个有K个数据的小堆
{
AdjustDown(top, j);
}
for (size_t i = K; i < N; i++)//比较第K+1--第N个数据,找出最大的K个数
{
if (arr[i] > top[0])
{
swap(arr[i], top[0]);
AdjustDown(top, 0);
}
}
}
template <class T>
void print(T *top)
{
for (size_t i = 0; i < K; i++)
{
cout << top[i] << " ";
}
cout << endl;
}
void TestGetTopK()
{
int arr[N] = { 0 };
int top[K] = { 0 };
for (size_t i = 0; i < N; i++)
{
arr[i] = i;
}
GetTopK(arr, top);
print(top);
}
应用3.堆排序
以升序为例,堆排序首先建一个小堆,找到最小的数据,放到最后。通过向下调整算法(此时不用管数组的最后一个元素),找到最小的数据,放到最后,这样依次操作,可把数组排序。
#pragma once
#include <cassert>
#include <iostream>
#include <cstdlib>
using namespace std;
template <class T>
void Adjustdown(T* arr ,size_t root,size_t size)
{
size_t parent = root;
size_t child = parent * 2 + 1;
while (parent < size)
{
if (arr[child] > arr[child + 1] && child + 1 < size)
{
++child;
}
if (arr[child] < arr[parent] && child < size)
{
swap(arr[child], arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
template <class T>
void HeapSort(T* arr, size_t size)
{
assert(arr);
for (int i = (size - 2) / 2; i >= 0; i--) //建小堆
{
Adjustdown(arr, i, size);
}
size_t end = size - 1;
while (end > 0)
{
swap(arr[0], arr[end]);
Adjustdown(arr, 0, end);
end--;
}
};
template<class T>
void print(T* arr,size_t size)
{
for (int i = 0; i < size; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
void TestHeapSort()
{
int a[] = {7,1,0,5,8,2};
HeapSort(a, sizeof(a)/sizeof(a[0]));
print(a, sizeof(a) / sizeof(a[0]));
}
堆的性质:
1、可以通过一个简单的数组实现
2、支持最坏情况为O(logN)的insert和deleteMin
3、支持常量平均时间的insert操作以及常量平均最坏时间的findMin操作。
4、二叉堆是实现优先级队列的典型方法
堆所支持的基本操作:
template<typename T>
class BinaryHeap
{
typedef HeapNode<T> Node;
public:
BinaryHeap();
BinaryHeap(const vector<Node> &v);
void Insert(const Node& data);
void DeleteMin(Node& data = Node()); //删除最小元素,可以通过参数返回最小值
void MakeHeap(); //置空堆
bool IsEmpty()const; //判断堆是不是为空
const Node& findMin()const; //查找堆中的最小元素
protected:
void buildHeap(); //恢复堆的顺序
void percolate(int hole); //从hole开始下滑调整
protected:
vector<Node> _Heap; //存储的堆中元素的数组
int _Size; //标记堆中元素的个数
};
一、堆的建立
堆的建立有两种方式,一种是建立一个空堆,另一种是通过复制一个记录数组并对其加以调整形成一个堆。
BinaryHeap()
:_Heap(0)
, _Size(0)
{}
BinaryHeap(const vector<Node> &v)
:_Heap(v.size()+1) //为了便于计算,多开辟一个空间
, _Size(v.size())
{
for (int i = 1; i <=_Size; i++) //_Heap[0]不存放
{
_Heap[i] = v[i-1];
}
buildHeap();
}
时间复杂度:O(N*lgN)
二、Insert操作
Insert所需要的是上滑调整,直接向_Heap中插入数据,然后通过上滑调整顺序。
void Insert(const Node& data)
{
_Heap.push_back(data);
_Size++;
int i = _Size;
int parent= 0;
for (; i/2>0; i /= 2) //i这点的结点要有父亲节点
{
parent = i / 2; //父亲结点的位置
if (_Heap[parent] < data) //如果父亲结点比插入的值小,则有序
{
break;
}
else //如果父亲结点比插入值大
{
_Heap[i] = _Heap[parent];
}
}
_Heap[i] = data;
}
时间复杂度:O(lgN)
三、deleteMin操作
由于最小堆的性质,根节点总是最小值,所以将数组中最后一个结点的值放到根节点的位置,然后_Size自减,再从根节点开始进行一次下滑操作恢复堆的顺序。
void DeleteMin(Node& data=Node()) //删除最小元素,可以通过参数返回最小值
{
assert(_Size>0);
data = findMin();
_Heap[1] = _Heap[_Size--];
int i = 0;
percolate(1); //从根结点处开始下滑调整顺序
}
时间复杂度:O(lgN)
完整代码:
#pragma once
#include<cassert>
#include<vector>
template<typename T>
struct HeapNode
{
T _data;
size_t _key;
HeapNode()
{}
HeapNode(const T& data,size_t key)
:_data(data)
, _key(key)
{}
friend ostream& operator<<(ostream& os,const HeapNode<T>& heap)
{
os << heap._data;
return os;
}
friend bool operator>(const HeapNode<T>& h1,const HeapNode<T>& h2)
{
if (h1._key > h2._key)
return true;
else
return false;
}
friend bool operator<(const HeapNode<T>& h1, const HeapNode<T>& h2)
{
if (h1._key < h2._key)
return true;
else
return false;
}
};
template<typename T>
class BinaryHeap
{
typedef HeapNode<T> Node;
public:
BinaryHeap()
:_Heap(0)
, _Size(0)
{}
BinaryHeap(const vector<Node> &v)
:_Heap(v.size()+1) //为了便于计算,多开辟一个空间
, _Size(v.size())
{
for (int i = 1; i <=_Size; i++) //_Heap[0]不存放
{
_Heap[i] = v[i-1];
}
buildHeap();
}
void Insert(const Node& data)
{
_Heap.push_back(data);
_Size++;
int i = _Size;
int parent= 0;
for (; i/2>0; i /= 2) //i这点的结点要有父亲节点
{
parent = i / 2; //父亲结点的位置
if (_Heap[parent] < data) //如果父亲结点比插入的值小,则有序
{
break;
}
else //如果父亲结点比插入值大
{
_Heap[i] = _Heap[parent];
}
}
_Heap[i] = data;
}
void DeleteMin(Node& data=Node()) //删除最小元素,可以通过参数返回最小值
{
assert(_Size>0);
data = findMin();
_Heap[1] = _Heap[_Size--];
int i = 0;
percolate(1); //从根结点处开始下滑调整顺序
}
void MakeHeap() //置空堆
{
BinaryHeap<T> tmp;
swap(tmp._Heap ,_Heap);
_Size = 0;
}
bool IsEmpty()const //判断堆是不是为空
{
return _Size == 0;
}
const Node& findMin()const //查找堆中的最小元素
{
assert(_Size>0);
return _Heap[1];
}
protected:
void buildHeap() //恢复堆的顺序
{
for (int i =_Size; i > 0; i--)
{
percolate(i);
}
}
void percolate(int hole) //从hole开始下滑调整
{
int child=0;
Node tmp = _Heap[hole];
for (;hole*2<=_Size;hole=child) //如果有左孩子则下滑调整
{
child = hole * 2;
if (child !=_Size&&_Heap[child]> _Heap[child + 1]) //找出左右孩子中值最小的
child++;
if (tmp < _Heap[child]) //如果tmp小于孩子的值,则调整结束
{
break;
}
else //如果tmp大于等于孩子的值,则继续调整
{
_Heap[hole] = _Heap[child];
}
}
_Heap[child] = tmp;
}
protected:
vector<Node> _Heap;
int _Size;
};