一、二叉堆简介:
二叉堆故名思议是一种特殊的堆,二叉堆具有堆的性质(父节点的键值总是大于或等于(小于或等于)任何一个子节点的键值),二叉堆又具有二叉树的性质(二叉堆是完全二叉树或者是近似完全二叉树)。当父节点的键值大于或等于(小于或等于)它的每一个子节点的键值时我们称它为最大堆(最小堆),或者是大根堆(小根堆)。
二叉堆多数是以数组作为它们底层元素的存储。
假如根节点在数组中的索引是1,那么根结点的左右子节点在数组中的位置是 2,3;存储在第n个位置的父节点,它的左右子节点在数组中的存储位置为2n与2n+1。
假如根节点在数组中的索引是0,那么根结点的左右子节点在数组中的位置是 1,2。存储在第n个位置的父节点,它的左右子节点在数组中的存储位置为2n+1与2n+2。
例如:
在这个图中,数组中的第一个元素(下标为0)就没有使用。
二、基本算法:
在二叉堆中,有两个基本的辅助算法:Sift-up(元素上移调整) 和 Sift-down(元素下移调整)。
下面分别给出其伪代码:
三、大根堆
下面直接贴出大根堆的实现代码:(在这个实现中,
二叉堆中根结点在数组中的位置是从下标0开始的)
//最大堆的下滑调整
void siftdown(int *heap,int start,int m) //m表示堆元素个数
{ //注意数组下标是从0开始的
int i=start,j=2*i+1;
int temp=heap[i];
while(j<=m)
{
if(j<m&&heap[j]<heap[j+1]) j++; //如果右结点大于左节点
if(temp>=heap[j]) break;
else
{
heap[i]=heap[j];
i=j;
j=2*j+1;
}
}
heap[i]=temp;
}
//最大堆的上滑调整
void siftup(int *heap,int start)
{
int j=start,i=(j-1)/2;
int temp=heap[j]; //调整值
while(j>0)
{
if(heap[i]>=temp) break; //调整值小于等于其根植,结束
else
{
heap[j]=heap[i];
j=i;
i=(i-1)/2;
}
}
heap[j]=temp;
}
//向最大堆插入一个元素x
bool insert(int *heap,int &n,int x) //将x插入到最大堆中
{ //n表示堆元素个数 这里的n加一之后返回
heap[n]=x;
siftup(heap,n); //从插入位置开始向上调整
n++;
return true;
}
//删除堆顶元素
bool remove(int *heap,int &n,int &x) //删除堆顶(最大元素)x返回堆顶元素
{ //n表示堆元素个数 这里的n减一之后返回
x=heap[0];
heap[0]=heap[n-1]; //将堆中的最后一个元素转移到堆顶
n--;
siftdown(heap,0,n); //从堆根位置开始从上向下调整
return true;
}
//创建一个最大堆
void Creat_maxheap(int *arr,int n) //建立最大堆
//n表示堆元素个数
{
int currentsize=n; //当前堆大小
int currentpos=(currentsize-2)/2; //开始调整位置
while(currentpos>=0)
{
siftdown(arr,currentpos,currentsize-1);
currentpos--;
}
}
//堆排序,利用大根堆从小到大排序数组
void HeapSort(int *heap,int n)
{
for(int i=(n-2)/2;i>=0;i--) //将数组先转换成为堆(即建堆),这里是大根堆
siftdown(heap,i,n-1);
for(int i=n-1;i>=1;i--) //排序
{
swap(heap[0],heap[i]);
siftdown(heap,0,i-1);
}
}
其中需要注意的是堆排序,在上面的堆排序中,需要排序的数组作为参数直接传入方法中,在堆排序方法中会执行建堆过程。
下面给出一个堆排序的例子:
int num[8]={53,17,78,9,45,65,87,23};
int n=8;
cout<<"排序前:";
for(int j=0;j<n;j++) cout<<num[j]<<" ";
cout<<endl;
HeapSort(num,n);
cout<<"排序后:";
for(int j=0;j<n;j++) cout<<num[j]<<" ";
cout<<endl;
从运行结果可以知道,
利用最大堆进行排序的结果是非降序的。
四、小根堆
对照上文中的大根堆的实现代码,很快我们可以写成小根堆的实现代码如下:(注意:小根堆的根结点元素在数组中的位置也是从0开始的)
//最小堆的下滑调整
void siftdown(int *heap,int start,int m) //m表示堆元素个数
{
int i=start,j=2*i+1;
int temp=heap[i];
while(j<=m)
{
if(j<m&&heap[j]>heap[j+1]) j++;
if(temp<heap[j]) break;
else
{
heap[i]=heap[j];
i=j;
j=2*j+1;
}
}
heap[i]=temp;
}
//最小堆的上滑调整
void siftup(int *heap,int start)
{
int j=start,i=(j-1)/2;
int temp=heap[j];
while(j>0)
{
if(heap[i]<=temp) break;
else
{
heap[j]=heap[i];
j=i;
i=(i-1)/2;
}
}
heap[j]=temp;
}
//建立最小堆
void Creat_minheap(int *arr,int n) //n表示堆元素个数
{
int currentsize=n; //当前堆大小
int currentpos=(currentsize-2)/2;
while(currentpos>=0)
{
siftdown(arr,currentpos,currentsize-1);
currentpos--;
}
}
//插入一个元素
bool insert(int *heap,int &n,int x) //将x插入到最小堆中
{ //n表示堆元素个数 这里的n加一之后返回
heap[n]=x;
siftup(heap,n);
n++;
return true;
}
//删除堆顶元素
bool remove(int *heap,int &n,int &x) //删除堆顶(最小元素)x返回堆顶元素
{ //n表示堆元素个数 这里的n减一之后返回
x=heap[0];
heap[0]=heap[n-1];
n--;
siftdown(heap,0,n);
return true;
}
//堆排序,利用小根堆从大到小排序数组
void HeapSort(int *heap,int n)
{
for(int i=(n-2)/2;i>=0;i--) //将数组(表)转换成为堆(建堆),这里是小根堆
siftdown(heap,i,n-1);
for(int i=n-1;i>=0;i--) //排序
{
swap(heap[0],heap[i]);
siftdown(heap,0,i-1);
}
}
同样我们还是注意到这里的堆排序:
同样执行上面的一个堆排序例子:
int num[8]={53,17,78,9,45,65,87,23};
int n=8;
cout<<"排序前:";
for(int j=0;j<n;j++) cout<<num[j]<<" ";
cout<<endl;
HeapSort(num,n);
cout<<"排序后:";
for(int j=0;j<n;j++) cout<<num[j]<<" ";
cout<<endl;
运行结果:
从运行结果可以知道,
利用最小堆进行排序的结果是非升序的。