堆的相关操作
堆是用完全二叉树存储一系列值,完全二叉树的父节点都比子节点小则成为最小堆,反之则称为最大堆
根据完全二叉树性质
如果子节点的编号为i,父节点为i/2(向下取整),左子节点为2*i,右子节点为2*i+1
树的最大层数为logN
相关操作
如果在堆顶插入一个新元素,要维护最小堆,则需要将该元素进行向下移动,保证父节点都小于子节点
向下调整
void shiftdown(int i){//i为节点编号
int t,flag = 0;//flag标记是否需要继续向下调整
while(2*i<=n && flag == 0){//至少有左子节点且flag=0
//与左子节点比较
if(h[i] > h[2*i]){
t = 2*i;
}else{
t = i;
}
//如果存在右子节点,比较
if(2*i+1<=n){
if(h[t] > h[2*i+1]){
t = 2*i + 1;
}
}
//如果不是自己,子节点无比它小的节点
if(t!=i){
//交换
swap(t,i);
i = t;//更新i为刚才交换的子节点,继续向下调整
}else{
flag = 1;
}
}
}
如果需要新增一个值,则插入队列的尾部,再进行相上调整,即如果父节点比该节点大则与之交换
相上调整
void shiftup(int i){
int flag = 0;//记录是否需要相上调整
if(i == 1) return;//如果为根节点则直接退出
while(i != 1 && flag == 0){//不在堆顶且需要向上调整
if(h[i] < h[i/2]){
swap(i,i/2);
}else{
flag = 1;
}
i = i/2;//更新节点
}
}
建立堆(最小堆)
- 直接边插入队列边调整
n=0;
for(int i=1;i<=m;i++){
n++;
h[n] = a[i]//或者cin>>h[n];
shiftup(n);
}
- 先将所有值入队,再历遍每一个除叶节点外的所有节点,与子节点进行调整
void create(){
int i;
for(int i=n/2;i>=1;i--){
shiftdown(i);
}
}
堆排序
例如从小到大排序,先建立最小堆,然后一次删除堆顶并将其存入数组中,然后维护堆,继续此操作
int deletemin(){
int t;
t = h[1];//临时变量存储堆顶
h[1] = h[n];//将堆的最后一个值放入堆顶
n--;//队列size-1
shiftdown(1);//堆顶向下调整
return t;
}
完整代码
#include <iostream>
using namespace std;
int h[101];//存放堆的数组
int n;//堆的大小
void swap(int x,int y){
int t;
t = h[x];
h[x] = h[y];
h[y] = t;
}
void shiftdown(int i){//i为节点编号
int t,flag = 0;//flag标记是否需要继续向下调整
while(2*i<=n && flag == 0){//至少有左子节点且flag=0
//与左子节点比较
if(h[i] > h[2*i]){
t = 2*i;
}else{
t = i;
}
//如果存在右子节点,比较
if(2*i+1<=n){
if(h[t] > h[2*i+1]){
t = 2*i + 1;
}
}
//如果不是自己,子节点无比它小的节点
if(t!=i){
//交换
swap(t,i);
i = t;//更新i为刚才交换的子节点,继续向下调整
}else{
flag = 1;
}
}
}
void shiftup(int i){
int flag = 0;//记录是否需要相上调整
if(i == 1) return;//如果为根节点则直接退出
while(i != 1 && flag == 0){//不在堆顶且需要向上调整
if(h[i] < h[i/2]){
swap(i,i/2);
}else{
flag = 1;
}
i = i/2;//更新节点
}
}
void create(){
int i;
for(int i=n/2;i>=1;i--){
shiftdown(i);
}
}
int deletemin(){
int t;
t = h[1];//临时变量存储堆顶
h[1] = h[n];//将堆的最后一个值放入堆顶
n--;//队列size-1
shiftdown(1);//堆顶向下调整
return t;
}
int main()
{
int num;
cin>>num;
for(int i=1;i<=num;i++){
cin>>h[i];
}
n = num;
//建立堆
create();
//从小到大输出,即连续删除n次
for(int i=1;i<=num;i++){
cout<<deletemin()<<" ";
}
return 0;
}
堆排序的优化方法
可以建立最大堆,将对立第n个值与堆顶交换,则队列中第n个为最大的值,然后将堆顶进行调整,再依次进行上述步骤,最后堆大小为1时停止,此时原理的队列即为从小到大的顺序队列
void heapsort(){
while(n > 1){
swap(1,n);
n--;
shiftdown(1);
}
}
完整代码
注意此时要建立最大堆才能从小到大排序
#include <iostream>
using namespace std;
int h[101];//存放堆
int n;//堆大小
void swap(int x,int y){
int t;
t = h[x];
h[x] = h[y];
h[y] = t;
}
void shiftdown(int i){
int t,flag = 0;
while(2*i <= n && flag == 0){
if(h[i] < h[2*i]){
t = 2*i;
}else{
t = i;
}
if(2*i+1<=n){
if(h[t] < h[2*i+1]){
t = 2*i + 1;
}
}
if(t!=i){
swap(t,i);
i = t;
}else{
flag = 1;
}
}
}
void create(){
for(int i=n/2;i>=1;i--){
shiftdown(i);
}
}
//堆排序
void heapsort(){
while(n > 1){
swap(1,n);
n--;
shiftdown(1);
}
}
int main()
{
int num;
cin>>num;
for(int i=1;i<=num;i++){
cin>>h[i];
}
n = num;
create();
heapsort();
for(int i=1;i<=num;i++){
cout<<h[i]<<" ";
}
return 0;
}
总结
支持插入元素和寻找最大/最小值元素的数据结构为优先队列
同样利用堆可以求第K大的数
只需要建立K大小的最小堆,堆顶即为结果
例如,10个数求第3大的数,任选1-3建立堆,历遍4-10,如果数比堆顶小,则放弃,如果数比堆顶大,则将其与当前堆顶替换,再进行维护,以此类推
利用堆可以求第K小的数
建立K个大小的最大堆,堆顶为求的第K小的数