排序算法系列之堆排序

思想:堆排序是一种树形选择排序,是对直接选择排序的有效改进。堆的定义如下:具有n个元素的序列(h1,h2,...,hn),当且仅当满足(hi>=h2i,hi>=h2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,...,n/2)
时称之为堆。

由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项。完全二叉树可以很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储顺序,使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。

从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。

二叉堆的定义

二叉堆是完全二叉树或者是近似完全二叉树。

二叉堆满足二个特性:

1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。

2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。

当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆。下图展示一个最小堆:

由于其它几种堆(二项式堆,斐波纳契堆等)用的较少,一般将二叉堆就简称为堆。

堆的存储

一般都用数组来表示堆,i结点的父结点下标就为(i –1) / 2。它的左右子结点下标分别为2* i + 1和2 * i + 2。如第0个结点左右子结点下标分别为1和2。

堆排序的实现

实现堆排序需要解决两个问题:

1.如何由一个无序序列建成一个堆?

2.如何在输出堆顶元素之后,调整剩余元素成为一个新的堆?

  先考虑第二个问题,一般在输出堆顶元素之后,视为将这个元素排除,然后用表中最后一个元素填补它的位置,自上向下进行调整:首先将堆顶元素和它的左右子树的根结点进行比较,把最小的元素交换到堆顶;然后顺着被破坏的路径一路调整下去,直至叶子结点,就得到新的堆。

我们称这个自堆顶至叶子的调整过程为“筛选”。

从无序序列建立堆的过程就是一个反复“筛选”的过程。

构造初始堆

初始化堆的时候是对所有的非叶子结点进行筛选。

最后一个非终端元素的下标是[n/2]向下取整,所以筛选只需要从第[n/2]向下取整个元素开始,从后往前进行调整。

比如,给定一个数组,首先根据该数组元素构造一个完全二叉树。

然后从最后一个非叶子结点开始,每次都是从父结点、左孩子、右孩子中进行比较交换,交换可能会引起孩子结点不满足堆的性质,所以每次交换之后需要重新对被交换的孩子结点进行调整。

  由排序过程可见,若想得到升序,则建立大顶堆,若想得到降序,则建立小顶堆

程序:

template<classT>
void  HeapAdjust(T*H,intstart,intend)//已知H[start~end]中除了start之外均满足堆的定义
//本函数进行调整,使H[start~end]成为一个大顶堆
{
 
    Ttemp = H[start];
    for(inti = 2*start + 1; i<=end;i*=2)
    {
        //因为假设根结点的序号为0而不是1,所以i结点左孩子和右孩子分别为2i+1和2i+2
        if(i<end&& H[i]<H[i+1])//左右孩子的比较
        {
            ++i;//i为较大的记录的下标
        }
 
        if(temp> H[i])//左右孩子中获胜者与父亲的比较
        {
            break;
        }
        //将孩子结点上位,则以孩子结点的位置进行下一轮的筛选
        H[start]=H[i];
        start= i;
    }
 
    H[start]=temp; //插入最开始不和谐的元素
}
 
 
template<classT>
voidHeapSort(T *H,constintn)
{
    int N = n-1;
    //建立最大堆
    for(inti = N/2; i>=0; --i)//认为叶子节点是有序的,则只需从叶子节点的父节点开始调整
    {
        HeapAdjust(H,i,N);
    }
    for(inti = N; i>=1;--i)
    {
        Ttemp = H[0];
        H[0]= H[i] ;
        H[i]= temp;
        HeapAdjust(H,0,i-1);
    }
}

分析:

稳定性:堆排序是不稳定的。

算法时间复杂度:O(nlog2n)

空间复杂度:O(1)

堆排序方法对记录数较少的文件并不值得提倡,但对n较大的文件还是很有效的。因为其运行时间主要耗费在建初始堆和调整建新堆时进行的反复“筛选”上。

         堆排序在最坏的情况下,其时间复杂度也为O(nlogn)。相对于快速排序来说,这是堆排序的最大优点。此外,堆排序仅需一个记录大小的供交换用的辅助存储空间。



 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值