堆排序之JAVA实现

      堆是一种特殊的树形数据结构,通常提到的堆都是指一棵完全二叉树,根节点的值小于(或大于)两个子节点的值,同时,根节点的两个子树也分别是一个堆。
      堆排序是一种树形选择排序,在排序过程中,将R[1…n]看作一棵完全二叉树的顺序存储结构,利用完全二叉树中父节点和子节点之间的关系来选择最小的元素。

排序思路

      堆排序的思想是:对于给定的n个记录,初始时把这些记录看作是一棵顺序存储的完全二叉树,然后将其调整为一个大顶堆(或小顶堆),然后将堆的最后一个元素与堆顶元素(即二叉树的根节点)交换,堆的最后一个元素即为最大记录(或最小记录);接着将前n-1个记录重新调整,再交换最后一个元素与堆顶元素得到次大的记录;重复该过程直到调整的堆中只有一个元素为止。

为了更直接的描述堆排序的思路,下面举一个例子:
现在有一组记录如下,使用堆排序对下面的记录进行排序,这里以最小堆为例。
这里写图片描述
第一步:构建堆
堆是一棵完全二叉树,所以首先要构造一颗完全二叉树,如下:
这里写图片描述
然后,将该树调整为最小堆,调整的过程后面再说,这里先说明堆排序的过程,调整好的堆如下:
这里写图片描述
可以看到,此时堆顶的元素即为最小记录,且每一个子树均满足:根节点的值小于左右子节点的值。

第二步:交换堆顶元素与最后一个元素,重新调整堆
交换堆顶元素与最后一个元素:
这里写图片描述
重新调整堆:
这里写图片描述
到这里,就完成了一个元素的排序,接下来就是对除去这一个元素,剩下的树重复第二个步骤,这里用蓝色表示已经排好序的元素,也就是除去的元素。
交换堆顶元素与最后一个元素:
这里写图片描述
重新调整堆:
这里写图片描述
到这里,就完成了两个元素的排序,接下来就是对除去这两个元素,剩下的树重复第二个步骤,这里用蓝色表示已经排好序的元素,也就是除去的元素。
交换堆顶元素与最后一个元素:
这里写图片描述
重新调整堆:
这里写图片描述
到这里,就完成了三个元素的排序,接下来就是对除去这三个元素,剩下的树重复第二个步骤,直到最后剩下的树中只有一个节点为止。
排序完成的树结构如下:
这里写图片描述


代码实现

了解了堆排序的思想,那么用代码来实现就不难了,主要分为三个步骤:
(1)构建堆;
(2)交换堆顶元素与最后一个元素;
(3)重新调整堆

(1)构建堆

构建堆实际上还是一个调整堆的过程,只是这一步需要对每一个节点都进行调整,使得每一个节点都满足“根节点小于左右子节点的值”这一条件。这样最后的形成的堆才是一个最小堆。
所以构建堆就是从最后一个节点遍历到根节点,堆每一个节点进行调整。

(2)交换堆顶元素与最后一个元素

这个比较简单,没什么可说的

(3)重新调整堆

这里的调整堆和构建堆不同的地方在于,这里的重新调整堆只需要调整根节点即可,而不是像构建对那样需要对全部节点进行调整。为什么呢?
因为在构建堆的时候已经调整了全部节点,每个节点都满足最小堆的条件:根节点小于左右子节点的值。
而(2)的步骤只修改了根节点的值,所以只需要调整根节点即可。


堆排序最关键的代码在于调整堆这部分的代码
完整代码如下:

public class HeapSort {

    public static void main(String[] args) {
        int a[] = { 5, 4, 9, 8, 7, 6, 0, 1, 3, 2 };
        myHeapSort(a);
        for (int i : a)
            System.out.print(i + " ");
    }

    public static void myHeapSort(int[] arr) {
        int i;
        int len = arr.length;
        // 构建一个最小堆
        for (i = len / 2 - 1; i >= 0; i--) {
            adjustment(arr, i, len);
        }
        // 每次将顶点与最后一个节点交换,然后重新调整顶点即可
        for (i = len - 1; i >= 0; i--) {
            int tmp = arr[0];
            arr[0] = arr[i];
            arr[i] = tmp;
            adjustment(arr, 0, i);
        }
    }

    public static void adjustment(int[] arr, int pos, int len) {
        int child = 2 * pos + 1;
        // 若pos的右节点存在,则child指向左右节点值较小的那个节点
        if (child + 1 < len && arr[child] > arr[child + 1]) {
            child++;
        }
        // 将pos节点的值取其与子节点中的最小值,然后继续调整
        if (child < len && arr[pos] > arr[child]) {
            int tmp = arr[pos];
            arr[pos] = arr[child];
            arr[child] = tmp;
            adjustment(arr, child, len);
        }
    }
}

main方法调用myHeapSort方法,传入一个数组,也就是待排序的记录集合。

// 构建一个最小堆
for (i = len / 2 - 1; i >= 0; i--) {
    adjustment(arr, i, len);
}

这几行代码就是构建堆的代码,遍历每一个节点,对每一个节点进行调整。
为什么i从len / 2 - 1开始呢?因为从这个节点开始,才至少有一个左右节点,比它大的节点都没有左右节点,也就无需调整。

// 每次将顶点与最后一个节点交换,然后重新调整顶点即可
for (i = len - 1; i >= 0; i--) {
    int tmp = arr[0];
    arr[0] = arr[i];
    arr[i] = tmp;
    adjustment(arr, 0, i);
}

这部分代码就是每次交换堆顶元素与最后一个元素,然后重新调整堆顶元素。

adjustment方法就是对执行的节点进行调整。要记住,如果某个节点调整的时候与左右节点进行值的交换,那么要继续调整交换了值的那个子节点,直到没有交换发生为止,所以使用了递归完成。

猜你喜欢

转载自blog.csdn.net/qq_16403141/article/details/80526313
今日推荐