一、原理
这里所讲的堆是具有如下性质的完全二叉树:
- 大顶堆:每个结点的值都大于或等于其左右孩子结点的值;
- 小顶堆:每个结点的值都小于或等于其左右孩子结点的值。
图1 大顶堆 图2 小顶堆
如果按照层序遍历的方式给结点从1开始编号的话,那么结点之间满足如下关系:
二、算法(要求数据从小到大排则创建大顶堆,反之创建小顶堆)
以大顶堆为例讲解。
下面代码实现如何创建大顶堆以及如何实现大顶堆排序使得数据从小到大排列:
1、在函数adjustHeap中原理如下:(将子树的根结点与这个根节点和其左右孩子这三个数中最大的一个交换位置,从而实现大数在上面,小数在下面)
所以构建大顶堆只需要如下代码:
//构建大顶堆 for( i = (L->length+1)/2-2; i >= 0; i-- ) //从最后一个非叶结点开始(L->length + 1)/2 ,从0开始编号 { adjustHeap(L, i, L->length); //从0 开始的编号 }
2、用大顶堆实现从小到大排序原理如下:(将树的根结点与最后结点交换位置后,将最后一个结点移除(当然是虚拟的移除),然后再将根结点变成满足大顶堆的定义(即使根结点变成最大值),再将根节点与虚拟的最后结点交换位置,依次类推下去,.............)
所以排序代码如下:
//堆排序 for( i = L->length-1; i > 1; i-- ) //i代表每次调整根结点满足大顶堆定以后的根结点 { //1.交换根结点和最后的叶结点(除去已经交换的叶结点),使最后的叶结点是最大的 swap(L, 0, i); //传入的都是编号为多少的,从0开始的编号 //2.调整根结点满足大顶堆定义 adjustHeap(L, 0, i-1); //从1开始的编号 }
3、总体代码:
#include "Seqlist.h" #include<stdio.h> //思想是从以i为根结点向下将较小的向下移,最大的放在i的位置 void adjustHeap( Tseqlist* L, int i, int length ) //穿进来的是从0开始的编号 { int temp = *((int*) L->node[i]); //用于临时存放根结点当前值,以便交换 List_Node* temp2 = L->node[i]; int j = 0; for( j = 2*i+1; j<length; j = 2*j+1 ) //2*i+1为他的左孩子 {//循环的目的就是为了将较小的数向下移 if ((j<length-1)&& ( *((int*) L->node[j]) < *((int*) L->node[j+1]) )) //沿着较大数来寻找最大数,并且使得交换后的数据保持小的在下面],(j<length-1)必须要,防止错误 j++; if( temp < *((int*) L->node[j]) ) { L->node[i] = L->node[j]; //第一次循环是将最大值放在根结点上,之后是万一原来根结点的值小于孩子的孩子的值 i = j; //将I标记为孩子的,用来下一次交换 } if( temp >= *((int*) L->node[j]) ) break; //表明已经按照大数在前的原理了,没必要在循环 } //最后将最后交换的数据变成根结点 L->node[i] = temp2; } void swap( Tseqlist* L, int i, int j) { List_Node* temp = L->node[i]; L->node[i] = L->node[j]; L->node[j] = temp; } void sortHeap( Tseqlist* L ) { int i = 0; //构建大顶堆 for( i = (L->length+1)/2-2; i >= 0; i-- ) //从最后一个非叶结点开始(L->length + 1)/2 ,从0开始编号 { adjustHeap(L, i, L->length); //从0 开始的编号 } //堆排序 for( i = L->length-1; i > 1; i-- ) //i代表每次调整根结点满足大顶堆定以后的根结点 { //1.交换根结点和最后的叶结点(除去已经交换的叶结点),使最后的叶结点是最大的 swap(L, 0, i); //传入的都是编号为多少的,从0开始的编号 //2.调整根结点满足大顶堆定义 adjustHeap(L, 0, i-1); //从1开始的编号 } if( 1==i ) { if(*((int*) L->node[i]) < *((int*) L->node[0])) swap(L,0,i); } }