【算法设计-分治】递归与尾递归
文章目录
- 1. 阶乘
- 尾递归:递归的进一步优化
 
- 2. 斐波那契数列
- 3. 最大公约数(GCD)
- 4. 上楼梯
- 5. 汉诺塔(1)
- 输出移动过程
- 输出移动步数
 
- 5. 汉诺塔(2)
- 输出移动过程
- 输出移动步数
 
- 6. 杨辉三角形
- 7. 完全二叉树
 
1. 阶乘
输入一个整数 n,输出 n 的阶乘。
传统的递归写法:
#include <cstdio>
using namespace std;long long func (int n){if (n == 0 || n == 1)return 1;elsereturn n * func(n-1);
}int main(){int n;while (scanf("%d", &n) != EOF){long long answer = func(n);printf("%lld\n", answer);}return 0;
}
若 n=5,递归过程如下:
- 调用 func(5)
- 调用 func(5) 后:5 * func(4)
- 调用 func(4) 后:5 * (4 * func(3))
- 调用 func(3) 后:5 * (4 * (3 * func(2)))
- 调用 func(2) 后:5 * (4 * (3 * (2 * func(1))))
- 调用 func(1) 后:5 * (4 * (3 * (2 * 1)))
- 返回到 func(2) :5 * (4 * (3 * 2))
- 返回到 func(3) :5 * (4 * 6)
- 返回到 func(4) :5 * 24
- 返回到 func(5) :120
可以看到,如果采用以上写法,那么要调用到func(1)时才能得到最终的计算式,所以程序需要记住诸如5 * (4 * (3 *…的内容,也即,之前的func(5)到func(2)的帧必须存储。这意味着,当 n 比较大的时候,程序运行时间比较长,以及占用大量的栈帧。
尾递归:递归的进一步优化
如果我们能一边调用,一边计算,那就不用存储中间的函数栈帧,就能大大加快程序的执行效率,因此可采用一种尾递归优化的技术。
如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。采用尾递归的写法,可防止大量的内存浪费。
把以上定义翻译成人话,就可以得出尾递归的特征:返回语句return除了返回函数自身以外,不再执行任何指令或语句。关于尾递归的详细原理,可参见:尾递归原理。
按照以上特征来判断:上面的代码是尾递归吗?看起来像,但其实不是,因为上面的代码可以等价成如下代码:
#include <cstdio>
using namespace std;long long func (int n){if (n == 0 || n == 1)return 1;else{long long tmp = func(n-1); // 先调用函数自身return n * tmp;            // 再进行乘法运算}                              // 递归调用没有出现在函数的末尾,因此不是尾递归
}int main(){int n;while (scanf("%d", &n) != EOF){long long answer = func(n);printf("%lld\n", answer);}return 0;
}
这才是真正的尾递归:
#include <cstdio>
using namespace std;long long func1 (int n, int result){if (n == 0 || n == 1)return result;elsereturn func1(n-1, result*n);   // 计算结果传递给下一次递归调用
}int main(){int n;while (scanf("%d", &n) != EOF){long long answer = func1(n, 1);printf("%lld\n", answer);}return 0;
}
若 n=5,递归过程如下:
- func1(5,1)
- func1(4,5)
- func1(3,20)
- func1(2,60)
- func1(1,120)
可以看到,当进行尾递归优化后,程序不需要存储每一个函数的栈帧。而且还应当注意到,传统的递归是调用到最内层时才得知边界条件,而尾递归是在开始调用函数时就已经得知初值或边界条件,这是一个重要的特征,在下面几个例子中,我希望大家可以好好体会这一点。
再次提醒,在传统的递归中,程序为什么要存储那么多函数栈帧?不就是因为函数调用完递归后还有别的操作嘛(在这里就是存储func(5)到func(2)的帧,因为这些帧都还没运行完)。现在采用尾递归,每一次调用都可以算出结果,而且每次计算结果都会成为下一次调用的参数,更重要的是,当程序调用自身运行完毕后,这个函数已经没有其他执行语句了。那程序还有必要存储当下的函数栈帧吗?没必要了。事实上,程序在每次调用尾递归后,上一次的函数栈帧都会被覆盖。
最后,这里应该无内鬼吧,那就来点知乎段子:
function story() {    
从前有座山,山上有座庙,庙里有个老和尚,一天老和尚对小和尚讲故事:story() 
// 尾递归,进入下一个函数不再需要上一个函数的环境了,得出结果以后直接返回。
}function story() {    
从前有座山,山上有座庙,庙里有个老和尚,一天老和尚对小和尚讲故事:story(),小和尚听了,找了块豆腐撞死了 
// 非尾递归,下一个函数结束以后此函数还有后续,所以必须保存本身的环境以供处理返回值。
}
【注1】在严重依赖递归的函数式编程(比如Lisp)和逻辑式编程(比如Prolog)中,尾递归优化非常重要,可以大大减轻程序运行的负担。
【注2】建议不要太纠结于递归的具体运行过程。相较于传统的手把手教电脑做事的命令式编程,递归更像是声明式编程,给一个函数递推式或几条规则,电脑就可以自己一路算下去了,颇有早期人工智能的感觉。人工智能的本质就是一个黑箱,比如神经网络、深度学习,经过上千万次训练后电脑自己就学会了,你不知道、甚至不必去理解黑箱里是怎么实现的,如果接触过Prolog和Lisp语言,对这一点会有更深的体会。所以,我个人觉得可以将递归视为一个黑箱子,不要太过纠结于递归的具体运行过程。
2. 斐波那契数列
已知斐波那契数列的递推式f(n) = f(n-1) + f(n-2),初值f(1)=1, f(2)=1,输入一个整数 n,输出 f(n) 的值。
传统的递归写法:
#include <cstdio>
using namespace std;long long func (int n){if (n == 0 || n == 1 || n == 2)return 1;elsereturn func(n-1) + func(n-2);
}int main(){int n;while (scanf("%d", &n) != EOF){long long answer = func(n);printf("%lld\n", answer);}return 0;
}
以上是尾递归吗?不是,因为可以等价为如下代码:
#include <cstdio>
using namespace std;long long func (int n){if (n == 1 || n == 2)return 1;else{long long a = func(n-1);long long b = func(n-2);  // 实际上到这一步就已经不是尾递归了,因为还要再使用一次n的值,// 这就要求程序把该函数的栈帧保留下来return a + b;}
}int main(){int n;while (scanf("%d", &n) != EOF){long long answer = func(n);printf("%lld\n", answer);}return 0;
}
现在请进行尾递归优化:
#include <cstdio>
using namespace std;long long func1 (int n, int f1, int f2){  // f1: f(n-2), f2: f(n-1)if (n == 1 || n == 2)return f2;elsereturn func1(n-1, f2, f1+f2);  // f2: f(n-1), f1+f2: f(n)
}int main(){int n;while (scanf("%d", &n) != EOF){long long answer = func1(n, 1, 1); // init: f(1)=1, f(2)=1printf("%lld\n", answer);}return 0;
}
实际上,当 n 非常非常大时,该代码的执行效率也会下降。若想快速算出 f(n),请使用矩阵快速幂算法,在本人的算法专栏里已有涉及。
3. 最大公约数(GCD)
辗转相除法:两个整数的最大公约数等于其中较小的数和两数相除的余数的最大公约数,即gcd(a,b) = gcd(b, a mod b)。
基本思想:分治。
原理:若整数 g 为 a、b 的最大公约数,则有:
a = g x l(1)
b = g x m(2)
a、b 又可以表示为:
a = b x k + r(即a / b = k···r)(3)
把(1)(2)代入到(3):
g x l = g x m x k + r,即r = g x (l - m x k)
注意到r = a mod b,因此a mod b = g x (l - m x k)(4)
联合(2)(4),这样问题变为了求 b 和 a mod b 的最大公约数:
b = g x m(2)
a mod b = g x (l - m x k)(5)
代码实现:
// 辗转相除法求最大公约数(12和18的最大公约数:6) 
int gcd (int a, int b){if (b == 0)return a;elsereturn gcd(b, a % b);
}
很明显,这是一个尾递归。
4. 上楼梯
楼梯有 n 级台阶(n ≤ 20),可以一步走 1 级,也可以一步走 2 级。请计算一共有多少种不同的走法。
分析:1 级台阶只有一种走法,2 级台阶有两种走法(1+1, 2),3 级台阶有三种走法(1+1+1,2+1,1+2)。
从另一个角度思考,我们采用分治思想:
- 到第 n 级台阶有两种方法,一种是从 n-1 级台阶走一步就到,一种是从 n-2 级台阶走两步就到;
- 所以,走到第 n 级台阶的方法总数等于走到 n-1 级台阶的方法总数加上走到 n-2 级台阶的方法总数;
- 那到第 n-1 级台阶的方法总数怎么算呢?首先考虑这两种方法,一种是从 n-2 级台阶走一步就到,一种是从 n-3 级台阶走两步就到;
- 所以,走到第 n-1 级台阶的方法总数等于走到 n-2 级台阶的方法总数加上走到 n-3 级台阶的方法总数;
- 那到第 n-2 级台阶的方法总数怎么算呢?首先考虑这两种方法,一种是从 n-3 级台阶走一步就到,一种是从 n-4 级台阶走两步就到;
- 所以,走到第 n-2 级台阶的方法总数等于走到 n-3 级台阶的方法总数加上走到 n-4 级台阶的方法总数;
- …
因此,该问题实质上是一个变形的斐波那契数列,递推式f(n) = f(n-1) + f(n-2),初值f(1)=1, f(2)=2。
代码如下:
#include <cstdio>
using namespace std;long long func1 (int n, int f1, int f2){  // f1: f(n-2), f2: f(n-1)if (n == 1 || n == 2)return f2;elsereturn func1(n-1, f2, f1+f2);  // f2: f(n-1), f1+f2: f(n)
}int main(){int n;while (scanf("%d", &n) != EOF){long long answer = func1(n, 1, 2); // init: f(1)=1, f(2)=2printf("%lld\n", answer);}return 0;
}
5. 汉诺塔(1)
汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着 64 片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
输出移动过程
现在用 A、B、C 表示这三个柱子,输入盘子总数 n,请输出每一次的移动过程。
输入和输出:
3
第1个盘子移动:A->C
第2个盘子移动:A->B
第1个盘子移动:C->B
第3个盘子移动:A->C
第1个盘子移动:B->A
第2个盘子移动:B->C
第1个盘子移动:A->C
分析:若只有一个盘子,从 A 移到 C;若只有两个盘子,小盘子从 A 移到 B,大盘子从 A 移到 C,小盘子从 B 移到 C。
现在考虑 n (n ≥ 3) 个盘子的情况。我们使用分治递归思想,看看能不能将问题变得更简单一些。几个问题:
- 移动的目的是什么?或者说我们想达成什么目标?我们要想把 n 个盘子全部转移到 C,那么我们可以将底下最大的盘子先转移到 C 柱,因为一旦最大的盘子移了过去,剩下 n-1 个盘子就可以无视这个最大的盘子,进行下一次移动了。
- 这剩余 n-1 个盘子是不是又可以这样:将底下次大的盘子先转移到 C 柱,剩下的 n-2 个盘子就可以无视次大的盘子,又可以进行下一次移动了。
- 怎么达成该目的?可以把 n 个盘子视为两组,一组是最大的盘子,另一组是剩余 n-1 个盘子。从宏观上看,我们可以把这两组视为两个“大盘子”。这样问题就回到了移动两个盘子的情况,我们按两个盘子的方法移动就行了。
- 但实际上这 n-1 个盘子是不能直接移动的,那我们是不是又可以把这 n-1 个盘子视为两个“大盘子”,一组是次大的盘子,另一组是剩余 n-2 个盘子,这样就又回到了移动两个盘子的情况。
因此通过这样的思考,得出将 n 个盘子从 A 移到 C 的步骤或规则,实际上跟两个盘子的移动方法是完全一致的:
- 将 n-1 个盘子从 A 移到 B,实质上就是将 C 当成缓冲区;
- 将第 n 个盘子(也是最大的盘子)从 A 移到 C,实质上就是将 B 当成缓冲区;
- 将 n-1 个盘子从 B 移到 C,实质上就是将 A 当成缓冲区。
将步骤或规则用代码实现如下:
#include <cstdio>
using namespace std;void func (int n, char init, char buffer, char dist){  if (n == 0){return;}else if (n == 1){printf("第%d个盘子移动:%c->%c\n", n, init, dist);return;}else{func(n-1, init, dist, buffer);  printf("第%d个盘子移动:%c->%c\n", n, init, dist);func(n-1, buffer, init, dist);}		 
}int main(){int n;while (scanf("%d", &n) != EOF){func(n, 'A', 'B', 'C'); }return 0;
}
请读者注意,这里是探讨算法的文章,不是探讨编程语言的特性,除了在讨论尾递归时我们不得不关注程序运行的细节以外,其余代码不必过分关注运行细节,请把目光放在如何思考得出步骤和规则这件事上来!
输出移动步数
输入盘子总数 n,请输出所需移动步数。
分析:结合以上步骤可得出递推式。
- 将 n-1 个盘子从 A 移到 B,需要的移动次数设为f(n-1);
- 将第 n 个盘子从 A 移到 C,需要的移动次数为 1;
- 将 n-1 个盘子从 B 移到 C,需要的移动次数依然为f(n-1)。
所以递推式为f(n) = 2f(n-1) + 1。
接下来考虑初值情况,从 A 到 B 移动需 1 次,所以易知f(1) = 1, f(2) = 3,n=2 时正好与递推式算得的结果相吻合。
代码实现如下:
long long func1 (int n){if (n == 0)return 0;else if (n == 1)return 1;elsereturn 2 * func1(n-1) + 1;
}
进行尾递归优化后:
long long func2 (int n, long long result = 1){if (n == 0)return 0;else if (n == 1)return result;elsereturn func2(n-1, 2*result+1);
}
5. 汉诺塔(2)
输出移动过程
问题改变!现在不允许将盘子从 A 移到 C,且不允许从 C 移到 A。输入盘子总数 n,请输出每一次的移动过程。
输入和输出:
2
第1个盘子移动:A->B
第1个盘子移动:B->C
第2个盘子移动:A->B
第1个盘子移动:C->B
第1个盘子移动:B->A
第2个盘子移动:B->C
第1个盘子移动:A->B
第1个盘子移动:B->C
分析:只有一个盘子,从 A 移到 B,从 B 移到 C;只有两个盘子,小盘子从 A 移到 B、从 B 移到 C,大盘子从 A 移到 B,小盘子从 C 移到 B、从 B 移到 A,大盘子从 B 移到 C,小盘子从 A 移到 B、从 B 移到 C。
继续使用分治思想,将 n 个盘子从 A 移到 C 的步骤与两个盘子是类似的:
- 将 n-1 个盘子从 A 移到 B;
- 将 n-1 个盘子从 B 移到 C;
- 将第 n 个盘子(也是最大的盘子)从 A 移到 B;
- 将 n-1 个盘子从 C 移到 B;
- 将 n-1 个盘子从 B 移到 A;
- 将第 n 个盘子(也是最大的盘子)从 B 移到 C;
- 将 n-1 个盘子从 A 移到 B;
- 将 n-1 个盘子从 B 移到 C。
以上步骤等价于:
- 将 n-1 个盘子从 A 移到 C,实质上就是将 B 当成缓冲区;
- 将第 n 个盘子(也是最大的盘子)从 A 移到 B,实质上就是将 C 当成缓冲区;
- 将 n-1 个盘子从 C 移到 A,实质上就是将 B 当成缓冲区;
- 将第 n 个盘子(也是最大的盘子)从 B 移到 C,实质上就是将 A 当成缓冲区;
- 将 n-1 个盘子从 A 移到 C,实质上就是将 B 当成缓冲区。
代码如下:
#include <cstdio>
using namespace std;void func (int n, char init, char buffer, char dist){  if (n == 0){return;}else if (n == 1){printf("第%d个盘子移动:%c->%c\n", n, init, buffer);printf("第%d个盘子移动:%c->%c\n", n, buffer, dist);return;}else{func(n-1, init, buffer, dist);printf("第%d个盘子移动:%c->%c\n", n, init, buffer);func(n-1, dist, buffer, init);printf("第%d个盘子移动:%c->%c\n", n, buffer, dist);func(n-1, init, buffer, dist);}		 
}int main(){int n;while (scanf("%d", &n) != EOF){func(n, 'A', 'B', 'C'); }return 0;
}
输出移动步数
输入盘子总数 n,请输出所需移动步数。
分析:结合以上步骤可得出递推式。
- 将 n-1 个盘子从 A 移到 C,需要的移动次数设为f(n-1);
- 将第 n 个盘子从 A 移到 B,需要的移动次数为 1;
- 将 n-1 个盘子从 C 移到 A,需要的移动次数依然为f(n-1);
- 将第 n 个盘子从 B 移到 C,需要的移动次数为 1;
- 将 n-1 个盘子从 A 移到 C,需要的移动次数依然为f(n-1)。
所以递推式为f(n) = 3f(n-1) + 2。
接下来考虑初值情况,从 A 到 C 移动需 2 次,所以易知f(1) = 2, f(2) = 8,n=2 时正好与递推式算得的结果相吻合。
有读者可能会觉得奇怪,为什么把“从 A 到 C”的移动次数设为f(n-1),而不把“从 A 到 B”或“从 B 到 C”的移动次数设为f(n-1),其实这样假设也没有什么区别,只是递推式不一样,但得出的结果肯定是一样的,我们来看看吧!
- 将 n-1 个盘子从 A 移到 B,需要的移动次数设为f(n-1);
- 将 n-1 个盘子从 B 移到 C,需要的移动次数依然为f(n-1);
- 将第 n 个盘子从 A 移到 B,需要的移动次数为 1;
- 将 n-1 个盘子从 C 移到 B,需要的移动次数依然为f(n-1);
- 将 n-1 个盘子从 B 移到 A,需要的移动次数依然为f(n-1);
- 将第 n 个盘子从 B 移到 C,需要的移动次数为 1;
- 将 n-1 个盘子从 A 移到 B,需要的移动次数依然为f(n-1);
- 将 n-1 个盘子从 B 移到 C,需要的移动次数依然为f(n-1)。
所以递推式为f(n) = 6f(n-1) + 2。
接下来考虑初值情况,从 A 到 B 移动仅需 1 次,所以易知f(1) = 1, f(2) = 8,n=2 时与递推式算得的结果也相吻合。
最后使用代码实现如下:
代码实现如下:
long long func1 (int n){if (n == 0)return 0;else if (n == 1)return 2;elsereturn 3 * func1(n-1) + 2;
}
进行尾递归优化后:
long long func2 (int n, long long result = 2){if (n == 0)return 0;else if (n == 1)return result;elsereturn func2(n-1, 3*result+2);
}
6. 杨辉三角形
输入行数 n,输出 n 行杨辉三角形。
输入和输出:
811   11   2   11   3   3   11   4   6   4   11   5  10  10   5   11   6  15  20  15   6   11   7  21  35  35  21   7   1
1011   11   2   11   3   3   11   4   6   4   11   5  10  10   5   11   6  15  20  15   6   11   7  21  35  35  21   7   11   8  28  56  70  56  28   8   11   9  36  84 126 126  84  36   9   1
分析:注意到第 n 行第 1 个元素和第 n 个元素均为 1,其余元素都是由递推式A[i][j] = A[i-1][j-1] + A[i-1][j]得来。
递归代码:
#include <cstdio>
using namespace std;int YH (int i, int j){if (j == 1 || i == j)return 1;else return YH(i-1, j-1) + YH(i-1, j);
}int main(){int n;while (scanf("%d", &n) != EOF){for (int i = 1; i <= n; i++){for (int j = 1; j <= i; j++){printf("%3d ", YH(i, j));}printf("\n");}}return 0;
}
7. 完全二叉树

【描述】如上所示,由正整数1,2,3……组成了一颗特殊二叉树。我们已知这个二叉树的最后一个结点是n。现在的问题是,结点m所在的子树中一共包括多少个结点。
比如,n = 12,m = 3那么上图中的结点13,14,15以及后面的结点都是不存在的,结点m所在子树中包括的结点有3,6,7,12,因此结点m的所在子树中共有4个结点。
【输入描述】输入数据包括多行,每行给出一组测试数据,包括两个整数m,n (1 <= m <= n <= 1000000000)。
【输出描述】对于每一组测试数据,输出一行,该行包含一个整数,给出结点m所在子树中包括的结点的数目。
【输入示例】
3 12
0 0
【输出示例】
4
【分析】求结点 m 所在子树总结点数,就是在求结点 m 的左子树总结点数和右子树总结点数,还要加上自身结点。
注意,对于一棵完全二叉树,结点 m 的左子树结点编号为 2m,结点 m 的右子树结点编号为 2m+1。
边界条件:当 m > n 时,结点 m 所在子树总结点数为 0。
【题解】
#include <cstdio>
using namespace std;// m:结点编号,n:总结点数 
int func (int m, int n){if (m > n)return 0;else return 1 + func(2*m, n) + func(2*m+1, n);
}int main(){int m, n;while (scanf("%d %d", &m, &n) != EOF){printf("%d\n", func(m, n));}return 0;
}
相关文章:
 
【算法设计-分治】递归与尾递归
文章目录1. 阶乘尾递归:递归的进一步优化2. 斐波那契数列3. 最大公约数(GCD)4. 上楼梯5. 汉诺塔(1)输出移动过程输出移动步数5. 汉诺塔(2)输出移动过程输出移动步数6. 杨辉三角形7. 完全二叉树1…...
 
HTML 编辑器
文章目录 HTML 编辑器HTML 编辑器推荐编辑器下载网站HBuilder步骤 1: 新建 HTML 文件步骤 2: 另存为 HTML 文件步骤 3: 在浏览器中运行这个 HTML 文件HTML 编辑器 HTML 编辑器推荐 可以使用专业的 HTML 编辑器来编辑 HTML,我为大家推荐几款常用的编辑器: Notepad++:Windows…...
 
css盒模型详解
一、引言 盒模型是网页开发中的一个基本概念,它描述了网页元素的外观和大小。盒模型由内容区域、内边距、边框和外边距四个部分组成,这些部分的大小和位置都可以通过CSS进行控制。在本文中,我们将介绍盒模型的概念和作用,并提出本…...
 
函数模板(template关键字的应用)
注释:本文主要介绍了函数模板的由来以及用法,还有关键字template。 我们感到时间的延续像一条我们无法逆行的小溪。 ——柏格森 文章目录一、语言的定式二、函数模板2.1 函数模板格式2.2 模板函数的实例化2.2.1隐式实例化/显式实例化2.3 模板参数的匹配…...
 
嵌入式学习笔记——使用寄存器编程操作GPIO
使用寄存器编程操作GPIO前言GPIO相关的寄存器GPIO 端口模式寄存器 (GPIOx_MODER) (x A..I)位操作GPIO 端口输出类型寄存器 (GPIOx_OTYPER) (x A..I)GPIO 端口输出速度寄存器 (GPIOx_OSPEEDR) (x A..I/)GPIO 端口上拉/下拉寄存器 (GPIOx_PUPDR) (x A..I/)GPIO 端口输入数据寄…...
 
图像的读取与保存
图像是由一个个像素点组成,像素点就是颜色点,而颜色最简单的方式就是用RGB或RGBA表示图像保存图像将像素信息按照 一定格式,一定顺序(即编码) 存在硬盘上的 二进制文件 中保存图像需要以下必要信息:1. 文件…...
【蓝桥杯集训·每日一题】AcWing 4074. 铁路与公路
文章目录一、题目1、原题链接2、题目描述二、解题报告1、思路分析2、时间复杂度3、代码详解三、知识风暴Floyd 算法Spfa 算法一、题目 1、原题链接 4074. 铁路与公路 2、题目描述 某国家有 n 个城市(编号 1∼n)和 m 条双向铁路。 每条铁路连接两个不同的…...
 
网络:TCP与UDP相关知识(详细)
目录:1、UDP 和 TCP 的特点与区别2、UDP 、TCP 首部格式3、TCP 的三次握手和四次挥手4、TCP 的三次握手(为什么三次?)5、TCP 的四次挥手(为什么四次?)6、TCP 长连接和短连接的区别7、TCP粘包、拆…...
 
不好!有敌情,遭到XSS攻击【网络安全篇】
XSS:当一个目标的站点,被我们用户去访问,在渲染HTMl的过程中,出现了没有预期到的脚本指令,然后就会执行攻击者用各种方法注入并执行的恶意脚本,这个时候就会产生XSS。 涉及方: 用户࿰…...
 
Mysql中Explain详解及索引的最佳实践
Mysql中Explain详解及索引的最佳实践1.Explan工具的介绍1.1 Explan 分析示例1.2 Explain中的列1.2.1 id1.2.2 select_type1.2.3 table1.2.4 partitions1.2.5 type1.2.6 possible_keys1.2.7 key1.2.8 key_len1.2.9 ref1.2.10 rows1.2.11 filtered1.2.12 Extra1.Explan工具的介绍…...
JavaScript 内的 this 指向
在 javascript 语言中, 有一个奇奇怪怪的 “关键字” 叫做 this为什么说它是 奇奇怪怪 呢, 是因为你写出 100 个 this, 可能有 100 个解释, 完全不挨边,但是, 在你的学习过程中, 搞清楚了 this 这个玩意, 那么会对你的开发生涯有很大帮助的,接下来咱们就…...
 
Java多种方法实现等待所有子线程完成再继续执行
简介 在现实世界中,我们常常需要等待其它任务完成,才能继续执行下一步。Java实现等待子线程完成再继续执行的方式很多。我们来一一查看一下。 Thread的join方法 该方法是Thread提供的方法,调用join()时,会阻塞主线程࿰…...
 
制造企业数字化工厂建设步骤的建议
随着工业4.0、中国制造2025的深度推进,越来越多的制造企业开始迈入智能制造的领域,那数字工厂要从何入手呢? 数字工厂规划的核心,也正是信息域和物理域这两个维度,那就从这两个维度来进行分析,看如何进行数…...
 
网上鲜花交易平台,可运行
文章目录项目介绍一、项目功能介绍1、用户模块主要功能包括:2、商家模块主要功能包括:3、管理员模块主要功能包括:二、部分页面展示1、用户模块部分功能页面展示2、商家模块部分功能页面展示3、管理员模块部分功能页面展示三、部分源码四、底…...
【实战】用 Custom Hook + TS泛型实现 useArray
文章目录一、题目二、答案(非标准)三、关键知识点1.Custom Hook关键点案例useMountuseDebounce2.TS 泛型关键点一、题目 完善自定义 Hook —— useArray ,使其能够完成 tryUseArray 组件中测试的功能: 入参:数组返回…...
 
【LeetCode】剑指 Offer(18)
目录 题目:剑指 Offer 35. 复杂链表的复制 - 力扣(Leetcode) 题目的接口: 解题思路: 代码: 过啦!!! 写在最后: 题目:剑指 Offer 35. 复杂链…...
Kubernetes节点运行时从Docker切换到Containerd
由于k8s将于1.24版本弃用dockershim,所以最近在升级前把本地的k8s切换到了Containerd运行时,目前我的k8s版本是1.22.5,一个master,二个Node的配置,以下做为一个操作记录日志整理,其它可以参考官网文档。 在…...
 
【编程基础之Python】12、Python中的语句
【编程基础之Python】12、Python中的语句Python中的语句赋值语句条件语句循环语句for循环while循环continue语句break语句continue与break的区别函数语句pass语句异常处理语句结论Python中的语句 Python是一种高级编程语言,具有简单易学的语法,适用于各…...
 
android h5餐饮管理系统myeclipse开发mysql数据库编程服务端java计算机程序设计
一、源码特点 android h5餐饮管理系统是一套完善的WEBandroid设计系统,对理解JSP java,安卓app编程开发语言有帮助(系统采用web服务端APP端 综合模式进行设计开发),系统具有完整的源代码和数据库,系统主要…...
 
容易混淆的嵌入式(Embedded)术语
因为做嵌入式开发工作虽然跳不出电子行业,但还是能接触到跨度较大的不同行当,身处不同的圈子。诸如医疗,银行,车载,工业;亦或者手机,PC,专用芯片;甚至可能横跨系统开发、…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
 
C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
 
QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
 
自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
 
企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...
 
招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...
 
Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...
