排序算法——堆排序(四)

郭卫东  论坛元老 | 2025-3-14 14:54:05 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1019|帖子 1019|积分 3057

一、实现思绪

堆排序因其奇妙的使用堆这种数据结构的特性来进行排序而得名。要相识实在现原理需要先相识堆这种数据结构。
堆是一种特殊的二叉树数据结构。有两种类型的堆:大顶堆与小顶堆。其具有任意的根节点均大于便是其子节点(大顶堆)或小于便是其子节点(小顶堆)的性质。同时也具有完全二叉树的性质。
堆具有二叉树与数组两种表现形式,如下图所示:

堆结构还可以用来实现优先级队列,优先级队列在很多场合都有应用,如A*算法中用于优化开放节点的查找速度,操纵系统中任务优先调理算法等等。
堆排序就是使用堆(假设使用大顶堆)进行排序的方法。它的基本思想是,将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(实在就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。
二、代码实现

  1. public class Heap
  2. {
  3.      public enum HeapType
  4.      {
  5.          SmallHeap,
  6.          BigHeap
  7.      }
  8.      //堆节点从1开始存储,这是为了方便计算其索引
  9.      int[] nodes;//节点容器
  10.      public int Capacity { get; set; }//容量
  11.      public int Count { get; private set; }//记录总数
  12.      public HeapType heapType { get; }//堆属性
  13.      public Heap(int capacity, HeapType type = HeapType.SmallHeap)
  14.      {
  15.          Capacity = capacity;
  16.          nodes = new int[capacity + 1];
  17.          heapType = type;
  18.      }
  19.      //压入节点返回压入成功结果
  20.      public bool PushNode(int node)
  21.      {
  22.          if (Count >= Capacity)
  23.          {
  24.              return false;
  25.          }
  26.          nodes[++Count] = node;
  27.          UpFilter(Count);
  28.          return true;
  29.      }
  30.      //弹出节点
  31.      public int PopNode()
  32.      {
  33.          if (Count <= 0)
  34.          {
  35.              throw new System.Exception("堆下溢");
  36.          }
  37.          int value = nodes[1];
  38.          nodes[1] = nodes[Count--];
  39.          DownFilter(1);
  40.          return value;
  41.      }
  42.      public void Clear()
  43.      {
  44.          Count = 0;
  45.      }
  46.      //堆排序
  47.      public void Sort(int[] arr)
  48.      {
  49.          Clear();
  50.          //构建堆
  51.          foreach (var item in arr)
  52.          {
  53.              PushNode(item);
  54.          }
  55.          int index = 0;
  56.          while (Count>0)
  57.          {
  58.              arr[index++] = PopNode();
  59.          }
  60.      }
  61.      //向上过滤
  62.      void UpFilter(int index)
  63.      {
  64.          if (index == 1)
  65.          {
  66.              return;
  67.          }
  68.          if (heapType == HeapType.SmallHeap)
  69.          {
  70.              //小跟堆
  71.              if (nodes[index] < nodes[index / 2])
  72.              {
  73.                  //执行交换
  74.                  SwapNode(index / 2, index);
  75.                  UpFilter(index / 2);//递归调用
  76.              }
  77.          }
  78.          else
  79.          {
  80.              //大根堆
  81.              if (nodes[index] > nodes[index / 2])
  82.              {
  83.                  //执行交换
  84.                  SwapNode(index / 2, index);
  85.                  UpFilter(index / 2);//递归调用
  86.              }
  87.          }
  88.      }
  89.      //向下过滤
  90.      void DownFilter(int index)
  91.      {
  92.          //没有子节点退出
  93.          if (index * 2 > Count)
  94.          {
  95.              return;
  96.          }
  97.          if (heapType == HeapType.SmallHeap)
  98.          {
  99.              //取当前节点与其左右子节点决出最小节点
  100.              int minIndex = nodes[index] > nodes[index * 2] ? index * 2 : index;
  101.              if (index * 2 + 1 <= Count)
  102.              {
  103.                  minIndex = nodes[minIndex] > nodes[index * 2 + 1] ? index * 2 + 1 : minIndex;
  104.              }
  105.              if (nodes[index].Equals(nodes[minIndex]))
  106.              {
  107.                  //最小的是自身则返回
  108.                  return;
  109.              }
  110.              else
  111.              {
  112.                  //交换节点
  113.                  SwapNode(minIndex, index);
  114.                  DownFilter(minIndex);
  115.              }
  116.          }
  117.          else
  118.          {
  119.              //取当前节点与其左右子节点决出最大节点
  120.              int maxIndex = nodes[index] < nodes[index * 2] ? index * 2 : index;
  121.              if (index * 2 + 1 <= Count)
  122.              {
  123.                  maxIndex = nodes[maxIndex] < nodes[index * 2 + 1] ? index * 2 + 1 : maxIndex;
  124.              }
  125.              if (nodes[index].Equals(nodes[maxIndex]))
  126.              {
  127.                  //最大的是自身则返回
  128.                  return;
  129.              }
  130.              else
  131.              {
  132.                  //交换节点
  133.                  SwapNode(maxIndex, index);
  134.                  DownFilter(maxIndex);
  135.              }
  136.          }
  137.      }
  138.      //交换两个节点
  139.      void SwapNode(int index1, int index2)
  140.      {
  141.          //执行交换
  142.          int temp = nodes[index1];
  143.          nodes[index1] = nodes[index2];
  144.          nodes[index2] = temp;
  145.      }
  146. }
  147.   public static void HeapSortNormal(int[] arr)
  148. {
  149.      //构建堆
  150.      Heap heap = new Heap(arr.Length);
  151.      heap.Sort(arr);
  152. }
复制代码
上述代码实现了一个堆数据结构,提供了容量、堆类型设置,以及push、pop、sort等基本操纵。
堆实现的焦点在于上浮与下沉两个操纵。通过这两个方法来维护堆的性质不被破坏。弹出一个节点时会将堆的末了一个节点插入到根节点,这样可以不破坏中部的结构,再通过下沉操纵将该节点下沉到符合的位置,下图演示了下沉操纵是如何进行的:

而上浮操纵则于之相反。在插入一个节点时,将其插入到堆的末尾,再通过上浮操纵浮动到适当位置。
在实现了push与pop操纵后,便可以简单的使用这两个操纵的组合来实现sort操纵。
当然我们只想使用堆结构来完成排序,并不需要它其他的功能,实现一个完整的堆结构是没有必要的,并且上述的这种实现,由于存储了待排序数组的副本需要额外的空间,并且其在构建堆时接纳了自上而下的构建方式。这样每插入一个新的节点都要从根节点开始下沉,其复杂度受到树深的影响。因此便有了下面的优化实现:
  1. public static void HeapSort(int[] arr)
  2. {
  3.     //初始化构建堆
  4.     BuildHeap(arr);
  5.     //始终将0号位置元素交换到末尾,使末尾有序,并缩减heap尺寸
  6.     for (int size = arr.Length; size > 0; size--)
  7.     {
  8.         //将第一个元素(最大的元素)与heap末尾元素交换,对应数组索引为size-1
  9.         Swap(arr, 0, size - 1);
  10.         //交换后heap实际尺寸变为size-1,重建堆
  11.         DownFilter(arr, size - 1, 0);
  12.     }
  13.     //自下而上构建堆
  14.     void BuildHeap(int[] arr)
  15.     {
  16.         for (int i = arr.Length/2-1; i >= 0; i--)
  17.         {
  18.             DownFilter(arr,arr.Length,i);
  19.         }
  20.     }
  21.    
  22.     //下沉
  23.     void DownFilter(int[] arr,int heapSize,int index)
  24.     {
  25.         //计算子节点索引
  26.         int left = (index + 1) * 2 - 1;
  27.         int right = left+1 ;
  28.         //目标位置
  29.         int target = index;
  30.         //如果存在左孩子且比目标节点大则目标下沉
  31.         if (left + 1 <= heapSize && arr[left] > arr[target]) target = left;
  32.         //如果存在右孩子且比目标节点大则目标下沉
  33.         if (right + 1 <= heapSize && arr[right] > arr[target]) target = right;
  34.         //如果最大的是目标节点本身则跳出递归
  35.         if (target==index)
  36.         {
  37.             return;
  38.         }
  39.         //节点交换,下沉目标节点
  40.         Swap(arr,index,target);
  41.         //递归调用
  42.         DownFilter(arr,heapSize,target);
  43.     }
  44. }
复制代码
这个实现中,整个排序过程,包罗堆的构建与排序等全部都在原址进行,因此其空间复杂度为常量级。并且其接纳了自下而上的方法初始化堆的构建,这种方法的思绪是从末了一个不为叶子的节点开始依次往进步行下沉操纵,因此每个节点仅需一次调整即可,相比上面的简单实现,建堆的过程的时间复杂度从O(nlogn)下降到了O(n)。其构建过程如下所示:

整个算法的运行过程图示如下:

三、复杂度分析

堆排序运行时间主要是斲丧在初始构建堆和在重建堆时的反复筛选上。
对于自下而上的构建堆过程由于只进行必要的交换因此其复杂度为O(n),在正式排序时,第1次取堆顶记载重建堆需要用O(logi)的时间,并且需要取n-1次堆顶记载,因此,重建堆的时间复杂度为O(nlogn)。
以是总体来说,堆排序的时间复杂度为O(nlogn)。由于堆排序对原始记载的排序
状态并不敏感,因此它无论是最好、最坏和平均时间复杂度均为O(nlogn)。
空间复杂度上,它只有一个用来交换的暂存单元,也非常的不错。
别的,由于初始构建堆所需的比较次数较多,因此,它并不得当待排序序列个数较少的情况。
总结

堆排序现实可以看作选择排序的一种改进实现,它通过在选择到最小的数时对其他记载进行调整,从而避免了冗余的比较来进步获取下一次选择效率。其在时间空间复杂度上都有不错的表现。
完整代码

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

郭卫东

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表