1、堆(Heap)是计算机科学中的一种特殊的树状数据结构:
若满足以下特性,则可称为堆:“给定堆中任意结点P和C若P是C的父结点,则P的值小于等于(或大于等于)C的值。” 若父结点的值恒小于等于子结点的值,则该堆称为最小堆(min heap);反之,若父结点的值恒大于等于子结点的值,则该堆称为最大堆(max heap)。堆中最顶端的那个结点称为根结点(rootnode),根结点本身没有父结点(parentnode)。平时在工作中,我们将最小堆称为小根堆或小顶堆,把最大堆称为大根堆或大顶堆,
2、假设我们有3,87,2,93,78,56,61,38,12,40共10个元素我们将这10个元素建成一棵完全二叉树
这里采用层次建树法,虽然只用一个数组存储元素,但是我们能将二叉树中任意一个位置的元素对应到数组下标上,我们将二叉树中每个元素对应到数组下标的这种数据结构称为堆,比如最后一个父元素的下标是N/2-1,也就是a[41,对应的值为78为什么是N/2-1?因为这是层次建立一棵完全二叉树的特性。 可以这样记忆:如果父结点的下标是dad,那么父结点对应的左子结点的下标值是2*dad+1。接着,依次将每棵子树都调整为父结点最大,最终将整棵树变为一个大根堆。
堆排序动画网址:https://www.cs.usfca.edu/~galles/visualization/HeapSort.html
除了快排,接着就是堆排会在初试中出大题,也会出选择题,所以重要!
3、代码实战步骤:
首先我们通过随机数生成10个元素,通过随机数生成,我们可以多次测试排序算法是否正确,然后打印随机生成后的元素顺序,然后通过堆排序对元素进行排序,然后再次打印排序后的元素顺序。
堆排序的步骤是首先把堆调整为大根堆,然后我们交换根部元素也就是 A[0]和最后一个元素,这样最大的元素就放到了数组最后,接着我们将剩余9个元素继续调整为大根堆,然后交换 A[0]和9个元素的最后一个,循环往复,直到有序。
核心代码:
void AdjustDown1(ElemType A[],int k,int len)
{
int dad=k;//父亲的下标
int son=2*dad+1;//左孩子的下标
while(son<len)
{
if(son+1<len && A[son]<A[son+1])//如果左孩子小于右孩子
{
son++;//拿右孩子
}
if(A[son]>A[dad])//比较孩子和父亲,如果孩子大于父亲,那么就进行交换
{
swap(A[son],A[dad]);//孩子和父亲进行交换
dad=son;//son重新作为dad去判断下面的字数是否符合大根堆
son=2*dad+1;
}else{
break;
}
}
}
void HeapSort1(ElemType A[],int len)
{
int i;
//就是把堆调整为大根堆
for(i=len/2-1;i>=0;i--)
{
AdjustDown1(A,i,len);
}
swap(A[0],A[len-1]);//交换根部元素和最后一个元素
for(i=len-1;i>1;i--)//i代表的是剩余的无序数的数组的长度
{
AdjustDown1(A,0,i);//调整说呢过于元素变为大根堆
swap(A[0],A[i-1]);//交换根部元素和无序树的数组的最后一个元素
}
}
完整代码:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
typedef int ElemType;
typedef struct{
ElemType *elem;//存储元素的起始地址
int TableLen;//元素个数
}SSTable;
void ST_Init(SSTable &ST, int len)
{
ST.TableLen = len;
ST.elem = (ElemType *) malloc(sizeof(ElemType)*ST.TableLen);//申请一块堆空间,当数组来使用int i;
srand(time(NULL));//随机数生成,每一次执行代码就会得到随机的 10个元素
int i;
for(i=0;i<ST.TableLen;i++)
{
ST.elem[i]=rand()%100;//生成的是 0-99之间
}
}
//打印数组中的元素
void ST_print(SSTable ST)
{
for (int i=0; i < ST.TableLen;i++)
{
printf("%3d", ST.elem[i]);
}
printf("\n");
}
//交换两个元素
void swap(int &a,int &b)
{
ElemType tmp;
tmp=a;
a=b;
b=tmp;
}
//王道书上调整某个父亲节点
void AdjustDown(ElemType A[],int k,int len)
{
int i;
A[0]=A[k];
for(i=2*k;i<=len;i*=2) {
if (i < len && A[i] < A[i + 1])//左子节点与右子节点比较大小
i++;
if (A[0] >= A[i])
break;
else {
A[k] = A[i];
k = i;
}
}
A[k]=A[0];
}
void BuildMaxHeap(ElemType A[],int len)
{
for(int i=len/2;i>0;i--)
{
AdjustDown(A,i,len);
}
}
//王道树上的堆排序
void HeapSort(ElemType A[],int len)
{
int i;
BuildMaxHeap(A,len);
for(i=len;i>1;i--)
{
swap(A[i],A[1]);
AdjustDown(A,1,i-1);
}
}
//将某个子树调整为大根堆
void AdjustDown1(ElemType A[],int k,int len)
{
int dad=k;//父亲的下标
int son=2*dad+1;//左孩子的下标
while(son<len)
{
if(son+1<len && A[son]<A[son+1])//如果左孩子小于右孩子
{
son++;//拿右孩子
}
if(A[son]>A[dad])//比较孩子和父亲,如果孩子大于父亲,那么就进行交换
{
swap(A[son],A[dad]);//孩子和父亲进行交换
dad=son;//son重新作为dad去判断下面的字数是否符合大根堆
son=2*dad+1;
}else{
break;
}
}
}
void HeapSort1(ElemType A[],int len)
{
int i;
//就是把堆调整为大根堆
for(i=len/2-1;i>=0;i--)
{
AdjustDown1(A,i,len);
}
swap(A[0],A[len-1]);//交换根部元素和最后一个元素
for(i=len-1;i>1;i--)//i代表的是剩余的无序数的数组的长度
{
AdjustDown1(A,0,i);//调整说呢过于元素变为大根堆
swap(A[0],A[i-1]);//交换根部元素和无序树的数组的最后一个元素
}
}
int main() {
SSTable ST;
ST_Init(ST,10);//初始化
//ElemType A[10]={3,87,2,93,78,56,61,38,12,40};
//memcpy(ST.elem,A,sizeof(A));
ST_print(ST);
HeapSort1(ST.elem,10);
ST_print(ST);
HeapSort(ST.elem,9);
ST_print(ST);
return 0;
}
4、时间复杂度与空间复杂度:
AdjustDown1 函数的循环次数是logzn,HeapSortl函数的第一个for循环了n2 次,第二个for 循环了n次,总计次数是 3/2nlogen 次,因此时间复杂度是O(nlogzn).堆排最好、最坏、平均时间复杂度都是 O(nlogzn);
堆排的空间复杂度是 O(1),因为没有使用与n相关的额外空间。