堆是数据结构的一种,堆分为大堆和小堆
堆的概念:堆是一颗完全二叉树,通常情况下我们用数组来表示堆,在下面我用的是vector其实也就是数组;大堆(是一颗二叉树,它的特点是父亲节点大于它左右孩子);小堆(即就是它的父亲节点小于它的左右孩子);
堆的实现
这里我们采用适配器模式来实现大小堆,避免一些重复的代码;
首先我们的了解堆一般都那些接口,push、pop、size、top、empty;
堆的实现的核心算法:向下调整、向上调整
当我们在构建一个堆时的用到向下调整算法,删除时也会用到向下调整算法;当插入一个元素时会用到向上调整算法。
向下调整算法:假设一棵有三个节点二叉树,那么调整它的时候只需要选出他左右孩子中那个的最大或者最小,然后交换最大或最小的孩子节点和根节点(即向下调整);那么当它有多个节点时只需从它的最后一个非叶子节点开始倒着向下调整。
向上调整算法:当插入一个数时,插入的数只会影响和他同一路径的祖先节点,所以从它开始向根节点调整整(向上调整)。
代码实现堆——heap
#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;
template<class T>
struct Less
{
bool operator()(const T& l, const T& r)
{
return l < r;
}
};
template<class T>
struct Greater
{
bool operator()(const T& l, const T& r)
{
return l > r;
}
};
template<class T, class Compare = Less<T>>
class heap
{
public:
heap()
{}
heap(T* a, size_t n)
{
_a.resize(n);
for (size_t i = 0; i < n; i++)
{
_a[i] = a[i];
}
for (int j = (_a.size() - 2) / 2; j >= 0; j--)
{
AdjustDown(j);
}
}
void Push(const T& data)
{
_a.push_back(data);
AdjustUp(_a.size()-1);
}
void Pop()
{
assert(_a.size() != 0);
swap(_a[0],_a[_a.size()-1]);
_a.pop_back();
AdjustDown(0);
}
void print()
{
vector<int> ::iterator it = _a.begin();
while (it != _a.end())
{
cout << *it << " ";
it++;
}
cout << endl;
}
size_t Size()
{
return _a.size();
}
bool Empty()
{
return _a.empty();
}
const T& Top()
{
assert(!_a.empty());
return _a[0];
}
private:
void AdjustDown(int root)
{
int parent = root;
int child = 2 * parent+1;
Compare com;
while (child < _a.size())
{
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 = 2 * parent + 1;
}
else
break;
}
}
void AdjustUp(int child)
{
int parent = (child -1)/ 2;
Compare com;
while(child > 0)
{
if (com(_a[child], _a[parent]))
{
swap(_a[child], _a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
break;
}
}
public:
vector<T> _a;
};
优先级堆列
优先级队列的概念:如果我们给每个元素都分配一个数字来标记其优先级,不妨设较小的数字具有较高的优先级,这样我们就可以在一个集合中访问优先级最高的元素并对其进行查找和删除操作了。这样,我们就引入了优先级队列 这种数据结构。 优先级队列(priority queue) 是0个或多个元素的集合,每个元素都有一个优先权,对优先级队列执行的操作有(1)查找(2)插入一个新元素 (3)删除 一般情况下,查找操作用来搜索优先权最大的元素,删除操作用来删除该元素 。对于优先权相同的元素,可按先进先出次序处理或按任意优先权进行。
总的来说一句话:大小堆问题
代码实现——priority_queue
template<class T,class Compare>
class PriorityQueue
{
public:
void Push(const T& data)
{
_h.Push(data);
}
void Pop()
{
_h.Pop();
}
const T& Top()
{
return _h.Top();
}
public:
heap<T, Compare> _h;
};
TopK
TopK概念:海量数据中的前K个最大或者最小的数。
下面实现前K个最大的数
实现思路:我们要求一组数据中的前K个最大数需要一个小堆,先将数组中的前K个数据入到堆中进行建立小堆;然后将剩下的数与堆顶比较如果大于堆顶就交换,然后重新调整,这样最大数会慢慢的往树的下面移动,这样堆顶数将会这K个数里最小的一个,当这组数据全部都进入堆后,前K最大的数就在堆里。
代码实现TopK
void TopK(const vector<int>& v, const int& K)
{
assert(K<v.size());
vector<int> TopK;
TopK.resize(K);
for (int i = 0; i < K; i++)
{
TopK[i] = v[i];
}
for (int i = (K - 2) / 2; i >= 0; i--)
{
AdjustDown(TopK,i);
}
for (size_t i = K; i < v.size(); i++)
{
if (TopK[0] < v[i])
{
TopK[0] = v[i];
AdjustDown(TopK,0);
}
}
vector<int> ::iterator it = TopK.begin();
while (it != TopK.end())
{
cout << *it << " ";
it++;
}
cout << endl;
}
向下调整算法:
void AdjustDown(vector<int>& TopK, const size_t& root)
{
int parent = root;
int child = parent * 2 + 1;
while(child < TopK.size())
{
if (child + 1 < TopK.size())
{
if (TopK[child] > TopK[child + 1])
child++;
}
if (TopK[parent]>TopK[child])
{
swap(TopK[parent], TopK[child]);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
堆排序
概念:即就是用堆来完成排序工作
堆排序算法:将所有要排数据,入到一个堆里(升序用大堆,降序用小堆),然后进行交换,交换堆顶元素和最后一个元素(从第一次开始最后一个是数组的最后一个(这样最大或最小的就在数组的最后面),第二次交换的最后一个是数组的倒数第二个。。。。依次类推,当交换到第二个时,排序就完成了)。
代码实现———
void AdjustDown(int* a, int size, int root)
{
int parent = root;
int child = 2 * parent + 1;
while (child < size)
{
if (child + 1 < size)
{
if (a[child + 1] > a[child])
child++;
}
if (a[child + 1]>a[parent])
{
swap(a[parent], a[child]);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
void HeapSort(int* a, int size)
{
for (int i = (size - 2) / 2; i >= 0; i--)
{
AdjustDown(a,size,i);
}
int end = size - 1;
while (end > 1)
{
swap(a[end],a[0]);
AdjustDown(a, end, 0);
--end;
}
for (int i = 0; i < size; i++)
{
cout << a[i] << " ";
}
cout << endl;
}