feat: 添加堆结构相关问题

This commit is contained in:
LouisFonda 2025-05-25 21:33:12 +08:00
parent a5e958c203
commit 7eb11b0136
Signed by: yigencong
GPG Key ID: 29CE877CED00E966
4 changed files with 237 additions and 0 deletions

View 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
View 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
View 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
View 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));