排序-堆排序

概述

堆排序,是将数组元素看做一棵按层平铺的完全二叉树,根据二叉树的性质进行操作。一般而言升序选择大顶堆,降序选择小顶堆
堆排序过程如下:

  • 用数列构建出一个大顶堆,取出堆顶的数字;
  • 调整剩余的数字,构建出新的大顶堆,再次取出堆顶的数字;
  • 循环往复,完成整个排序。

性质

完全二叉树的几个性质。将根节点的下标视为 0,则完全二叉树有如下性质:
  - 对于完全二叉树中的第 i 个数,它的左子节点下标:left = 2i + 1
  - 对于完全二叉树中的第 i 个数,它的右子节点下标:right = left + 1
  - 对于有 n 个元素的完全二叉树(n>=2),它的最后一个非叶子结点的下标:n/2 - 1

代码实现

用堆排序的方式解决leetcode第215题

小顶堆

 /**
 * 第K大,降序排序的话,排序完成时,结果应该在index=k-1的地方
 */
class MinHeapSolution {
    public int findKthLargest(int[] nums, int k) {
        //堆排序,降序排序,小顶堆,排序length-k次
        buildHeap(nums, nums.length);
        for (int i = nums.length - 1; i >= k - 1; i--) {
            swap(nums, 0, i);//每次取了堆顶数据后,heap的size就需要-1
            justifyHeap(nums, 0, i);
        }
        return nums[k - 1];
    }

    public void buildHeap(int[] nums, int j) {
        for (int i = j / 2 - 1; i >= 0; i--) {
            justifyHeap(nums, i, j);
        }
    }

    private void justifyHeap(int[] nums, int currentIndex, int heapSize) {
        int minIndex = currentIndex;
        if (2 * currentIndex + 2 < heapSize && nums[2 * currentIndex + 2] < nums[minIndex]) {
            minIndex = 2 * currentIndex + 2;
        }
        if (2 * currentIndex + 1 < heapSize && nums[2 * currentIndex + 1] < nums[minIndex]) {
            minIndex = 2 * currentIndex + 1;
        }
        if (currentIndex != minIndex) {
            swap(nums, currentIndex, minIndex);
            justifyHeap(nums, minIndex, heapSize);
        }
    }

    public void swap(int[] nums, int i, int j) {
        int t = nums[i];
        nums[i] = nums[j];
        nums[j] = t;
    }

}

大顶堆

/**
 * 第K大的元素,升序排列时,结果在length-k的位置,大顶堆需要排序k次
 */
class MaxHeapSolution {
    public int findKthLargest(int[] nums, int k) {
        buildMaxHeap(nums);
        // 调整 k-1 次,排序K次
        for (int i = nums.length - 1; i >= nums.length - k; i--) {
            swap(nums, 0, i);
            maxHeapify(nums, 0, i);
        }
        // 此时,堆顶的元素就是第 k 大的数
        return nums[nums.length - k];
    }

    // 构建初始大顶堆
    public void buildMaxHeap(int[] arr) {
        // 从最后一个非叶子结点开始调整大顶堆,最后一个非叶子结点的下标就是 arr.length / 2-1
        for (int i = arr.length / 2 - 1; i >= 0; i--) {
            maxHeapify(arr, i, arr.length);
        }
    }

    // 调整大顶堆,第三个参数表示剩余未排序的数字的数量,也就是剩余堆的大小
    private void maxHeapify(int[] arr, int i, int heapSize) {
        // 左子结点下标
        int l = 2 * i + 1;
        // 右子结点下标
        int r = l + 1;
        // 记录根结点、左子树结点、右子树结点三者中的最大值下标
        int largest = i;
        // 与左子树结点比较
        if (l < heapSize && arr[l] > arr[largest]) {
            largest = l;
        }
        // 与右子树结点比较
        if (r < heapSize && arr[r] > arr[largest]) {
            largest = r;
        }
        if (largest != i) {
            // 将最大值交换为根结点
            swap(arr, i, largest);
            // 再次调整交换数字后的大顶堆
            maxHeapify(arr, largest, heapSize);
        }
    }

    public void swap(int[] nums, int i, int j) {
        int t = nums[i];
        nums[i] = nums[j];
        nums[j] = t;
    }
}
posted @ 2021-07-01 11:57  noidler  阅读(11)  评论(0编辑  收藏  举报