前言
堆的数据结构是一个完全二叉树,其能实现优先队列的功能,主要的作用是能够动态地维护一个有关优先级的序列,使其在插入和删除都能在 O ( l o g n ) O(logn) O(logn)时间内完成,查询能在 O ( 1 ) O(1) O(1)完成。
性质
对于小根堆而言,这个二叉树形的结构只要满足所有的父节点都小于子节点就是小根堆,显然,这是个递归的定义。对于大根堆则是父节点大于子节点。
抽象
对于要操作一个二叉堆我们要实现以下几个接口:
- shift_Down:即下沉的操作,这个接口的功能是当让某个位置的元素下沉,使其这个位置为根的堆仍满足二叉堆的定义
- shift_Up:即上浮的操作,同理,让这个元素所属的树都满足二叉堆的定义,即如果是小根堆,父节点比该位置的结点大则要交换两个值,然后继续向上探测
- isEmpty:判断堆是否为空,即判断tot是否为0
- push:插入的操作,具体做法有三步:一,将结点加入最后使得二叉堆仍是一个完全二叉树的形状;二,结点数tot个数+1;三,将新插入的这个数上浮使得整个堆仍具有原来的性质
- pop:删除的操作,具体做法有三步:一,将最后一个元素替代第一个元素,使得二叉堆仍是一个完全二叉树的形状;二,结点数tot个数-1;三,将堆顶的这个数下沉使得整个堆仍具有原来的性质
- top:查询的操作,就是查堆顶元素即第一个元素,如果是小根堆就是最小的元素。
- heapify:构建堆的操作,比较暴力的做法是加入对一个空堆插入n个结点,但复杂度显然是 O ( n l o g n ) O(nlogn) O(nlogn)的,不能让我们满意,有个 O ( n ) O(n) O(n)的做法就是让最后一个非叶子结点的结点开始递归地构建自己地子堆,直到第一个元素,这样整个堆也就符合堆的定义了。至于为什么是 O ( n ) O(n) O(n)的可以百度一下,这里不是重点。关于为什么要从 ( t o t − 1 ) / 2 (tot-1)/2 (tot−1)/2开始往前做是因为这个位置的结点一定有孩子结点并且是最后一个,因为大于它的结点通过加1乘2的数字显然会大于等于tot。
- heap_sort:堆排序,首先交换堆首和堆尾,之后堆大小-1,接着把交换的那个数下沉。做n-1次得出来的序列就是一个从大到小(从小到大)的序列。复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
实现
#include <bits/stdc++.h>
using namespace std;
#define ElemType int
#define MAXN 1005
void swap(ElemType& a, ElemType& b) {
ElemType tmp = a;
a = b;
b = tmp;
}
//堆是否为空
bool isEmpty(ElemType heap[], int& tot) {
return tot <= 0 ? true : false;
}
//上浮,先记忆我们要上浮的值,找到空位后再把这个值填上去
void shift_Up(ElemType heap[], int& tot, int index) {
int fa, tmp = heap[index];
while(index) { //如果index本身是根就没有上浮的必要
fa = index - 1 >> 1; //找index的父亲结点
if(tmp < heap[fa]) heap[index] = heap[fa];
else break;
index = fa;
}
heap[index] = tmp;
}
//下沉,先记忆我们要下沉的值,找到空位后再把这个值填上去
void shift_Down(ElemType heap[], int& tot, int index) {
int son, tmp = heap[index];
while((index << 1 | 1) < tot) { //如果index没有左孩子就没有下沉的必要
son = index << 1 | 1; //左孩子
if (son+1 < tot && heap[son+1] < heap[son]) son++; //如果右孩子存在并且比左孩子小就取它
if (heap[son] >= tmp) break; //此时该位置就是我们要填的
else heap[index] = heap[son];
index = son; //下沉
}
heap[index] = tmp; //找到那个位置填下去
}
//插入
bool push(ElemType heap[], int& tot, int val) {
if(tot >= MAXN) return false;
heap[tot++] = val;
shift_Up(heap, tot, tot-1); //把新插入的结点上浮
return true;
}
//删除
bool pop(ElemType heap[], int& tot) {
if (isEmpty(heap, tot)) return false;
swap(heap[0], heap[--tot]);
shift_Down(heap, tot, 0); //把新的根下沉
return true;
}
//查询
bool top(ElemType heap[], int& tot, ElemType& val) {
if(isEmpty(heap, tot)) return false; //查询失败
val = heap[0];
return true;;
}
//构建
void heapify(ElemType heap[], int& tot) {
for(int i=(tot-1)/2; i>=0; i--)
shift_Down(heap, tot, i);
}
//堆排序
void heap_sort(ElemType heap[], int& tot) {
for(int i=tot-1; i>0; i--) {
swap(heap[0], heap[i]);
shift_Down(heap, i, 0);
}
}
递归下沉///
void shift_Down2(ElemType heap[], int tot, int CurRootIndex) {
if(CurRootIndex < tot) {
int fa = CurRootIndex; //存父节点
int left = CurRootIndex << 1 | 1;
int right = left + 1;
if (left < tot && heap[CurRootIndex] > heap[left])
CurRootIndex = left;
if (right < tot && heap[CurRootIndex] > heap[right])
CurRootIndex = right;
if (CurRootIndex ^ fa) { //存在下移的可能
ElemType tmp = heap[CurRootIndex];
heap[CurRootIndex] = heap[fa];
heap[fa] = tmp;
shift_Down2(heap, tot, CurRootIndex); //递归建堆
}
}
}
//构建2
void heapify2(ElemType heap[], int& tot) {
for(int i=(tot-1)/2; i>=0; i--)
shift_Down2(heap, tot, i);
}
//
int main() {
int val;
int arr[MAXN]={10,9,8,7,6,5,4,3,2,1}, tot=10;
heapify(arr, tot);
for(int i=0; i<tot; i++) cout << arr[i] <<' ' ; cout << endl;
if( top(arr, tot, val) == true) {
cout << "val = " << val << '\n';
cout << "tot = " << tot << '\n';
}
if( pop(arr, tot) == true) {
if( top(arr, tot, val) == true) {
cout << "val = " << val << '\n';
cout << "tot = " << tot << '\n';
}
}
if( push(arr, tot, 4) == true) {
for(int i=0; i<tot; i++) cout << arr[i] <<' ' ; cout << endl;
if( top(arr, tot, val) == true) {
cout << "val = " << val << '\n';
cout << "tot = " << tot << '\n';
}
}
heap_sort(arr, tot);
for(int i=0; i<tot; i++) cout << arr[i] <<' ' ; cout << endl;
return 0;
}