美文网首页
编程马拉松 Day05 堆、二叉堆、堆排序

编程马拉松 Day05 堆、二叉堆、堆排序

作者: Geek5Nan | 来源:发表于2018-07-01 02:54 被阅读0次

堆排序需要用到二叉堆,在开始之前,我们先来了解一下什么是二叉堆。

当二叉树满足满足如下条件时,我们说这个二叉树是堆有序的:

  1. 每一个父结点的值都比它的子结点大(称为大顶堆)或小(称为小顶堆)
  2. 子结点的大小与其左右位置无关

堆有序的二叉树,也可称为二叉堆。二叉堆是最常见的堆结构,因此也常将二叉堆直接称为,可以采用如下两种方式来表示二叉堆

  1. 使用指针,二叉树的每个结点需存储三个指针,分别指向其父结点和两个子结点
  2. 使用数组,对二叉树做层序遍历,按层级顺序放入数组中,根结点在数组索引0的位置存放,其子结点分别在索引1和2的位置,1和2个子结点分别在位置3、4和5、6中存放,以此类推

就排序来讲,其所需处理的数据较为连续,没有空隙,可用完全二叉树来表示。对于完全二叉树,采用数组的表示方法也更方便些,下图展示了采用数组实现的两个二叉堆。


二叉堆

对于数组实现的二叉堆,索引为k的结点的父结点的索引为(k-1)/2,它的子结点的索引分别为2k+1和2k+2。

堆有序化

以大顶堆为例,有序化的过程中我们会遇到两种情况

  1. 在堆底加入一个较大元素时,我们需要由下至上恢复堆的顺序
  2. 当将根结点替换为一个较小元素时,我们需要由上到下恢复堆的顺序

由下至上的堆有序化(上浮)

如果堆的有序状态因为某个结点变的比它的父结点更大而被打破,就需要通过将它与它的父结点交换来恢复堆有序。交换后,这个结点比它的两个子结点都大,但这个结点仍然可能比它现在的父结点更大。我们可以一遍遍的用同样的方式来将其向上移动,直到遇到一个比它更大的父结点或到达了堆的根结点,如下图所示。


由下至上的堆有序化(上浮)

上浮操作对应的代码如下

private void swim(Integer arr[], int k) {
    while(k > 0 && arr[(k - 1) / 2] < arr[k]) { //若k>0且索引为k的结点大于其父结点时,将该结点与其父结点交换
        swap(arr, k, (k - 1) / 2);
        k = (k - 1) / 2;
    }
}

由上至下的堆有序化(下沉)

如果堆的有序状态因为某个结点变的比它的某个子结点更小而被打破,就需要通过将它和它的子结点中较大者交换位置来恢复堆有序。交换可能会在子结点处继续打破堆的有序状态,此时可以采用相同的方式,将结点向下移动直到它的子结点都比它小或是到达了堆的底部,如下图所示。


由上至下的堆有序化(下沉)

下沉操作对应的代码如下

private void sink(Integer arr[], int k) {
    while(2 * k + 1 <= arr.length - 1) {//若k存在子结点,则进入循环
        int j = 2 * k + 1; //获取k的第一个子结点
        if (j < arr.length - 1 && arr[j] < arr[j + 1]) {//若存在两个子结点,则找到其中较大的子结点
            j++;
        }
        if (arr[j] > arr[k]) {//若k的较大子结点比k大,则交换它们的位置
            swap(arr, j, k);
            k = j;
        }
    }
}

堆排序

在介绍完堆的数据结构和操作方式后,我们来看堆排序是如何进行的。

堆的构造

  1. 将原数组看做堆的话,则最后一个分支结点(含有子结点的结点)在原数组中的索引为 (n-1)/2 -1
  2. 从(n-1)/2-1向前依次执行下沉操作,从而得到堆有序的数组
堆初始化

堆的排序

  1. 取出堆的根结点,与数组最后一个元素交换。交换后堆有序状态可能会被打破,需要在新的根结点进行下沉操作,使其恢复为堆有序状态。此时数组中最大(大顶堆)/最小(小顶堆)的值存放在数组末位,除它以外的最 大/小 值位于堆顶。
  2. 从数组中排除最后一个元素,重复步骤2,直到数组中的元素全部排除时,完成排序
堆排序
public static void heapSort(Integer arr[]) {
    int n = arr.length;
    //堆的构造,对每一个含有孩子的结点做下沉操作,得到大顶堆
    for (int i = (n-1) /2 -1; i >= 0; i--) {
        heapSink(arr, i, n);
    }
    printArr(arr, "大顶堆");
    for (int i = n - 1; i > 0; i--) {
        swap(arr, 0, i);
        heapSink(arr, 0, i);
    }
    printArr(arr, "堆排序");

}

public static void heapSink(Integer arr[], int i, int length) {
    while(2 * k + 1 <= length - 1) {//若k存在子结点,则进入循环
        int j = 2 * k + 1; //获取k的第一个子结点
        if (j < length - 1 && arr[j] < arr[j + 1]) {//若存在两个子结点,则找到其中较大的子结点
            j++;
        }
        if (arr[j] > arr[k]) {//若k的较大子结点比k大,则交换它们的位置
            swap(arr, j, k);
            k = j;
        }
    }
}

堆排序动态图


堆排序动态图

小结

堆排序算法也是一种选择排序算法,整体由堆的构建、堆的交换与下沉两个步骤组成。其中堆的构建需要比较(n-1)/2-1次下沉,每次下沉至多交换一次,时间复杂度为O(n);堆的交换与下沉中需交换n次,下沉依次需要执行\log_2(n-1),log_2(n-2)...1次交换,近似为Nlog_2N。因此堆排序的时间复杂度为O(N\log_2N)

相关文章

  • 编程马拉松 Day05 堆、二叉堆、堆排序

    堆 堆排序需要用到二叉堆,在开始之前,我们先来了解一下什么是二叉堆。 当二叉树满足满足如下条件时,我们说这个二叉树...

  • PHP算法系列教程(三)-堆排序

    PHP算法系列教程(三)-堆排序 介绍 要介绍堆排序我们就要先了解什么是堆. 什么是堆 堆(二叉堆)可以视为一棵完...

  • 堆排序

    堆排序是利用二叉堆的自调整特性将数组变为有序序列的排序方法二叉堆的特性: 最大堆的堆顶是整个堆中的最大元素。 最小...

  • java堆排序

    什么是堆排序:图解堆排序堆排序:利用了堆这种数据结构堆数据结构:特殊的完全二叉树,因为具有以下的特点:1)每个结点...

  • HeapSort学习笔记

    完全二叉树 堆排序 什么是堆(Heap)? 堆本质上是一棵二叉树,而且是完全二叉树。 (注:从严格意义上讲,堆可以...

  • 堆排序

    在上篇文章中已经讲过了什么是二叉堆。那么这个二叉堆怎样使用呢?so,这篇文章讲讲堆排序。 首先回顾一下二叉堆的特性...

  • 堆排序

    堆 堆排序中用到的是二叉堆,它其实就是一棵近似于完全二叉树树按照层次遍历得到的数组。而堆排序中只要是利用最大(小)...

  • Java排序算法 - 堆排序

    堆排序 堆排序是基于堆这种数据结构的一种排序算法,通过每一次弹出堆顶元素,实现排序。预备知识: 堆是一棵完全二叉树...

  • 堆排序

    堆排序 堆排序(Heapsort) 是指利用堆这种数据结构所设计的一种排序算法. 堆是一个近似完全二叉树的结构, ...

  • 数据结构与算法-堆排序

    堆排序 堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆的性质:即子节...

网友评论

      本文标题:编程马拉松 Day05 堆、二叉堆、堆排序

      本文链接:https://www.haomeiwen.com/subject/jqzxuftx.html