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

news2025/7/18 19:01:25

文章目录

    • 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;
	else
		return 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;
	else
		return 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;
	else
		return 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;
	else
		return 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)=1
		printf("%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;
    else
        return 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;
	else
		return 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)=2
		printf("%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;
	else
		return 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;
	else
		return 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;
	else
		return 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;
	else
		return func2(n-1, 3*result+2);
}

6. 杨辉三角形

输入行数 n,输出 n 行杨辉三角形。

输入和输出:

8
  1
  1   1
  1   2   1
  1   3   3   1
  1   4   6   4   1
  1   5  10  10   5   1
  1   6  15  20  15   6   1
  1   7  21  35  35  21   7   1
10
  1
  1   1
  1   2   1
  1   3   3   1
  1   4   6   4   1
  1   5  10  10   5   1
  1   6  15  20  15   6   1
  1   7  21  35  35  21   7   1
  1   8  28  56  70  56  28   8   1
  1   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;
}

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

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

相关文章

管理系统-学科列表-增删改查

查 1.布局2.定义api3.导入api4.进入页面就调用api5.获取数据6.存储并渲染7.与分页建立关联a.请求参数值要与分页组件绑定b.total值存储并绑定到分页组件c.页码改变与页容量改变都要请求api1.布局 <template><div><el-card><el-form :inline"true&q…

【Leetcode】反转链表 合并链表 相交链表 链表的回文结构

目录 一.【Leetcode206】反转链表 1.链接 2.题目再现 3.解法A&#xff1a;三指针法 二.【Leetcode21】合并两个有序链表 1.链接 2.题目再现 3.三指针尾插法 三.【Leetcode160】相交链表 1.链接 2.题目再现 3.解法 四.链表的回文结构 1.链接 2.题目再现 3.解法 一.…

【python爬虫】获取cookie和uid的方式

本文以微博网站为例。 一、获取cookie &#xff08;1&#xff09;在浏览器中输入“weibo.cn” &#xff08;2&#xff09;登录自己的账号。 &#xff08;3&#xff09;登录后&#xff0c;右键空白处点击【检查】 &#xff08;4&#xff09;点击【网络】或者【Network】 浏…

Sql执行流程与Redo log、 Undo log、 Bin log日志文件

文章目录Sql执行流程与日志文件Sql的执行流程Redo LogBin logUndo logSql执行流程与日志文件 Sql的执行流程 mysql的内部组件结构如下图所示 连接器 与客户端建立连接&#xff0c;检验登录密码&#xff0c;分配相应权限 查询缓存 执行sql语句时会先从这里找一下&#xff0c;…

雷电模拟器运行非常卡顿有效解决方法分享

雷电模拟器运行非常卡顿有效解决方法分享。有用户在电脑上开启雷电模拟器来使用的时候&#xff0c;遇到了软件使用非常卡顿的情况。那么这样的软件卡顿问题要怎么去进行处理呢&#xff1f;接下来我们一起来看看以下的解决方法教学吧。 雷电模拟器卡顿的解决方法 一、关闭360核晶…

华大(小华)HC32L130工程创建

一、我们先来认识一下华大驱动库包下的文件功能。注意&#xff0c;华大提供的包是没有M0内核标准文件的&#xff0c;&#xff08;HC32L130 是M0&#xff09;ST提供的驱动库包是有的&#xff0c;就是CORE文件夹。第一步&#xff1a;创建文件夹目录具体这个创建文件夹目录的含义可…

matlab基础到实战(1)

目录概述sin函数例子四则运算实数复数逻辑运算复数运算模幅角共轭向量二维向量定义序列生成向量向量索引方式加减乘除向量间运算加减乘法除法概述 MATLAB是美国MathWorks公司出品的商业数学软件&#xff0c;用于数据分析、无线通信、深度学习、图像处理与计算机视觉、信号处理…

基于阿里云物联网平台设计的实时图传系统_采用MQTT协议传输图像

一、项目功能介绍 当前基于MQTT协议设计了一个实时图传系统,通过这个项目来演示,两个MQTT设备如何互相订阅,进行消息流转。 在阿里云服务器上创建2个设备,分为为设备A和设备B;设备A负责采集本地摄像头画面上传,设备B负责接收设备A上传的数据然后解析显示出来。在阿里云服…

0106广度优先搜索和最短路径-无向图-数据结构和算法(Java)

1 单点最短路径 单点最短路径。 给定一幅图和一个起点s&#xff0c;回答“从s到给定目的顶点v是否存在一条路径&#xff1f;如果有&#xff0c;找出其中最短的那条&#xff08;所含边数最少&#xff09;。“等类似问题。 深度优先搜索在这个问题上没有什么作为&#xff0c;因为…

个人创业做什么好?哪些互联网创业项目可长期收益?

打工没前途&#xff0c;这是现在年轻人看透了的一个事情&#xff0c;日复一日地工作&#xff0c;不如自己创业挣钱&#xff0c;那么个人创业做什么好呢&#xff1f;哪些项目可以长期稳定收益呢&#xff1f; 一、高体力项目不要做&#xff1b; 创业项目有很多&#xff0c;尤其是…

RK3588平台开发系列讲解(同步与互斥篇)自旋锁介绍

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、自旋锁介绍二、自旋锁相关的函数1、普通场景2、进程上下文和下半部3、中断相关三、相关结构体四、函数实现1、初始化2、获取自旋锁沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇介绍自旋锁的使用和基…

Clickhouse数据去重

1. Hive去重 先以两个简单的sql启发我们的话题 select count(distinct id)from order_combine;select count(id) from (select id from order_combine group by id ) t;从执行日志当中我们可以看到二者的差异&#xff08;只摘取关键部分&#xff09; # distinctStage-Stage…

kube-scheduler

kube-scheduler&#xff0c;它是 k8s 的默认调度器&#xff0c;负责为新创建出来的 pod寻找一个最合适的节点&#xff0c;这里的“最合适”指两种最优解&#xff1a;从集群中的所有节点中找出的全局最优解&#xff0c;和从集群中的部分节点中找出的局部最优解。它们分别可以解决…

电气系统中防雷接地保护的综合解决方案

防雷接地保护是通过导体和接地&#xff0c;可靠地连接一种保护性接线方法&#xff0c;在该保护性接线方法中&#xff0c;可能会在绝缘材料损坏后或在其他情况下对电器的金属部分&#xff08;即与带电部分绝缘的金属结构部分&#xff09;进行绝缘。接地保护系统仅具有相线和零线…

VSCode使用技巧,代码编写效率提升2倍以上!

VSCode是一款开源免费的跨平台文本编辑器&#xff0c;它的可扩展性和丰富的功能使得它成为了许多程序员的首选编辑器。在本文中&#xff0c;我将分享一些VSCode的使用技巧&#xff0c;帮助您更高效地使用它。 1. 插件 VSCode具有非常丰富的插件生态系统&#xff0c;通过安装插…

大数据分析案例-基于逻辑回归算法构建微博评论情感分类模型

🤵‍♂️ 个人主页:@艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏 📂加关注+ 喜欢大数据分析项目的小伙伴,希望可以多多支持该系列的其他文章 大数据分析案例合集…

【WEB前端进阶之路】 HTML 全路线学习知识点梳理(下)

前言 本文是HTML零基础小白学习系列的第三篇文章&#xff0c;点此阅读 上一篇文章 文章目录前言十五.HTML布局1.使用div元素添加网页布局2.使用table元素添加网页布局十六.HTML表单和输入1.文本域2.密码字段3.单选按钮4.复选框5.提交按钮十七.HTML框架1.iframe语法2.iframe设置…

java中如何判断map是否为空

java中判断map是否为空的方法是&#xff1a;利用isEmpty()函数来判断。函数介绍&#xff1a;isEmpty()是Java中用于判断某种容器是否有元素的系统库函数。如用来判断ArrayList&#xff0c;HashSet&#xff0c;HashMap是否有元素等。在Java中&#xff0c;可以用isEmpty();判断一…

燃料电池聚合物电解质膜高低温退化性能测试中的TEC半导体制冷温控解决方案

摘要&#xff1a;针对燃料电池质子交换膜高低温退化机理表征&#xff0c;基于德国慕尼黑工业大学团队提出的替代环境试验箱的TEC半导体制冷温控方案及其功能指标&#xff0c;本文给出此方案具体实施内容的补充&#xff0c;详细介绍了用于TEC半导体制冷温控系统的PID调节器和大功…

Springboot项目连接neo4j数据库

首先创建一个springboot项目&#xff0c;这里不再介绍。1 导入依赖包连接neo4j数据库的依赖包<dependency><groupId>org.neo4j</groupId><artifactId>neo4j-ogm-http-driver</artifactId><version>2.1.5</version> </dependency&…