【数据结构】【C#】018-选择类排序:🔊堆排序(不稳定)

选择排序:堆排序(不稳定)

堆的定义:n 个元素的序列 (k1, k2, …, kn),当且仅当满足下列关系:任何一个非终端结点的值都大于等于(或小于等于)它左右孩子的值时,称之为堆。若序列{k1,k2,…,kn}是堆,则堆顶元素(即完全二叉树的根)必为序列中n个元素的最小值(或最大值) 。

可将堆序列看成完全二叉树,则: k2i 是 ki 的左孩子; k2i+1 是 ki 的右孩子。所有非终端结点的值均不大(小)于其左右孩子结点的值。堆顶元素必为序列中 n 个元素的最小值或最大值。

若:ki <= k2i , ki <= k2i+1,也就是说父小孩大,则为小顶堆(小根堆,正堆),反之,父大孩小,叫大顶堆(大根堆,逆堆)

【堆排序基本思想】:将无序序列建成一个堆,得到关键字最小(大)的记录;输出堆顶的最小(大)值后,将剩余的 n-1 个元素重又建成一个堆,则可得到 n 个元素的次小值;如此重复执行,直到堆中只有一个记录为止,每个记录出堆的顺序就是一个有序序列,这个过程叫堆排序。

堆排序需解决的两个问题:

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

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


第二个问题解决方法——筛选:

所谓“筛选”指的是,对一棵左/右子树均为堆的完全二叉树,“调整”根结点使整个二叉树也成为一个堆。具体是:输出堆顶元素之后,以堆中最后一个元素替代之;然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换;重复上述操作,直至叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为“筛选”。

例: (13, 38, 27, 49, 76, 65, 49, 97)

image

输出堆顶元素之后,以堆中最后一个元素替代之;

image

然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换

image
image

输出堆顶元素之后,以堆中最后一个元素替代之;

image

然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换

image
image

输出堆顶元素之后,以堆中最后一个元素替代之;

image

然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换

image

输出堆顶元素之后,以堆中最后一个元素替代之;

image

然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换

image

输出堆顶元素之后,以堆中最后一个元素替代之;

image

然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换

image
image

对深度为 k 的堆,“筛选”所需进行的关键字比较的次数至多为 2(k-1)。


第一个问题解决方法:

从无序序列的第n/2个元素(即无序序列对应的完全二叉树的最后一个内部结点)起,至第一个元素止,进行反复筛选。建堆是一个从下往上进行“筛选”的过程。把原始的序列一一对应的(从左到右,从上到下)建立一个完全二叉树即可,然后建立小顶堆(大顶堆)

image

1、建堆

image

2、调整,筛选过程

image
image
image
image
image

一趟堆排序完毕,选出了最值和堆里最后一个元素交换,继续

image
image
image

第二趟堆排序完毕,选出了次最值和剩下的元素的最后一个元素交换

image
image
image

第三趟堆排序完毕,重复往复,这样进行堆调整。

image
image

第四躺排序完毕,继续

image
image

第五躺排序完毕

image
image

第六趟排序完毕

image

最后全部堆排序完毕

image

这是整个建堆,调整为小顶堆的过程,也就是递增排序。具体是自上而下调整完全二叉树里的关键字,使其成为一个大顶堆(递减排序过程)

操作过程如下:

1、初始化堆:将R[1..n]构造为堆;

2、将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。

对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有节点都进行调整


C#代码实现:

自定义工具类:

参考笔记-016

堆排序类:

/// <summary>
/// 选择类排序
/// </summary>
public class SelectSort
{

    //调整推
    public static void HeapAdjust(int[] List, int s, int last) //s 为临时堆顶 索引  last 为 堆尾索引
    {
       
        int maxTemp = s;

        int sLChild = 2 * s;   // s 节点的 左孩子索引
        int sRChild = 2 * s + 1; // s 节点的 右孩子索引


        if (s <= last / 2)  //完全二叉树的叶子结点不需要调整,没有孩子
        {
            if (sLChild <= last && List[sLChild] > List[maxTemp])//如果 当前 结点的左孩子比当前结点记录值大,调整,大顶堆
            {
                //更新 maxtemp
                maxTemp = sLChild;
            }
            if (sRChild <= last && List[sRChild] > List[maxTemp])//如果 当前 结点的右孩子比当前结点记录值大,调整,大顶堆
            {
                //更新 maxtemp
                maxTemp = sRChild;
            }

            //如果调整了就交换,否则不需要交换
            if (List[maxTemp] != List[s])
            {
                //不使用中间变量交换两数的值
                List[maxTemp] = List[maxTemp] + List[s];
                List[s] = List[maxTemp] - List[s];
                List[maxTemp] = List[maxTemp] - List[s];

                //交换完毕,防止调整之后的新的以 maxtemp 为父节点的子树不是大顶堆,再调整一次
                HeapAdjust(List, maxTemp, last);
            }

        }
    }

    //建立堆
    public static void BuildHeap(int[] List, int last)
    {
        //明确,具有 n 个结点的完全二叉树(从左到右,从上到下),编号后,有如下关系,设 a 结点编号为 i,若 i 不是第一个结点,那么 a 结点的双亲结点的编号为[i / 2]

        //非叶节点的最大序号值为 last / 2
        for (int i = last / 2; i >= 0; i--)
        {
            //从头开始调整为大顶堆
            HeapAdjust(List, i, last);
        }
    }

    /// <summary>
    /// 3、选择排序:堆排序
    /// </summary>
    /// <param name="List">数组</param>    
    public static void HeapSort(int[] List)
    {
        //建立大顶堆
        BuildHeap(List, List.Length-1);

        //调整堆
        for (int i = List.Length - 1; i >= 1; i--)
        {
            //将堆顶记录和当前未经排序子序列中最后一个记录相互交换
            //即每次将剩余元素中的最大者list[0] 放到最后面 list[i]
            List[i] = List[i] + List[0];
            List[0] = List[i] - List[0];
            List[i] = List[i] - List[0];

            //重新筛选余下的节点成为新的大顶堆
            HeapAdjust(List, 0, i - 1);
            Utilit.Print(List, i);
        }
    }
}

测试用例:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class _013_SelectSort : MonoBehaviour
{


    void Start()
    {
        int[] a = Utilit.RandArray(20, 10);

        int[] b = (int[])a.Clone();

        Debug.Log("---------------简单选择排序--------------");
        SelectSort.SampleSelectSort(a);

        //Debug.Log("-------------错误简单选择排序--------------");
        //SelectSort.ErrorSampleSelectSort(b);

        Debug.Log("-------------选择排序:堆排序--------------");
        SelectSort.HeapSort(b);

    }

}

输出结果:

img.jpg

注:

堆排序的时间复杂度和空间复杂度:

  1. 对深度为 k 的堆,“筛选”所需进行的关键字比较的次数至多为 2(k-1);

  2. 对 n 个关键字,建成深度为
    image

的堆,所需进行的关键字比较的次数至多 4n;

  1. 调整“堆顶” n-1 次,总共进行的关键字比较的次数不超过
    image

,因此,堆排序的时间复杂度为 O(nlogn),与简单选择排序 O(n^2) 相比时间效率提高了很多。

空间复杂度:S(n) = O(1)

堆排序是一种速度快且省空间的排序方法。相对于快速排序的最大优点:最坏时间复杂度和最好时间复杂度都是 O(n log n),适用于记录较多的场景(记录较少就不实用),类似折半插入排序,在 T(n)=O(n log n)的排序算法中堆排序的空间复杂度最小为1。

堆排序的稳定性:不稳定排序算法
[参考文献]https://www.cnblogs.com/kubixuesheng/p/4359406.html


选择类排序总结:

img.jpg
img.jpg
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1)这本书为什么值得看: Python语言描述,如果学的Python用这本书学数据结构更合适 2016年出版,内容...
    孙怀阔阅读 14,331评论 0 15
  • 一些概念 数据结构就是研究数据的逻辑结构和物理结构以及它们之间相互关系,并对这种结构定义相应的运算,而且确保经过这...
    Winterfell_Z阅读 11,391评论 0 13
  • 第一章 绪论 什么是数据结构? 数据结构的定义:数据结构是相互之间存在一种或多种特定关系的数据元素的集合。 第二章...
    SeanCheney阅读 11,095评论 0 19
  • 天雨湿阶苔, 云开几浮白。 花枝出清瘦, 桃颜一笑开。
    乱棋阅读 1,313评论 0 0
  • 爱到肋骨里 累到手无缚鸡之力 心还想 勒紧你
    段童阅读 2,087评论 0 2