堆是数据结构的一种,可用于排序和海量数据处理中的TopK问题
堆的逻辑结构:是一颗完全二叉树,由于是是一颗完全二叉树我们就可通过数组来实现他的存储方式。
上面是一颗完全二叉树,分别为树状存储、数组存储。
堆的性质:
1.堆分为大堆和小堆。
2.大堆的对顶大于它的左子树和右子树(小堆相反)。
3.左右子树分别为大堆(或者小堆)。
4.为方便计数满足数组的存储方式有以下规律:
a.lchild(左孩子下标) = parent2+1;
b.rchild(右孩子下标) = parent2+2;
c.parent(父亲节点下标) = (child-1)/2;
有了上述性质对堆在数组中表示和操作就方便多了
对堆有一下操作:
1.建堆(利用向下调整)
2.删除(利用向下调整)
3.插入(利用向上调整)
在讲建堆之前先说明一下向下调整算法(以大堆为实例)
建堆:通过了解到向下调整算法建堆的步骤就很简单了(从第一个非叶子节点开始调用向下调整算法)
删除:实际就是删除堆顶元素,只需要交换堆顶和最后一个元素(即数组末尾)然后删除最后一个元素。对堆顶进行一次向下调整即可。
插入:实际上是在堆尾插入一个元素而非任意位置。插入只会影响一条路径上的元素,故只需调用向上调整算法即可。
向上调整算法:
通过上面分析,整体思路已经有了,下面是实现的代码(由于为了说明TopK问题和堆排问题,用到堆,所以将一些接口放开以便TopK和堆排调用):
#include<iostream>
using namespace std;
#define MAX 100
//仿函数
template<class T>
class Greate{
public:
bool operator()(const T& L, const T& R)
{
return L > R;
}
};
template<class T>
class Little{
public:
bool operator()(const T& L, const T& R)
{
return L < R;
}
};
template<class T,class com>
class Heap
{
public:
Heap(int size)
:_size(size)
,heap(new T[MAX])
{ }
void CreatHeap(int* array,int size)
{
for (int i = 0; i <size; i++)
{
heap[i] = array[i];
}
//建堆
for (int i = (_size - 2) / 2; i >= 0; i--)
{
AdjustDown(heap, i);
}
}
void InsertHeap(int num)
{
heap[_size] = num;
_size += 1;
AdjustUp(heap, _size-1);
}
bool EarseHeap()
{
if (_size)
{
swap(heap[0], heap[_size - 1]);
_size--;
AdjustDown(heap, 0);
return true;
}
return false;
}
//向下调整
void AdjustDown(int* h, int root)
{
int parent = root;
int lchild = parent * 2 + 1;
while (lchild < _size)
{
if (lchild + 1 < _size && com(h[lchild+1] , h[lchild]))
lchild++;
if (com(h[lchild], h[parent]))
{
swap(h[parent], h[lchild]);
parent = lchild;
lchild = parent * 2 + 1;
}
else
break;
}
}
//向上调整
void AdjustUp(int* h, int child)
{
int parent = (child - 1) / 2;
while (parent >= 0)
{
if (com(h[child], h[parent]))
{
swap(h[child], h[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
break;
}
}
void print()
{
for (int i = 0; i < _size; i++)
{
cout << heap[i] << " ";
}
cout << endl;
}
public:
T _size;
T* heap;
com com;
};
为了实现大小堆,这里用到了仿函数,可以通过模板来选择构建大堆或小堆。
上述堆已经实现了,下面我们借助堆来解决一些实际问题:TopK、堆排
1.TopK问题描述:有百万数据需要我们处理,即找到这百万数据中的前K个最大的或者最小的。
在此之前我将一个现实生活的实例:比如我们想进入全国学霸的前二十个,我们需要做神魔,其实我们的分数只需要比前二十个中成绩最低的同学高就可以进入学霸排行榜了。现在我们可以类似的套用这种模式解决我们的TopK问题了吧!
步骤(以前k个最大的为例):1.建一个大小为k小堆(百万个中1~k来建立)2.从第k+1个数据开始和堆顶比较比堆顶大,入堆,然后从根开始向下调整,最后堆里面剩下的就是最大的k个。(介于方法一样,这里就不讲前k个最小的了)。
代码实现:
void TopK(int array[],int size,int K)
{
Heap<int, Little<int>> h1(3);
h1.CreatHeap(array, 3);
for (int i = 3; i < size; i++)
{
if (array[i]>h1.heap[0])
h1.heap[0] = array[i];
h1.AdjustDown(h1.heap, 0);
}
h1.print();
}
void testTopK()
{
int array[] = { 1000, 99, 78, 53, 17, 78, 9, 45, 65, 87, 23, 31 };
int k = 3;
TopK(array, sizeof(array) / sizeof(array[0]), k);
}
说明:为了直观看到效果,我在堆里实现了打印的接口
堆排:利用堆排序的算法。
还是画图比较好理解:
代码实现:
void HeapSort(int array[],int size)
{
Heap<int, Greate<int>> Gheap(size);
Gheap.CreatHeap(array, size);
//堆排序
while (Gheap._size-1)
{
swap(Gheap.heap[0], Gheap.heap[Gheap._size - 1]);
Gheap._size--;
Gheap.AdjustDown(Gheap.heap,0);
}
Gheap._size = size;
Gheap.print();
}
void test_HeapSort()
{
int array[] = { 1000, 99, 78, 53, 17, 78, 9, 45, 65, 87, 23, 31 };
int size = sizeof(array) / sizeof(array[0]);
HeapSort(array, size);
}