JavaScript中的[堆heap]数据结构

前言

javaScript 中数据结构都是由数组对象构建出来的,堆由数组构建【】,本质是完全二叉树结构

JS中的堆:找到节点位置的,写插入删除查找替换等方法


一、节点位置和堆的用处

1、节点位置

  • 左侧子节点的位置 : 2 * index + 1
  • 右侧子节点的位置 : 2 * index + 2
  • 父节点的位置 : (index -1) / 2 。 可以用位运算表示 (i - 1) >> 1

2、堆的运用

  • 快速找最大最小值,时间复杂度O(1)
  • 找第k个最大元素

最大最小元素

二、最小堆类和方法

1.构建最小堆类

插入涉及移动父节点元素,getParentIndex(i) 方法获取父节点,upShift(index) 方法来移动当前节点, swap(i1,i2)方法来交换位置的值。


// 构建最小堆类
class MinHeap {
  constructor() {
    this.heap = []
  }
  // 交换
  swap(i1, i2) {
    let 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
  }
  // 上移
  upShift(index) {
    if(index == 0) return
    const parentIndex = this.getParentIndex(index)
    if(this.heap[parentIndex] > this.heap[index]) {
      this.swap(index, parentIndex)
      this.upShift(parentIndex)
    }
  }
  // 下移
  downShift(index) {
    const leftIndex = this.getLeftIndex(index)
    const rightIndex = this.getRightIndex(index)
    if(this.heap[leftIndex] < this.heap[index]) {
      this.swap(leftIndex, index)
      this.downShift(leftIndex)
    }
    if(this.heap[rightIndex] < this.heap[index]) {
      this.swap(rightIndex, index)
      this.downShift(rightIndex)
    }
  }
  // 插入
  insert(value) {
    this.heap.push(value)
    this.upShift(this.heap.length - 1)
  }
  // 删除堆顶
  pop() {
    this.heap[0] = this.heap.pop()
    this.downShift(0)
  }
}

复制代码

2. 插入方法

根据最小堆的结构进行插入 时间复杂度为O(logk),

 // 插入
  insert(value) {
    this.heap.push(value)
    this.upShift(this.heap.length - 1)
  }
复制代码

测试插入方法


let minheap = new MinHeap()

minheap.insert(6)
minheap.insert(2)
minheap.insert(3)
minheap.insert(1)


console.log(minheap.heap);
复制代码

结果 在这里插入图片描述

3. 删除堆顶

  • 用数组尾部元素替换堆顶(直接删除堆顶会破环堆结构)
  • 下移: 将新堆顶和它的子节点进行交换,直到子节点大于大于这新的堆顶
  • 大小为k 的堆顶中删除堆顶的时间复杂度为O(logk),和插入一样

使用获取左右节点的方法 getLeftIndex(i) ,getRightIndex(i)和下移方法 downShift(index),交换方法swap(i1,i2)

 // 删除堆顶
 /** this.heap[0] 堆顶值
 *  this.heap.pop() 数组尾部元素的值
 */
  pop() {
    this.heap[0] = this.heap.pop()
    this.downShift(0)
  }
复制代码
  • 验证一下

let minheap = new MinHeap()

minheap.insert(6)
minheap.insert(2)
minheap.insert(3)
minheap.insert(1)
minheap.pop()

console.log(minheap.heap);
复制代码

结果: 在这里插入图片描述

4. 获取堆顶和堆的大小vscode 调试

这两个比较简单,直接返回数组第一个元素和长度

 // 获取堆顶
  peek() {
    return this.heap[0]
  }
  // 获取堆大小
  size() {
    return this.heap.length
  }
复制代码
  • 可以在vs code 中使用node.js 的调试环境进行查看变量方法等,这里可以监听peek() 和size()
  • F5启动调试

在这里插入图片描述

  • 在调用堆栈栏目进行单步调试在这里插入图片描述
  • 在监视栏目设置断点监听

在这里插入图片描述

三. LeetCode 算法题中的堆

以下的算法题,在我们上面的堆类的基础上进行实现

1、215 数组中第k个最大元素

在这里插入图片描述 解题步骤 在这里插入图片描述


let findKLargest = function (nums, k) {
  // 创建堆实例
  const heap = new MinHeap()
  for(let num of nums) {
    // 将数组的值依次插入到堆里
    heap.insert(num)
    // 判断堆的容量是否超过k 
    if (heap.size() > k) {
      // 如果超过,就删除堆顶
      heap.pop()
    }
  }
  // 返回堆顶
  return heap.peek()
}

let arr1 = [3,2,1,5,6,4]
let arr2 = [3,2,2,1,2,4,5,5,6]

console.log(findKLargest(arr1, 2)); // 5

console.log(findKLargest(arr2, 4)); // 4
复制代码
  • 时间复杂度:O(n*logK)
  • 空间复杂度: O(logK)

2、347.前K个高频元素

在这里插入图片描述

非堆结构的算法实现 k <= n


let topKFrequent = function (nums, k) {
  const map = new Map();
  for(let n of nums) {
    map.set(n, map.has(n) ? map.get(n) + 1 : 1)
  }
 
  const list = Array.from(map).sort((a, b) => b[1] - a[1])
  console.log(list.slice(0, k));
  return list.slice(0, k).map(n => n[0])
}
let arr3 = [1,1,1,2,2,3]
console.log(topKFrequent(arr3, 2));// [1, 2]
console.log(topKFrequent([1], 1)); // [1]
复制代码
  • 时间复杂度:O(n*log n)
  • 空间复杂度: O(log n)

堆结构的实现 …… 下面的最小堆类符合传入对象时,对对象的value的比较的情况, 因此只在上移、下移的方法中的 if 判断时进行修改, 分别是判断父、左、右 节点是否存在, 因为要取里面的value,防止undefined的时候报错, 其余没有修改,为方便阅读就都啪的贴上了

 if(this.heap[parentIndex] && this.heap[parentIndex].value > this.heap[index].value) 
 
 if(this.heap[leftIndex] && this.heap[leftIndex].value < this.heap[index].value)
 
 if(this.heap[rightIndex] && this.heap[rightIndex].value < this.heap[index].value)
复制代码

堆类和题目的算法和输出结果如下:


class MinHeap {
  constructor() {
    this.heap = []
  }
  // 交换
  swap(i1, i2) {
    let 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
  }
  // 上移
  upShift(index) {
    if(index == 0) return
    const parentIndex = this.getParentIndex(index)
    if(this.heap[parentIndex]&& this.heap[parentIndex].value > this.heap[index].value) {
      this.swap(index, parentIndex)
      this.upShift(parentIndex)
    }
  }
  // 下移
  downShift(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.downShift(leftIndex)
    }
    if(this.heap[rightIndex] && this.heap[rightIndex].value < this.heap[index].value) {
      this.swap(rightIndex, index)
      this.downShift(rightIndex)
    }
  }
  // 插入
  insert(value) {
    this.heap.push(value)
    this.upShift(this.heap.length - 1)
  }
  // 删除堆顶
  pop() {
    this.heap[0] = this.heap.pop()
    this.downShift(0)
  }
  // 获取堆顶
  peek() {
    return this.heap[0]
  }
  // 获取堆大小
  size() {
    return this.heap.length
  }
}

let topKFrequent = function (nums, k) {
  const map = new Map();
  for(let n of nums) {
    map.set(n, map.has(n) ? map.get(n) + 1 : 1)
  }
  const heap = new MinHeap()
  map.forEach((value, key) => {
    heap.insert({value, key})
    if(heap.size() > k) {
      heap.pop()
    }
  })
  return heap.heap.map(a => a.key)
}
let arr3 = [1,1,1,2,2,3]
console.log(topKFrequent(arr3, 2));// [2, 1]
console.log(topKFrequent([1], 1)); // [1]


复制代码

注意: 这里题目对输出的顺序没有要求,和非堆结构相比发现了吗【1,2】 【2,1】

  • 时间复杂度:O(n*logK)
  • 空间复杂度: O(logK)

3、23.合并K个排序链表

在这里插入图片描述 解题思路

  • 新链表的下一个节点一定是K个链表头重的最小节点

解题步骤 在这里插入图片描述


// 构建最小堆类
class MinHeap {
  constructor() {
    this.heap = []
  }
  // 交换
  swap(i1, i2) {
    let 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
  }
  // 上移
  upShift(index) {
    if(index == 0) return
    const parentIndex = this.getParentIndex(index)
    if(this.heap[parentIndex]&& this.heap[parentIndex].value > this.heap[index].value) {
      this.swap(index, parentIndex)
      this.upShift(parentIndex)
    }
  }
  // 下移
  downShift(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.downShift(leftIndex)
    }
    if(this.heap[rightIndex] && this.heap[rightIndex].value < this.heap[index].value) {
      this.swap(rightIndex, index)
      this.downShift(rightIndex)
    }
  }
  // 插入
  insert(value) {
    this.heap.push(value)
    this.upShift(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.downShift(0)
    return top
  }
  // 获取堆顶
  peek() {
    return this.heap[0]
  }
  // 获取堆大小
  size() {
    return this.heap.length
  }
}


function ListNode(value) {
  this.value = value
  this.next = null
}

let mergeKlists = function (lists) {
  const res = new ListNode(0)
  let p = res
  const heap = new MinHeap()
  lists.forEach(list => {
    if (list) {
      heap.insert(list)
    }
  })
  while (heap.size()){
    const n = heap.pop()
    p.next = n
    p = p.next
    if (n.next) {
      heap.insert(n.next)
    }
  }
  return res.next
}

let lists = [[1, 4, 5], [1, 3, 4], [2, 6]]
console.log(mergeKlists(lists));

复制代码

这道题有点难啊

  • 时间复杂度:O(n*logK)
  • 空间复杂度: O(K)

6.文章结构框架md

@[TOC](<center>Title</center>)
<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">

# 前言

<font color=#999AAA >**简述文章内容或者创作目的**</font>

<font face = '宋体' color = black size = 5>**完成目标:**
&emsp;&emsp;文章想要达到什么样的目标

<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">

# 一、
<font face = '宋体' color = black size = 5>

# 二、
## 1.

## 2.

## 3.

## 4.

## 5.

## 6.

# 最后
><font face="宋体"  color = black size = 4>最后想和读者说的话

复制代码

最后

堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象。

堆在实际的应用中还是右出现的,调用堆栈这些,堆本质上是完全二叉树,堆在js中用数组表示【】,

万字文还真有点多。

在这里插入图片描述

释义

堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质: 堆中某个结点的值总是不大于或不小于其父结点的值; 堆总是一棵完全二叉树。 将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。 堆是非线性数据结构,相当于一维数组,有两个直接后继。 堆的定义如下:n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。 (且)或者(), () 若将和此次序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)

算法思想

不必将值一个个地插入堆中,通过交换形成堆。假设一个小根堆的左、右子树都已是堆,并且根的元素名为root ,其左右子结点为 leftright ,这种情况下,有两种可能: (1)root <= left 并且root <= right,此时堆已完成; (2) root >= left或者root >= right ,此时root 应与两个子女中值较小的一个交换,结果得到一个堆,除非 root仍然大于其新子女的一个或全部的两个。这种情况下,我们只需简单地继续这种将root “拉下来”的过程,直至到达某一个层使它小于它的子女,或者它成了叶结点。

傍晚了要吃饭了

Guess you like

Origin juejin.im/post/7075959934961057799