专栏目录(数据结构与算法解析):https://blog.youkuaiyun.com/qq_40344524/article/details/107785323
堆(完全二叉树)
定义
堆是计算机科学中一类特殊的数据结构的统称。n个元素的序列{k1,k2,ki,…,kn}当且仅当满足(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4…n/2)时,称之为堆。若将和此次序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)。
堆通常是一个可以被看做一棵树(完全二叉树)的数组对象。根据结点特征可以将堆分为大顶堆和小顶堆两种,将根节点最大的堆叫做最大堆或大顶堆,根节点最小的堆叫做最小堆或小顶堆。
性质
1.堆中某个节点的值总是不大于或不小于其父节点的值。
2.堆总是一棵完全二叉树。
堆的结构
typedef struct HNode * Heap; //堆的类型定义
struct HNode{
ElementType *Data; //存储元素的数组
int Size; //堆中当前元素的个数
int Capacity; //堆的最大容量
};
typedef Heap MaxHeap; //最大堆
typedef Heap MinHeap; //最小堆
基本操作
1、堆的构造
2、堆的初始化
3、插入节点
4、删除节点
5、堆排序
代码实现
接下来通过C语言实现堆的基本操作,以下讲解均以原始数据为a[]={4,1,3,2,16,9.10.14.8.7}为基础,实现的操作在最大堆上进行。
1、堆的构造
数据结构如下:
typedef struct HNode * Heap; //堆的类型定义
struct HNode{
ElementType *Data; //存储元素的数组
int Size; //堆中当前元素的个数
int Capacity; //堆的最大容量
};
typedef Heap MaxHeap; //最大堆
2、堆的初始化
基本思想:首先将每个叶子结点视为一个堆,再将每个叶子结点于其父节点一起构成一个包含更多结点的堆。所以在构造堆的时候,首先需要找到最后一个结点的父节点,从这个节点开始构造最大堆,直到该节点前面的所有分支节点都处理完毕,具体过程可用如下代码表示。
void MaxHeapInit(MaxHeap &H)
{
for(int i=H.HeapSize/2;i>=1;i--)
{
H.heap[0]=H.heap[i];
int son=i*2;
while(son<H.HeapSize)
{
if(son<H.HeapSize&&H.heap[son]<H.heap[son+1])
son++;
if(H.heap[i]>H.heap[son])
break;
else if(son<H.heapSize&&H.heap[son]>H.heap[son+1]
{
H.heap[son/2]=H.heap[son];
son*=2;
}
}
H.heap[son/2]=H.heap[0];
}
}
注意: 在二叉树中,若当前节点的下标为 i, 则其父节点的下标为 i/2,其左子节点的下标为 i2,其右子节点的下标为i2+1;
3、插入节点
最大堆中插入节点,先在堆末尾插入该节点,然后按照堆的初始化过程将该节点放入到合适的位置。
void MaxHeapInsert(MaxHeap &H, EType &x)
{
if(H.HeapSize==H.MaxSize) return false;
int i=++H.HeapSize;
while(i!=1&&x>H.heap[i/2])
{
H.heap[i]=H.heap[i/2];
i/=2;
}
H.heap[i]=x;
return true;
}
4、删除节点
将最大堆的最后一个节点放到根节点,然后删除最大值,然后再把新的根节点放到合适的位置
void MaxHeapDelete(MaxHeap &H, EType &x)
{
if(H.HeapSize==0) return false;
x=H.heap[1];
H.heap[0]=H.heap[H.HeapSize--];
int i=1, son=i*2;
while(son<H.HeapSize)
{
if(son<H.HeapSize&&H.heap[son]<H.heap[son+1])
son++;
if(H.heap[i]>H.heap[son])
break;
H.heao[i]=H.heap[son];
i=son;
son*=2;
}
H.heap[i]=H.heap[0];
return true;
}
5、堆排序
1、将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;2、将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;3、重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。最后,就得到一个有序的序列了。
#include<iostream>
using namespace std;
void swap(int &a, int &b)
{
int temp=a;
a=b;
b=temp;
}
void quick_build(int a[], int len, int root)
{
int left=root*2+1;
int flag=left;
while(left<len)
{
int right=left+1;
while(right<len&&a[right]>a[left])
flag=right;
}
if(a[root]<a[flag])
{
swap(a[root],a[flag]);
heap_build(a,len,flag);
}
void quick_sort(int a[], int len)
{
for(int i=len/2;i>0;i--)
heap_build(a,len, i);
for(int j=len-1;j>0;j--)
{
swap(a[0],a[j]);
heap_build(a,0,j);
}
}
总结
通过上述学习我们知道堆的本质就是一棵二叉树,由于堆的特殊性质我们可以用它来简化排序算法,而且堆是实现抽象数据类型优先队列的一种方式,优先队列有很广泛的应用,例如Huffman编码中使用优先队列利用贪心算法构建最优前缀编码树。
堆的概念就先讲到这里,下一节我们学习另一个特殊的数据结构——图结构。
以上是我的一些粗浅的见解,有表述不当的地方欢迎指正,谢谢!
表述能力有限,部分内容讲解的不到位,有需要可评论或私信,看到必回…