堆的定义
堆是一棵完全二叉树,并且树中的每一个结点的值都不小于(或者不大于)其左右孩子结点的值。分为两种:大顶堆:父亲结点的值不小于(大于或者等于)其左右孩子结点的值。小顶堆:父亲结点的值不大于(小于或者大于)其左右孩子结点的值。堆一般借助于优先队列来实现(前面已经写过了优先队列的文章),今天我们将会以数组实现大顶堆为例。
1. 首先实现向下调整函数:
总是将结点x和它的左右孩子(如果存在的话)进行比较,如果孩子中存在比x结点更大的值,就将其中那个值更大的孩子结点和x交换。交换之后,要接续将x和新的孩子结点进行比较,一直到x结点的值大于或等于孩子结点的值或者x已经不存在孩子结点为止。
//向下调整建堆函数。
void downadjust(int heap[],int adjust,int last){ //heap[]为实现堆的数组,adjust是待调整结点的下标,last为最后一个数组元素的小标。
int i=adjust,j=2*i; //i为工作下标,j是预调整结点的左孩子。
while(j<=last){
if(j+1<=last&&heap[j+1]>heap[j]){ //选择左右孩子结点值更大的那一个
j++;
}
if(heap[j]>heap[i]){ // 如果左右孩子中最大的那个都比待调整结点要大
swap(heap[i],heap[j]); //交换两个结点的值
i=j; //i更新,始终记录待调整节点的下标
j=2*i; //j更新为i的左孩子
}
else{ //如果待调整结点的值比左右孩子结点都要大,那么停止。
break;
}
}
}
2 建堆函数:
假设共有n个结点,那么根据完全二叉树的性质很容易想到在[1,n/2]的范围内的结点都是非叶子结点,数组下标超过n/2的都是叶子结点。我们选择从n/2的结点开始倒着往上枚举。因为这样每次调整完一个结点后,之前以该节点为根的子树就一定是一个大根堆了,根节点大于孩子结点,那么之后再调整该根节点的父亲结点时,只要调整到这一步就直接使用就好了,保证了每个结点都是以其作为根节点子树的最大值。
void creatheap(int heap[],int n){ //堆的建立
for(int i=n/2;i>=1;i--){ //从最后一个非叶子结点进行
downadjust(heap,i,n);
}
}
3 删除堆顶元素:
删除堆顶元素后为了保证剩下的结构仍然是堆结构,那么可以将最后一个元素覆盖原来的堆顶元素,然后再依次向下调整结构即可。
void del(int heap[],int n){ //从堆中删除结点。 堆顶元素出去。 n为结点的个数
heap[1]=heap[n]; //用最后一个元素将第一个元素(堆顶元素弹出)覆盖,然后从上往下调整
n--;
downadjust(heap,1,n);
}
4.向上调整和插入函数:
插入元素时,首先将其插入到最末尾的位置,然后对其进行向上调整。每次将该新结点x与其父亲结点比较,如果x结点更大,那么将x结点和其父亲结点进行交换,直至x达到堆顶或者x小于父亲结点。
void upadjust(int heap[],int last){ //添加一个元素,首先将其添加到堆的最后一个位置,然后向上调整即可
int i=last,j=i/2; //i记录最后一个元素(即待调整元素)的下标,j记录了i的父亲结点的下标
while(j>=1){
if(heap[i]>heap[j]){ //如果待调整元素的值要大于其父亲节点的值,那么交换
swap(heap[i],heap[j]);
i=j;
j=i/2; //更新i,j
}
else{
break;
}
}
}
void insert(int heap[],int n,int x){ // 将新元素x添加到最后一个位置。然后向上调整
heap[++n]=x;
upadjust(heap,n);
}
堆排序
堆排序就是使用堆结构对一个序列进行排序。我们以递增排序为例。
最容易想到的就是已经对序列建立了堆结构之后,不断的输出堆顶元素,然后调整堆结构,直至整个堆只有一个元素为止。这样做的话需要另开辟数组来记录最后的排序过程。
为了节约空间,我们可以换一种思路:假设由n个结点,从后往前进行遍历,每次将堆顶元素和最后一个元素进交换,然后堆[1,n-1]进行结构调整,那么堆低的元素就是最大的元素。不难看出,当访问到i位置时只需将堆顶元素和i号位的元素交换,之后对[1,i-1]进行结构调整。
void heapsort(int heap[],int n){
creatheap(heap,n); //建堆
for(int i=n;i>=1;i--){ //从后往前遍历
swap(heap[1],heap[i]); //交换堆顶和i号位的结点
downadjust(heap,1,i-1); //调整
}
}
int main()
{
int heap[13]={0,3,6,1,9,3,13,56,77,12,45,43,65};
heapsort(heap,12) ;
for(int i=1;i<=12;i++){
printf("%d ",heap[i]);
}
}
本文介绍了堆的基本概念,包括大顶堆和小顶堆,并详细讲解了如何用数组实现大顶堆的创建、调整、删除和插入操作。特别地,文章深入探讨了堆排序的原理,通过不断调整堆结构并交换堆顶元素,实现序列的排序,既节省空间又高效。
1万+

被折叠的 条评论
为什么被折叠?



