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) */
因权限不够,只能上传20M,故分两部分上传 提供了有关使用算法和数据结构的一个详尽的介绍。Bucknall先从算法性能的讨论开始,涵盖了诸如数组、链表和二叉树等内容。这本书强调了查找算法(如顺序和二分查找),另外也重点介绍了排序算法(包括冒泡排序、插入排序、希尔排序、快速排序和排序),此外还提供了有关的优化技术。不仅如此,作者还介绍了散列和散列表、优先队列、状态机和正则表达式以及诸如哈夫曼和LZ77等数据压缩技术。 随附光盘中有作者所开发的一个相当成功的自由软件库EZDSL,另外还有可运行于各版本Delphi上和Kylix上的源代码,此外还提供了TurboPower Software公司的可执行程序。 目录 前言 致谢 第1章什么是算法 1.1什么是算法 1.2算法和平台 1.3调试与测试 1.4小结 第2章数组 2.1数组 2.2Delphi中的数组类型 2.3TList类和指针数组 2.4磁盘数组 2.5小结 第3章链表、栈和队列 3.1单链表 3.2双向链表 3.3链表的优缺点 3.4栈 3.5队列 3.6小结 .第4章查找 4.1比较例程 4.2顺序查找 4.3二分查找 4.4小结 第5章排序 5.1排序算法 5.2排序基础知识 5.3小结 第6章随机算法 6.1随机数生成 6.2其他随机数分布 6.3跳表 6.4小结 第7章散列和散列表 7.1散列函数 7.2利用线性探测方法实现冲突解决 7.3其他开放定址机制 7.4利用链式方法解决冲突 7.5利用桶式方法解决冲突 7.6磁盘上的散列表 7.7小结 第8章二叉树 8.1创建一个二叉树 8.2叉树的插入和删除 8.3二叉树的遍历 8.4二叉树的类实现 8.5二叉查找树 8.6伸展树 8.7红黑树 8.8小结 第9章 优先队列和排序 9.1优先队列 9.2 9.3排序 9.4扩展优先队列 9.5小结 第10章 状态机和正则表达式 10.1状态机 10.2正则表达式 10.3小结 第11章数据压缩 11.1数据表示 11.2数据压缩 11.3位流 11.4最小冗余压缩 11.5字典压缩 11.6小结 第12章 高级主题 12.1读者-写者算法 12.2生产者-消费者算法 12.3查找两文件的差别 12.4小结 后记
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值