堆和堆排序

菜鸟写的堆和堆排序,都是根据个人理解,欢迎指出不当的地方。。。。。。大神就飘过吧。。。。。。

从poj上刷堆排序和优先队列,只知道堆排序貌似和优先队列有关系,不知道堆排序(似乎排序一般用快排)在其他能干什么用,也就是不知道堆的题型是什么,不管怎样,先把它搞懂再说吧,自己能接触的也就是堆排序了,先把堆排序写出来。

堆:

说到堆,应该先说二叉树,堆是建立在二叉树树的基础上的,是一个近似完全二叉树的数据结构。如图:(最大堆)

图 1

图 2

堆分最大堆(大根堆)和最小堆(小根堆),最大堆是任何一个非叶子节点都比其孩子大,最小堆就是任何一个非叶子节点都比他的孩子小。图1和图2对应着看,不难发现,一个节点的左孩子(如果它有的话)下标是这个节点的下标的2倍,右孩子(如果它有的话)是它的2倍再+1,而它的父节点(如果它有的话)就是它的下标/2,记住这个对应关系。

堆操作:

1 插入:

插入一个元素就是把插入的元素放到最后,然后再把目前的数组调整成一个堆,相当如重新建立堆,调整方法在下面。

2 删除:

删除就是把某一个元素删除,然后把最后一个元素放到删除的元素的位置,然后重新调整数组使之成为堆,方法在下面。

堆排序:

堆排序和其他排序效果是一样的,时间复杂度为O(nlogn),空间复杂度为O(n+k),n是输入长度,k是一个常数,这个常数很小,几乎可以忽略。堆排序的大致想法是:用第一个元素和最后一个元素交换,然后堆的元素个数–,然后把剩下的元素再次调整成一个堆,不断重复这个过程,直到元素个数为0,输出原来的数组,就是堆排序的结果。

堆排序之前首先要建立一个堆:(这里以对一串int数排序为例子,建立最大堆)

1 从控制台读入一串数,存入数组,数组下标从1开始

2 从len(数的个数)/ 2开始(因为这个下标正好是非叶子节点的最大的一个下标),判断它的左右孩子是否比它大,如果不比它大,判断下一个节点,如果比它大,它和比最大的那个孩子交换,然后判断交换后的它的孩子那棵子树是否构成最大堆,用先前的方法判断,知道不再交换,再判断下一个节点。一直判断到1,也就是根节点。

例如,用16,4,10,14,7,9,3,2,8,1这一串数作为例子,建立最大堆,然后从小到大排序:

初始化(输入完后)是:

图 3

调整:从数的个数/2的下标开始,本图从下标为5的地方开始,因为7比它的左孩子大,所以,这里满足最大堆的性质,然后判断下标为4的子树,发现也符合最大堆性质,那么,继续判断,下一个该判断的就是下标为3的节点,发现依然符合,那就判断下标为2的节点,现在发现不符合了,根据上面的介绍,4应该和14交换,变成下面这个图:

图 4

这样交换完之后,下表为4的节点又不满足最大堆的性质了,根据规则,4应该和8交换,交换完成后如图:

图 5

这样,都满足最大堆性质了,那么,调整完成,继续我们的检查,下标为2的子树检查完了,那么就该检查下标为1的子树了,发现符合最大堆,建堆完成。随着调整,相应的数组也会变化。

排序:

排序很简单,就是让第一个元素和最后一个元素交换,然后调整整个数组再次成为一个最大堆,当然,每交换一次,数组长度就要-1,因为第一个元素,永远都是最大的。然后再交换,然后再调整,直到数组长度减为0,数组中存储的就是从小到大的排序后的序列。

图不给了,直接上代码:

#include <stdio.h>
#include <string.h>

int s[1000010];

void adjustheap(int len,int i)
{
    int lt = i << 1;//设置当前节点的左孩子
    int rt = i << 1 | 1;//设置当前节点的右孩子
    int mx = i;//当前节点,当前节点的左右孩子中最大值的下标,默认为当前节点的下标
    while(lt <= len || rt <= len)//当左孩子或右孩子在数组长度范围内
    {
        if(lt <= len && s[mx] < s[lt])
            mx = lt;
        if(rt <= len && s[mx] < s[rt])
            mx = rt;//这两个if是在当前节点和当前节点的左右孩子中找最大值,记录下标
        if(mx != i)//最大值默认为当前节点,在上面两个if判断后mx变化了,说明当前节点的值小于它的某一个孩子的值,应该调整
        {
            int tmp = s[mx];
            s[mx] = s[i];
            s[i] = tmp;//当前节点的值和当前节点孩子中的最大值交换

            lt = mx << 1;
            rt = mx << 1 | 1;
            i = mx;//交换完成,判断以交换的那个孩子为根的子树是否为最大堆,所以,左右子树的下标都要改成交换后的最大值的左右孩子的下标,默认的最大值也要改变
        }
        else
            break;//如果未发生交换,说明不用调整,就跳出
    }
}

void build(int len)
{
    for(int i = len >> 1;i > 0;i--)//从下表最大且不为叶子节点的数的下标开始判断,调整,直到判断到根节点
        adjustheap(len,i);//判断和调整
}

void heapsort(int len)
{
    while(len)
    {
        int tmp = s[len];
        s[len] = s[1];
        s[1] = tmp;//第一个值和最后一个值交换
        len--;//长度-1
        adjustheap(len,1);//第一个节点(根节点)值改变,调整当前堆
    }
}

int main()
{
    int n;
    scanf("%d",&n);//设定输入多少个数
    for(int i = 1;i <= n;i++)
        scanf("%d",&s[i]);//输入n个数,下表从1开始接收
    build(n);//建立堆,参数为输入数的个数
    heapsort(n);//堆排序
    for(int i = 1;i <= n;i++)
    {
        printf("%d",s[i]);
        if(i <= n - 1)
            printf(" ");
    }
    printf("\n");
    return 0;
}


这就是堆排序了,关键是理解如何调整。

C++ STL中有函数可以直接调用:

#include <stdio.h>
#include <algorithm>

using namespace std;

int s[1000010];

int main()
{
    int n;
    scanf("%d",&n);
    for(int i = 0;i < n;i++)
    {
        scanf("%d",&s[i]);
    }
    make_heap(s,s + n);//没有参数,默认为最大堆
    sort_heap(s,s + n);
    for(int i = 0;i < n;i++)
    {
        printf("%d",s[i]);
        if(i < n - 1)
            printf(" ");
    }
    printf("\n");
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值