前端宝典十七:算法之复杂度、链表、队列、栈的代码实现

news2024/9/20 15:09:35

引文

从本文开始主要探讨前端的算法,这一篇主要涉及:

  • 时间复杂度&空间复杂度;
  • 链表;
  • 队列;
  • 栈;

希望通过学习能掌握好

  • 具体代码时间复杂度&空间复杂度的算法;
  • 链表、队列、栈的JavaScript的完整代码实现;

一、时间复杂度

复杂度分析是整个算法学习的精髓,只要掌握了它,数据结构和算法的内容基本上就掌握了一半了。复杂度分为时间复杂度和空间复杂度,由于空间复杂度很少用于计算分析,这里主要讨论时间复杂度:

1、大 O 表示法

如何进行复杂度分析 ?
大 O 表示法

算法的执行时间与每行代码的执行次数成正比

T(n) = O(f(n)) 表示

T(n) 表示算法执行总时间
f(n) 表示每行代码执行总次数
n 往往表示数据的规模
这就是大 O 时间复杂度表示法。

大 O 时间复杂度表示法 实际上并不具体表示代码真正的执行时间,而是表示 代码执行时间随数据规模增长的变化趋势,所以也叫 渐进时间复杂度,简称 时间复杂度(asymptotic time complexity)。
asymptotic [æsɪmpˈtɑːtɪk] 渐进的

2、特点

以时间复杂度为例,由于 时间复杂度 描述的是算法执行时间与数据规模的 增长变化趋势,所以 常量、低阶、系数 实际上对这种增长趋势不产生决定性影响,所以在做时间复杂度分析时 忽略 这些项。

 function cal(n) {
   let sum = 0; // 1 次
   let i = 1; // 1 次
   let j = 1; // 1 次
   for (; i <= n; ++i) {  // n 次
     j = 1;  // n 次
     for (; j <= n; ++j) {  // n * n ,也即是  n平方次
       sum = sum +  i * j;  // n * n ,也即是  n平方次
     }
   }
 }

这里是二层 for 循环,所以第二层执行的是 n * n = n(2) 次,而且这里的循环是 ++i,和例子 2 的是 i++,是不同的,是先加与后加的区别。
那么这个方法需要执行 ( n(2) + n(2) + n + n + 1 + 1 +1 ) = 2n(2) +2n + 3 。
所以,上面例子的时间复杂度为 T(n) = O(n(2))。

3、时间复杂度分析方法

3.1 只关注循环执行次数最多的一段代码

单段代码看高频:比如循环。

function cal(n) { 
   let sum = 0;
   let i = 1;
   for (; i <= n; ++i) {
     sum = sum + i;
   }
   return sum;
 }

执行次数最多的是 for 循环及里面的代码,执行了 n 次,所以时间复杂度为 O(n)。

3.2 加法法则:总复杂度等于量级最大的那段代码的复杂度

多段代码取最大:比如一段代码中有单循环和多重循环,那么取多重循环的复杂度。

function cal(n) {
   let sum_1 = 0;
   let p = 1;
   for (; p < 100; ++p) {
     sum_1 = sum_1 + p;
   }

   let sum_2 = 0;
   let q = 1;
   for (; q < n; ++q) {
     sum_2 = sum_2 + q;
   }
 
   let sum_3 = 0;
   let i = 1;
   let j = 1;
   for (; i <= n; ++i) {
     j = 1; 
     for (; j <= n; ++j) {
       sum_3 = sum_3 +  i * j;
     }
   }
 
   return sum_1 + sum_2 + sum_3;
 }

上面代码分为三部分,分别求 sum_1、sum_2、sum_3 ,主要看循环部分。
第一部分,求 sum_1 ,明确知道执行了 100 次,而和 n 的规模无关,是个常量的执行时间,不能反映增长变化趋势,所以时间复杂度为 O(1)。

第二和第三部分,求 sum_2 和 sum_3 ,时间复杂度是和 n 的规模有关的,为别为 O(n) 和 O(n(2))。
所以,取三段代码的最大量级,上面例子的最终的时间复杂度为 O(n(2))。

同理类推,如果有 3 层 for 循环,那么时间复杂度为 O(n(3)),4 层就是 O(n(4))。
所以,总的时间复杂度就等于量级最大的那段代码的时间复杂度。

2.3 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

嵌套代码求乘积:比如递归、多重循环等。

function cal(n) {
   let ret = 0; 
   let i = 1;
   for (; i < n; ++i) {
     ret = ret + f(i); // 重点为  f(i)
   } 
 } 
 
function f(n) {
  let sum = 0;
  let i = 1;
  for (; i < n; ++i) {
    sum = sum + i;
  } 
  return sum;
 }

方法 cal 循环里面调用 f 方法,而 f 方法里面也有循环。
所以,整个 cal() 函数的时间复杂度就是,T(n) = T1(n) * T2(n) = O(n*n) = O(n(2)) 。

2.4 多个规模求加法:比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相加

function cal(m, n) {
  let sum_1 = 0;
  let i = 1;
  for (; i < m; ++i) {
    sum_1 = sum_1 + i;
  }

  let sum_2 = 0;
  let j = 1;
  for (; j < n; ++j) {
    sum_2 = sum_2 + j;
  }

  return sum_1 + sum_2;
}

以上代码也是求和 ,求 sum_1 的数据规模为 m、求 sum_2 的数据规模为 n,所以时间复杂度为 O(m+n)。
公式:T1(m) + T2(n) = O(f(m) + g(n)) 。

2.5 多个规模求乘法:比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相乘

function cal(m, n) {
  let sum_3 = 0;
   let i = 1;
   let j = 1;
   for (; i <= m; ++i) {
     j = 1; 
     for (; j <= n; ++j) {
       sum_3 = sum_3 +  i * j;
     }
   }
}

以上代码也是求和,两层 for 循环 ,求 sum_3 的数据规模为 m 和 n,所以时间复杂度为 O(m*n)。
公式:T1(m) * T2(n) = O(f(m) * g(n)) 。

4、常用的时间复杂度所耗费的时间从小到大依次是:

O(1) < O(logn) 对数< (n) 线性< O(nlogn)线性对数 < O(n(2))平方 < O(n(3)) 立方< O(2(n)) 指数< O(n!)阶乘 < O(n(n))
常见的时间复杂度:
在这里插入图片描述

二、链表

链表相对于数组来说,要复杂的多,首先,链表不需要连续的内存空间,它是由一组零散的内存块透过指针连接而成,所以,每一个块中必须包含当前节点内容以及后继指针。最常见的链表类型有单链表、双链表以及循环链表。
学习链表最重要的是 多画图多练习 :

  • 确定解题的数据结构:单链表、双链表或循环链表等
  • 确定解题思路:如何解决问题
  • 画图实现:画图可以帮助我们发现思维中的漏洞(一些思路不周的情况)
  • 确定边界条件:思考解题中是否有边界问题以及如何解决

1、单链表

在这里插入图片描述

function List () {
  // 节点
  let Node = function (element) {
    this.element = element
    this.next = null
  }
  // 初始头节点为 null
  let head = null
  
  // 链表长度
  let length = 0
  // 操作
  this.getList = function() {return head}
  this.search = function(list, element) {}
  this.append = function(element) {}
  this.insert = function(position, element) {}
  this.remove = function(element){}
  this.isEmpty = function(){}
  this.size = function(){}
}

插入节点
初始化一个节点(待插入节点 node ),遍历到 position 前一个位置节点,在该节点后插入 node
在这里插入图片描述
确定边界条件:

  • 当 position 为 0 时,直接将插入节点 node.next 指向 head , head 指向 node 即可,不需要遍历
  • 当待插入位置 position < 0 或超出链表长度 position > length ,都是有问题的,不可插入,此时直接返回 null ,插入失败
// 插入 position 的后继节点
function insert (position, element) {
  // 创建插入节点
  let node = new createNode(element)
  if (position >= 0 && position <= length) {
    let prev = head, 
        curr = head,
        index = 0
    if(position === 0) {
      node.next = head
      head = node
    } else {
      while(index < position) {
        prev = curr
        curr = curr.next
        index ++
      }
      prev.next = node
      node.next = curr
    }
    length += 1
  } else {
    return null
  }
}

// 测试
list.insert(10)

另外添加、删除、查找节点的代码实现比较简单,这里就不举例说明了。

2、双链表

顾名思义,单链表只有一个方向,从头节点到尾节点,那么双链表就有两个方向,从尾节点到头节点:
在这里插入图片描述

function DoublyLinkedList() {
    let Node = function(element) {
        this.element = element
        // 前驱指针
        this.prev = null
        // 后继指针
        this.next = null
    }
    // 初始头节点为 null
          let head = null
    // 新增尾节点
    let tail = null
  
          // 链表长度
          let length = 0
    // 操作
    this.search = function(element) {}
    this.insert = function(position, element) {}
    this.removeAt = function(position){}
    this.isEmpty = function(){ return length === 0 } 
    this.size = function(){ return length }
}

插入节点
初始化一个节点(待插入节点 node ),遍历链表到 position 前一个位置节点,在该节点位置后插入 node
在这里插入图片描述
当待插入位置 position < 0 或超出链表长度 position > length ,都是有问题的,不可插入,此时直接返回 null ,插入失败

// 插入 position 的后继节点
function insert (position, element) {
  // 创建插入节点
  let node = new Node(element)
  if (position >= 0 && position < length) {
    let prev = head, 
        curr = head,
        index = 0
    if(position === 0) {
      // 在第一个位置添加
        if(!head) { // 注意这里与单链表不同
          head = node
          tail = node
        } else {
          // 双向
          node.next = head
          head.prev = node
          // head 指向新的头节点
          head = node
        }
    } else if(position === length) {
      // 插入到尾节点
      curr = tial
      curr.next = node
      node.prev = curr
      // tail 指向新的尾节点
      tail = node
    } else {
      while(index < position) {
        prev = curr
        curr = curr.next
        index ++
      }
      // 插入到 prev 后,curr 前
      prev.next = node
      node.next = curr
      curr.prev = node
      node.prev = prev
    }
    length += 1
    return true
  } else {
    return false
  }
}

// 测试
list.insert(10)

三、循环单链表

循环单链表是一种特殊的单链表,它和单链表的唯一区别是:单链表的尾节点指向的是 NULL,而循环单链表的尾节点指向的是头节点,这就形成了一个首尾相连的环:
在这里插入图片描述
既然有循环单链表,当然也有循环双链表,循环双链表和双链表不同的是:

  • 循环双链表的 tail.next( tail 的后继指针) 为 null ,循环双链表的 tail.next 为 head
  • 循环双链表的 head.prev( head 的前驱指针) 为 null ,循环双链表的 head.prev 为 tail

给定一个链表,判断链表中是否有环。

1、标记法

给每个已遍历过的节点加标志位,遍历链表,当出现下一个节点已被标志时,则证明单链表有环

let hasCycle = function(head) {
    while(head) {
        if(head.flag) return true
        head.flag = true
        head = head.next
    }
    return false
};

时间复杂度:O(n)
空间复杂度:O(n)

2、利用 JSON.stringify() 不能序列化含有循环引用的结构

let hasCycle = function(head) {
    try{
        JSON.stringify(head);
        return false;
    }
    catch(err){
        return true;
    }
};

时间复杂度:O(n)
空间复杂度:O(n)

3、快慢指针(双指针法)

设置快慢两个指针,遍历单链表,快指针一次走两步,慢指针一次走一步,如果单链表中存在环,则快慢指针终会指向同一个节点,否则直到快指针指向 null 时,快慢指针都不可能相遇

let hasCycle = function(head) {
    if(!head || !head.next) {
        return false
    }
    let fast = head.next.next, slow = head.next
    while(fast !== slow) {
        if(!fast || !fast.next) return false
        fast = fast.next.next
        slow = slow.next
    }
    return true
};

时间复杂度:O(n)
空间复杂度:O(1)

四、队列

1、特点

队列(Queue)是一种运算受限的线性表

特点:先进先出。(FIFO:First In First Out)

  • 只允许在表的前端(front)进行删除操作。
  • 只允许在表的后端(rear)进行插入操作。

2、生活中类似队列结构的场景:

  • 排队,比如在电影院,商场,甚至是厕所排队。
  • 优先排队的人,优先处理。 (买票、结账、WC)。

在这里插入图片描述

3、 队列的应用

  • 打印队列:计算机打印多个文件的时候,需要排队打印。
  • 线程队列:当开启多线程时,当新开启的线程所需的资源不足时就先放入线程队列,等待 CPU 处理。

4、队列的实现

队列的实现和栈一样,有两种方案:

  • 基于数组实现。
  • 基于链表实现。

5、队列常见的操作

  • enqueue(element) 向队列尾部添加一个(或多个)新的项。
  • dequeue() 移除队列的第一(即排在队列最前面的)项,并返回被移除的元素。
  • front() 返回队列中的第一个元素——最先被添加,也将是最先被移除的元素。队列不做任何变动(不移除元素,只返回元素信息与 Map 类的 peek 方法非常类似)。
  • isEmpty() 如果队列中不包含任何元素,返回 true,否则返回 false。
  • size() 返回队列包含的元素个数,与数组的 length 属性类似。
  • toString() 将队列中的内容,转成字符串形式。
class Queue {
  constructor() {
    this.items = [];
  }

  // enqueue(item) 入队,将元素加入到队列中
  enqueue(item) {
    this.items.push(item);
  }

  // dequeue() 出队,从队列中删除队头元素,返回删除的那个元素
  dequeue() {
    return this.items.shift();
  }

  // front() 查看队列的队头元素
  front() {
    return this.items[0];
  }

  // isEmpty() 查看队列是否为空
  isEmpty() {
    return this.items.length === 0;
  }

  // size() 查看队列中元素的个数
  size() {
    return this.items.length;
  }

  // toString() 将队列中的元素以字符串形式返回
  toString() {
    let result = "";
    for (let item of this.items) {
      result += item + " ";
    }
    return result;
  }
}

测试代码

const queue = new Queue();

// enqueue() 测试
queue.enqueue("a");
queue.enqueue("b");
queue.enqueue("c");
queue.enqueue("d");
console.log(queue.items); //--> ["a", "b", "c", "d"]

// dequeue() 测试
queue.dequeue();
queue.dequeue();
console.log(queue.items); //--> ["c", "d"]

// front() 测试
console.log(queue.front()); //--> c

// isEmpty() 测试
console.log(queue.isEmpty()); //--> false

// size() 测试
console.log(queue.size()); //--> 2

// toString() 测试
console.log(queue.toString()); //--> c d

6、队列的例子

使用队列实现小游戏:击鼓传花。
分析:传入一组数据集合和设定的数字 number,循环遍历数组内元素,遍历到的元素为指定数字 number 时将该元素删除,直至数组剩下一个元素。

// 利用队列结构的特点实现击鼓传花游戏求解方法的封装
function passGame(nameList, number) {
  // 1、new 一个 Queue 对象
  const queue = new Queue();

  // 2、将 nameList 里面的每一个元素入队
  for (const name of nameList) {
    queue.enqueue(name);
  }

  // 3、开始数数
  // 队列中只剩下 1 个元素时就停止数数
  while (queue.size() > 1) {
    // 不是 number 时,重新加入到队尾
    // 是 number 时,将其删除

    for (let i = 0; i < number - 1; i++) {
      // number 数字之前的人重新放入到队尾(即把队头删除的元素,重新加入到队列中)
      queue.enqueue(queue.dequeue());
    }

    // number 对应这个人,直接从队列中删除
    // 由于队列没有像数组一样的下标值不能直接取到某一元素,
    // 所以采用,把 number 前面的 number - 1 个元素先删除后添加到队列末尾,
    // 这样第 number 个元素就排到了队列的最前面,可以直接使用 dequeue 方法进行删除
    queue.dequeue();
  }

  // 4、获取最后剩下的那个人
  const endName = queue.front();

  // 5、返回这个人在原数组中对应的索引
  return nameList.indexOf(endName);
}

7、优先队列

优先队列的应用
生活中类似优先队列的场景:

  • 优先排队的人,优先处理。 (买票、结账、WC)。
  • 排队中,有紧急情况(特殊情况)的人可优先处理。

优先级队列主要考虑的问题:

  • 每个元素不再只是一个数据,还包含优先级。
  • 在添加元素过程中,根据优先级放入到正确位置。
    6.2.2 优先队列的实现
// 优先队列内部的元素类
class QueueElement {
  constructor(element, priority) {
    this.element = element;
    this.priority = priority;
  }
}

// 优先队列类(继承 Queue 类)
export class PriorityQueue extends Queue {
  constructor() {
    super();
  }

  // enqueue(element, priority) 入队,将元素按优先级加入到队列中
  // 重写 enqueue()
  enqueue(element, priority) {
    // 根据传入的元素,创建 QueueElement 对象
    const queueElement = new QueueElement(element, priority);

    // 判断队列是否为空
    if (this.isEmpty()) {
      // 如果为空,不用判断优先级,直接添加
      this.items.push(queueElement);
    } else {
      // 定义一个变量记录是否成功添加了新元素
      let added = false;

      for (let i = 0; i < this.items.length; i++) {
        // 让新插入的元素进行优先级比较,priority 值越小,优先级越大
        if (queueElement.priority < this.items[i].priority) {
          // 在指定的位置插入元素
          this.items.splice(i, 0, queueElement);
          added = true;
          break;
        }
      }

      // 如果遍历完所有元素,优先级都大于新插入的元素,就将新插入的元素插入到最后
      if (!added) {
        this.items.push(queueElement);
      }
    }
  }

  // dequeue() 出队,从队列中删除前端元素,返回删除的元素
  // 继承 Queue 类的 dequeue()
  dequeue() {
    return super.dequeue();
  }

  // front() 查看队列的前端元素
  // 继承 Queue 类的 front()
  front() {
    return super.front();
  }

  // isEmpty() 查看队列是否为空
  // 继承 Queue 类的 isEmpty()
  isEmpty() {
    return super.isEmpty();
  }

  // size() 查看队列中元素的个数
  // 继承 Queue 类的 size()
  size() {
    return super.size();
  }

  // toString() 将队列中元素以字符串形式返回
  // 重写 toString()
  toString() {
    let result = "";
    for (let item of this.items) {
      result += item.element + "-" + item.priority + " ";
    }
    return result;
  }
}

五、栈

数组是一个线性结构,并且可以在数组的任意位置插入和删除元素。 但是有时候,我们为了实现某些功能,必须对这种任意性加以限制。 栈和队列就是比较常见的受限的线性结构。

1、栈的定义

栈(stack)是一种运算受限的线性表:

  • LIFO(last in first out)表示就是后进入的元素,第一个弹出栈空间。类似于自动餐托盘,最后放上的托盘,往往先被拿出去使用。
  • 其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。
  • 向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;
  • 从一个栈删除元
    在这里插入图片描述
    栈的特点:先进后出,后进先出。

2、栈的实现

  • push() 添加一个新元素到栈顶位置。
  • pop() 移除栈顶的元素,同时返回被移除的元素。
  • peek() 返回栈顶的元素,不对栈做任何修改(该方法不会移除栈顶的元素,仅仅返回它)。
  • isEmpty() 如果栈里没有任何元素就返回 true,否则返回 false。
  • size() 返回栈里的元素个数。这个方法和数组的 length 属性类似。
  • toString() 将栈结构的内容以字符串的形式返回。
// 栈结构的封装
class Stack {
  constructor() {
    this.items = [];
  }

  // push(item) 压栈操作,往栈里面添加元素
  push(item) {
    this.items.push(item);
  }

  // pop() 出栈操作,从栈中取出元素,并返回取出的那个元素
  pop() {
    return this.items.pop();
  }

  // peek() 查看栈顶元素
  peek() {
    return this.items[this.items.length - 1];
  }

  // isEmpty() 判断栈是否为空
  isEmpty() {
    return this.items.length === 0;
  }

  // size() 获取栈中元素个数
  size() {
    return this.items.length;
  }

  // toString() 返回以字符串形式的栈内元素数据
  toString() {
    let result = "";
    for (let item of this.items) {
      result += item + " ";
    }
    return result;
  }
}

3、栈的应用

利用栈结构的特点封装实现十进制转换为二进制的方法。

十进制数转换为二进制的规则如下:

除 2 取余法

  1. 用十进制数除以 2,取余数。
  2. 然后将商继续除以 2,再取余数。
  3. 重复这个过程,直到商为 0。
  4. 最后将所有的余数从下往上排列,就得到了二进制数。

例如,将十进制数 10 转换为二进制:

  • 10÷2 = 5……0
  • 5÷2 = 2……1
  • 2÷2 = 1……0
  • 1÷2 = 0……1

从下往上排列余数,得到二进制数 1010。

function dec2bin(dec) {
  // new 一个 Stack,保存余数
  const stack = new Stack();

  // 当不确定循环次数时,使用 while 循环
  while (dec > 0) {
    // 除二取余法
    stack.push(dec % 2); // 获取余数,放入栈中
    dec = Math.floor(dec / 2); // 除数除以二,向下取整
  }

  let binaryString = "";
  // 不断地从栈中取出元素(0 或 1),并拼接到一起。
  while (!stack.isEmpty()) {
    binaryString += stack.pop();
  }

  return binaryString;
}

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

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

相关文章

Nginx - 反向代理、缓存详解

概述 本篇博客对配置Nginx的第二篇&#xff0c;主要介绍Nginx设置反向代理、缓存、和负载均衡三个知识点&#xff0c;在之前的生产实践中遇到的问题进行归纳和总结&#xff0c;分享出来&#xff0c;以方便同学们有更好的成长。 Nginx 核心参数配置 在写Nginx反向代理时&…

公众号(H5)及小程序的发布流程

⚠️ Web平台本API之前调用了腾讯地图的gcj02坐标免费转换接口&#xff0c;该接口从2024年7月18日起被腾讯逐步下线&#xff0c;导致老版本中本API无法使用。请立即升级到 uni-app 4.24版。 ⚠️ 这里说的 uniapp 升级到 4.24 版的意思&#xff0c;就是使用 4.24 版本 HBuilde…

【JVM】亿级流量调优(一)

亿级流量调优 oop模型 前面的klass模型&#xff0c;它是Java类的元信息在JVM中的存在形式。这个oop模型是Java对象在JVM中的存在形式 内存分配策略: 1.空闲列表2.指针碰撞(jvm采用的) 2.1 top指针:执行的是可用内存的起始位置 2.2 采用CAS的方式3.TLAB 线程私有堆4.PLAB 老年…

使用PhaGCN2/vConTACT2进行病毒分类注释

Introduction 在微生物群落的研究中&#xff0c;分类和注释数量庞大的未培养古细菌和细菌病毒一直是一个难题&#xff0c;主要原因是缺乏统一的分类框架。 目前&#xff0c;用于病毒分类的多种基于基因组的方法已经被提出&#xff0c;主要集中在细菌、古细菌和真核生物病毒的…

隧道代理ip使用

简介 隧道代理&#xff08;Tunnel Proxy&#xff09;是一种特殊的代理服务&#xff0c;它的工作方式是在客户端与远程服务器之间建立一条“隧道”。这种技术常被用来绕过网络限制或提高网络安全性。 主要功能 IP地址变换&#xff1a;隧道代理能够改变客户端的IP地址&#xf…

《javaEE篇》--线程池

线程池是什么 线程的诞生是因为进程创建和销毁的成本太大&#xff0c;但是也是相对而言&#xff0c;如果频繁的创建和销毁线程那么这个成本就不能忽略了。 一般有两种方法来进一步提高效率&#xff0c;一种是协程(这里不多做讨论),另一种就是线程池 假如说有一个学校食堂窗口…

【Node】【2】创建node应用

创建node应用 node应用&#xff0c;不仅可以实现web应用&#xff0c;也能实现http服务器。 如果是php写后端&#xff0c;还需要有http服务器&#xff0c;比如apache 或者 nginx。 但是现在主流都是java写后端&#xff0c;也可以像 Node.js 一样用于实现 Web 应用和 HTTP 服务…

chapter08-面向对象编程(包、访问修饰符、封装、继承)day08

目录 277-包的使用细节 278-访问修饰符规则 279-访问修饰符细节 281-封装介绍 282-封装步骤 283-封装快速入门 284-封装与构造器 285-封装课堂练习 286-为什么需要继承 277-包的使用细节 1、需要哪个包就导入哪个包&#xff0c;不建议全部导入* 2、包的使用细节&…

宵暗的妖怪

宵暗的妖怪 错误代码和正确代码对比 #include <iostream> #include <string.h> using namespace std; const int N1e510; long long a[N],f[N],g[N]; // f 以i为结尾&#xff0c;饱食度最大值 // g 0-i中&#xff0c;饱食度最大值int main() {int n;cin>>…

Linux 内核源码分析---IPv6 数据包

IPv6是英文“Internet Protocol Version 6”&#xff08;互联网协议第6版&#xff09;的缩写&#xff0c;是互联网工程任务组&#xff08;IETF&#xff09;设计的用于替代IPv4的下一代IP协议&#xff0c;其地址数量号称可以为全世界的每一粒沙子编上一个地址。 由于IPv4最大的…

看图学sql之sql中的子查询

&#xfeff;&#xfeff; &#xfeff;where子句子查询 语法&#xff1a; SELECT column_name [, column_name ] FROM table1 [, table2 ] WHERE column_name OPERATOR(SELECT column_name [, column_name ]FROM table1 [, table2 ][WHERE]) 子查询需要放在括号( )内。O…

课后作业-第四次

1.将web-ssrfme.zip解压缩在Ubuntu下 Docker-compose up -d 更新后的镜像重新启动容器 2.拉取成功ssrfme镜像 3.使用端口访问文件&#xff0c; 可以看到有一个过滤条件&#xff0c;它限制了file&#xff0c;dict协议&#xff0c;127.0.0.1和localhost 也不能用&#xff0c;…

中仕公考怎么样?2025年国考现在准备来得及吗?

2025年国考时间线 2024年10月发布公告——2024年11月报名确认——2024年11月打印准考证——2024年12月笔试——2024年3月现场资格审查——2024年3-4月面试——2025年5月补录环节 考试科目&#xff1a; 笔试的主要科目是:行测申论 ①行测:常识、言语理解、判断推理、数量关系…

2025智能电动窗帘推荐!电动窗帘应该如何选择?看一篇窗帘省钱攻略!一体化电动窗帘

史新华 智能家居已经走进我们的生活。 智能马桶、智能穿衣镜、语音茶吧机、小爱音箱、天猫精灵这些家居好物让生活更加精致&#xff0c;智能窗帘走进千家万户还会远吗&#xff1f;&#xff01; 我最开始关注电动窗帘&#xff0c;就是从住酒店开始的&#xff0c;经常出差的同学…

uni-app里引入阿里彩色矢量图标(Symbol),却发现图标显示为黑白

当使用uniapp并尝试引入阿里iconfont的彩色图标时&#xff0c;发现图标显示为黑白。原因是Fontclass模式不支持彩色图标。 解决方法是下载Symbol模式的SVG文件&#xff0c;使用iconfont-tools进行转换&#xff0c;然后在项目中全局引入转换后的CSS文件&#xff0c;最终在组件中…

探索联邦学习:保护隐私的机器学习新范式

探索联邦学习&#xff1a;保护隐私的机器学习新范式 前言联邦学习简介联邦学习的原理联邦学习的应用场景联邦学习示例代码结语 前言 在数字化浪潮的推动下&#xff0c;我们步入了一个前所未有的数据驱动时代。海量的数据不仅为科学研究、商业决策和日常生活带来了革命性的变化&…

Datawhale X 李宏毅苹果书 AI夏令营_深度学习基础学习心得

本次学习了深度学习中的局部最小值 1、书上说有时候模型一开始就训练不起来&#xff0c;不管怎么更新参数损失都不下降。我之前遇到过这种情况&#xff0c;大概是做一个数据很不平衡的二分类&#xff0c;正负样本比例大概为9&#xff1a;1&#xff0c;模型倾向于全部预测为正样…

docker-compose自动化部署前后端项目--最终篇

docker-compose部署 一个项目肯定包含多个容器&#xff0c;每个容器都手动单独部署肯定费时费力。docker-compose可以通过脚本来批量构建镜 像和启动容器&#xff0c;快速的部署项目。 使用docker-compose部署主要是编写 docker-compose.yml 脚本。 项目结构 不论是 Dockerfil…

set的所有操作

1.基本概念 2.构造和赋值 3.大小和交换 4.插入和删除 5.查找和统计 6.set和multiset的区别 7.pair对组创建 用p.first和p.second调用前后两个属性。 8.仿函数实现降序排列 自定义数据类型也一样用仿函数&#xff1a;

【ubuntu20.4 常用经验分享】

文章目录 背景&#xff1a;问题解答1、软件替换3、办公4、提供多少价值 总结 背景&#xff1a; 个人AI深度学习&#xff0c;在windows下很不方便&#xff0c;容易各种莫名错误&#xff0c;各种生态也不好 那么linux是一个选择&#xff0c;开始时候时保守安装了双系统&#xff…