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

【算法设计-分治】递归与尾递归

文章目录

    • 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. 完全二叉树

image

【描述】如上所示,由正整数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. 阶乘尾递归&#xff1a;递归的进一步优化2. 斐波那契数列3. 最大公约数&#xff08;GCD&#xff09;4. 上楼梯5. 汉诺塔&#xff08;1&#xff09;输出移动过程输出移动步数5. 汉诺塔&#xff08;2&#xff09;输出移动过程输出移动步数6. 杨辉三角形7. 完全二叉树1…...

HTML 编辑器

文章目录 HTML 编辑器HTML 编辑器推荐编辑器下载网站HBuilder步骤 1: 新建 HTML 文件步骤 2: 另存为 HTML 文件步骤 3: 在浏览器中运行这个 HTML 文件HTML 编辑器 HTML 编辑器推荐 可以使用专业的 HTML 编辑器来编辑 HTML,我为大家推荐几款常用的编辑器: Notepad++:Windows…...

css盒模型详解

一、引言 盒模型是网页开发中的一个基本概念&#xff0c;它描述了网页元素的外观和大小。盒模型由内容区域、内边距、边框和外边距四个部分组成&#xff0c;这些部分的大小和位置都可以通过CSS进行控制。在本文中&#xff0c;我们将介绍盒模型的概念和作用&#xff0c;并提出本…...

函数模板(template关键字的应用)

注释&#xff1a;本文主要介绍了函数模板的由来以及用法&#xff0c;还有关键字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 端口输入数据寄…...

图像的读取与保存

图像是由一个个像素点组成&#xff0c;像素点就是颜色点&#xff0c;而颜色最简单的方式就是用RGB或RGBA表示图像保存图像将像素信息按照 一定格式&#xff0c;一定顺序&#xff08;即编码&#xff09; 存在硬盘上的 二进制文件 中保存图像需要以下必要信息&#xff1a;1. 文件…...

【蓝桥杯集训·每日一题】AcWing 4074. 铁路与公路

文章目录一、题目1、原题链接2、题目描述二、解题报告1、思路分析2、时间复杂度3、代码详解三、知识风暴Floyd 算法Spfa 算法一、题目 1、原题链接 4074. 铁路与公路 2、题目描述 某国家有 n 个城市&#xff08;编号 1∼n&#xff09;和 m 条双向铁路。 每条铁路连接两个不同的…...

网络:TCP与UDP相关知识(详细)

目录&#xff1a;1、UDP 和 TCP 的特点与区别2、UDP 、TCP 首部格式3、TCP 的三次握手和四次挥手4、TCP 的三次握手&#xff08;为什么三次&#xff1f;&#xff09;5、TCP 的四次挥手&#xff08;为什么四次&#xff1f;&#xff09;6、TCP 长连接和短连接的区别7、TCP粘包、拆…...

不好!有敌情,遭到XSS攻击【网络安全篇】

XSS&#xff1a;当一个目标的站点&#xff0c;被我们用户去访问&#xff0c;在渲染HTMl的过程中&#xff0c;出现了没有预期到的脚本指令&#xff0c;然后就会执行攻击者用各种方法注入并执行的恶意脚本&#xff0c;这个时候就会产生XSS。 涉及方&#xff1a; 用户&#xff0…...

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 个解释, 完全不挨边&#xff0c;但是, 在你的学习过程中, 搞清楚了 this 这个玩意, 那么会对你的开发生涯有很大帮助的&#xff0c;接下来咱们就…...

Java多种方法实现等待所有子线程完成再继续执行

简介 在现实世界中&#xff0c;我们常常需要等待其它任务完成&#xff0c;才能继续执行下一步。Java实现等待子线程完成再继续执行的方式很多。我们来一一查看一下。 Thread的join方法 该方法是Thread提供的方法&#xff0c;调用join()时&#xff0c;会阻塞主线程&#xff0…...

制造企业数字化工厂建设步骤的建议

随着工业4.0、中国制造2025的深度推进&#xff0c;越来越多的制造企业开始迈入智能制造的领域&#xff0c;那数字工厂要从何入手呢&#xff1f; 数字工厂规划的核心&#xff0c;也正是信息域和物理域这两个维度&#xff0c;那就从这两个维度来进行分析&#xff0c;看如何进行数…...

网上鲜花交易平台,可运行

文章目录项目介绍一、项目功能介绍1、用户模块主要功能包括&#xff1a;2、商家模块主要功能包括&#xff1a;3、管理员模块主要功能包括&#xff1a;二、部分页面展示1、用户模块部分功能页面展示2、商家模块部分功能页面展示3、管理员模块部分功能页面展示三、部分源码四、底…...

【实战】用 Custom Hook + TS泛型实现 useArray

文章目录一、题目二、答案&#xff08;非标准&#xff09;三、关键知识点1.Custom Hook关键点案例useMountuseDebounce2.TS 泛型关键点一、题目 完善自定义 Hook —— useArray &#xff0c;使其能够完成 tryUseArray 组件中测试的功能&#xff1a; 入参&#xff1a;数组返回…...

【LeetCode】剑指 Offer(18)

目录 题目&#xff1a;剑指 Offer 35. 复杂链表的复制 - 力扣&#xff08;Leetcode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 写在最后&#xff1a; 题目&#xff1a;剑指 Offer 35. 复杂链…...

Kubernetes节点运行时从Docker切换到Containerd

由于k8s将于1.24版本弃用dockershim&#xff0c;所以最近在升级前把本地的k8s切换到了Containerd运行时&#xff0c;目前我的k8s版本是1.22.5&#xff0c;一个master&#xff0c;二个Node的配置&#xff0c;以下做为一个操作记录日志整理&#xff0c;其它可以参考官网文档。 在…...

【编程基础之Python】12、Python中的语句

【编程基础之Python】12、Python中的语句Python中的语句赋值语句条件语句循环语句for循环while循环continue语句break语句continue与break的区别函数语句pass语句异常处理语句结论Python中的语句 Python是一种高级编程语言&#xff0c;具有简单易学的语法&#xff0c;适用于各…...

android h5餐饮管理系统myeclipse开发mysql数据库编程服务端java计算机程序设计

一、源码特点 android h5餐饮管理系统是一套完善的WEBandroid设计系统&#xff0c;对理解JSP java&#xff0c;安卓app编程开发语言有帮助&#xff08;系统采用web服务端APP端 综合模式进行设计开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要…...

容易混淆的嵌入式(Embedded)术语

因为做嵌入式开发工作虽然跳不出电子行业&#xff0c;但还是能接触到跨度较大的不同行当&#xff0c;身处不同的圈子。诸如医疗&#xff0c;银行&#xff0c;车载&#xff0c;工业&#xff1b;亦或者手机&#xff0c;PC&#xff0c;专用芯片&#xff1b;甚至可能横跨系统开发、…...

docker详细操作--未完待续

docker介绍 docker官网: Docker&#xff1a;加速容器应用程序开发 harbor官网&#xff1a;Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台&#xff0c;用于将应用程序及其依赖项&#xff08;如库、运行时环…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。

1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj&#xff0c;再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...

Python ROS2【机器人中间件框架】 简介

销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

【生成模型】视频生成论文调研

工作清单 上游应用方向&#xff1a;控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式&#xff08;本地调用&#xff09; SSE模式&#xff08;远程调用&#xff09; 4. 注册工具提…...

Python Einops库:深度学习中的张量操作革命

Einops&#xff08;爱因斯坦操作库&#xff09;就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库&#xff0c;用类似自然语言的表达式替代了晦涩的API调用&#xff0c;彻底改变了深度学习工程…...

C# 表达式和运算符(求值顺序)

求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如&#xff0c;已知表达式3*52&#xff0c;依照子表达式的求值顺序&#xff0c;有两种可能的结果&#xff0c;如图9-3所示。 如果乘法先执行&#xff0c;结果是17。如果5…...