TypeScript实现十大排序算法(六) - 堆排序

938 阅读4分钟

一. 堆排序的定义

堆排序(Heap Sort)是一种选择排序,它的特点是:对需要排序的数据建立一个堆,然后每次取出堆顶元素,直到堆为空。

  • 每次取出堆顶元素后,剩下的元素就是一个新的待排序的序列,因此,每次取出的元素都是序列中最大(最小)的元素。

在堆排序中,可以使用大根堆或小根堆。

  • 大根堆排序时,每次取出的都是最大的元素。
  • 小根堆排序时,每次取出的都是最小的元素。

堆排序的时间复杂度为 O(nlogn)。

学习堆排序之前最好先理解堆结构,这样更有利于对堆排序的理解。

二. 堆排序的流程

堆排序可以分成两大步骤:构建最大堆和排序

构建最大堆:

①遍历待排序序列,从最后一个非叶子节点开始,依次对每个节点进行调整。

②假设当前节点的下标为 i,左子节点的下标为 2i+1,右子节点的下标为 2i+2,父节点的下标为 (i-1)/2。

③对于每个节点 i,比较它和左右子节点的值,找出其中最大的值,并将其与节点 i 进行交换。

④重复进行这个过程,直到节点 i 满足最大堆的性质。

⑤依次对每个非叶子节点进行上述操作,直到根节点,这样我们就得到了一个最大堆。

排序:

①将堆的根节点(也就是最大值)与堆的最后一个元素交换,这样最大值就被放在了正确的位置上。

②将堆的大小减小一,并将剩余的元素重新构建成一个最大堆。

③重复进行步骤 ① 和步骤 ②,直到堆的大小为 1,这样我们就得到了一个有序的序列。

三. 堆排序的图解

堆排序

整体流程:

案例步骤:

堆排序

四. 堆排序的代码

下面是TypeScript实现的堆排序代码,带有详细的注释:

// 堆排序的函数
function heapSort(arr: number[]) {
  // 先建立大根堆(从最后一个非叶子节点向上调整)
  for (let i = Math.floor(arr.length / 2) - 1; i >= 0; i--) {
    adjustHeap(arr, i, arr.length);
  }
  // 每次把堆顶的数与最后一个数交换,并重新调整堆
  for (let i = arr.length - 1; i > 0; i--) {
    [arr[0], arr[i]] = [arr[i], arr[0]];
    adjustHeap(arr, 0, i);
  }
  return arr;
}

// 堆调整函数
function adjustHeap(arr: number[], i: number, len: number) {
  let temp = arr[i];
  // 将当前节点与其左右子节点比较,找出最大的那个
  for (let j = 2 * i + 1; j < len; j = 2 * j + 1) {
    if (j + 1 < len && arr[j + 1] > arr[j]) {
      j++;
    }
    // 如果子节点比父节点大,就交换
    if (arr[j] > temp) {
      arr[i] = arr[j];
      i = j;
    } else {
      break;
    }
  }
  arr[i] = temp;
}



// 测试数据
const testArr = [5, 2, 9, 1, 5, 6];
// 调用插入排序函数
const sortedArr = heapSort(testArr);
// 打印结果
console.log(sortedArr);

整个代码实现了快速排序的算法流程:

  • heapSort 函数是堆排序的主体函数,使用大根堆实现从小到大的排序
  • adjustHeap 函数是堆调整函数,用来调整大根堆,以保证堆顶的数是整个堆中最大的数

五. 堆排序的时间复杂度

堆排序的时间复杂度为O(nlogn),详细的计算过程如下:

堆排序的主要过程是建堆和排序。

  • 建堆的时间复杂度为O(n),因为需要对每一个节点都进行比较,以确保堆性质得到满足。

  • 排序的时间复杂度为O(nlogn),因为每一次排序都需要交换根节点和最后一个节点,并且重新堆化剩下的元素。

    • 这个过程的时间复杂度与树的高度相关,由于这是一个完全二叉树,所以高度为logn。
    • 因此,排序的总时间复杂度为O(nlogn)。

总的来说,堆排序的时间复杂度为O(nlogn),是一种高效的排序算法。

六. 堆排序的总结

堆排序是一种选择排序的改进版,利用了堆这种数据结构的性质。

  • 它的时间复杂度为O(nlogn),空间复杂度为O(1)。

  • 它的优点在于其不需要额外的数组,只需要在原数组上操作,因此空间复杂度比较低。

  • 同时,它还比较快,比较适合大规模数据的排序。不过,它的缺点是实现较为复杂,需要对堆数据结构有较深的了解。

  • 同时,在实际应用中,由于需要频繁的交换元素,因此在排序速度上可能比较慢。

  • 因此,需要根据实际情况选择排序方式。

coderwhy公众号分享各种编程技术、生活、感悟,欢迎关注、交流、分享。