堆是一棵完全二叉树。
堆按照关键字的设定,分大顶堆和小顶堆,大顶堆指父亲节点关键字比儿子节点都要大,小顶堆指父亲节点关键字比儿子节点要小。
数组实现堆的思路:
设数组arr[];
在arr[0]处设立一个哨兵,大顶堆则赋一个大的数,小顶堆则赋一个小的数。
1号节点放置在数字下标为1的位置,其他的以此类推。
1号节点的左儿子是2,右儿子是3,根据二叉树的特性,以此类推,假设一个节点的编号为i,则左儿子编号为i*2,右儿子编号为i*2+1.
元素的插入:
1)由于使用数组来实现堆,那么设置一个计数Size来记录当前插入的是第几个元素。(Size根据个人喜好初始化为0或者-1, 我这里初始化为0)
2)插入一个新元素时,为了保证树的特性,我们要在数组下标为Size+1的位置存入新的元素,Size++; 脑补一发树的模样,我们总是逐层从左到右的插入元素,这样就能保证不破坏二叉树的结构。
3)插入一个元素,要进行上滤,上滤时把不满足优先条件的节点的权值进行改变,这个操作类似于一趟插入排序,详情见代码。
删除堆顶元素:
1)删除堆顶元素,为了保证树的结构,我们将编号最大(即最后一个节点)的叶子结点取出来,Size减1,将最后一个节点当初堆顶元素,执行下滤,下滤存在分支选择,根据优先级设置,大顶堆选择权值较大的分支,小顶堆相反。
2)下滤和上滤的操作差不多,都与插入排序中的寻找位置,其他元素挪位这一操作类似,主要差别在于多了一个分支选择。
时间复杂度:
插入n个节点花费O(1)的平均时间和O(log n)的最坏情形时间,总的运行时O(n)运行时间
删除堆顶元素的最坏时间是O(log n)
代码:
#include <iostream>
using namespace std;
const int CAPACITY = 10005;
const int MIN = -0x3f3f3f3f;
const int MAX = 0x3f3f3f3f;
class Heap
{
public:
int *arr, capacity, Size;
public:
Heap(){
arr = new int[CAPACITY];
capacity = CAPACITY;
arr[0] = MIN;
Size = 0;
}
~Heap(){
if(arr!=NULL)
delete []arr;
}
bool IsFull(){
return capacity<=Size;
}
bool IsEmpty(){
return !Size;
}
int GetTop(){
return arr[1];
}
void Insert(int x){
if(IsFull()) return;
int i;
for(i=(++Size); arr[i>>1]>x; i>>=1){
arr[i] = arr[i>>1];
}
arr[i] = x;
}
void DeleteMin(){
if(IsEmpty()) return;
int Last = arr[Size--], i, child;
for(i=1; (i<<1)<=Size; child=i){
child = i<<1;
if(child<Size && arr[child]>arr[child+1]) child++;
if(Last > arr[child]) arr[i] = arr[child];
else break;
}
arr[i] = Last;
}
};
int main(){
int n, m;
int input[5] = {46, 23, 26, 24, 10};
Heap heap;
for(int i=0; i<5; i++){
heap.Insert(input[i]);
}
for(int i=1; i<=5; i++)
cout<<heap.arr[i]<<" ";
cout<<endl;
return 0;
}
未完待续