当前位置: 首页 > 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;甚至可能横跨系统开发、…...

Nodejs 中 JSON 和 YAML 互相转换

JSON 转换成 YAML 1. 安装 js-yaml 库: npm install js-yaml2. 在程序中引入依赖库 const yaml require(js-yaml);3. 创建一个 js 对象, 代表 json 数据 const jsonData {name: John,age: 30,city: New York };4. 使用 yaml.dump() 把 js 对象转换成 YAML, 返回 YAML 字符…...

C++入门教程||C++ 修饰符类型||C++ 存储类

C 修饰符类型 C 允许在 char、int 和 double 数据类型前放置修饰符。修饰符用于改变基本类型的含义&#xff0c;所以它更能满足各种情境的需求。 下面列出了数据类型修饰符&#xff1a; signedunsignedlongshort 修饰符 signed、unsigned、long 和 short 可应用于整型&#…...

Android开发面试:Java知识答案精解

目录 Java 集合 集合概述 HashMap ConcurrentHashMap 泛型 反射 注解 IO流 异常、深浅拷贝与Java8新特性 Java异常 深浅拷贝 Java8新特性 并发 线程 线程池 锁 volatile JVM 内存区域 内存模型 类加载机制 垃圾回收机制 如何判断对象已死 Java 集合 …...

Windows上一款特别好用的画图软件

安装 废话不多说&#xff0c;打开windows的应用商店&#xff0c;搜索draw.io&#xff0c;点击获取即可。 画图 draw.io的布局左边是各种图形组件&#xff0c;中间是画布&#xff0c;右边是属性设置&#xff0c;文件扩展名是.drawio。 点击左边列表中的图形可以将它添加到画…...

html--学习

javascrapt交互&#xff0c;网页控制JavaScript&#xff1a;改变 HTML 图像本例会动态地改变 HTML <image> 的来源&#xff08;src&#xff09;&#xff1a;点亮灯泡<script>function changeImage() {elementdocument.getElementById(myimage) #内存变量&#xff0…...

关于递归处理,应该怎么处理,思路是什么?

其实问题很简单&#xff0c;就是想要循环遍历整个data对象&#xff0c;来实现所有name转成label&#xff0c;但是想到里面还有children属性&#xff0c;整个children里面可能还会嵌套很多很多的name&#xff0c;如此循环&#xff0c;很难搞&#xff0c;知道使用递归&#xff0c…...

重磅!牛客笔试客户端可防ChatGPT作弊

上线俩月&#xff0c;月活过亿。爆火的ChatGPT能代写文&#xff0c;撕代码&#xff0c;善玩梗&#xff0c;秒答题&#xff0c;几乎“无所不能”&#xff0c;争议也随之而来。调查显示&#xff0c;截至2023年1月&#xff0c;美国89%的大学生利用ChatGPT应付作业&#xff0c;53%的…...

春季训练营 | 前端+验证直通车-全实操项目实践,履历加成就业无忧

“芯动的offer”是2023年E课网联合企业全新推出集训培优班&#xff08;线下&#xff09;&#xff0c;针对有一定基础&#xff08;linux、verilog、uvm等&#xff09;在校学生以及想要通过短时间的学习进入到IC行业中的转行人士&#xff0c;由资深IC设计工程师带教&#xff0c;通…...

2.详解URL

文章目录视图函数1.1endpoint简介1.2 装饰器注册路由源码浅析1.3 另一种注册路由的方式---app.add_url_rule()1.4 视图函数中添加自定义装饰器2 视图类2.1 视图类的基本写法3 详细讲解注册路由的参数3.1常用的参数3.2不常用的参数(了解)视图函数 1.1endpoint简介 endpint参数…...

Android特别的数据结构(二)ArrayMap源码解析

1. 数据结构 public final class ArrayMap<K,V> implements Map<K,V> 由两个数组组成&#xff0c;一个int[] mHashes用来存放Key的hash值&#xff0c;一个Object[] mArrays用来连续存放成对的Key和ValuemHashes数组按非严格升序排列初始默认容量为0减容&#xff…...

Iis wordpress无法发表文章/培训心得体会模板

1.与map的结合使用 我们知道map的元素类型是pair&#xff0c;元素<key,value>是成对出现的。key值是const类型&#xff0c;而value是可变的&#xff0c;我们尝试把value值改变 如图所示&#xff0c;我们发现一个问题&#xff0c;value的值应该由“haha”变为“haha1”&am…...

鹤庆县公路建设网站/山东服务好的seo

1.正常执行事务 第一步&#xff0c;开启事物&#xff1a;Multi第二部&#xff0c;命令入队开启事务时候就需要向该事务中加入命令&#xff0c;将命令入队&#xff0c;这一步你可以任意写入你想要执行的命令。注意&#xff1a;在将命令添加到队列之后&#xff0c;命令并不会立即…...

福州网站建设q.479185700強/安徽seo优化规则

Android系统中TextView默认行间距比较窄&#xff0c;不美观。 我们可以设置每行的行间距&#xff0c;可以通过属性android:lineSpacingExtra或android:lineSpacingMultiplier来做。 在你要设置的TextView中加入如下代码&#xff1a; 1、android:lineSpacingExtra 设置行间距&a…...

如何删除网站的信息吗/app注册推广

最近接了一个私人项目&#xff0c;今天在处理项目中数据的时候遇到这样的一组数据(数据已经处理过&#xff0c;只为示例&#xff09;&#xff1a; 看掌柜用红色框住的两处&#xff0c;可以发现该多行单元格里不止一个值在里面&#xff0c;而且该数据集不止一列是这样的情况。…...

百度做任务的网站/色目人

原创作品&#xff0c;允许转载&#xff0c;转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://navyaijm.blog.51cto.com/4647068/809439 名称解释&#xff1a; Disk Group&#xff1a;磁盘组&#xff0c;这里相当于是阵列&#xff…...

基金公司网站建设方案/大连网站优化

修改服务器配置 让asp.net文件后缀名随心所欲更新时间&#xff1a;2012年06月27日 12:06:45 作者&#xff1a;asp或php的方法对.net就不行了&#xff0c;同样的办法&#xff0c;修改应用程序映射后&#xff0c;仍然没有得到预期的结果&#xff0c;文件什么内容&#xff0c;返…...