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

简介: 该文章详细解释了堆数据结构(特别是最小堆)的概念与性质,并提供了使用JavaScript实现最小堆的具体代码示例,包括堆的插入、删除等操作方法。

封面

⚡序言

我们都知道树是一个数据结构,但可能很少听到堆这个数据结构。其实,堆就是一种特殊的完全二叉树。而对于前端来说,我们通常了解最大堆和最小堆,也经常用最大堆和最小堆来解决各种问题。比如,数组中的第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再走哦~
  • 以上就是本文的全部内容!我们下期见!👋👋👋
相关文章
|
28天前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
119 2
|
29天前
|
前端开发 机器人 API
前端大模型入门(一):用 js+langchain 构建基于 LLM 的应用
本文介绍了大语言模型(LLM)的HTTP API流式调用机制及其在前端的实现方法。通过流式调用,服务器可以逐步发送生成的文本内容,前端则实时处理并展示这些数据块,从而提升用户体验和实时性。文章详细讲解了如何使用`fetch`发起流式请求、处理响应流数据、逐步更新界面、处理中断和错误,以及优化用户交互。流式调用特别适用于聊天机器人、搜索建议等应用场景,能够显著减少用户的等待时间,增强交互性。
213 2
|
13天前
|
前端开发 JavaScript 安全
前端性能调优:HTTP/2与HTTPS在Web加速中的应用
【10月更文挑战第27天】本文介绍了HTTP/2和HTTPS在前端性能调优中的应用。通过多路复用、服务器推送和头部压缩等特性,HTTP/2显著提升了Web性能。同时,HTTPS确保了数据传输的安全性。文章提供了示例代码,展示了如何使用Node.js创建一个HTTP/2服务器。
27 3
|
14天前
|
Rust 前端开发 JavaScript
前端性能革命:WebAssembly在高性能计算中的应用探索
【10月更文挑战第26天】随着Web应用功能的日益复杂,传统JavaScript解释执行模式逐渐成为性能瓶颈。WebAssembly(Wasm)应运而生,作为一种二进制代码格式,支持C/C++、Rust等语言编写的代码在浏览器中高效运行。Wasm不仅提升了应用的执行速度,还具备跨平台兼容性和安全性,显著改善了Web应用的响应速度和用户体验。
29 4
|
13天前
|
前端开发 数据管理 测试技术
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第27天】本文介绍了前端自动化测试中Jest和Cypress的实战应用与最佳实践。Jest适合React应用的单元测试和快照测试,Cypress则擅长端到端测试,模拟用户交互。通过结合使用这两种工具,可以有效提升代码质量和开发效率。最佳实践包括单元测试与集成测试结合、快照测试、并行执行、代码覆盖率分析、测试环境管理和测试数据管理。
29 2
|
14天前
|
前端开发 安全 应用服务中间件
前端性能调优:HTTP/2与HTTPS在Web加速中的应用
【10月更文挑战第26天】随着互联网的快速发展,前端性能调优成为开发者的重要任务。本文探讨了HTTP/2与HTTPS在前端性能优化中的应用,介绍了二进制分帧、多路复用和服务器推送等特性,并通过Nginx配置示例展示了如何启用HTTP/2和HTTPS,以提升Web应用的性能和安全性。
16 3
|
14天前
|
前端开发 JavaScript 数据可视化
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第26天】前端自动化测试在现代软件开发中至关重要,Jest和Cypress分别是单元测试和端到端测试的流行工具。本文通过解答一系列问题,介绍Jest与Cypress的实战应用与最佳实践,帮助开发者提高测试效率和代码质量。
26 2
|
14天前
|
前端开发 JavaScript API
前端框架新探索:Svelte在构建高性能Web应用中的优势
【10月更文挑战第26天】近年来,前端技术飞速发展,Svelte凭借独特的编译时优化和简洁的API设计,成为构建高性能Web应用的优选。本文介绍Svelte的特点和优势,包括编译而非虚拟DOM、组件化开发、状态管理及响应式更新机制,并通过示例代码展示其使用方法。
32 2
|
15天前
|
前端开发 JavaScript 开发者
“揭秘React Hooks的神秘面纱:如何掌握这些改变游戏规则的超能力以打造无敌前端应用”
【10月更文挑战第25天】React Hooks 自 2018 年推出以来,已成为 React 功能组件的重要组成部分。本文全面解析了 React Hooks 的核心概念,包括 `useState` 和 `useEffect` 的使用方法,并提供了最佳实践,如避免过度使用 Hooks、保持 Hooks 调用顺序一致、使用 `useReducer` 管理复杂状态逻辑、自定义 Hooks 封装复用逻辑等,帮助开发者更高效地使用 Hooks,构建健壮且易于维护的 React 应用。
28 2
|
21天前
|
JavaScript 前端开发 测试技术
前端全栈之路Deno篇(五):如何快速创建 WebSocket 服务端应用 + 客户端应用 - 可能是2025最佳的Websocket全栈实时应用框架
本文介绍了如何使用Deno 2.0快速构建WebSocket全栈应用,包括服务端和客户端的创建。通过一个简单的代码示例,展示了Deno在WebSocket实现中的便捷与强大,无需额外依赖,即可轻松搭建具备基本功能的WebSocket应用。Deno 2.0被认为是最佳的WebSocket全栈应用JS运行时,适合全栈开发者学习和使用。