zoukankan      html  css  js  c++  java
  • 堆排序算法详解及源代码分析

            之前介绍过几种排序算法,今天说一说堆排序算法。虽然堆排序在实践中不常用,经常被快速排序的效率打败,但堆排序的优点是与输入的数据无关,时间复杂度稳定在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

     

  • 相关阅读:
    SQL Server Audit监控触发器状态
    SQL Server 数据变更时间戳(timestamp)在复制中的运用
    SQL Server 更改跟踪(Chang Tracking)监控表数据
    SQL Server 变更数据捕获(CDC)监控表数据
    SQL Server 事件通知(Event notifications)
    SQL Server 堆表行存储大小(Record Size)
    SQL Server DDL触发器运用
    SQL Server 默认跟踪(Default Trace)
    SQL Server 创建数据库邮件
    SQL Server 跨网段(跨机房)FTP复制
  • 原文地址:https://www.cnblogs.com/outerspace/p/11098461.html
Copyright © 2011-2022 走看看