堆排序

堆:可以被视为一个完全二叉树,树的每一层都是填满的,除最后一层外。表示堆的数组A具有两个属性:length[A]是数组中元素的个数,heap_size[A]是存放在A中的堆的元素的个数。就是说,A[heap_size[A]]之后的元素都不属于相应的堆,heap_size[A]<=length[A]。树的根为A[1],给定节点下标,可以很快算出它的父节点,左孩子和右孩子节点。

PARENT(i)=i/2

LEFT(i)=2*i

RIGHT(i)=2*i+1


堆分为两种:大顶堆和小顶堆,在大顶堆中,A[PARENT(i)]>=A[i],父节点的值要大于孩子节点的值,容易知道在大顶堆中最大值存储在根节点中。而小顶堆则恰好相反。

在本次排序中,我们使用的是大顶堆。在进行排序之前,需要了解堆的一些操作。

1.保持堆的性质

所谓堆的性质,指的就是父节点的值总是大于孩子节点的值,而在刚刚建成的一颗完全二叉树中,并不一定满足该性质,所以需要做某些调整,使得二叉树成为一个大顶堆。

如下图所示,A[2]由于比它的孩子节点要大,所以不符合大顶堆的性质,于是就拿A[2]和它的左孩子A[4]和右孩子A[5]进行比较,而左孩子的值要大于右孩子,所以拿A[2]和A[4]进行交换,然后就以左孩子A[4]为讨论的节点,一直递归下去,直到到达底层或是大于它的两个孩子节点。


在程序代码中,用MAX_HEAPILF(int* A,int i)实现,A为建堆的数组,i为讨论的节点。

2.建堆

没什么可说的,就是自底向上依次调用MAX_HEAPILF函数就可以了,具体过程可参照下图:



3.堆排序

堆排序的过程是建立在前两个步骤的基础之上,由于大顶堆的根节点总是最大的,因此给定一个数组现将其化为大顶堆(用到上面那个方法建堆),然后输出根节点的数值,此时,将最后一个节点与根节点进行交换。注意,这个时候有两个变化发生,第一个堆的数目Heap_Size会减小一个,因为输出的根节点已经不需要了,又因为根节点与最后一个节点兑换了位置,此时的二叉树已经不满足堆的性质了,使用第一个方法,调用MAX_HEAPILF(A,1)使其重新成为一个堆(这里数组下标从1开始)。重复上述过程,直到将堆中所有元素全部输出。

一下是图示过程:




最后是程序的代码:

#include <stdio.h>
#include <stdlib.h>

#define MAX 20

//堆的大小,注意,和数组本身的大小事有区别的,在堆排序的函数中要尤其小心
int Heap_Size=20;

//保持堆的性质,其实就是拿A[i]和左右两个孩子比较大小,然后递归下去就行了
void MAX_HEAPIFY(int* A,int i)
{
    int Largest;
    int L=2*i;
    int R=2*i+1;
    if(L<=Heap_Size&&A[L]>A[i])
        Largest=L;
    else
        Largest=i;
    if(R<=Heap_Size&&A[R]>A[Largest])
        Largest=R;
    if(Largest!=i)
    {
        int temp=A[i];
        A[i]=A[Largest];
        A[Largest]=temp;
        MAX_HEAPIFY(A,Largest);
    }
}

//建堆的过程
void Build_Max_Heap(int* A)
{
    int i=MAX/2;
    for(;i>=1;i--)
    {
        MAX_HEAPIFY(A,i);
    }
}

//堆排序算法的过程
void Heap_Sort(int* A)
{
    Build_Max_Heap(A);
    int i=MAX;
    for(;i>2;i--)
    {
        printf("%d ",A[1]);
        int temp=A[1];
        A[1]=A[i];
        A[i]=temp;
        Heap_Size--;
        MAX_HEAPIFY(A,1);
    }
}

//主函数进行测试,注意,数组下标从1开始,第一个元素存放数组的大小
int main(void)
{
    int A[MAX+1]={MAX,34,23,67,34,2,5,6,1,78,34,2,4,5,44,12,6,97,23,54,10};
    Heap_Sort(A);
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值