排序算法 —— 堆排序(图文超详细)


堆排序

1. 排序思路

  • 将一组数据建成大根堆。
  • 定义一个end来记录此时堆末尾元素的位置。
  • 每排序好一个,end值就减一个。
  • 在排序的过程中要保障堆时刻为大根堆。
  • 开始排序时,也要保证是大根堆。

待排序的数据:27, 15, 19, 18, 28, 34, 65, 49, 25, 37

因为堆实际上是在二叉树的基础上做的一些调整。
在操作堆的时候本质是利用了二叉树的思想。

2. 如何建成大根堆

给每一个数据一个下标值。

2.1. 将待排序的数据看成一个完全二叉树。

下图是排列好的二叉树。

但是它此时既不是大根堆也不是小根堆,即不是一个堆。

要将它变成一个堆,要先将它变成大根堆或者小根堆。

2.2. 建堆思想

如果是要建立成大根堆,先将它的子树全部建成大根堆。此时,这棵树整体也就变成了一个大根堆。


先将图中画红圈的建成大根堆,建成后,整棵树就是一个大根堆了。

有两个方法:

  1. 从无到有的建立堆,获取一个数据的同时将它建立为一个大根堆。一边获取数据,一边的建堆。
    如果有两个数据,就将两个数据建成堆;若四个数据,就将四个数据建成堆。
  2. 先从最后一棵子树的根节点开始调整,将它建成大根堆;再将它前面的结点逐渐建成大根堆;最后整体就是一个大根堆了。(向下调整)

2.3. 建堆步骤

实现向下调整

2.3.1.先将最后一棵子树建成大根堆
  • 最后一棵子树的孩子结点的下标是数组的长度减1。(10 - 1)
  • 已知孩子结点,求父亲结点的公式:(i - 1) / 2,i 是孩子结点的下标。
  • 现在得到了最后一棵子树的孩子和父亲结点的下标。(孩子:9,父亲:4)
  • 比较两个结点的值,将较大的结点交换到对应父节点的位置。
  • 若父节点为最大值,则不需要交换。

最后一棵子树建堆后:

图中的4下标和9下标的结点进行了交换。

2.3.2.将其余子树建成大根堆

第1步:

  • r 记录父亲结点的下标;i 记录左右最大值的下标。
  • r - - ,找到新的子树的根节点。
  • 找到了根节点,可以访问到子节点。
    在这里插入图片描述


第2步:

  • i 结点始终记录左右子节点的最大值。
  • 将这个最大值节点与当前 r 节点交换。


    交换后:

下图的3下标和7下标交换了。


第3步:

  • r - -,r 指向了2下标所在根节点的位置。
  • i 指向左右子节点的最大值。



第4步:

  • 交换 r 和 i 指向的两个结点。

第5步:

  • r - -,r 指向了1下标所在根节点的位置。
  • i 指向左右子节点的最大值。



第6步:

  • 交换 r 和 i 指向的两个结点。


走到这里就会发现,3下标位置的根节点又不是大根堆了。
要让 r 重新指向3下标,i 再指向左右孩子最大值。


第7步:

  • r 指向 i ,i 指向(2 * p + 1)的位置(下标7处),p 代表的是父节点下标。
  • i 再指向左右孩子最大值。


第8步:

  • 比较 r 和 i 之后,交换较大到根节点位置。


程序并不知道这一步交换完成后,15有没有左右节点了。
因此并不能确定交换一次 r 和 i 结点之后就为大根堆了。


解决办法:

  • r 指向 i 的位置,i 指向 (2 * p + 1) 的位置(下标17处),p 代表的是父节点下标。
  • i 再指向左右孩子最大值。


此时 i 的值大于数据个数(10)的最大下标值(9),此时说明15左右没有孩子。
此时如果 i 的值乘2加1大于9,就说明此时的 i 指向的就是最后一个结点了。

2.3.3. 代码分析
 for (int parent = (array.length - 1 - 1) / 2; parent >= 0 ; parent--) {
 }
  • parent 代表的是父节点 r 。
  • 先求出数组长度,减1表示得到末尾元素下标,再减1表示得到每一个父节点。
  • 当 parent 小于0时,调整完毕。
 shiftDown(array, parent, array.length);
  • 循环调用向下调整方法。
 public static void shiftDown(int[] array, int parent, int len) {
 }
  • 参数 len 是每棵子树调整的结束位置。
  • 结束位置不能大于 len 。
int child = (2 * parent) + 1;
  • 求得孩子结点,代表的是 i 。
 while (child < len) {
     if (child + 1 < len && array[child] < array[child + 1]) {
         child++;
     }
 }

  • child < len 保证有左孩子,len 是数组长度。
  • child + 1 < len 保证不越界。
  • array[child] < array[child + 1] 保证 child 是左右最大值。
if (array[child] > array[parent]) {
}
  • 比较孩子结点和父节点的值。
 if (array[child] > array[parent]) {
       swap(array, child, parent);
       parent = child;
       child = 2 * parent + 1;
 }
  • 如果孩子结点大于父节点就交换这两个结点。
  • 父节点指向子节点。
  • 孩子结点指向新的位置。
public static void swap(int[] array, int i, int j) {
    int tmp = array[i];
    array[i] = array[j];
    array[j] = tmp;
}
  • 交换方法的实现。

最终实现的大根堆:

3 如何实现堆排序

3.1 排序思路

  • 将堆顶元素与堆的最后一个元素交换,此时堆的末尾位置就是最大的元素了。
  • 每一次交换后要保证堆是大根堆。
  • 若堆不是大根堆,就调用向下调整重新建成大根堆。
  • 重新交换堆顶元素与堆的末位置元素。

3.2 代码实现的思路

  • 定义一个 end 来记录此时堆的末尾位置元素的位置。(元素个数 - 1)
  • 每排序好一个数据,end 就减去1。
  • 若此时的堆不是大根堆,调用向下调整重新实现为大根堆。
  • 若刚开始排序时,堆不为大根堆,则要将它建成大根堆。

3.3 代码分析

 creatBigHeap(array);
  • 先调用建堆方法建堆。
int end = array.length - 1;
  • 数组长度减1求的是堆末尾位置元素的下标。
 while (end > 0) {
       swap(array, 0, end);
       shiftDown(array, 0, end);
       end--;
 }
  • 循环调用交换方法交换0下标和end位置的值。
  • 0 下标即使堆顶元素的位置。
  • 循环调用向下调整方法建成大根堆。
  • 每次交换后,end每次减少一个。

3.4 排序过程

  1. 交换一次后

  2. 此时不为大根堆了,先建成大根堆,再交换。
    在这里插入图片描述

  3. 65 是排序好的了,所以在重新建成大根堆的时候不需要处理65,交换第2次。

  4. 按照上面的步骤会逐渐排成:
    在这里插入图片描述

4. 整体代码展示

 public static void heapSort(int[] array) {
        creatBigHeap(array);
        int end = array.length - 1;
        while (end > 0) {
            swap(array, 0, end);
            shiftDown(array, 0, end);
            end--;
        }
 }

//建堆
public static void creatBigHeap(int[] array) {
    for (int parent = (array.length - 1 - 1) / 2; parent >= 0 ; parent--) {
        //调用向下调整
        shiftDown(array, parent, array.length);
    }
}

//向下调整的方法
public static void shiftDown(int[] array, int parent, int len) {
    int child = (2 * parent) + 1;
    while (child < len) {
        if (child + 1 < len && array[child] < array[child + 1]) {
            child++;
        }
        if (array[child] > array[parent]) {
            swap(array, child, parent);
            parent = child;
            child = 2 * parent + 1;
        }else {
            break;
        }
    }
}

//交换的方法
public static void swap(int[] array, int i, int j) {
    int tmp = array[i];
    array[i] = array[j];
    array[j] = tmp;
}

在这里插入图片描述

  • 11
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

与大师约会

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值