目前接触的一些思路:滑动窗口、动态规划、贪心、斐波那契、二分法、递归、前/中/后序/层序(辅助队列)遍历、DFS(深度优先遍历)、BFS(广度优先遍历)

昨天在剑指-41 数据流中的中位数中看到了两种思路解题

  • 大顶堆和小顶堆找出中位数
  • 通过有序集合和两个指针来指向中位数所在地址(因为元素数目存在奇数和偶数的情况)

大顶堆和小顶堆的思路比较有趣,因为这个思路以前常听但是没有深入了解。常用用途:排序,优先队列 PriorityQueue

堆排序虽然名字是堆,但是并不是Java虚拟机模型中的堆,而是根据二叉树特性构造的数组(该二叉树除了最后一层不一定满,其他各层都是满节点的,因为数组中的元素映射到堆中是从上到下,从左到右的)

数组 heap
|1|2|3|4|5|6|
      1
     / \
    2   3
   / \ /
  4  5 6 
下标为i(这里为了方便对齐,下标从1开始)的元素父节点,左节点,右节点下标可以分别表示为
父节点 =》i/2
左节点 =》2i
右节点 =》2i+1

大顶堆/小顶堆定义,堆顶元素为最大/最小值(注意,这不代表整个数组中的元素是完全有序的,只保证堆顶元素的最大/最小性质

基础的堆排序分可以拆分成两步实现

  • 构造最大/最小堆
  • 循环以下步骤
    • 交换数组首元素和最后一个元素
    • 重构交换元素后的堆

构造的步骤

  • 从数组的length/2~0循环进行堆化
  • 堆化步骤
    • 判断根节点和左右节点的大小,如果不符合定义则进行元素值交换
    • 如果产生了交换,则递归堆化子数,使其仍符合堆定义

以下给出大小顶堆以及堆排序的实现

    /**
   * 堆排序
   *
   * @param nums 待排序数组
   * @param ascOrder true 升序 false 降序
   */
  public static void heapSort(int[] nums, boolean ascOrder) {
    for (int i = nums.length - 1; i > 0; i--) {
      buildHeap(nums, i, ascOrder);
      swap(nums, 0, i);
    }
  }

  /**
   * 数据交换
   * @param nums 数组
   * @param a 下标1
   * @param b 下标2
   */
  private static void swap(int[] nums, int a, int b) {
    int tmp = nums[a];
    nums[a] = nums[b];
    nums[b] = tmp;
  }

  /**
   * 建堆
   *
   * @param nums 数组
   */
  private static void buildHeap(int[] nums, int heapSize, boolean maxHeapPify) {
    int index = heapSize / 2;
    // 必须自底向上进行堆化,确保整个堆一定符合定义,避免前面的元素堆化后,后面出现大于堆顶的元素导致堆化失败。类似于冒泡排序的思想,必须让最大值从下到上浮
    while (index >= 0) {
      heapPify(nums, heapSize, index, maxHeapPify);
      index--;
    }
  }

  /**
   * 堆化
   *
   * @param nums 数组
   * @param heapSize 堆有效边界
   * @param index 要堆化的根节点下标
   * @param maxHeapPify true - 最大堆 false - 最小堆
   */
  private static void heapPify(int[] nums, int heapSize, int index, boolean maxHeapPify) {
    /* 
      1.比较根节点和左右两个子节点下标的大小,如果不符合堆排性质,则调整
      2.如果根节点和左右节点进行了调换,那么要递归调用该子树堆化
    */
    int left = getLeft(index);
    int right = getRight(index);
    // 最大值下标
    int largest = index;
    if (left <= heapSize) {
      if (maxHeapPify) {
        // 大顶堆
        if (nums[left] > nums[largest]) {
          largest = left;
        }
      } else {
        // 小顶堆
        if (nums[left] < nums[largest]) {
          largest = left;
        }
      }
    }
    if (right <= heapSize) {
      if (maxHeapPify) {
        // 大顶堆
        if (nums[right] > nums[largest]) {
          largest = right;
        }
      } else {
        // 小顶堆
        if (nums[right] < nums[largest]) {
          largest = right;
        }
      }
    }
    if (largest != index) {
      // 交换元素值
      swap(nums, index, largest);
      // 递归重新堆化该子树
      heapPify(nums, heapSize, largest, maxHeapPify);
    }
  }

  /**
   * 获取左子节点下标,下标从0开始
   *
   * @param parent 父节点下标
   * @return 左节点下标
   */
  private static int getLeft(int parent) {
    return 2 * parent + 1;
  }

  /**
   * 获取右节点下标,下标从0开始
   *
   * @param parent 父节点下标
   * @return 右节点下标
   */
  private static int getRight(int parent) {
    return 2 * parent + 2;
  }

Q.E.D.


每一个平凡的日常都是连续的奇迹