数据结构 - 堆

315 阅读3分钟

定义

堆是一种树形结构。堆总是满足下列性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆

堆的插入

  1. 将值插入到最后一个节点
  2. 将节点值和根节点进行比较,不符合(最大/小堆规则的进行交换,依次,直到栈顶或者符合规则)

插入时间复杂度 O(logn) 即树的高度

// 插入 数字 5    // 把5 放入最后节点之后,和根 6比较,小于根,需要交换
     1              1               1
   /   \          /   \           /   \
  3    6   ===>  3     6  ===>   3     5
 / \  / \       / \   / \       / \   / \
4   8 7        4   8 7   5     4   8 7   6

堆的取值

由于最大/小堆的特性,堆顶一定是最大/小值,直接获取最大/小值是O(1),取值之后,堆的结构需要调整。调整步骤

  1. 把最后一个节点放到堆顶
  2. 把堆顶和左右节点比较,是否符合规则,不符合进行交换

取值之后堆的调整:时间复杂度 O(logn) 即树的高度

// 获取堆顶 1   // 把最后一个元素 7 放到堆顶,然后进行比较
     1              7               3              3
   /   \          /   \           /   \          /   \
  3    6   ===>  3     6  ===>   7     6  ===>  4     6
 / \  / \       / \   / \       / \   / \      / \   / \
4   8 7        4   8           4   8          7   8

根据数组 [1, 2, 3, 4, 5] 建立一个堆最大堆的过程就是一个一个的插入

// 初始  // 插入2,交换,依次
1        1       2
   ==>  / \ ==> / \
       2       1

代码如下:

const createHeap = (nums) => {
  const result = { parent: null, val: nums[0], left: null, right: null }
  const queue = [result]
  for (let i = 1; i < nums.length; i++) {
    const tail = queue[0]
    let node = { parent: tail, val: nums[i], left: null, right: null }
    if (!tail.left) {
      tail.left = node
    } else if (!tail.right) {
      tail.right = node
    }
    while(node && node.parent && node.val > node.parent.val) {
      const temp = node.val
      node.val = node.parent.val
      node.parent.val = temp
      node = node.parent
    }
    if (tail.left && tail.right) {
      queue.shift()
      queue.push(tail.left)
      queue.push(tail.right)
    }
  }
  return result
}
用数组去模拟堆的格式
const createHeapByArray = (nums) => {
  const result = []
  for (let i = 0; i < nums.length; i++) {
    result.push(nums[i])
    let currentPosition = i
    let parentPoisition = (currentPosition - 1) >> 1
    while (currentPosition > 0 && result[currentPosition] > result[parentPoisition]) {
      const temp = result[currentPosition]
      result[currentPosition] = result[parentPoisition]
      result[parentPoisition] = temp
      currentPosition = parentPoisition
      parentPoisition = (currentPosition - 1) >> 1
    }
  }
  return result
}

console.log(createHeapByArray([1, 2, 3, 4, 5, 6, 7, 8])) // [8, 7, 6, 4, 3, 2, 5, 1]
抽象一个最大堆的类MaxHeap
class MaxHeap {
  constructor(nums) {
    this.data = []
    this.init(nums)
  }

  init (nums) {
    for (let i = 0; i < nums.length; i++) {
      this.insert(nums[i])
    }
  }

  swap (index1, index2) {
    const temp = this.data[index1]
    this.data[index1] = this.data[index2]
    this.data[index2] = temp
  }

  insert (num) {
    this.data.push(num)
    let currentPosition = this.data.length - 1
    let parentPoisition = (currentPosition - 1) >> 1
    while (currentPosition > 0 && this.data[currentPosition] > this.data[parentPoisition]) {
      this.swap(currentPosition, parentPoisition)
      currentPosition = parentPoisition
      parentPoisition = (currentPosition - 1) >> 1
    }
    return true
  }

  remove () {
    if (this.data.length === 0) {
      return null
    }
    const top = this.data[0]
    if (this.data.length > 1) {
      this.data[0] = this.data.pop()
    } else {
      this.data.pop()
    }
    let parentPoisition = 0
    let leftIndex = 2 * parentPoisition + 1
    let rightIndex = leftIndex + 1
    while (leftIndex < this.data.length) {
      let maxChildIndex = leftIndex
      if (rightIndex < this.data.length && this.data[leftIndex] < this.data[rightIndex]) {
        maxChildIndex = rightIndex
      }
      if (this.data[parentPoisition] >= this.data[maxChildIndex]) {
        break
      }
      this.swap(parentPoisition, maxChildIndex)
      parentPoisition = maxChildIndex
      leftIndex = 2 * parentPoisition + 1
      rightIndex = leftIndex + 1
    }
    return top
  }
}
堆排序
/**
 * 堆排序调整
 * 从index开始往下调整,index节点和子节点是否满足堆条件
 * 一直往下,一直到符合条件/堆结尾
*/
const shift = (nums, index, size) => {
  let parentIndex = index
  let childIndex = parentIndex * 2 + 1
  while(childIndex < size) {
    if (childIndex + 1 < size && nums[childIndex + 1] > nums[childIndex]) {
      childIndex = childIndex + 1
    }
    if (nums[parentIndex] >= nums[childIndex]) {
      break
    }
    const temp = nums[parentIndex]
    nums[parentIndex] = nums[childIndex]
    nums[childIndex] = temp
    parentIndex = childIndex
    childIndex = parentIndex * 2 + 1
  }
  return nums
}

/**
 * 堆排序流程:
 * 1. 调整当前的数组,把当前的数组当成一个完全二叉树,从第一个非叶子节点,开始调整成堆
 * 一步一步直到 0,当前的nums就是一个堆
 * 2. 取出堆顶的元素,放到最右,然后继续,放到最右之后,不影响堆调整的下标计算,放到最左,会有偏移
 * 因为堆顶是放到最右,如果从小到大排序,构建最大堆,反之最小堆
 * 
 * 时间复杂度: O(nlogn)
 * 空间复杂度: O(1)
 * 不稳定排序
*/
const heapSort = (nums) => {
  // 调整成堆
  for (let i = Math.floor((nums.length - 1) / 2); i >= 0; i--) {
    shift(nums, i, nums.length)
  }
  // 循环取堆顶,放到最右
  for (let i = nums.length - 1; i >= 1; i--) {
    const temp = nums[i]
    nums[i] = nums[0]
    nums[0] = temp
    // 调整堆
    shift(nums, 0, i)
  }
  return nums
}

leetcode练习题:

参考资料:www.jianshu.com/p/7f9da80a5…