• 堆排序算法详解及源代码分析


            之前介绍过几种排序算法,今天说一说堆排序算法。虽然堆排序在实践中不常用,经常被快速排序的效率打败,但堆排序的优点是与输入的数据无关,时间复杂度稳定在O(N*lgN),不像快排,最坏的情况下时间复杂度为O(N2)。

      说明,了解堆排序的前提是要掌握二叉树的概念,可自行百度,本文不做介绍。

            说到堆排序,首先需要了解一种数据结构——堆。堆是一种完全二叉树,这种结构通常可以用数组表示。在实际应用中,堆又可以分为最小堆和最大堆,两者的区别如下:

      -max-heap property :对于所有除了根节点(root)的节点 i,A[Parent(i)]A[i]

      -min-heap property :对于所有除了根节点(root)的节点 i,A[Parent(i)]A[i]

              对于用数组表示的堆来说,每个节点的父子节点索引关系如下:

    索引为i的节点的父节点:Parent(i)parent(i)A[floor((i1)/2)]

    左孩子:Left(i)left(i)A[2i+1]

    右孩子:Right(i)right(i)A[2i+2]

         

        了解完堆的基本概念以后,可以开始进行堆排序了。堆排序可以分解为两步:

    1、将待排序数组,根据堆的定义,构建成一个堆。

    2、排序:每次取下堆的顶部元素(最大或最小值),与堆的最后一个元素交换位置,并重构堆;在本步骤中,每取走一个顶部元素后,堆的大小都会减1.

    重复第2步,直至堆的大小为1为止,此时堆只剩下1个元素,所有比它大(或小)的元素,均已按顺序排列在数组中,排序完成。

    以下的分析以最大堆为例。

    在以上过程中,用到了一个关键的函数:构建以某个元素为父元素的堆——_maxHeapify,该方法的源码及详细分析如下。具体:

     1 void _maxHeapify<E extends Comparable<E>>(List<E> a, int i, int size) {
     2   // 父节点为i,假设父节点元素为最大;左孩子为l,右孩子为r;
     3   var mi = i, l = (i << 1) + 1, r = (i << 1) + 2;
     4 
     5   // 寻找父、左、右中最大的那个节点
     6   if (l < size && a[l].compareTo(a[mi]) > 0) mi = l;
     7   if (r < size && a[r].compareTo(a[mi]) > 0) mi = r;
     8 
     9   // 如果最大节点不是父节点,则将最大节点交换至父节点,完成节点i的最大堆构建;
    10   if (mi != i) {
    11     _swap(a, i, mi);
    12 
    13   // 注意,交换以后,其对应的mi孩子节点的最大堆的性质可能被破坏,因此需递归调用
    14   // 以保证堆的有序性;
    15     _maxHeapify(a, mi, size);
    16   }
    17 }

        对于一个数组,构建最大堆的过程,则是从最后一个元素的父元素开始,递归向上构建,直至根元素A[0]。具体代码分析如下:

    1 void _buildMaxHeap<E extends Comparable<E>>(List<E> a) {
    2   // 从最后一个节点的父节点开始,逐个向上
    3   for (var i = (a.length >> 1) - 1; i >= 0; i--) _maxHeapify(a, i, a.length);
    4 }

         最后,开始使用堆排序,详细代码分析如下:

     1 void heapSort<E extends Comparable<E>>(List<E> a) {
     2   // 第一步:构建堆
     3   _buildMaxHeap(a);
     4 
     5   // i+1表示堆的大小.每次循环堆的大小减1;
     6   // 当堆只剩1个元素时,排序结束。此时数组为升序排列
     7   for (var i = a.length - 1; i > 0; i--) {
     8     // 每次取出堆顶元素(也是最大的元素)放在堆的尾部。
     9     _swap(a, 0, i);
    10     
    11     // 交换上来的元素可能会破坏堆的性质,因此重构堆;
    12     _maxHeapify(a, 0, i);
    13   }
    14 }

        最后附上swap的代码:

    1 void _swap<E>(List<E> a, int i, int j) {
    2   var t = a[i];
    3   a[i] = a[j];
    4   a[j] = t;
    5 }

        从以上代码也可以看出,堆排序也不需要额外的存储空间,因此,堆排序的空间复杂度为O(1).

        测试代码如下:

     1 import 'dart:math';
     2 import 'package:data_struct/sort/heap_sort.dart';
     3 
     4 void main() {
     5   var rd = Random();
     6   // 随机生成一个数组
     7   List<num> a = List.generate(12, (_) => rd.nextInt(200));
     8 
     9   // 原始数组
    10   print(a);
    11   print('---------------------------------');
    12 
    13   // 排序
    14   heapSort(a);
    15   // 排序后数组
    16   print(a);
    17 }

        输出结果:

    1 [182, 9, 105, 86, 181, 87, 166, 98, 90, 142, 192, 176]
    2 ---------------------------------
    3 [9, 86, 87, 90, 98, 105, 142, 166, 176, 181, 182, 192]
    4 Exited

     

  • 相关阅读:
    tcl基本语法
    linux hostid与lmhostid
    linux下uptime命令
    介绍一下 WebApplicationContext?
    Spring 由哪些模块组成?
    怎样开启注解装配?
    解释不同方式的自动装配 ?
    @Qualifier 注解有什么用?
    解释 Spring 框架中 bean 的生命周期?
    如何在 spring 中启动注解装配?
  • 原文地址:https://www.cnblogs.com/outerspace/p/11098461.html
Copyright © 2020-2023  润新知