delphi 数据 上移 下移_数据结构之“堆(Heap)”

本文介绍了最小堆的概念及其在JavaScript中的实现,并展示了如何使用最小堆解决LeetCode中的第K个最大元素、前K个高频元素和合并K个升序链表的问题。通过修改最小堆的比较条件,还能用于找到数据流中的第K大元素。文章详细阐述了每个问题的解题思路和最小堆在其中的作用。
摘要由CSDN通过智能技术生成

简介

堆是一种特殊的完全二叉树。

当堆中的所有的节点大于等于他的子节点,我们可以称这个堆为最大堆,相反,当堆中的所有的节点小于等于他的子节点,我们称这个堆为最小堆。

6e9d8dd61262d779fa86d484a185b68d.png

JS中的堆

Javascript通常用数组来表示堆

513d4d3a2cd73eb6c94084410b6318bd.png

堆的节点位置遵循以下规则:

• 左侧子节点的位置是当前节点索引 * 2 + 1,即2 * index + 1

• 右侧子节点的位置是当前节点索引 * 2 + 1,即2 * index + 2

• 父节点的位置(index - 1)/ 2

Javascript实现:最小堆类

实现步骤:

• 在类里,声明一个数组,用来装元素

• 主要方法:插入、删除堆顶、获取堆顶、获取堆大小

插入:

• 将值插入堆的底部,即数组的尾部

• 然后上移:将这个值和它的父节点进行交换,直到父节点小于等于这个插入的值(因为最小堆的父节点小于子节点)

• 大小为K的堆中插入元素的时间复杂度为O(logk)

删除堆顶:

• 用数组尾部元素替换堆顶(直接删除堆顶会破坏堆结构)

• 然后下移:将新堆顶和它的子节点进行交换,知道子节点大于等于这个新的堆顶

• 大小为k的堆中删除堆顶的时间复杂度为O(logk)

获取堆顶元素和堆的大小

• 返回头部元素

• 返回数组长度

class MinHeap {  constructor() {    this.heap = []  }  swap(i1, i2) {    const temp = this.heap[i1]    this.heap[i1] = this.heap[i2]    this.heap[i2] = temp  }  getParentIndex(i) {    // return Math.floor((i - 1) / 2)    // 二进制右移一位就是除2的商    return (i - 1) >> 1  }  getLeftIndex(i) {    return 2 * i + 1  }  getRightIndex(i) {    return 2 * i + 2  }  shiftUp(index) {    if (index == 0) return;    const parentIndex = this.getParentIndex(index)    if (this.heap[parentIndex] > this.heap[index]) {      this.swap(parentIndex, index)      this.shiftUp(parentIndex)    }  }  shiftDown(index) {    const leftIndex = this.getLeftIndex(index)    const rightIndex = this.getRightIndex(index)    if (this.heap[leftIndex] < this.heap[index]) {      this.swap(leftIndex, index)      this.shiftDown(leftIndex)    }    if (this.heap[rightIndex] < this.heap[index]) {      this.swap(rightIndex, index)      this.shiftDown(rightIndex)    }  }  // 插入  insert(value) {    this.heap.push(value)    this.shiftUp(this.heap.length - 1)  }  // 删除堆顶元素  pop() {    this.heap[0] = this.heap.pop()    this.shiftDown(0)  }  // 获取堆顶元素  peek() {    return this.heap[0]  }  // 获取堆长度  size() {    return this.heap.length  }}

以下LeetCode题都可以用MinHeap类求出。

LeetCode 215.数组中的第K个最大元素

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4输出: 4
/** * @param {number[]} nums * @param {number} k * @return {number} */var findKthLargest = function(nums, k) {  const h = new MinHeap()  nums.forEach(n => {    h.insert(n)    if (h.size() > k) {      h.pop()    }  })  return h.peek()};

LeetCode 347.前K个高频元素

给定一个非空的整数数组,返回其中出现频率前 高的元素。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2输出: [1,2]

示例 2:

输入: nums = [1], k = 1输出: [1]

解题思路:

• 由于这题需要返回的是出现频率前K高的元素,所以需要多两步操作

• 通过map记录每个元素出现的频数

• 调整MinHeap的比较条件

class MinHeap {  constructor() {    this.heap = []  }  swap(i1, i2) {    const temp = this.heap[i1]    this.heap[i1] = this.heap[i2]    this.heap[i2] = temp  }  getParentIndex(i) {    return (i - 1) >> 1  }  getLeftIndex(i) {    return 2 * i + 1  }  getRightIndex(i) {    return 2 * i + 2  }  shiftUp(index) {    if (index == 0) { return }    const parentIndex = this.getParentIndex(index)    if (this.heap[parentIndex] && this.heap[parentIndex].value > this.heap[index].value) {      this.swap(parentIndex, index)      this.shiftUp(parentIndex)    }  }  shiftDown(index) {    const leftIndex = this.getLeftIndex(index)    const rightIndex = this.getRightIndex(index)    if (this.heap[leftIndex] && this.heap[leftIndex].value < this.heap[index].value) {      this.swap(leftIndex, index)      this.shiftDown(leftIndex)    }    if (this.heap[rightIndex] && this.heap[rightIndex].value < this.heap[index].value) {      this.swap(rightIndex, index)      this.shiftDown(rightIndex)    }  }  insert(value) {    this.heap.push(value)    this.shiftUp(this.heap.length - 1)  }  pop() {    this.heap[0] = this.heap.pop()    this.shiftDown(0)  }  size() {    return this.heap.length  }}/** * @param {number[]} nums * @param {number} k * @return {number[]} */// 时间复杂度O(nlogk) 空间复杂度O(n)var topKFrequent = function(nums, k) {    const map = new Map()    nums.forEach(n => {        map.set(n, map.has(n) ? map.get(n) + 1 : 1)    })    const h = new MinHeap()    map.forEach((value, key) => {        h.insert({ value, key })        if (h.size() > k) {            h.pop()        }    })    return h.heap.map(v => v.key)};

LeetCode 23.合并K个升序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:

输入:lists = [[1,4,5],[1,3,4],[2,6]]输出:[1,1,2,3,4,4,5,6]解释:链表数组如下:[  1->4->5,  1->3->4,  2->6]将它们合并到一个有序链表中得到。1->1->2->3->4->4->5->6

示例 2:

输入:lists = []输出:[]

示例 3:

输入:lists = [[]]输出:[]
class MinHeap {  constructor() {    this.heap = []  }  // 对换位置  swap(i1, i2) {    const temp = this.heap[i1]    this.heap[i1] = this.heap[i2]    this.heap[i2] = temp  }  // 获取父节点索引  getParentIndex(i) {    return (i - 1) >> 1  }  // 获取左节点  getLeftIndex(i) {    return 2 * i + 1  }  // 获取右节点  getRightIndex(i) {    return 2 * i + 2  }  // 进行上移操作,直至堆顶为最小值  shiftUp(index) {    if (index == 0) { return }    // 父节点索引    const pIndex = this.getParentIndex(index)    if (this.heap[pIndex] && this.heap[pIndex].val > this.heap[index].val) {      this.swap(pIndex, index)      this.shiftUp(pIndex)    }  }  // 下移操作,直至堆顶为最小值  shiftDown(index) {    const lIndex = this.getLeftIndex(index)    const rIndex = this.getRightIndex(index)    if (this.heap[lIndex] && this.heap[lIndex].val < this.heap[index].val) {      this.swap(lIndex, index)      this.shiftDown(lIndex)    }    if (this.heap[rIndex] && this.heap[rIndex].val < this.heap[index].val) {      this.swap(rIndex, index)      this.shiftDown(rIndex)    }  }  // 插入  insert(value) {    this.heap.push(value)    this.shiftUp(this.heap.length - 1)  }  // 弹出堆顶  pop() {    if (this.size() === 1) return this.heap.shift()    const top = this.heap[0]    this.heap[0] = this.heap.pop()    this.shiftDown(0)    return top  }  // 获取堆顶  peek() {    return this.heap[0]  }  size() {    return this.heap.length  }}/** * Definition for singly-linked list. * function ListNode(val, next) { *     this.val = (val===undefined ? 0 : val) *     this.next = (next===undefined ? null : next) * } *//** * @param {ListNode[]} lists * @return {ListNode} */var mergeKLists = function(lists) {  const h = new MinHeap()  const res = new ListNode(0)  let p = res  lists.forEach(l => {    if (l) h.insert(l);  })  while(h.size()) {    const n = h.pop()    p.next = n    p = p.next    if (n.next) h.insert(n.next)  }  return res.next};

LeetCode 703.数据流中的第 K 大元素

设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

请实现 KthLargest 类:

KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。

int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。

示例:

输入:["KthLargest", "add", "add", "add", "add", "add"][[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]输出:[null, 4, 5, 5, 8, 8]解释:KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);kthLargest.add(3);   // return 4kthLargest.add(5);   // return 5kthLargest.add(10);  // return 5kthLargest.add(9);   // return 8kthLargest.add(4);   // return 8
/** * @param {number} k * @param {number[]} nums */var KthLargest = function(k, nums) {    this.h = new MinHeap()    this.k = k    nums.forEach(n => {        this.add(n)    })};/**  * @param {number} val * @return {number} */KthLargest.prototype.add = function(val) {    this.h.insert(val)    if (this.h.size() > this.k ) {        this.h.pop()    }    return this.h.peek()};/** * Your KthLargest object will be instantiated and called as such: * var obj = new KthLargest(k, nums) * var param_1 = obj.add(val) */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值