一.堆的概念
堆是一种特殊的数据结构,相当于数组实现的一颗二叉树,但是和一般的二叉搜索树不同
1.堆分为最大堆(父结点的值必大于其孩子结点的值)和最小堆(父结点的值必小于其孩子结点的值),
而二叉搜索树中左孩子结点的值小于父节点的值,右孩子结点的值大于父节点的值。
2.堆采用数组形式存储,相较于二叉树的链表结构减少内存消耗
3.堆排序的时间复杂度可以稳定在O(nlogn),而当二叉树为非平衡状态时,相应操作的时间复杂度不能稳定
二.堆的应用
1.优先队列
优先队列的使用场景十分之广,比如此时此刻你的计算机操作系统正在处理很多的进程,进程与进程之间存在着优先执行关系,对应的每个进程都有一个优先级。
举个更简单的例子:云顶之弈中,很多英雄的技能会优先攻击血量最少的敌方英雄。LoL中也存在着类似的技能机制。实质上就是一个动态的优先队列。
2.索引堆
堆中的内容经过heapify后交换了位置,从而形成最大堆或者最小堆。对简单的数据内容进行交换空间、时间消耗还不大,但是若对于上述提到的进程、英雄单位这些对象,操作起来则会比较麻烦并且很多情况下我们并不想改变元素的位置。
索引堆就是为了解决这个问题,对于原数组中的元素不进行交换,而是额外创造一个对应于原数组的索引数组(int[]),索引数组中每个元素记录原数组中每个元素的索引,在索引数组中进行heapify交换索引位置,让索引生成最大堆或者是最小堆,从而对于原数组没有改变。
索引堆运用在优先队列、图的最小生成树等算法当中。
3.堆排序
根据最大堆、最小堆堆顶元素的性质,从而可以实现堆排序,具体实现如下。
三.cpp实现
1.最大堆
根结点以1开始
template<typename T>
class MaxHeap{
private:
T *arr;
int count;
int capacity;
//核心代码:shiftUp()、shiftDown()
void shiftUp(int count){ //迭代条件:该节点的值大于父结点的值 且该节点不为根结点
while(count>1 && arr[count/2]<arr[count]){
swap(arr[count/2],arr[count]);
count / =2;
}
}
void shiftDown(int index){
while(index*2<=count){ //迭代条件:某个结点存在左孩子结点
int i = index*2;
if(i+1<=count && arr[i]<arr[i+1])//如果右孩子存在 选择左右孩子结点中值最大的结点
i++;
if(arr[index]>arr[i])
break;
swap(arr[i],arr[index]);
index = i;
}
}
public:
MaxHeap(int n){
arr = new int[n+1];
count = 0;
capacity = n;
}
MaxHeap(int n,int *arr){ //param: a Random Array(int)、array.length
arr = new int[n+1];
for (int i = 0; i < n; ++i)
{
arr[i+1] = arr[i];
}
capacity=n;
count = n;
for (int i = n/2; i >0; --i) //n/2表示第一个非叶子结点的结点
{ //从n/2这个结点开始做shiftDown()操作 直至达到根结点
shiftDown(i);
}
}
void insert(T x){
assert(count+1<capacity);
arr[++count] = x;
shiftUp(count);
}
T extractMax(){
assert(count>=1);
T max = arr[1];
swap(arr[1],arr[count--]);
shiftDown(1);
return max;
}
};
2.最小堆
根结点从0开始:
#include"iostream"
#include"assert.h"
using namespace std;
template<typename Item>
class MinHeap
{
private:
/* data */
int count;
int capacity;
Item *data;
void shiftUp(int k){
while(k-1>0 && data[(k-1-1)/2]>data[k-1] ){
swap(data[k-1],data[(k-2)/2]);
k = k/2;
}
}
void shiftDown(int k){ //时间复杂度 logn
while(2*k+1<count){
int i = 2*k+1;
if(2*k+2<count && data[i]>data[i+1])
i++;
if(data[i]>=data[k])
break;
swap(data[i],data[k]);
k = i;
}
}
public:
MinHeap(int n){
data = new Item[n];
count = 0;
capacity = n;
}
void add(Item x){
assert(count+1<=capacity);
data[count++] = x;
shiftUp(count);
}
Item extractMin(){
assert(count>0);
Item k = data[0];
swap(data[0],data[--count]);
shiftDown(0);
return k;
}
bool empty(){
return count == 0;
}
~MinHeap(){
delete []data;
}
};
四.堆排序及性能测试
1.最大堆排序
template<typename T>
void shiftDown(T arr[],int n,int index){
//heapify条件 该节点至少存在左子树
while(2*index+1<n){
int i=2*index+1;
if(i+1<n && arr[i]<arr[i+1])
i++;
if(arr[index]>arr[i])
break;
swap(arr[index],arr[i]);
index = i;
}
}
template<typename T>
void heapSort(T arr[],int n){
//heapify
for(int i=(n-1-1)/2;i>=0;i--)
shiftDown(arr,n,i);
//将最大堆的堆顶交换至最后,再对剩余元素进行heapify
for(int i=n-1;i>0;i--){
swap(arr[0],arr[i]);
shiftDown(arr,i,0);
}
}
2.性能测试
n=50000 测试范围0~50000
测试随机生成以及完全有序两种情况