feat: 添加堆结构相关问题
This commit is contained in:
parent
a5e958c203
commit
7eb11b0136
41
heap/76数组中第k个最大的元素.js
Normal file
41
heap/76数组中第k个最大的元素.js
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @param {number[]} nums
|
||||
* @param {number} k
|
||||
* @return {number}
|
||||
*/
|
||||
const findKthLargest = function (nums, k) {
|
||||
// 将数组的前k个元素作为堆,然后将其堆化
|
||||
const heap = nums.slice(0, k);
|
||||
// 从最后一个非叶子节点遍历,自底向上下沉堆化
|
||||
for (let i = Math.floor(k / 2 - 1); i >= 0; i--) {
|
||||
siftDown(heap, i, k);
|
||||
}
|
||||
// 处理剩余元素
|
||||
for (let i = k; i < nums.length; i++) {
|
||||
// 如果剩余元素大于堆顶元素,替换然后下沉维护堆
|
||||
if (nums[i] > heap[0]) {
|
||||
heap[0] = nums[i];
|
||||
siftDown(heap, 0, k);
|
||||
}
|
||||
}
|
||||
|
||||
return heap[0];
|
||||
};
|
||||
|
||||
// 下沉函数(维护堆性质)
|
||||
function siftDown(heap, start, heapSize) {
|
||||
let cur = start;
|
||||
while (true) {
|
||||
const left = 2 * cur + 1;
|
||||
const right = 2 * cur + 2;
|
||||
let smallest = cur;
|
||||
|
||||
if (left < heapSize && heap[left] < heap[smallest]) smallest = left;
|
||||
if (right < heapSize && heap[right] < heap[smallest]) smallest = right;
|
||||
|
||||
if (smallest === cur) break;
|
||||
|
||||
[heap[cur], heap[smallest]] = [heap[smallest], heap[cur]];
|
||||
cur = smallest;
|
||||
}
|
||||
}
|
86
heap/base/topk问题.js
Normal file
86
heap/base/topk问题.js
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
topk 问题是一个经典的问题,我们维持一个大小为k的堆,如果要求最大的第三个数我们就维持小顶堆,反之维持大小为k的大顶堆
|
||||
为什么?这有点反直觉,为什么求第k大的数要维持小顶堆,而不是大顶堆,这是因为第k大的数是数组中k个最大的数中最小的那一个,
|
||||
用小顶堆来维持那么要求的第k个最大的数刚好在小顶堆的堆顶
|
||||
*/
|
||||
|
||||
const numbers = [5, 0, 8, 2, 1, 4, 7, 2, 5];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number[]} nums
|
||||
* @param {number} k
|
||||
*/
|
||||
function topK(nums, k) {
|
||||
const heap = Array(k).fill(-Infinity); // 定义一个大小为k数组用来维护堆
|
||||
|
||||
for (let i = 0; i < nums.length; i++) {
|
||||
// 如果当前元素大于堆顶元素,替换堆顶元素然后下沉调整堆
|
||||
if (nums[i] > heap[0]) {
|
||||
heap[0] = nums[i];
|
||||
let cur = 0; // cur指向下沉元素的下标,一开始就在堆顶
|
||||
while (true) {
|
||||
const left = cur * 2 + 1;
|
||||
const right = cur * 2 + 2;
|
||||
let smallest = cur;
|
||||
|
||||
if (left < k && heap[left] < heap[smallest]) smallest = left;
|
||||
if (right < k && heap[right] < heap[smallest]) smallest = right;
|
||||
|
||||
// 如果smallest 和cur相等,表示当前元素来到了合适的位置cur,结束下沉
|
||||
if (smallest === cur) break;
|
||||
|
||||
// 交换位置下沉
|
||||
[heap[cur], heap[smallest]] = [heap[smallest], heap[cur]];
|
||||
cur = smallest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 返回堆顶元素
|
||||
return heap[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用小顶堆找出数组中第 K 大的数
|
||||
* @param {number[]} nums
|
||||
* @param {number} k
|
||||
* @returns {number}
|
||||
*/
|
||||
function topKbest(nums, k) {
|
||||
const heap = nums.slice(0, k); // 先取前 k 个元素构建小顶堆
|
||||
|
||||
// 下沉函数(维护堆性质)
|
||||
function siftDown(heap, start, heapSize) {
|
||||
let cur = start;
|
||||
while (true) {
|
||||
const left = 2 * cur + 1;
|
||||
const right = 2 * cur + 2;
|
||||
let smallest = cur;
|
||||
|
||||
if (left < heapSize && heap[left] < heap[smallest]) smallest = left;
|
||||
if (right < heapSize && heap[right] < heap[smallest]) smallest = right;
|
||||
|
||||
if (smallest === cur) break;
|
||||
|
||||
[heap[cur], heap[smallest]] = [heap[smallest], heap[cur]];
|
||||
cur = smallest;
|
||||
}
|
||||
}
|
||||
|
||||
// 构建小顶堆(自底向上建堆)
|
||||
for (let i = Math.floor(k / 2) - 1; i >= 0; i--) {
|
||||
siftDown(heap, i, k);
|
||||
}
|
||||
|
||||
// 遍历剩余元素,维护一个大小为 k 的小顶堆
|
||||
for (let i = k; i < nums.length; i++) {
|
||||
if (nums[i] > heap[0]) {
|
||||
heap[0] = nums[i];
|
||||
siftDown(heap, 0, k);
|
||||
}
|
||||
}
|
||||
|
||||
// 堆顶即为第 k 大元素
|
||||
return heap[0];
|
||||
}
|
75
heap/base/原地构建.js
Normal file
75
heap/base/原地构建.js
Normal file
@ -0,0 +1,75 @@
|
||||
const numbers = [5, 0, 8, 2, 1, 4, 7, 2, 5, -1]; // 测试用例
|
||||
|
||||
const vaild = [-1, 0, 4, 2, 1, 8, 7, 5, 5, 2]; // 验证用例
|
||||
|
||||
/**
|
||||
* 自底向上下沉
|
||||
* @param {*} nums
|
||||
*/
|
||||
function buildHeap(nums) {
|
||||
const n = nums.length;
|
||||
// 寻找最后一个非叶子节点,从这里开始倒序遍历(自底向上)
|
||||
for (let i = Math.floor(n / 2 - 1); i >= 0; i--) {
|
||||
// 取当前节点左右节点较小的
|
||||
let min = i * 2 + 1;
|
||||
if (i * 2 + 2 < n && nums[i * 2 + 2] < nums[min]) min = i * 2 + 2;
|
||||
let cur = i; // 表示当前节点下沉的位置
|
||||
|
||||
// 如果父节点大于子节点中较小的哪一个那么就交换位置,如果交换位置之后还是大于当前位置的子节点小的那个,继续下沉
|
||||
while (min < n && nums[cur] > nums[min]) {
|
||||
[nums[cur], nums[min]] = [nums[min], nums[cur]];
|
||||
cur = min;
|
||||
min = cur * 2 + 1;
|
||||
if (cur * 2 + 2 < n && nums[cur * 2 + 2] < nums[min]) min = cur * 2 + 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 原地构建小顶堆(heapify),使用下沉(sift down)法
|
||||
* @param {number[]} nums 完全二叉树的层序遍历(原始数组)
|
||||
* @returns {number[]} 构建完成的小顶堆(原地修改 nums 并返回)
|
||||
*/
|
||||
function buildHeapBest(nums) {
|
||||
const n = nums.length;
|
||||
|
||||
// 从最后一个非叶子节点开始,依次向前,对每个节点执行下沉
|
||||
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
|
||||
siftDown(nums, i, n);
|
||||
}
|
||||
|
||||
return nums;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对 nums[i] 进行下沉调整,确保以 i 为根的子树满足小顶堆性质
|
||||
* @param {number[]} nums 堆数组
|
||||
* @param {number} i 当前要下沉的节点索引
|
||||
* @param {number} n 数组长度(限制范围)yiw
|
||||
*/
|
||||
function siftDown(nums, i, n) {
|
||||
let cur = i;
|
||||
|
||||
while (true) {
|
||||
const left = 2 * cur + 1;
|
||||
const right = 2 * cur + 2;
|
||||
let smallest = cur;
|
||||
|
||||
// 与左孩子比较
|
||||
if (left < n && nums[left] < nums[smallest]) {
|
||||
smallest = left;
|
||||
}
|
||||
|
||||
// 与右孩子比较
|
||||
if (right < n && nums[right] < nums[smallest]) {
|
||||
smallest = right;
|
||||
}
|
||||
|
||||
// 如果当前节点已经是最小的,堆性质满足,退出循环
|
||||
if (smallest === cur) break;
|
||||
|
||||
// 否则交换并继续下沉
|
||||
[nums[cur], nums[smallest]] = [nums[smallest], nums[cur]];
|
||||
cur = smallest;
|
||||
}
|
||||
}
|
35
heap/base/逐个插入.js
Normal file
35
heap/base/逐个插入.js
Normal file
@ -0,0 +1,35 @@
|
||||
const numbers = [5, 0, 8, 2, 1, 4, 7, 2, 5, -1]; // 测试用例
|
||||
|
||||
const vaild = [-1, 0, 4, 2, 1, 8, 7, 5, 5, 2]; // 验证用例
|
||||
|
||||
/**
|
||||
* 使用逐个插入法构造小顶堆
|
||||
* @param {number[]} nums 需要处理的完全二叉树,用数组表示(层序遍历)
|
||||
* @returns {number[]} 返回一个小顶堆的层序遍历
|
||||
*/
|
||||
function buildHeap(nums) {
|
||||
const heap = [];
|
||||
|
||||
for (let i = 0; i < nums.length; i++) {
|
||||
heap.push(nums[i]);
|
||||
let cur = heap.length - 1;
|
||||
let parent = Math.floor((cur - 1) / 2);
|
||||
|
||||
while (cur > 0 && heap[cur] < heap[parent]) {
|
||||
[heap[cur], heap[parent]] = [heap[parent], heap[cur]];
|
||||
cur = parent;
|
||||
parent = Math.floor((cur - 1) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
return heap;
|
||||
}
|
||||
|
||||
function test(nums1, nums2) {
|
||||
for (let i = 0; i < nums1; i++) {
|
||||
if (nums1[i] !== nums2[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log(test(buildeap(numbers), vaild));
|
Loading…
x
Reference in New Issue
Block a user