堆排序的理解

364 阅读3分钟

堆排序

堆的概念和特性

堆是一颗顺序存储的完全二叉树。

其中每个节点的关键字都不大于其孩子节点的关键字,称为小根堆。

其中每个节点的关键字都不小于其孩子节点的关键字,称为大根堆。

节点的两个子节点之间的大小关系没有约束。

堆一般采用数组实现,二叉树的根节点是下标0。 对于下标为i的节点,其左孩子下标为2i+1,右孩子下标为2i+2,其父节点下标为(i-1)/2。

堆排序解释

以升序为例,堆排序步骤如下:

  1. 首先将数组堆化(heapify),此时满足所有节点都大于等于其子节点,根元素为最大元素;
  2. 将根元素与堆中最后一个元素交换位置(swap),可以看作是把最大的元素移出堆,堆的大小减一,新的堆由于来了新的根节点而处于无序状态;
  3. 将根节点进行“下沉”(siftDown),也就是让根节点与其较大的子节点进行交换位置,然后迭代地处理子节点,直到当前节点比其左右子节点都大,此时堆有序。 重复2、3直到堆大小为1。

为什么swap之后,“下沉”可以使堆有序? 此时只有根节点不满足“每个节点不小于其子节点”,将根节点与较大的子节点交换后,现在的根节点即是二叉树中的最大元素了,迭代地处理交换后的子节点,类似冒泡排序,可将路径上的最小值下沉到叶子节点。

最终满足了“每个节点不小于其子节点”。

siftDown方法用来处理这种情况:有序堆中出现一个元素破坏了有序性,那么将该元素siftDown,可以使得堆有序。

堆化heapify的原理? 堆化是从最后一个非叶子节点开始,向前(根)逐个siftDown。 处理某个节点时,其左右子树已经是有序堆了,恰好满足siftDown的适用情况。

复杂度

堆排序属于选择排序类型,是一种不稳定的排序算法。 时间复杂度O(NlogN),空间复杂度O(1)。

代码

堆排序三部曲:堆化、交换、下沉。

public class HeapSort {

    /**
     * 堆化、交换、下沉
     */
    public int[] sortArray(int[] nums) {
        int len = nums.length;
        heapify(nums);

        for (int i = len - 1; i >= 1; ) {
            swap(nums, 0, i);
            i--;
            siftDown(nums, 0, i);
        }
        return nums;
    }

    private void heapify(int[] nums) {
        int len = nums.length;
        // 从最后一个非叶子节点,到根逐个下沉
        for (int i = (len - 2) / 2; i >= 0; i--) {
            siftDown(nums, i, len - 1);
        }
    }

    /**
     * 下沉指定下标的元素
     * @param k 要下沉的元素下标
     * @param end 堆的最后一个元素下标
     */
    private void siftDown(int[] nums, int k, int end) {
        // 2*k+1 <= end 的含义是k存在孩子
        while (2 * k + 1 <= end) {
            // j是k的左孩子
            int j = 2 * k + 1;
            // 如果有右孩子且右孩子比左孩子大,那么取右孩子
            if (j + 1 <= end && nums[j + 1] > nums[j]) {
                j++;
            }
            // 现在j是k的孩子中较大的那个
            // 如果k的孩子j比k大,那么交换j和k,接着迭代处理j
            if (nums[j] > nums[k]) {
                swap(nums, j, k);
                k = j;
            } else {
                break;
            }
        }
    }

    private void swap(int[] nums, int a, int b) {
        int tmp = nums[a];
        nums[a] = nums[b];
        nums[b] = tmp;
    }
}