最小堆最大堆了解吗?一文了解堆在前端中的应用

封面

一文了解堆在前端中的应用

  • ⚡序言
  • 🦘一、堆是什么?
  • 🐥二、JS中的堆
  • 🐝三、堆的应用
  • 🐈四、构建一个最小堆
    • 1. 定义
    • 2. 方法
    • 3. 用js代码实现最小堆
      • (1)初始化一个堆
      • (2)交换位置swap()
      • (3)获取父节点的位置getParentIndex()
      • (4)获取左侧子节点的位置getLeftIndex()
      • (5)获取右侧子节点的位置getRightIndex()
      • (6)进行上移操作shiftUp()
      • (7)进行下移操作shiftDown()
      • (8)插入节点的值insert()
      • (9)删除堆顶操作pop()
      • (10)获取堆顶的值peek()
      • (11)获取堆的大小size()
      • (12)结果展示
  • 🐤五、leetcode经典题目剖析
    • 1. leetcode215数组中的第K个最大元素(中等)
    • 2. leetcode347前K个高频元素(中等)
    • 3. leetcode23合并K个排序链表(困难)
  • 🐪六、结束语
  • 🐣彩蛋 One More Thing
    • (:往期推荐
    • (:番外篇

⚡序言

我们都知道树是一个数据结构,但可能很少听到堆这个数据结构。其实,堆就是一种特殊的完全二叉树。而对于前端来说,我们通常了解最大堆和最小堆,也经常用最大堆和最小堆来解决各种问题。比如,数组中的第K个最大元素、文档中前K个高频元素等等。

在下面的这篇文章中,将讲解堆的基础知识,并手动地用 js 来构建一个最小堆,同时剖析几道经典的 leetcode 算法题。

接下来开始进入本文的讲解~🔥

🦘一、堆是什么?

  • 堆是一种特殊的 完全二叉树 ,完全二叉树意味着每个节点都有两个孩子节点
  • 最大堆:所有的节点都 大于等于≥ 它的子节点;
  • 最小堆:所有的节点都 小于等于≤ 它的子节点。

🐥二、JS中的堆

  • JS 通常用数组来表示堆。
  • 左侧节点的位置是 2*index+1
  • 右侧节点的位置是 2*index+2
  • 父节点位置是 (index - 1) / 2

🐝三、堆的应用

  • 堆能够高效、快速地找出最大值最小值,时间复杂度 O(1)
  • 在开发中,有时候我们可能会想要找到一个数组中的最大或者最小元素,而堆,就可以找出第K个最大(小)元素

🐈四、构建一个最小堆

1. 定义

从上面的小知识中我们可以了解到,对于最小堆来说,它的所有节点都小于等于它的子节点。接下来我们来看堆这个数据结构的一些常见实现方法。

2. 方法

方法含义
swap()交换两个节点的位置
getParentIndex()获取父节点的位置
getLeftIndex()获取左侧子节点的位置
getRightIndex()获取右侧子节点的位置
shiftUp()进行上移操作
shiftDown()进行下移操作
insert()插入节点的值
pop()删除堆顶操作
peek()获取堆顶的值
size()获取堆的大小

3. 用js代码实现最小堆

(1)初始化一个堆

首先我们需要先来定义一个空数组,这个数组用来存放一个堆。具体代码如下:

class MinHeap{//创建一个构造器,存放一个堆constructor(){this.heap = [];}
}

(2)交换位置swap()

初始化完一个堆之后,如果想要实现上下移操作,我们时不时的还需要对两个节点进行位置交换。那么我们再来写一个交换节点位置的方法。具体代码如下:

class MinHeap{//创建一个构造器,存放一个堆constructor(){this.heap = [];}//交换节点i1和i2之间的位置swap(i1, i2){const temp = this.heap[i1];this.heap[i1] = this.heap[i2];this.heap[i2] = temp;}
}

(3)获取父节点的位置getParentIndex()

上面我们讲到,父节点的位置是在 (index - 1) / 2 。 因此,我们需要传入当前节点的值索引 index ,来进行一个地板除操作,获取具体的父节点位置。具体代码如下:

class MinHeap{//创建一个构造器,存放一个堆constructor(){this.heap = [];}//交换节点i1和i2之间的位置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);//也可以用以下这种右移操作的方法//return (i - 1) >> 1;}
}

(4)获取左侧子节点的位置getLeftIndex()

对于左侧子节点来说,其索引为 2 * index + 1 ,也就是说,它是 当前节点的索引值的2倍 + 1具体实现代码如下:

class MinHeap{//创建一个构造器,存放一个堆constructor(){this.heap = [];}//交换节点i1和i2之间的位置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);//也可以用以下这种右移操作的方法//return (i - 1) >> 1;}//获取左侧子节点,i为当前节点的索引getLeftIndex(i){return i * 2 + 1;}
}

(5)获取右侧子节点的位置getRightIndex()

对于右侧子节点来说,其索引为 2 * index + 2 ,也就是说,它是 当前节点的索引值的2倍 + 2具体实现代码如下:

class MinHeap{//创建一个构造器,存放一个堆constructor(){this.heap = [];}//交换节点i1和i2之间的位置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);//也可以用以下这种右移操作的方法//return (i - 1) >> 1;}//获取左侧子节点,i为当前节点的索引getLeftIndex(i){return i * 2 + 1;}//获取右侧子节点,i为当前节点的索引getRightIndex(i){return i * 2 + 2;}
}

(6)进行上移操作shiftUp()

上面我们实现了获取父节点等获取各种索引的操作,现在,我们来实现上移操作。

对于上移操作来说,实现思路如下:

  • 先判断当前节点的位置是否在堆的顶点处,如果是,则不进行上移操作;如果否,则继续进行比较;
  • 获取父节点的位置索引,获取索引的目的是为了获取该索引的具体值;
  • 将当前节点的值与父节点的值进行对比,如果父节点的值大于当前节点的值,则进行上移操作;
  • 递归进行上移操作,直到到达堆顶为止。

下面给出具体的代码实现方法:

class MinHeap{//创建一个构造器,存放一个堆constructor(){this.heap = [];}//交换节点i1和i2之间的位置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);//也可以用以下这种右移操作的方法//return (i - 1) >> 1;}//shiftUp进行上移操作shiftUp(index){//如果在堆的顶点处,则不进行上移操作,直接返回结果if(index === 0){return;}//获取父节点(即获取当前节点的父节点的值,且每个节点的父节点只有一个)const parentIndex = this.getParentIndex(index);//判断如果堆的父节点如果大于子节点,则进行位置交换if(this.heap[parentIndex] > this.heap[index]){this.swap(parentIndex, index);//交换完成之后,继续递归进行上移操作this.shinftUp(parentIndex);}}
}

(7)进行下移操作shiftDown()

对于下移操作来说,实现思路如下:

  • 先获取左右侧节点;
  • 将左侧子节点与当前节点进行比较,如果左侧子节点比当前节点小,则进行位置交换,之后将交换完的节点继续进行比较;
  • 左侧节点比较完之后,接下来比较右侧节点;
  • 将右侧子节点与当前节点进行比较,如果右侧子节点比当前节点小,则进行位置交换,之后将交换完的节点继续进行比较;
  • 如此循环操作,直到最后一个节点为止。

下面给出具体的代码实现方法:

class MinHeap{//创建一个构造器,存放一个堆constructor(){this.heap = [];}//交换节点i1和i2之间的位置swap(i1, i2){const temp = this.heap[i1];this.heap[i1] = this.heap[i2];this.heap[i2] = temp;}//获取左侧子节点,i为当前节点的索引getLeftIndex(i){return i * 2 + 1;}//获取右侧子节点,i为当前节点的索引getRightIndex(i){return i * 2 + 2;}// 进行下移操作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);}}
}

(8)插入节点的值insert()

对于插入节点操作来说,实现思路如下:

  • 将值插入堆的底部,即数组的尾部。
  • 然后上移:将这个值和它的父节点进行交换,直到父节点小于等于这个插入的值。
  • 大小为k的堆中插入元素的时间复杂度为 O(logK)

下面给出具体的代码实现方法:

class MinHeap{//创建一个构造器,存放一个堆constructor(){this.heap = [];}//交换节点i1和i2之间的位置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);//也可以用以下这种右移操作的方法//return (i - 1) >> 1;}//shiftUp进行上移操作shiftUp(index){//如果在堆的顶点处,则不进行上移操作,直接返回结果if(index === 0){return;}//获取父节点(即获取当前节点的父节点的值,且每个节点的父节点只有一个)const parentIndex = this.getParentIndex(index);//判断如果堆的父节点如果大于子节点,则进行位置交换if(this.heap[parentIndex] > this.heap[index]){this.swap(parentIndex, index);//交换完成之后,继续递归进行上移操作this.shinftUp(parentIndex);}}//插入结点值的操作,value为被插入的值insert(value){//把新的值放到数组的最后一位this.heap.push(value);//将值进行上移操作this.shiftUp(this.heap.length - 1);}
}

(9)删除堆顶操作pop()

对于删除堆顶操作来说,实现思路如下:

  • 用数组尾部元素替换堆顶(因为直接删除堆顶会破坏堆结构)。
  • 然后下移:将新堆顶和它的子节点进行交换,直到子节点大于等于这个新堆顶。
  • 大小为 k 的堆中删除堆顶的时间复杂度为 O(logK)

下面给出具体的代码实现方法:

class MinHeap{//创建一个构造器,存放一个堆constructor(){this.heap = [];}//交换节点i1和i2之间的位置swap(i1, i2){const temp = this.heap[i1];this.heap[i1] = this.heap[i2];this.heap[i2] = temp;}//获取左侧子节点,i为当前节点的索引getLeftIndex(i){return i * 2 + 1;}//获取右侧子节点,i为当前节点的索引getRightIndex(i){return i * 2 + 2;}// 进行下移操作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);}}//删除堆顶操作pop(){//将尾部的值赋值给堆顶this.heap[0] = this.heap.pop();//进行下移操作this.shiftDown(0);}
}

(10)获取堆顶的值peek()

对于获取堆顶的值操作来说,实现思路较为简单,也就是返回数组的头部即可获取堆顶的值。具体实现代码如下:

class MinHeap{//创建一个构造器,存放一个堆constructor(){this.heap = [];}//获取堆顶的值peek(){return this.heap[0];}
}

(11)获取堆的大小size()

对于获取堆的大小操作来说,实现思路其实就是获取整个堆的长度,也就是返回数组的长度。具体实现代码如下:

class MinHeap{//创建一个构造器,存放一个堆constructor(){this.heap = [];}//获取堆的大小size(){return this.heap.length;}
}

(12)结果展示

完成上面的操作以后,接下来,我们来对写一组测试用例,演示具体的结果。具体代码如下:

const h = new MinHeap();
h.insert(3);
h.insert(2);
h.insert(1);
h.pop();
console.log(h); // MinHeap { heap: [ 2, 4, 3 ] }
h.peek();
h.size();
console.log(h.peek()); // 2
console.log(h.size()); // 3

🐤五、leetcode经典题目剖析

接下来我们引用几道经典的 leetcode 算法,来巩固树和二叉树的知识。

1. leetcode215数组中的第K个最大元素(中等)

(1)题意

附上题目链接:leetcode215数组中的第K个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

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

输入输出示例:

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

(2)解题思路

  • 看到“第K个最大元素”。
  • 考虑选择使用最小堆。

(3)解题步骤

  • 构建一个最小堆,并以此把数组的值插入堆中。
  • 当堆的容量超过K,就删除堆顶。
  • 插入结束后,堆顶就是第K个最大元素。

(4)代码实现

依据上面我们构建的最小堆,接下来,我们用这个最小堆,来完成这道题。具体代码如下:

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);// return (i - 1) >> 1;}getLeftIndex(i){return i*2 + 1;}getRightIndex(i){return i*2 + 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;}
}/*** @param {number[]} nums* @param {number} k* @return {number}*/
let findKthLargest = function(nums, k){const h = new MinHeap();nums.forEach(n => {h.insert(n);if(h.size() > k){h.pop();}});return h.peek();
}console.log(findKthLargest([3,2,1,5,6,4],2)); // 5

2. leetcode347前K个高频元素(中等)

(1)题意

附上题目链接:leetcode347前K个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

输入输出示例:

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

(2)解题思路

  • 字典解法:将字典转换为数组,且堆数组进行排序;
  • 堆解法:构建一个最小堆,利用字典的键值关系,来记录元素出现的频率。

(3)代码实现

这道题我们用两种做法来实现,一种是字典解法,另外一种是堆解法具体如下:

1)字典解法:

/*** @param {number[]} nums* @param {number} k* @return {number[]}*/
// 字典解法
let topKFrequent1 = function(nums, k) {//定义一个数组const map = new Map();//先将数组中的元素存放到字典中nums.forEach(n => {map.set(n, map.has(n) ? map.get(n) + 1 : 1 );});// 将字典转换为数组,且对数组进行排序// 对数组中的第二项进行降序(从大到小)排序,从大到小const list = Array.from(map).sort((a, b) => b[1] - a[1]);//使用map()方法,创建一个新数组,来存放前k个元素return list.slice(0, k).map(n => n[0]);
};console.log(topKFrequent1([1, 1, 1, 2, 2, 3], 2)); // [1, 2]

2)堆解法:

/*** @param {number[]} nums* @param {number} k* @return {number[]}*/
// 堆解法
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);// return (i - 1) >> 1;}getLeftIndex(i){return i*2 + 1;}getRightIndex(i){return i*2 + 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);}peek(){return this.heap[0];}size(){return this.heap.length;}
}let topKFrequent2 = 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});//判断当前堆的大小是否大于k值if(h.size() > k){h.pop();}});//返回值,对字典进行遍历,得到遍历后的键即为结果;//并通过map()方法创建一个新数组,才存放具体的值。return h.heap.map(a => a.key);
};console.log(topKFrequent2([1, 1, 1, 2, 2, 3], 2)); // [2, 1]

3. leetcode23合并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)解题思路

  • 新链表的下一个节点一定是k个链表头中的最小节点。
  • 考虑选择使用最小堆。

(3)解题步骤

  • 构建一个最小堆,并以此把链表头插入堆中。
  • 弹出堆顶接到输出链表,并将堆顶所在链表的新链表头插入堆中。
  • 等堆元素全部弹出,合并工作就完成了。

(4)代码实现

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);// return (i - 1) >> 1;}getLeftIndex(i){return i*2 + 1;}getRightIndex(i){return i*2 + 2;}shiftUp(index){if(index === 0){return;}const parentIndex = this.getParentIndex(index);if(this.heap[parentIndex] && this.heap[parentIndex].val > this.heap[index].val){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].val < this.heap[index].val){this.swap(leftIndex, index);this.shiftDown(leftIndex);}if(this.heap[rightIndex] && this.heap[rightIndex].val < this.heap[index].val){this.swap(rightIndex, index);this.shiftDown(rightIndex);}}insert(val){this.heap.push(val);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 res = new ListNode(0);//定义一个p指针,指向空链表let p = res;//实例化一个最小堆const h = new MinHeap();//将题目给的链表,挨个进行遍历lists.forEach(l => {//遍历后的链表如果不为空,则插入最小堆当中if(l){h.insert(l);}});//判断堆中是否有内容while(h.size()){//删除并返回堆顶const n = h.pop();//让p指针的next节点指向堆顶元素p.next = n;//p.next的值赋给p指针p = p.next;//如果堆顶元素有下一个节点,则将其插入堆中if(n.next){h.insert(n.next);}}return res.next;
};

🐪六、结束语

学完这个数据结构的时候,想到上回看面经时有一道算法题。那道题目问到说,假设现在有一个文件,里面有很多个单词,请找出前10个出现频率最高的词汇。

当时我的心里想的是:遍历?但其实今天学完这个数据结构以后,回想起来,这道题的做法就是用最小堆来实现。

所以,堆在日常的应用中都是相通的,只要明白了其中的思想,间接地,也将可以应用到对应的各种场景当中。

到这里,关于堆在前端中的应用的讲解就结束啦!希望对大家有帮助~

如文章有误或有不理解的地方,欢迎小伙伴们评论区留言撒💬

🐣彩蛋 One More Thing

(:往期推荐

栈👉栈在前端中的应用,顺便再了解下深拷贝和浅拷贝!

队列👉详解队列在前端的应用,深剖JS中的事件循环Eventloop,再了解微任务和宏任务

链表👉详解链表在前端的应用,顺便再弄懂原型和原型链!

字典和集合👉ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用

树👉一文了解树在前端中的应用,掌握数据结构中树的生命线

图👉太平洋大西洋水流问题如何解决?一文了解图在前端中的应用

动态规则和分而治之算法👉一文了解分而治之和动态规则算法在前端中的应用

贪心算法和回溯算法👉一文了解贪心算法和回溯算法在前端中的应用

(:番外篇

  • 关注公众号星期一研究室,第一时间关注学习干货,更多精选专栏待你解锁~
  • 如果这篇文章对你有用,记得留个脚印jio再走哦~
  • 以上就是本文的全部内容!我们下期见!👋👋👋

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307925.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

​如何编写高质量的C#代码(一)

如何编写高质量的C#代码&#xff08;一&#xff09;从”整洁代码“谈起一千个读者&#xff0c;就有一千个哈姆雷特&#xff0c;代码质量也同样如此。想必每一个对于代码有追求的开发者&#xff0c;对于“高质量”这个词&#xff0c;或多或少都有自己的一丝理解。当我在长沙.NET…

可视化太酷辽!一文了解排序和搜索算法在前端中的应用

一文了解排序和搜索算法在前端中的应用⏳序言&#x1f9ed;一、文章结构抢先知⌚二、排序和搜索1、定义2、JS中的排序和搜索⏰三、排序算法1、冒泡排序&#x1f4a1;&#xff08;1&#xff09;定义&#xff08;2&#xff09;实现思路&#xff08;3&#xff09;图例&#xff08;…

leetcode双指针合集

27. 移除元素 class Solution { public:int removeElement(vector<int>& nums, int val) {/**思路:使用快慢指针&#xff0c;快指针正常往后移动,并将获取的值给慢指针&#xff0c;但是当快指针所指向的值是val的时候慢指针便不再更新;**/ int slowIndex 0;for(…

网络安全逐渐成为程序员的必备技能

大家好&#xff0c;我是Z哥。不知道大家有没有发现。如今&#xff0c;曝光某些知名公司信息泄露的事件频率越来越高。与之对应的&#xff0c;网络安全问题也越来越受到重视。从百度指数摘录了两张图给大家分享下。可以看到&#xff0c;对网络安全相关的信息和关注度在逐渐走高&…

webpack入门核心知识还看不过瘾?速来围观万字入门进阶知识

一文了解webpack入门进阶知识&#x1f93e;‍♀️序言&#x1f3d3;一、Tree Shaking1. 引例阐述2. Tree Shaking配置&#x1f3f8;二、Development和Prodiction模式的区分打包1. 项目打包结构2. 共有配置webpack.common.js3. 开发环境webpack.dev.js4. 生产环境webpack.prod.j…

Power Automate生产现场实例分享回顾

Power Automate生产现场实例分享回顾8月28日&#xff08;周五&#xff09;19&#xff1a;30-21&#xff1a;00&#xff0c;Danfos智慧工厂数字化解决方案高级顾问Helena Wang通过Teams和B站为大家分享了Power Platform开发以及它在工业生产当中的应用。一、什么是低代码开发&am…

万字总结webpack实战案例配置

一文了解webpack中常见实战案例配置&#x1f6f4;序言&#x1f68c;一、Library的打包1. webpack打包库2. 库引用冲突&#x1f68d;二、PWA的打包配置1. PWA是什么2. webpack中的PWA&#x1f68e;三、TypeScript的打包配置1. 引例阐述2. webpack对ts的配置&#xff08;1&#x…

程序员过关斩将--应对高并发系统有没有通用的解决方案呢?

“灵魂拷问&#xff1a;应对高并发系统有没有一些通用的解决方案呢&#xff1f;这些方案解决了什么问题呢&#xff1f;这些方案有那些优势和劣势呢&#xff1f;对性能孜孜不倦的追求是互联网技术不断发展的根本驱动力&#xff0c;从最初的大型机到现在的微型机&#xff0c;在本…

org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the

一:报错 org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement com.wyj.Dao.Bill.BillMapper.getBill. Its likely that neither a Result Type nor a Result Map was specified.二:解决 未在mapper.xml…

.NET5.0 单文件发布打包操作深度剖析

.NET5.0 单文件发布打包操作深度剖析前言随着 .NET5.0 Preview 8 的发布&#xff0c;许多新功能正在被社区成员一一探索&#xff1b;这其中就包含了“单文件发布”这个炫酷的功能&#xff0c;实际上&#xff0c;这也是社区一直以来的呼声&#xff0c;从 WinForm 的 msi 开始&am…

webpack实战之手写一个loader和plugin

webpack实战之编写一个简易的loader和plugin&#x1f514;序言&#x1f3b5;一、如何编写一个Loader1. 碎碎念2. 项目结构3. 业务代码编写&#xff08;1&#xff09;入口文件代码&#xff08;2&#xff09;编写loader&#xff08;3&#xff09;引用loader&#xff08;4&#xf…

手写一个简易bundler打包工具带你了解Webpack原理

用原生js手写一个简易的打包工具bundler&#x1f95d;序言&#x1f349;一、模块分析(入口文件代码分析)1. 项目结构2. 安装第三方依赖3. 业务代码4. 开始打包&#x1f951;二、依赖图谱Dependencies Graph1. 结果分析2. 分析所有模块的依赖关系&#x1f350;三、生成代码1. 逻…

不喜欢 merge 分叉,那就用 rebase 吧

阅读本文大概需要 3 分钟。有些人不喜欢 merge&#xff0c;因为在 merge 之后&#xff0c;commit 历史就会出现分叉&#xff0c;这种分叉再汇合的结构会让有些人觉得混乱而难以管理。如果你不希望 commit 历史出现分叉&#xff0c;可以用 rebase 来代替 merge。rebase &#xf…

你可能对position和z-index有一些误解

一文详解css中的position和z-index&#x1f9c3;序言&#x1f377;一、文章结构抢先知&#x1f378;二、position1. position的取值2. 标准文档流3. 各取值解析&#xff08;1&#xff09;static&#xff08;2&#xff09;relative&#xff08;3&#xff09;absolute&#xff08…

数据结构与算法专题——第九题 外排序

说到排序&#xff0c;大家第一反应基本上是内排序&#xff0c;是的&#xff0c;算法嘛&#xff0c;玩的就是内存&#xff0c;然而内存是有限制的&#xff0c;总有装不下的那一天&#xff0c;此时就可以来玩玩外排序&#xff0c;当然在我看来&#xff0c;外排序考验的是一个程序…

谁动了我的选择器?深入理解CSS选择器优先级

深入理解CSS选择器优先级&#x1f60f;序言&#x1f9d0;文章内容抢先看&#x1f910;一、基础知识1、为什么CSS选择器很强2、CSS选择器的一些基本概念&#xff08;1&#xff09;4种基本概念Ⅰ. 选择器Ⅱ. 选择符Ⅲ. 伪类Ⅳ. 伪元素&#xff08;2&#xff09;CSS选择器的命名空…

leetcode239. 滑动窗口最大值(思路+详解)

一&#xff1a;题目 二:思路 1.这个题不能用优先队列&#xff0c;虽然我们可以通过优先队列得到最大值&#xff0c;但是我们在移动 窗口的时候,便不可以正常的删除元素了 2.虽然不能用优先对列&#xff0c;但是我们依然希望可以得到队首的元素的时候是最大值&#xff0c;同时还…

《ASP.NET Core 与 RESTful API 开发实战》-- (第10章)-- 读书笔记

第 10 章 部署10.1 部署到 IISASP.NET Core 应用程序支持部署到 IIS 中&#xff0c;之后它将作为应用程序的反向代理服务器和负载均衡器&#xff0c;向应用程序中转传入的 HTTP 请求默认情况下&#xff0c;ASP.NET Core 项目的 Program 类使用如下方式创建 WebHostpublic stati…

翠香猕猴桃 和 薄皮核桃,快来下单

猴桃品种有很多&#xff0c;但不是所有的果子都叫翠香。椭圆形&#xff0c;果喙端较尖&#xff0c;黄褐色硬短茸毛&#xff1b;果肉翠绿色&#xff0c;质细多汁&#xff0c;香甜爽口&#xff0c;有芳香味&#xff0c;白色果心。这就是“翠香”&#xff0c;是集酸甜香于一身的猕…

你可能没有听说过 js中的 DOM操作还有这个: HTMLCollection 和 NodeList

一文了解DOM操作中的HTMLCollection和NodeList⛱️序言&#x1f388;一、基础知识1. 定义&#xff08;1&#xff09;HTMLCollection&#xff08;2&#xff09;NodeList2. 属性和方法&#xff08;1&#xff09;HTMLCollection&#xff08;2&#xff09;NodeList&#x1fa81;二、…