排序算法系列之堆排序

堆排序

1 基本原理

1 核心思想:在堆的数据结构中,堆中的最大值总是位于根节点(在优先队列中使用堆的话堆中的最小值位于根节点),堆排序就是把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。
** 算法分析:
   1. 最大堆调整(Max-Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
   2. 创建最大堆(Build-Max-Heap):将堆所有数据重新排序,使其成为最大堆
   3. 堆排序(Heap-Sort):移除位在第一个数据的根节点,并做最大堆调整的递归运算 **


相关概念:
1. 二叉树:二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。
   二叉树的每个结点至多只有二棵子树(不存在度大于 2 的结点),二叉树的子树有左右之分,次序不能颠倒。二叉树的第 i 层至多有 2i - 1 个结点;深度为 k 的二叉树至多有 2k - 1 个结点;对任何一棵二叉树 T,如果其终端结点数为 n0,度为 2 的结点数为 n2,则n0 = n2 + 1。
—————————————————————————————————————
2. 二叉树又分为完全二叉树(complete binary tree)和满二叉树(full binary tree)
  (1) 满二叉树:一棵深度为 k,且有 2k - 1 个节点称之为满二叉树
  (2) 完全二叉树:深度为 k,有 n 个节点的二叉树,当且仅当其每一个节点都与深度为 k 的满二叉树中序号为 1 至 n 的节点对应时,称之为完全二叉树
—————————————————————————————————————-
3. 堆: 堆(二叉堆)可以视为一棵完全的二叉树,完全二叉树的一个“优秀”的性质是,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示(普通的一般的二叉树通常用链表作为基本容器表示),每一个结点对应数组中的一个元素。

2 实例说明

如上图所示,以一组数据a {4,1,3,2,16,9,10,14,8,7} 为例,进行堆排序的算法演示:
  1. 构建最大堆:将数组转化为二叉树形式,依次从(n/2 - 1)节点递减,判断是否符合最大堆性质。
    (1)16>7,符合;
    (2)2<14,不符合,且14>8,则交换14为父节点。
    (3)3<10,且10>9,交换10至父节点。
    (4)1<16,16>14,交换16至父节点。
    (5)4<16,16>10,交换16至父节点。最大堆构造完成。


  2.堆排序:
(1)将最大堆顶部元素16与尾部元素1交换,将16去除,再重新构建最大堆,如图(b)。
(2)重复操作,去除顶部元素14,再构建最大堆,如图(c)。
(3)直至堆中只剩最后一元素,则堆排序完成。

3 代码实现

// 堆排序(C++)

void HeapAdjust(vector<int> &a, int i, int len) //构建最大堆
{
    int left, right, j;
    while ((left = 2 * i + 1) <= len) //判断当前父节点有无左节点(有无孩子节点)
    {
        right = left + 1;          //右节点
        j = left;
        if (j<len && a[left]<a[right])
        {
            j++;
        }
        if (a[i]<a[j])
        {
            swap(a[i], a[j]);
        }
        else
        {
            break;     //父节点比孩子节点都大
        }
        i = j;           //交换位置后,对子节点继续进行判断
    }
}

void HeapSort(vector<int> &a)
{
    int len = a.size() - 1;
    for (int i = len / 2 - 1; i >= 0; i--) //构造堆
    {
        HeapAdjust(a, i, len);

        //中间排序过程输出
        for (auto x : a)
            cout << x << " ";
        cout << endl;
    }
    cout<<"堆构造完成!"<< endl;
    //堆排序
    while (len >= 0)
    {
        swap(a[0], a[len--]);  //首尾元素交换,长度减一,尾部元素最大
        HeapAdjust(a, 0, len);

        //中间排序过程输出
        for (auto x : a)
            cout << x << " ";
        cout << endl;
    }
}

4 性能分析

  • 1 时间复杂度
    堆排序的时间等于建堆和进行堆调整的时间。构建最大堆大概需进行n/2 * 2 = n次比较和n/2次交换,故时间复杂度为O(n)。而堆调整的时间为(n - 1)*O(log2(n))。所以堆排序的时间复杂度为O(n*logn)。

  • 2 空间复杂度
    堆排序为就地排序,因此空间复杂度为O(1)

  • 3 算法稳定性
    堆排序是不稳定的算法,在构建堆过程中元素的顺序可能会发生变化。

本文主要参考文章 常见排序算法 - 堆排序 (Heap Sort)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值