堆排序

欢迎大家访问我的微博:http://weibo.com/u/2887401030

堆是具有下列性质的完全二叉树:每个结点的值都大于等于其走有孩子节点的值,称为大顶堆;或者每个结点的值都小于等于其走有孩子节点的值,称为小顶堆。

再看堆排序。堆排序就是利用堆(假设是大顶堆)进行排序的方法。它的基本思路是将待排序的序列造成大顶堆。此时,整个序列的最大值就是堆顶的根节点。将他移走(其实就是将其与堆数组的末尾元素交换,此时末尾就是最大值),然后将剩余的n-1序列重新构造成一个堆,这样就会得到n个元素的次小值,如此反复执行,便能得到一个有序序列。

上面这段话,基本把堆排序的精华都讲出来了。现在我们来看一个构造堆的例子。假设int a[] = {50,10,90,30,70,40,100,60,20};我们要将这个数组中的数据造成大顶堆。将这个数组看成完全二叉树,按层序遍历,如下图:
这里写图片描述

这里的长度为9,从[9/2]=4开始调整,一直到1。原因是30是最后一个有子结点的结点。

先看30的子结点,左结点是60,右节点是20,60>20,就将父节点30与60比较,30<60。将30与60交换。(注意这里如果是右结点的值大于左结点,就拿父节点的值与右结点的值比较,父节点的值大于子节点就不换,小就换)。

交换后:
这里写图片描述
注意此时交换后的30已没有子节点。

再看90的子节点,同样100>40,父节点值与右节点值比较。90<100,交换两个的值。
交换后:
这里写图片描述

再看10的子节点,70>60,父节点值与右节点值比较。10<70,交换两个的值。
交换后:
这里写图片描述

看最后一个节点50的子节点,100>70,父节点值与右节点值比较。50<100,交换两个的值。
交换后:
这里写图片描述
注意此时的交换后的50还有子节点,再次比较子节点,90>40,父节点值与右节点值比较。50<90,交换两个的值。
交换后:
这里写图片描述

ok,观察此时的图,根结点的值最大,每个根结点的值都大于子结点的值,构造大顶堆成功,再以层序遍历,装到数组,int a[]={100,70,90,60,10,40,50,30,20};

这就是调整堆的方法。用代码的形式表现出来:

/*
假设只有i没有调整好
i : 要调整的下标
m : 要调整的最大的下标
*/
void HeapAdjust(int *a,int i,int m)
{
    int j,temp;
    if(a == NULL || m <= 0)
    {
        return;
    }

    temp = a[i];
    //假设i是0,对应50
    //注意j<=m
    for(j = 2*i+1;j <= m;j = j*2+1 )//注意左子节点是下标*2+1
    {
        if(j < m && a[j] < a[j+1])
            j++;//如果左结点小于右结点,移动到右结点
        if(temp >= a[j])
            break;//没必要调整
        a[i] = a[j];
        i = j;//要调整的序列变化,对应90
    }
    a[i] = temp;//最后i的序列对应的就是temp保留的值

}

注意的是上面的代码是按照数组的下标来的,与之前的图上左边的小数组不同,小一个1。也就是说,每个根节点的子节点的小标是根结点的小标*2+1(按照之前的图的话,应该是根节点小标*2),这点大家要注意。

另外,for(j = 2*i+1;j <= m;j = j*2+1 ) 之所以循环这么写,也是因为上面所讲的,交换后如果发现还有子节点,得继续交换,子节点再到自己的子节点;j = j*2+1,下标的变换。

再看真正的堆排序:

void HeapSort(int *a,int len)
{
    int i;
    if(a == NULL || len <= 0)
    {
        return;
    }

    //开始的数组建立大顶堆
    for(i=len/2;i>0;i--)
    {
    //在这里是3 -- 0
        HeapAdjust(a,i-1,len-1);
    }

    for(i=len-1;i>0;i--)
    {
        //进行排序
        swap(a,0,i);//将第一个与最后一个交换,最后一个最大
        //重新建立大顶堆
        HeapAdjust(a,0,i-1);//不包括最后一个已经出来的最大的数
    }
}

看这里:

//开始的数组建立大顶堆
for(i=len/2;i>0;i--)
{
//在这里是3 -- 0
    HeapAdjust(a,i-1,len-1);
}

就像我之前讲的,从[len/2]开始,先将无序的数组变成大顶堆。到此时数组已经变成大顶堆后的数组。

for(i=len-1;i>0;i--)
{
    //进行排序
    swap(a,0,i);//将第一个与最后一个交换,最后一个最大
    //重新建立大顶堆
    HeapAdjust(a,0,i-1);//不包括最后一个已经出来的最大的数
}

再看这个 swap(a,0,i);,将数组中的第一个数与最后一个数交换,swap就是一个简单的交换数组内数据的函数(后面会给代码)。因为现在的数组的已经内构造成大顶堆,根据定义,第一个数的值最大。所以,此时就是把最大的数放最后了。

再看这个HeapAdjust(a,0,i-1); 就是之前构造堆的函数,注意这里i-1。HeapAdjust这个函数最后一个参数表示的是调整最大的下标,这也就是说现在是不包括已经换了之后的数。例如:100已经到了数组最后一个,此时调用的是HeapAdjust(a,0,7),在这之后的90又到了堆顶,此时调用的是HeapAdjust(a,0,6),…….以此类推。

到这里,我们再回头看我们对堆排序的定义:

堆排序就是利用堆(假设是大顶堆)进行排序的方法。它的基本思路是将待排序的序列造成大顶堆。此时,整个序列的最大值就是堆顶的根节点。将他移走(其实就是将其与堆数组的末尾元素交换,此时末尾就是最大值),然后将剩余的n-1序列重新构造成一个堆,这样就会得到n个元素的次小值,如此反复执行,便能得到一个有序序列。

发现没有,我们完全就是按这样的套路在走,一分不差。如果你此时对堆排序的定义非常清楚,说明你已经掌握了它。如果你还是不能完全明白,那你可能要在思考几下。

总的来说,堆排序,就是利用了完全二叉树来进行的排序。整体的思路没有那么难。大家可以试着自己写代码。向大家提个建议,试着将我的代码改一下,因为我是按照数组的标号(从0开始)来写的,你们可以试着从1开始,数组的第一位试着先空掉,真正的数据从下标1开始,就像我之前展示的图片一样。这样的话,会有真正的体会。另外,我把完整的代码放下面,给大家参考一下:

完整代码:

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

void swap(int *a,int i,int j)
{
    int tmp;
    if(a == NULL || i < 0 || j < 0)
    {
        return;
    }
    tmp = a[i];
    a[i] = a[j];
    a[j] = tmp;
}

void printAll(int *a,int len)
{
    int i;
    if(a == NULL || len < 0)
    {
        return;
    }

    for(i=0;i<len;i++)
    {
        printf("%d  ",a[i]);
        if((i+1)%10 == 0)
            printf("\n");
    }
}
/*
                    50
            10               90
        30      70       40       100
    60      20
*/
/*
i : 要调整的下标
m : 要调整的最大的下标
*/
void HeapAdjust(int *a,int i,int m)
{
    int j,temp;
    if(a == NULL || m <= 0)
    {
        return;
    }

    temp = a[i];
    //假设i是0,对应50
    //注意j<=m
    for(j = 2*i+1;j <= m;j = j*2+1 )//注意左子节点是下标*2+1
    {
        if(j < m && a[j] < a[j+1])
            j++;//如果左结点小于右结点,移动到右结点
        if(temp >= a[j])
            break;//没必要调整
        a[i] = a[j];
        i = j;//要调整的序列变化,对应90
    }
    a[i] = temp;//最后i的序列对应的就是temp保留的值

}

void HeapSort(int *a,int len)
{
    int i;
    if(a == NULL || len <= 0)
    {
        return;
    }

    //开始的数组建立大顶堆
    for(i=len/2;i>0;i--)
    {
    //在这里是3 -- 0
        HeapAdjust(a,i-1,len-1);
    }

    for(i=len-1;i>0;i--)
    {
        //进行排序
        swap(a,0,i);//将第一个与最后一个交换,最后一个最大
        //重新建立大顶堆
        HeapAdjust(a,0,i-1);//不包括最后一个已经出来的最大的数
    }
}

int main()
{
    int a[] = {50,10,90,30,70,40,100,60,20};
    int len = sizeof(a)/sizeof(*a);

    printf("调整前:\n");
    printAll(a,len);

    HeapSort(a,len);

    printf("\n调整后:\n");
    printAll(a,len);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值