当前位置: 首页 > news >正文

递归为什么这么难?一篇文章带你了解递归

递归为什么这么难?一篇文章带你了解递归

美国计算机科学家——彼得·多伊奇(L Peter Deutsch)在《程序员修炼之道》(The Pragmatic Programmer)一书中提到“To Iterate is Human, to Recurse, Divine”——我理解的这句话为:人理解迭代,神理解递归。

毋庸置疑递归的代码是非常简洁的,但是想要理解递归也是非常不容易的,本文介绍了递归的常见场景与例题和递归的基本用法与思想,希望能帮助新人理解递归的思想,相信看完这篇文章再动手敲一下代码,一定对递归有更加深入的了解。

文章列举了一些递归的经典操作包括:斐波纳契数列、汉诺塔、冒泡排序的递归写法。以及力扣的一些链表的练习题使用递归去完成——206. 反转链表 - 力扣(LeetCode)、203. 移除链表元素、19. 删除链表的倒数第 N 个结点、83. 删除排序链表中的重复元素、82. 删除排序链表中的重复元素 II、21. 合并两个有序链表、23. 合并 K 个升序链表。

首先让我们思考:

  1. 什么是递归?
  2. 递归的思想是什么?
  3. 怎么使用递归?
  4. 使用递归应该注意什么问题?
  5. 递归的时间复杂度应该怎么计算

一、什么是递归?

在计算机中,递归(Recursion)是指在函数的定义中使用函数自身的方法。实际上,递归,顾名思义,其包含了两个意思:

二、递归的思想是什么?

既然叫做递归,那么肯定分为“递”与“归”。

image-20231027174229279

递归的基本思想就是将大规模问题转为小规模问题,问题一直被不断缩小,一直递归到符合结束条件为止。

因为每次递归都是相同的函数,所以很重要的一件事是寻找到递归的条件,找到应该如何解决大问题和小问题的同一个方法。

三、怎么使用递归

我们在了解了递归的基本概念以后就需要思考递归应该怎么用?

首先需要明确递归的三要素:

  • 明确递归终止条件;

    递归既然有去有回,那么必须有一个明确的结束条件。当到达这个条件递归就会终止。

  • 给出递归终止时的处理办法;

    当递归结束时,递归函数每一次返回值都需要有处理的方法,我们需要在这里给出问题的解决方法。

  • 提取重复的逻辑,缩小问题规模。找出递归关系式

    寻找一个递归的关系,如何将这个问题不停分解为小问题。

注意:判断“递”还是“归”

判断是在“递”的过程中解决问题还是在“归”的过程中解决问题

四、使用递归应该注意什么?

首先我们要知道递归有两种模型。

1、在“递”的的过程中解决问题。

{1、递归结束条件2、问题的解决方法3、递归的等价关系,缩小规模的方法。
}

2、在“归”的的过程中解决问题。

{1、递归结束条件2、递归的等价关系,缩小规模的方法。3、问题的解决方法
}

这两种模型都是属于单路递归的模型,既然有单路递归那么肯定会有多路递归。

在之后的经典场景分析会介绍到冒泡排序的递归写法,就是单路递归。汉诺塔、斐波纳契数列就是属于多路递归。

五、递归的时间复杂度怎么计算?

若有递归式
T ( n ) = a T ( n b ) + f ( n ) T(n) = aT(\frac{n}{b}) + f(n) T(n)=aT(bn)+f(n)
其中

  • T ( n ) T(n) T(n) 是问题的运行时间, n n n 是数据规模
  • a a a 是子问题个数
  • T ( n b ) T(\frac{n}{b}) T(bn) 是子问题运行时间,每个子问题被拆成原问题数据规模的 n b \frac{n}{b} bn
  • $ f(n)$ 是除递归外执行的计算

x = log ⁡ b a x = \log_{b}{a} x=logba,即 x = log ⁡ 子问题缩小倍数 子问题个数 x = \log_{子问题缩小倍数}{子问题个数} x=log子问题缩小倍数子问题个数

那么
T ( n ) = { Θ ( n x ) f ( n ) = O ( n c ) 并且 c < x Θ ( n x log ⁡ n ) f ( n ) = Θ ( n x ) Θ ( n c ) f ( n ) = Ω ( n c ) 并且 c > x T(n) = \begin{cases} \Theta(n^x) & f(n) = O(n^c) 并且 c \lt x\\ \Theta(n^x\log{n}) & f(n) = \Theta(n^x)\\ \Theta(n^c) & f(n) = \Omega(n^c) 并且 c \gt x \end{cases} T(n)= Θ(nx)Θ(nxlogn)Θ(nc)f(n)=O(nc)并且c<xf(n)=Θ(nx)f(n)=Ω(nc)并且c>x

例1

T ( n ) = 16 T ( n 4 ) + n 2 T(n) = 16T(\frac{n}{4}) + n^2 T(n)=16T(4n)+n2

  • a = 16 , b = 4 , x = 2 , c = 2 a=16, b=4, x=2, c=2 a=16,b=4,x=2,c=2
  • 此时 x = 2 = c x=2 = c x=2=c,时间复杂度 Θ ( n 2 log ⁡ n ) \Theta(n^2 \log{n}) Θ(n2logn)

例2 二分查找递归

int f(int[] a, int target, int i, int j) {if (i > j) {return -1;}int m = (i + j) / 1;if (target < a[m]) {return f(a, target, i, m - 1);} else if (a[m] < target) {return f(a, target, m + 1, j);} else {return m;}
}
  • 子问题个数 a = 1 a = 1 a=1
  • 子问题数据规模缩小倍数 b = 2 b = 2 b=2
  • 除递归外执行的计算是常数级 c = 0 c=0 c=0

T ( n ) = T ( n 2 ) + n 0 T(n) = T(\frac{n}{2}) + n^0 T(n)=T(2n)+n0

  • 此时 x = 0 = c x=0 = c x=0=c,时间复杂度 Θ ( log ⁡ n ) \Theta(\log{n}) Θ(logn)

六、递归的实战

遇见递归请不要害怕,只是因为你做题少了而已。做完这些题一定对递归的感悟会更深刻。

1、基本运算中的递归

a、冒泡排序的递归写法
public class bubble {public static void main(String[] args) {int a[] = {1, 5, 7, 2, 0, 3, 6};Bubble(a, a.length - 1);for (int i : a) {System.out.println(i);}}public static void Bubble(int[] a, int len) {//1、递归结束条件if (len == 0) return;//2、处理方法for (int i = 0; i < len; i++) {if (a[i] > a[i + 1]) {int temp = a[i];a[i] = a[i + 1];a[i + 1] = temp;}}//3、递归关系,缩小问题规模Bubble(a, len - 1);}
}

通过这个简单算法,可以看出这个就属于在递的过程中解决问题的模型。

我们逐轮分析:

初始数据:{1, 5, 7, 2, 0, 3, 6}

第一轮:找到最大的数进行下沉,得到:{1, 5, 2, 0, 3, 6,7},将7移动到最后一位,那么第二轮就不需要对7进行排序。

第二轮:找到最大的数进行下沉,因为上一轮已经找到7,那么这一轮只需要找7前面的数就行了,得到:{1, 5, 2, 0, 3, 6,7},那么第三轮就不需要对6进行排序。

b、汉诺塔的实现

image-20231027182318701

通过这个移动过程我们很容易找到一个移动规律,具体就不多说了,代码如下:

import java.util.LinkedList;public class Hanoi {static LinkedList<Integer> a = new LinkedList<>();static LinkedList<Integer> b = new LinkedList<>();static LinkedList<Integer> c = new LinkedList<>();public static void main(String[] args) {long startTime = System.nanoTime();init(3);long ebdTime  = System.nanoTime();print();}private static void print() {System.out.println("**************************");System.out.println(a);System.out.println(b);System.out.println(c);}/*** 汉诺塔的递归* @param n 塔的层数* @param a 原* @param b 借* @param c 目标*/public static void towerOfHanoi(int n,LinkedList<Integer> a,LinkedList<Integer> b,LinkedList<Integer> c){//结束条件if (n == 0){return;}//等价关系towerOfHanoi(n-1,a,c,b);//处理方法c.add(a.removeLast());  //将a移动到c//等价关系towerOfHanoi(n-1,b,a,c);}public static void init(int n){for (int i = n; i>=1; i--){a.add(i);}System.out.println(a);towerOfHanoi(n,a,b,c);}
}
c、斐波纳契数列

如果不知道斐波纳契的具体请看这篇文章——用C语言写爬楼梯(斐波那契数列的应用,迭代与递归)爬楼梯问题超详细,看完这一篇就够了。,就不多赘述了,这里主要介绍一下在递归中的减枝操作。

未减枝前的递归分解过程:

image-20221207092417933

可以看到(颜色相同的是重复的):

  • f ( 3 ) f(3) f(3) 重复了 2 次
  • f ( 2 ) f(2) f(2) 重复了 3 次
  • f ( 1 ) f(1) f(1) 重复了 5 次
  • f ( 0 ) f(0) f(0) 重复了 3 次

随着 n n n 的增大,重复次数非常可观,如何优化呢?

Memoization 记忆法(也称备忘录)是一种优化技术,通过存储函数调用结果(通常比较昂贵),当再次出现相同的输入(子问题)时,就能实现加速效果,改进后的代码

public class Fibonacci {public static void main(String[] args) {Scanner cin = new Scanner(System.in);for (int i = 1; i < 30; i++) {System.out.println(pruning(i));}}力kou//进行剪枝public static int pruning(int n){int[] cache = new int[n+1];Arrays.fill(cache,-1);cache[0] = 0;cache[1] = 1;return f(n,cache);}public static int f(int n,int[] cache) {if (cache[n] != -1){return cache[n];}cache[n] = f(n - 1,cache) + f(n - 2,cache);return cache[n];}
}

在这儿我们使用了一个数组去保存了重复的运算结果。

image-20221213173225807

2、链表操作的递归

在使用递归进行链表的操作时,希望大家牢记这句话:

在链表的递归过程中以单个结点操作的思想递归

a、206. 反转链表 - 力扣(LeetCode)
    /*** 递归实现反转链表,在同一个链表上反转** @param head 待反转链表* @return 反转后的新头节点*/public ListNode reverseList(ListNode head) {//递归结束条件if (head == null || head.next == null) {return head;}ListNode list = reverseList2(head.next);//操作链表进行反转head.next.next = head;head.next = null;System.out.println(list);return list;}
b、203. 移除链表元素
    /*** 使用递归的方法进行删除指定数据** @param head 待处理链表* @param val 待删除值* @return 处理完成的链表*/public  ListNode removeElements(ListNode head, int val) {if(head == null){return null;}if(head.val == val){//如果是删除该节点那么就相当于返回下一个节点的递归结果,// 此时上一个节点就会避开当前节点,而去链接下一个节点return removeElements1(head.next,val);}else {//当前节点链接到后面的链表head.next = removeElements1(head.next,val);return head;}}
c、19. 删除链表的倒数第 N 个结点
    /*** 使用递归的方法* @param head* @param n* @return*/public  int recursion(ListNode head, int n){if(head == null){return 0;}int nth = recursion(head.next,n);//下一个节点的位置if (nth == n){//判断出下一个节点的的位置刚好是需要被删除的节点head.next = head.next.next;}return nth+1;   //当前节点的位置}
d、83. 删除排序链表中的重复元素
    /*** 使用递归的方法** @param head 待处理链表* @return 处理完成的链表*/public static ListNode deleteDuplicates1(ListNode head) {if (head == null || head.next == null) {return head;}if (head.val == head.next.val) {return deleteDuplicates1(head.next);} else {head.next = deleteDuplicates1(head.next);return head;}}
e、82. 删除排序链表中的重复元素 II
    /*** 使用递归的方法** @param head 待处理链表* @return 处理完成的链表*/public  ListNode deleteDuplicates(ListNode head) {if (head == null || head.next == null) {return head;}if (head.val == head.next.val) {//如果一直相同则不停移动指针,一直到找到不相同的节点为止ListNode t = head.next.next;while (t != null && t.val == head.val) {t = t.next;}return deleteDuplicates(t);} else {head.next = deleteDuplicates(head.next);return head;}}
f、21. 合并两个有序链表
    /*** 使用递归的方法进行合并链表** @param list1* @param list2* @return 返回添加以后的链表*/public  ListNode mergeTwoLists1(ListNode list1, ListNode list2) {if(list1 == null){return list2;}else if (list2 == null){return list1;}if(list1.val < list2.val){list1.next = mergeTwoLists1(list1.next,list2);return list1;}else {list2.next = mergeTwoLists1(list1,list2.next);return list2;}}
g、23. 合并 K 个升序链表
    /*** 合并两个有序链表** @param list1* @param list2* @return*/public static ListNode mergeTwoLists(ListNode list1, ListNode list2) {if (list1 == null) {return list2;} else if (list2 == null) {return list1;}if (list1.val < list2.val) {list1.next = mergeTwoLists(list1.next, list2);return list1;} else {list2.next = mergeTwoLists(list1, list2.next);return list2;}}/*** 合并K个有序链表** @param lists* @return*/public  ListNode mergeKLists(ListNode[] lists) {if (lists.length == 0) {return null;}return split(lists, 0, lists.length-1);}/*** 进行拆分利用分治的思想(类似于快排)** @param listNodes* @param i 左值* @param j 右值* @return 返回两个链表合并的结果*/public static ListNode split(ListNode[] listNodes, int i, int j) {if(i == j){return listNodes[i];}int t = (i + j) / 2;    //中间值ListNode left = split(listNodes,i,t);ListNode right = split(listNodes,t+1,j);return mergeTwoLists(left,right);}

如果你看到这里,并且上手敲了这几道题,我相信你对于递归一定有自己的理解了。递归今天就暂时学到这吧,“To Iterate is Human, to Recurse, Divine”,你距离God又近了一步。

img-1

七、最后的最后——力扣 2698. 求一个整数的惩罚数

自己动手练练吧!2698. 求一个整数的惩罚数

image-20231027185302409

这个题比较难,多动手画一下递归过程。

    public  int punishmentNumber(int n) {int sum = 0;for (int i = 1; i <= n; i++) {if (check(i * i, i)) {sum += i * i;}}return sum;}public  boolean check(int n, int i) {if (n == i) {return true;}int k = 10;/***   判断数据是否可以进行拆分,可以拆分的条件为:数字应该大于10并且拆分后的尾数应该小于基准数*   例如:121与11,可以拆分为1和21,此时n>k符合条件,*   但是n%k = 21,大于11,不可能出现这样的情况符合条件*/while (n >= k && n % k <= i) {/*将拆分后的数据进行比较,例如121拆分为12与1此时n/10 = 12,n%k = 1。得到i-(n%k) = 11判断出12 + 1 != i*/if (check(n / k, i - (n % k))) {return true;}//依次从个位,百位....开始拆分。//例如121,第一次拆分为1,21;第二次为12,1k *= 10;}return false;}

相关文章:

递归为什么这么难?一篇文章带你了解递归

递归为什么这么难&#xff1f;一篇文章带你了解递归 美国计算机科学家——彼得多伊奇(L Peter Deutsch)在《程序员修炼之道》(The Pragmatic Programmer)一书中提到“To Iterate is Human, to Recurse, Divine”——我理解的这句话为&#xff1a;人理解迭代&#xff0c;神理解…...

X86(32位)汇编指令与机器码转换原理

X86&#xff08;32位&#xff09;汇编指令与机器码转换原理 1 32位寻址形式下的ModR/M字节2 汇编指令转机器码2.1 mov ecx,[eaxebx*2]2.1.1 查Opcode和ModR/M2.1.2 查SIB 2.2 mov ecx,[eaxebx*210h]2.3 mov ecx,[eaxebx*200000100h] 本文属于《 X86指令基础系列教程》之一&…...

ES 全字段模糊检索时分词方式对检索结果的影响

文章目录 背景创建索引指定 _all 分词为空格创建索引插入索引数据全字段的模糊检索 创建索引指定 _all 分词为 keyword索引创建插入数据模糊检索 创建索引不配置 _all不同分词的结果启示录 背景 2018年参与使用 ES 和 Kafka 项目的开发&#xff0c;当时主要是做前端开发&#…...

基于Python Django 的微博舆论、微博情感分析可视化系统(V2.0)

文章目录 1 简介2 意义3 技术栈Django 4 效果图微博首页情感分析关键词分析热门评论舆情预测 5 推荐阅读 1 简介 基于Python的微博舆论分析&#xff0c;微博情感分析可视化系统&#xff0c;项目后端分爬虫模块、数据分析模块、数据存储模块、业务逻辑模块组成。 Python基于微博…...

python读取Excel到mysql

常见问题&#xff1a; 1.数据库密码有特殊字符 使用urllib.parse.quote_plus 编译密码 mysql_engine create_engine((f"mysqlpymysql://root:%s10.0.0.2:3306/mydb")%urllib.parse.quote_plus("passaaaa")) 2.设置字段类型 设置特定类型&#xff0c;和指…...

C++八股文面经

1.介绍一下你对面向对象的理解&#xff0c; 面向对象编程&#xff08;Object-Oriented Programming&#xff0c;简称OOP&#xff09;是一种编程范式&#xff0c;它将数据和操作数据的方法组合成一个对象&#xff0c;以此来描述现实世界中的事物和概念。在面向对象编程中&#…...

【Linux】静态库和共享库一分钟快速上手

Linux 前言对比创建静态库动态库 前言 程序库&#xff0c;对于程序原来说是非常重要的。但不少人对其不太了解&#xff0c;接下来一起学习其中的奥秘吧&#xff01; 简单来说&#xff0c;程序库可以分为静态库和共享库。它们包含了数据和执行代码的文件。其不能单独执行&#…...

C++继承总结(下)——菱形继承

一.什么是菱形继承 菱形继承是多继承的一种特殊情况&#xff0c;一个类有多个父类&#xff0c;这些父类又有相同的父类或者祖先类&#xff0c;那么该类就会有多份重复的成员&#xff0c;从而造成调用二义性和数据冗余。 class Person {public:Person(){cout << "P…...

CCF CCSP2023参赛记 + 算法题题解

大家好啊&#xff0c;时隔多年&#xff0c;作为大四老年人&#xff0c;再次来到这个地方记录算法竞赛相关&#xff0c;可能也是最后一次参加这种算法赛事了&#xff0c;我觉得还是很有纪念意义的。虽然我高中搞OI被强基背刺&#xff0c;以至于到了大学有点躲着竞赛&#xff0c;…...

buuctf_练[GYCTF2020]FlaskApp

[GYCTF2020]FlaskApp 文章目录 [GYCTF2020]FlaskApp常用绕过方法掌握知识解题思路解题一 -- 计算pin码解题二 -- 拼接绕过 执行命令 关键paylaod 常用绕过方法 ssti详解与例题以及绕过payload大全_ssti绕过空格_HoAd’s blog的博客-CSDN博客 CTF 对SSTI的一些总结 - FreeBuf网…...

针对element-plus,跳转jump(快速翻页)

待补充 const goToPage () > {const inputElement document.querySelector(.el-pagination .el-input__inner);console.log(inputElement, inputElement); } 打印之后可以看到分页跳转的数字输入框&#xff0c;是有进行处理的&#xff0c;max"102",是我自己的…...

【软件安装】Windows系统中使用miniserve搭建一个文件服务器

这篇文章&#xff0c;主要介绍如何在Windows系统中使用miniserve搭建一个文件服务器。 目录 一、搭建文件服务器 1.1、下载miniserve 1.2、启动miniserve服务 1.3、指定根目录 1.4、开启访问日志 1.5、指定启动端口 1.6、设置用户认证 1.7、设置界面主题 &#xff08;…...

iOS .a类型静态库使用终端进行拆解和合并生成

项目中会用到许多第三方的.a类型的静态库&#xff0c;有时候会有一些静态库回包含相同文件而产生冲突&#xff0c;我们就需要对这个库进行去重的一个操作。一般有哪些文件冲突了&#xff0c;xcode报错都会有详细的提示。我们可以将这两个库合并&#xff0c;也可以其中一方中的文…...

react-组件间的通讯

一、父传子 父组件在使用子组件时&#xff0c;提供要传递的数据子组件通过props接收数据 class Parent extends React.Component {render() {return (<div><div>我是父组件</div><Child name"张" age{16} /></div>)} }const Child …...

【广州华锐互动】VR公司工厂消防逃生演练带来沉浸式的互动体验

在工业生产过程中&#xff0c;安全问题始终是我们不能忽视的重要环节。特别是火灾事故&#xff0c;不仅会造成重大的经济损失&#xff0c;更会威胁到员工的生命安全。传统的消防安全训练方法&#xff0c;如讲座、实地演练等&#xff0c;虽然具有一定的效果&#xff0c;但是无法…...

可观察性支柱:探索日志、指标和跟踪

通过检查系统输出来测量系统内部状态的能力称为可观察性。当可以仅使用输出信息&#xff08;即传感器数据&#xff09;来估计当前状态时&#xff0c;系统就变得“可观察”。您可以使用来自 Observability 的数据来识别和解决问题、优化性能并提高安全性。 在接下来的几节中&am…...

nginx浏览器缓存和上流缓存expires指令_nginx配置HTTPS

1.nginx控制浏览器缓存是针对于静态资源[js,css,图片等] 1.1 expires指令 location /static {alias/home/imooc;#设置浏览器缓存10s过期expires 10s;#设置浏览器缓存时间晚上22:30分过期expires @22h30m;#设置浏览器缓存1小时候过期expires -1h;#设置浏览器不缓存expires …...

硬件安全与机器学习的结合

文章目录 1. A HT Detection and Diagnosis Method for Gate-level Netlists based on Machine Learning摘要Introduction 2. 基于多维结构特征的硬件木马检测技术摘要Instruction 3. A Hardware Trojan Detection and Diagnosis Method for Gate-Level Netlists Based on Diff…...

腾讯云国际-如何使用对象存储COS在 CKafka 控制台创建数据异步拉取任务?腾讯云代充

操作场景 Datahub 支持接入各种数据源产生的不同类型的数据&#xff0c;统一管理&#xff0c;再分发给下游的离线/在线处理平台&#xff0c;构建清晰的数据通道。 本文以 COS 数据为例介绍如何在 CKafka 控制台创建数据异步拉取任务&#xff0c;并对任务进行修改配置&#xf…...

内存马概念

内存马概念 文章目录 内存马概念木马演变内存使用条件内存缺点JAVA Web三大组件Listener:监听器servelet请求流程内存马分类内存演示内存马植入方式案例shiro反序列化漏洞植入内存马 木马演变 内存使用条件 1. 禁止外联 2. 文件监控、查杀 3. spring Boot&#xff0c;不支持js…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

ElasticSearch搜索引擎之倒排索引及其底层算法

文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

C++.OpenGL (10/64)基础光照(Basic Lighting)

基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存

文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难&#xff0c;相信大家会学的很愉快&#xff0c;当然对于有后端基础的朋友来说&#xff0c;本期内容更加容易了解&#xff0c;当然没有基础的也别担心&#xff0c;本期内容会详细解释有关内容 本期用到的软件&#xff1a;yakit&#xff08;因为经过之前好多期…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...