二叉堆是完全二叉树或者是近似完全二叉树。
二叉堆满足二个特性:
1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆。下图展示一个大根堆:
接下来是一个小根堆:
堆的操作——插入删除
下面先给出《数据结构C++语言描述》中最小堆的建立插入删除的图解
这样有什么好处呢?好处就是能方便地把指针省略掉,用一个简单的数组来表示一棵树,如图:
下面接着介绍一个维护方法:
假设要在这个二叉堆里入队一个单元,键值为2,那只需在数组末尾加入这个元素,然后尽可能把这个元素往上挪,直到挪不动,经过了这种复杂度为Ο(logn)的操作,二叉堆还是二叉堆。
那如何出队呢?也不难,看图:
堆排序:
一个动态的堆排序过程
最差时间复杂度 | ![]() |
---|---|
最优时间复杂度 | ![]() |
平均时间复杂度 | ![]() |
最差空间复杂度 | ![]() ![]() |
下面是一个堆排序的代码:
#include <iostream>
#include <stdlib.h>
int data[2000],n,len;
FILE *fin,*fout;
void change(int &a,int &b){
//快速交换2个数
a^=b;b^=a;a^=b;
}
void sift(int a){//维护堆过程
int l=a*2;
if (l>len) return ;
if (l+1<len && data[l+1]>data[l]) l++;//选取左右子节点中最大的一个
if (data[l]>data[a]) {//比较子节点与父节点
change(data[a],data[l]);
sift(l);//维护子节点堆
}
}
void heap_sort(){
//堆排序主过程
int i;len=n;
for (i=n/2;i>0;i--)
sift(i);//维护以i为父节点的堆,将数据中最大的交换到data[1],即堆顶
for (i=1;i<=n;i++){
change(data[1],data[len]);//交换堆顶和堆底元素
len--;//减少堆元素
sift(1); //整理堆
}
}
void input(){
//读入元素
int i;
fscanf(fin,"%d",&n);//表示读入的数为n
for (i=1;i<=n;i++)
fscanf(fin,"%d",&data[i]);//将n个数读入到data数组中,注意这里数组从1开始
fclose(fin);
}
void output(){
//输出排序后结果
int i;
for (i=1;i<=n;i++)
fprintf(fout,"%d ",data[i]);
fclose(fout);
}
int main(){
fin=fopen("heapin.txt","r");
fout=fopen("heapout.txt","w");
input();
heap_sort();
output();
return 0;
}