数据结构——堆排序

数据结构——堆排序

排序方法很多,各有优势,我们常用的排序方法是属于内部排序的,对辅助空间的使用很少,而时间复杂度可能遇到了限制了,我所知道几种排序方法最快也没有突破 n l o g n nlogn nlogn 这个限制。对于平均时间复杂度来说,快速排序已经是这种算法的佼佼者了,但是它的最坏情况恐怕是不能接受的, n 2 n^2 n2 ;这里推荐一种稍微复杂的排序,叫做堆排序,它基于一种数据结构——堆。才有了很小的时间复杂度,同时又有很稳定的特性。

先介绍一下堆的定义:

一颗二叉树,任意非叶子节点的左右子节点都同时比它大,或者同时比它小。

表现在数组中是这样的:

一串n个元素的序列, a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an ,下标由1开始;
a i ≤ a 2 i a i ≤ a 2 i + 1 a_i\le{a_{2i}} \\ a_i{\le{a_{2i+1}}} aia2iaia2i+1

a i ≥ a 2 i a i ≥ a 2 i + 1 a_i\ge{a_{2i}} \\ a_i{\ge{a_{2i+1}}} aia2iaia2i+1

( i = 1 , 2 , . . . , n / 2. ) (i=1,2,...,n/2.) (i=1,2,...,n/2.)

堆的定义有很明显的作用,最小的或者最大的数字永远在堆顶,对吗?这样子每次能够从堆顶拿出一个数字,并且从堆里面删去这个数字,再拿,如此往复,组成一个序列不就是一个有序的数组吗?并且用时是 n n n 。但是维护一个堆删除了堆顶的数字依然有序这是需要代价的,你需要调整几乎一个堆,从上至下。让数字的位置又满足堆的定义。

重新调整堆的方法叫做“筛选”,是这样的:

假设一个标准的堆,拿出了堆顶的元素,要删除堆顶的元素;

这里写图片描述

拿出13 ,把编号最后一个节点,也就是97放到堆顶。

这里写图片描述

97破坏了以1节点下的堆,所以交换编号1节点和它的子节点,选择编号2,3节点中小的那一个交换,那就是27这个编号3节点,这样子编号1节点下的堆就维护好了,但是破坏了编号3节点下的堆。所以依法炮制,换编号3节点和它的子节点。直到叶子节点。

这里写图片描述

接下来再拿出堆顶元素27,并且重复以上方式维护最小堆:

这里写图片描述

每一次从堆顶拿出元素的同时要将堆的大小减少1。

建立堆

还有个问题没解决的,就是初始的时候建立一个堆。

具体的操作就是:

对一棵二叉树由下往上操作,从第一个非叶子节点开始(也就是拥有至少一个子节点),逐步进行之前所说的“筛选”操作,直至根节点。

例如下面这棵二叉树,想要变化为最大堆;

img

编号最小的非叶子节点是编号3,值为40的那个节点;

img

然后是编号2节点,但它不需要筛选,下一个是编号1节点筛选;

img

再最后是筛选根节点;

img

完成后变成了最大堆。

img

这个图片是借用一下别人的,所以数字不同于上面的了,但是是一个思路,在这只做演示使用。(画图有点累啊)!!

很详细了。按图索骥都成了,所以贴代码了。

#include <iostream>
#include<cstring>
#include<cstdio>

using namespace std;

/*
 * 调整为最大堆,以a[s]为根节点
 * e的存在是为了限制数组避免越界
 * e取以a[s]为根的堆的最大编号叶子节点的编号
*/
void HeapAdjust(int a[],int s,int e)
{
    int length = e-s;
    int rc=a[s];
    // 每次取当前节点的左子节点
    for(int i=2*s;i<=e;i*=2)
    {
    	// 如果存在右节点,选择左右节点中大的那个
        if(i<e &&a[i]<a[i+1])
            i++;
        // 子节点都比根节点小,退出循环
        if(a[i]<rc)
            break;
        a[s] = a[i];
        s=i;
    }
    // 交换根节点和最后一个不小于根节点的节点
    a[s] = rc;
}

/*
 * 利用最大堆,顺序排序
 * 数组必须由下标1开始储存
*/
void HeapSort(int a[],int length)
{
	// 先建立最大堆,length/2就是编号最大的非叶子节点下标
    for(int i=length/2;i>0;i--)
    {
        HeapAdjust(a,i,length);
    }
    int t;
    // 由最后一个节点开始,从堆中拿出元素放到堆尾
    for(int i=length;i>1;i--)
    {
        t=a[i];
        a[i]=a[1];
        a[1]=t;
        // i-1是因为堆的大小变成了i-1了
        HeapAdjust(a,1,i-1);
    }
}
int main()
{
    int a[10]={0,49,38,65,97,76,13,27,49};
    HeapSort(a,8);
    for(int i=1;i<=8;i++)
        printf("%d ",a[i]);
    return 0;
}

更新

2021/1/4更新了一次,更加详细了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值