20 递归算法精髓解析:基准、性质、案例(阶乘、斐波拉契、猴子吃桃、汉诺塔等)、与循环的对比

news2024/9/29 19:12:30

目录

1 概述

2 递归的基本组成部分

2.1 基准情况

2.2 递归步骤

2.3 案例:循环实现阶乘的计算

2.4 案例:递归函数实现阶乘的计算

3 递归的性质

3.1 自我调用

3.2 栈的使用

3.3 问题分解

3.4 性能考虑

3.5 案例:递归的回溯

4 综合案例

4.1 计算斐波那契数列的第 N 项

4.1.1 循环实现

4.1.2 递归函数实现

4.2 输出斐波那契数列的前 N 项

4.3 逆推猴子吃桃问题

4.4 汉诺塔问题

4.5 反转字符串

4.6 求数字之和

4.6.1 循环实现

4.6.2 递归函数实现

4.7 求两个数的最大公约数(辗转相除法)

4.7.1 循环实现

4.7.2 递归函数实现

5 递归与循环的区别与联系

5.1 区别

5.2 联系


1 概述

        在 C 语言中,递归是一种编程技术它允许一个函数直接或间接地调用自身。递归函数通常用于解决那些可以被分解为更小的子问题的问题,这些子问题具有与原始问题相同的结构。递归函数的设计需要特别小心,以确保递归最终能够终止,并且每一步都朝着解决最终问题的方向前进


2 递归的基本组成部分

2.1 基准情况

        基准情况(Base Case)是递归函数中的特殊条件,当满足这些条件时,函数将停止调用自身并直接返回一个结果。它是递归的出口,防止了无限递归的发生。每个递归函数都必须明确指定至少一个基准情况(即必须有一个明显的结束条件),这些情况是问题最简单或最直接的解决方案,无需进一步递归。

        如在 2.4 案例:递归实现阶乘的计算中,基准情况可以是 n == 0 或 n == 1,因为 0 的阶乘和 1 的阶乘都定义为 1,这是可以直接给出的答案。

2.2 递归步骤

        递归步骤(Recursive Step)是函数调用自身以解决更小或更简单问题的过程。在这一步中,函数通过某种方式减小问题的规模,使其更接近基准情况。递归步骤的设计至关重要,因为它必须确保每次递归调用都朝着基准情况的方向前进,即问题规模逐渐减小,复杂度逐渐降低,最终递归结束

        如在 2.4 案例:递归实现阶乘的计算中,递归步骤是 factorial(n) = n * factorial(n-1)。这里,每次调用 factorial 时,都通过减小 n 的值来缩小问题的规模,直到达到基准情况 n == 0 或 n == 1。这个过程体现了递归步骤如何确保问题规模趋近于基准条件,并最终导致递归的终止。

2.3 案例:循环实现阶乘的计算

#include <stdio.h>

int factorial(int n)
{
    // result 初始设置为 1,因为任何数的阶乘乘以 1 都不会改变其值
    // 且 0 和 1 的阶乘就是 1
    int result = 1;
    // 从 2 遍历到 n(包括 n ),并在每次迭代中将当前的 result 乘以循环变量 i
    for (int i = 2; i <= n; i++)
    {
        result *= i;
    }
    // 返回最终结果
    return result;
}

int main()
{
    int number = 0;
    printf("请输出一个需要计算其阶乘的非负整数:");
    scanf("%d", &number);

    // 验证输入数据的合法性
    if (number >= 0)
    {
        printf("%d 的阶乘是: %d\n", number, factorial(number));
    }
    else
    {
        printf("对于负数,阶乘是未定义的。\n");
    }

    return 0;
}

        在 CMD 中多次运行程序,输出结果如下所示:

2.4 案例:递归函数实现阶乘的计算

#include <stdio.h>

// 递归函数来计算阶乘
long factorial(int n)
{
    // 基准情况:递归的出口
    if (n == 0 || n == 1) // 或者 if(n <=1 )
    {
        return 1;
    }
    // 递归步骤:调用自身以解决更小或更简单问题的过程,使其更接近基准情况
    else
    {
        return n * factorial(n - 1);
    }
}

int main()
{
    int number = 0;
    printf("请输出一个需要计算其阶乘的非负整数:");
    scanf("%d", &number);

    // 验证输入数据的合法性
    if (number >= 0)
    {
        printf("%d 的阶乘是: %ld\n", number, factorial(number));
    }
    else
    {
        printf("对于负数,阶乘是未定义的。\n");
    }

    return 0;
}

        在 CMD 中多次运行程序,输出结果如下所示:

        以 factorial(5) 为例,递归函数结构分析如下所示:

提示:

        还可以在程序的关键位置设置断点,通过调试工具逐步执行,来深入探究递归函数的运行情况。


3 递归的性质

3.1 自我调用

        递归函数在其定义内部调用自身。这是递归的核心特性。通过自我调用,函数能够不断地将问题分解为更小的子问题,直到达到一个基准情况(或称为基本情况、边界条件),此时函数将停止调用自身并返回一个结果

3.2 栈的使用

        在 C 语言中(以及大多数其他编程语言中),函数调用是通过栈来实现的。当函数被调用时,它的执行环境(包括局部变量、参数、返回地址等)会被压入调用栈中。递归调用也不例外,每次递归调用都会创建一个新的栈帧(stack frame),并压入调用栈。当递归函数开始返回时,这些栈帧会按照后进先出(LIFO)的顺序被弹出,从而实现了从最深递归层次开始回溯到最初调用的过程

3.3 问题分解

        递归调用的过程实际上是一个问题分解的过程。每次递归调用都将原问题分解为一个或多个更小的子问题,直到子问题变得足够简单,可以直接解决(即达到基准情况)。然后,递归函数通过组合这些子问题的解来得到原问题的解。

3.4 性能考虑

        虽然递归调用在解决某些问题时非常直观和方便,但它也可能导致性能问题。特别是当递归深度很大时,由于需要大量的栈空间来存储每次调用的执行环境,这可能会导致栈溢出错误。此外,递归调用还可能引入额外的函数调用开销

3.5 案例:递归的回溯

#include <stdio.h>

// 函数声明,用于演示递归调用并打印数字
void test(int n)
{
    // 首先打印当前传入的数字
    printf("%d\n", n);

    // 检查 n 是否大于 1,如果是,则递归调用自身并传入 n-1
    if (n > 1)
    {
        test(n - 1);
    }

    // 递归返回后,再次打印当前数字(此时是从最深的递归层次返回时打印)
    printf("%d\n", n);
}

int main()
{
    // 调用 test 函数,传入数字 3 作为参数
    // 这将展示递归函数如何工作,并打印出特定的数字序列
    test(3);

    return 0;
}

        输出结果如下所示:

        程序分析如下所示:

        上面这个程序演示了一个简单的递归函数 test,它接受一个整数 n 作为参数。函数首先打印当前的 n 值,然后检查 n 是否大于 1。如果是,则递归调用自身,但传入的参数是 n-1。这导致了一个递归过程,其中数字被连续减小并打印,直到 n 不再大于 1,此时递归调用停止。但是,由于递归调用的性质,函数在返回过程中还会再次打印每个数字,这次是从最深的递归层次开始回溯到最初的调用。因此,对于给定的输入 3,输出将是 3、2、1(递减过程),然后是1、2、3(回溯过程)。


4 综合案例

4.1 计算斐波那契数列的第 N 项

        斐波那契数列是指这样一个数列:1,1,2,3,5,8,13,21,34,55,89……这个数列从第 3 项开始 ,每一项都等于前两项之和。现在要求编写一个程序,接收用户输入的一个正整数 n,然后输出斐波那契数列的第 n 项。

4.1.1 循环实现

        下面是通过 for 循环结构来实现斐波那契数列第 n 项计算的程序示例:

#include <stdio.h>

// 函数原型声明
int fibonacci(int n);

int main()
{
    int n;
    printf("请输入一个正整数n,以获取斐波那契数列的第n项: ");
    scanf("%d", &n);

    // 检查输入值是否合法
    if (n < 1)
    {
        printf("输入的数字必须大于等于1。\n");
        return 1; // 退出程序
    }

    // 调用函数并打印结果
    printf("斐波那契数列的第%d项是: %d\n", n, fibonacci(n));

    return 0;
}

// 使用循环实现斐波那契数列第 n 项的函数
int fibonacci(int n)
{
    if (n == 1 || n == 2)
    {
        // 如果 n 是 1 或 2,则直接返回 1
        return 1;
    }

    // 初始化第一项和第二项
    int first = 1, second = 1, third;
    // 从3 开始循环遍历
    for (int i = 3; i <= n; i++)
    {
        third = first + second; // 计算前两项之和
        first = second;         // 更新 first 为前一项
        second = third;         // 更新 second 为当前项(即前两项之和)
    }

    return second; // 循环结束后,second 中存储的就是第 n 项的值
}

        在 CMD 中多次运行程序,输出结果如下所示:

4.1.2 递归函数实现

        下面是通过递归函数来实现斐波那契数列第 n 项计算的程序示例: 

#include <stdio.h>

// 函数原型声明
int fib(int n);

int main()
{
    int n;
    printf("请输入一个正整数n,以获取斐波那契数列的第n项: ");
    scanf("%d", &n);

    // 检查输入值是否合法
    if (n < 1)
    {
        printf("输入的数字必须大于等于1。\n");
        return 1; // 退出程序
    }

    // 调用 fib 函数计算斐波那契数列的第 n 项,并输出结果
    printf("%d 的斐波那契数是:%d\n", n, fib(n));

    return 0;
}

// 函数声明:计算斐波那契数列的第 n 项
int fib(int n)
{
    // 基准情况:如果 n 是 1 或 2,则斐波那契数为 1
    if (n == 1 || n == 2) // 或者 if (n <= 1)
    {
        return 1;
    }
    // 递归情况:斐波那契数列的第 n 项是第 n-1 项和第 n-2 项之和
    else
    {
        return fib(n - 1) + fib(n - 2);
    }
}

        在 CMD 中多次运行程序,输出结果如下所示:

4.2 输出斐波那契数列的前 N 项

        现在要求编写一个程序,程序接收一个整数 n 作为输入,并输出斐波那契数列的前 n 项。

#include <stdio.h>

// 函数声明:计算斐波那契数列的第 n 项
int fib(int n);

int main()
{
    int n;
    printf("请输入一个正整数n,以获取斐波那契数列的第n项: ");
    scanf("%d", &n);

    // 检查输入值是否合法
    if (n < 1)
    {
        printf("输入的数字必须大于等于1。\n");
        return 1; // 退出程序
    }

    // 循环输出斐波那契数列的前 n 项
    for (int i = 1; i <= n; i++)
    {
        printf("%d ", fib(i)); // 调用 fib 函数并打印结果
    }
    printf("\n");

    return 0;
}

// 函数声明:计算斐波那契数列的第 n 项
int fib(int n)
{
    // 基准情况:如果 n 是 1 或 2,则斐波那契数为 1
    if (n == 1 || n == 2) // 或者 if (n <= 1)
    {
        return 1;
    }
    // 递归情况:斐波那契数列的第 n 项是第 n-1 项和第 n-2 项之和
    else
    {
        return fib(n - 1) + fib(n - 2);
    }
}

        在 CMD 中多次运行程序,输出结果如下所示:

4.3 逆推猴子吃桃问题

        有一堆桃子,猴子第一天吃了其中的一半,并多吃一个。以后每天猴子都吃其中的一半,然后再多吃一个。当到第十天早晨,猴子想再吃时(注意:此时还没吃),发现只有 1 个桃子了。问:最初共多少个桃子?

        题目分析:根据题意,第 10 天早晨还剩下最后 1 个桃子,这意味着第 9 天猴子吃完桃子后,实际上只剩下了这 1 个桃子。因此,在第 9 天早晨(猴子还未吃桃前),桃子的数量应当是第 10 天早晨剩余桃子数量(1 个)加上 1,然后乘以 2 的结果。据此,第 9 天早晨桃子的数量为 (1+1)*2=4 ;同样地,第 8 天早晨(猴子还未吃桃前)的桃子数量可以通过第 9 天早晨(猴子还未吃桃前)的桃子数量来计算:(4+1)*2=10。以此类推,【当天桃子的初始数量=(明天桃子的初始数量 + 1)* 2 ,即 f(day) = [f(day+1)+1]*2】我们可以通过这种方法一直逆推回去,直到求得第 1 天早晨最初的桃子数量。

        为了实现这一逻辑,我们可以编写一个递归函数来帮助计算。

#include <stdio.h>

// 定义递归函数,参数 day 表示当前计算的是第几天的桃子数量
int peachCount(int day)
{
    if (day == 10)
    { // 如果达到第 10 天,直接返回 1(题目已知条件)
        return 1;
    }
    else
    {
        // 前一天的初始桃子数量=(当天的初始桃子数量 + 1)* 2 ,即 f(day) = [f(day+1)+1]*2
        return (peachCount(day + 1) + 1) * 2;
    }
}

int main()
{
    printf("第十天开始时桃子的数量(验证基准情况): %d \n", peachCount(10));
    // 第九天开始时的桃子数量
    printf("第九天开始时桃子的数量: %d \n", peachCount(9));
    // ... 以此类推,直到第一天
    printf("最初共有桃子 %d 个。\n", peachCount(1));

    return 0;
}

        输出结果如下所示:

4.4 汉诺塔问题

        汉诺塔(Tower of Hanoi)是一个源于印度古老传说的经典递归问题。它包含三根柱子和一系列不同大小的圆盘,这些圆盘原本按照大小顺序穿在一根柱子上,并且大的圆盘在下面,小的圆盘在上面。目标是将这些圆盘移动到另一根柱子上,同时满足以下规则:

  • 每次只能移动一个圆盘。
  • 在任何时候,较大的圆盘都不能放在较小的圆盘上面。
  • 可以使用第三根柱子作为辅助。

#include <stdio.h>

// 函数声明
void hanoi(int n, char from_rod, char to_rod, char aux_rod);

int main()
{
    int n;
    printf("请输入圆盘的数量: ");
    scanf("%d", &n);
    // 假设有三个柱子分别命名为 'A', 'B', 'C'
    // 我们需要将圆盘从柱子 A 移动到柱子 C,使用柱子 B 作为辅助
    printf("\nA:起始柱子 B: 辅助柱子 C:目标柱子\n");
    printf("A:起始柱子上的圆盘,从上到下(小盘到大盘),编号为: 1 2 3 …… n\n");
    printf("步骤如下所示:");

    hanoi(n, 'A', 'C', 'B');

    return 0;
}

// 汉诺塔问题的递归实现
/**
 * @brief 使用递归函数解决汉诺塔问题
 *
 * @param n 圆盘的数量
 * @param from_rod 起始柱子
 * @param to_rod 目标柱子
 * @param aux_rod 辅助柱子,递归时可以拿其他柱子当辅助,即参数顺序可以按照需要变化
 */
void hanoi(int n, char from_rod, char to_rod, char aux_rod)
{
    if (n == 1)
    {
        // 当只有一个圆盘时,直接将其从起始柱子移动到目标柱子
        printf("\n  移动圆盘 1 号从 %c 到 %c", from_rod, to_rod);
        return;
    }
    // 将上面的 n-1 个圆盘从起始柱子移动到辅助柱子
    hanoi(n - 1, from_rod, aux_rod, to_rod);
    // 将最大的圆盘(第 n 个)移动到目标柱子
    printf("\n  移动圆盘 %d 号从 %c 到 %c", n, from_rod, to_rod);
    // 最后将 n-1 个圆盘从辅助柱子移动到目标柱子
    hanoi(n - 1, aux_rod, to_rod, from_rod);
}

        当只有一个圆盘时,直接将其从起始柱子移动到目标柱子。

        程序输出结果如下所示:

        当只有两个圆盘时,圆盘移动步骤如下图所示:

        程序输出结果如下所示:

        当只有三个圆盘时,圆盘移动步骤如下图所示:

        程序输出结果如下所示:

4.5 反转字符串

        编写一个递归函数来反转一个字符串。例如,输入字符串 "hello",输出 "olleh"。

#include <stdio.h>
#include <string.h>

// 声明递归函数
void reverseString(char *str, int start, int end);

int main()
{
    char str[] = "hello";
    int length = strlen(str);
    printf("反转之前的字符串为: %s\n", str);

    // 调用递归函数,注意结束索引为 length-1
    reverseString(str, 0, length - 1);

    printf("反转之后的字符串为: %s\n", str);

    return 0;
}

// 递归函数定义
/**
 * @brief 反转字符串
 *
 * 该函数使用递归方式反转字符串中从 start 索引到 end 索引(包含)之间的字符。
 * 注意,字符串的索引从 0 开始,因此 start 通常是 0,而 end 是字符串长度减 1。
 *
 * @param str 指向要反转的字符串的指针
 * @param start 反转开始的索引(包含)
 * @param end 反转结束的索引(包含)
 *
 * 注意:该函数会直接修改传入的字符串。
 */
void reverseString(char *str, int start, int end)
{
    // 递归终止条件
    if (start >= end)
    {
        return;
    }

    // 交换字符
    char temp = str[start];
    str[start] = str[end];
    str[end] = temp;

    // 递归调用,只改变 start 的值
    reverseString(str, start + 1, end);
}

        输出结果如下所示:

4.6 求数字之和

        输入一个非负整数,返回组成它的数字之和。例如,对于输入 1234,输出为 1+2+3+4=10。

4.6.1 循环实现

#include <stdio.h>

// 函数声明
int sumOfDigits(int n);

int main()
{
    int num;
    printf("请输入一个非负整数: ");
    scanf("%d", &num);                            // 读取用户输入的非负整数
    printf("数字之和为: %d\n", sumOfDigits(num)); // 调用函数并打印结果
    return 0;
}

// 使用循环实现计算数字之和的函数
int sumOfDigits(int n)
{
    int sum = 0; // 初始化总和为 0

    while (n > 0)
    {                  // 当 n 大于 0 时循环
        sum += n % 10; // 将 n 的最低位加到 sum 上
        n /= 10;       // 去掉 n 的最低位
    }
    return sum; // 返回总和
}

        输出结果如下所示:

4.6.2 递归函数实现

#include <stdio.h>

// 函数声明
int sumOfDigits(int num);

int main()
{
    int num;
    printf("请输入一个非负整数: ");
    scanf("%d", &num);                            // 读取用户输入的非负整数
    printf("数字之和为: %d\n", sumOfDigits(num)); // 调用函数并打印结果
    return 0;
}

// 使用递归实现计算数字之和的函数
/**
 * @brief 计算一个非负整数的各位数字之和
 *
 * @param num 要计算的整数
 * @return 整数各位数字之和
 */
int sumOfDigits(int num)
{
    // 递归终止条件:当 num 为 0 时,说明所有位数都已经被加过了
    if (num == 0)
    {
        return 0;
    }
    // 递归调用,传入 num 除以 10 的结果(去掉最低位),并加上 num 的最低位(num % 10)
    return sumOfDigits(num / 10) + (num % 10);
}

        程序分析图如下所示:

4.7 求两个数的最大公约数(辗转相除法)

        最大公约数是两个或多个整数共有的最大的那个正整数约数。欧几里得算法(也称为辗转相除法)是求解两个或多个整数的最大公约数(GCD,Greatest Common Divisor)的一种高效算法。

        欧几里得算法(也称为辗转相除法)的基本步骤是:对于两个正整数 a 和 b(假设 a > b),

  1. 计算 a 除以 b 的余数,记为 r。
  2. 如果 r 等于 0,则 b 就是 a 和 b 的最大公约数。
  3. 如果 r 不等于 0,则将 b 的值赋给 a,将 r 的值赋给 b,然后回到步骤 1。

        这个过程会重复进行,直到余数为 0,此时,最后的非零除数就是两个数的最大公约数。

以求 98 56 的最大公约数为例:

98 / 56 = 1……42(余数)
56 / 42 = 1……14(余数)
42 / 14 = 3……0(余数)

或者

56 / 98 = 0………56(余数)
98 / 56 = 1……42(余数)
56 / 42 = 1……14(余数)
42 / 14 = 3……0(余数)

所以 98 和 56 的最大公约数是 14

4.7.1 循环实现

        根据欧几里得算法的基本思想,可以通过在循环内部动态交换变量的值,不断地用较大的数除以较小的数,并取其余数,直到余数为零,从而逐步求出最大公约数。

#include <stdio.h>

// 使用循环实现欧几里得算法
int gcd(int a, int b)
{
    while (b != 0)
    {
        // 计算余数
        int r = a % b;
        // 交换数据
        a = b;
        b = r;
    }
    return a;
}

int main()
{
    int num1, num2;

    // 输入两个数
    printf("请输入两个整数(用空格分隔): ");
    scanf("%d %d", &num1, &num2);

    // 调用 gcd 函数并打印结果
    printf("%d 和 %d 的最大公约数是: %d\n", num1, num2, gcd(num1, num2));

    return 0;
}

        输出结果如下所示:

4.7.2 递归函数实现

        使用递归函数实现欧几里得算法来计算两个数的最大公约数(GCD)是一种既经典又高效的解决方案,它通过递归调用简化问题规模,直至找到最大公约数。

#include <stdio.h>

// 递归函数实现欧几里得算法
int gcd(int a, int b)
{
    // 基本情况:当 b 为 0 时,a 即为两数的最大公约数
    if (b == 0)
    {
        return a;
    }
    // 递归调用,计算 b 和 a%b 的最大公约数
    return gcd(b, a % b);
}

int main()
{
    int num1, num2;

    // 输入两个数
    printf("请输入两个整数(用空格分隔): ");
    scanf("%d %d", &num1, &num2);

    // 调用 gcd 函数并打印结果
    printf("%d 和 %d 的最大公约数是: %d\n", num1, num2, gcd(num1, num2));

    return 0;
}

        输出结果如下所示:


5 递归与循环的区别与联系

5.1 区别

语法结构:

  • 循环:使用诸如 for, while, do-while 等控制结构来反复执行一段代码块,直到满足某个终止条件
  • 递归:通过函数调用自身来解决问题的一部分,并逐步减少问题规模,直到达到基本情况

内存使用:

  • 循环:通常占用较少的栈空间,因为循环变量保存在栈上的开销相对较小。
  • 递归:可能导致大量的栈空间消耗,尤其是在递归层次较深的情况下,因为每次函数调用都需要分配新的栈帧。

执行效率:

  • 循环:一般而言,循环的执行效率较高,因为它没有函数调用的开销。
  • 递归:可能会有较高的函数调用开销,尤其是当递归层数较多时。

可读性和调试:

  • 循环:通常更容易理解和调试,因为其逻辑较为直观。
  • 递归:虽然简洁,但在某些情况下可能难以理解和调试,特别是当递归逻辑复杂时。

5.2 联系

        解决问题的能力:循环和递归都是解决需要重复执行某一任务的有效手段,它们可以用来实现相同的功能。

        转换可行性:大多数可以通过递归解决的问题,也可以通过循环来实现。实际上,任何递归算法都可以转换为等效的迭代算法(尽管可能不如递归版本那么直观)。

        设计模式:在设计算法时,选择使用循环还是递归往往取决于问题的具体需求和个人偏好。递归通常更适合于自然地递归分解的问题,而循环则适合于有明确边界条件的问题。

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

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

相关文章

WPF DataGrid 列表中,DataGrid.Columns 列根据不同的值显示不同内容

需求&#xff1a;在WPF DataGrid 控件中&#xff0c;有以下列&#xff0c;绑定了一个LogType&#xff0c;值分别是0,1,2&#xff0c;根据不同的值&#xff0c;显示不同的内容以及背景 <DataGrid ItemsSource"{Binding EventLog}"><DataGrid.Columns><…

力扣之1777.每家商店的产品价格

文章目录 1. 1777.每家商店的产品价格1.1 题干1.2 建表1.3 题解1.4 结果截图 1. 1777.每家商店的产品价格 1.1 题干 表&#xff1a;Products -------------------- | Column Name | Type | -------------------- | product_id | int | | store | enum | | price | int | ---…

猜数-while-python

题目要求&#xff1a; 设置一个范围1-100的随机整数变量&#xff0c;通过while循环&#xff0c;诶和input语句&#xff0c;判断输入的数字是否等于随机数 无限次机会&#xff0c;直到猜中为止每一次不猜中都&#xff0c;会提示大了小了猜完数字后&#xff0c;提示裁了几次 imp…

K8s 之Pod的定义及详细资源调用案例

资源管理介绍 在kubernetes中&#xff0c;所有的内容都抽象为资源&#xff0c;用户需要通过操作资源来管理kubernetes。kubernetes的本质上就是一个集群系统&#xff0c;用户可以在集群中部署各种服务所谓的部署服务&#xff0c;其实就是在kubernetes集群中运行一个个的容器&a…

Day25_0.1基础学习MATLAB学习小技巧总结(25)——四维图形的可视化

利用空闲时间把碎片化的MATLAB知识重新系统的学习一遍&#xff0c;为了在这个过程中加深印象&#xff0c;也为了能够有所足迹&#xff0c;我会把自己的学习总结发在专栏中&#xff0c;以便学习交流。 参考书目&#xff1a; 1、《MATLAB基础教程 (第三版) (薛山)》 2、《MATL…

开发后台管理系统-开发环境搭建

文章目录 需求设计环境搭建创建项目工程测试结果 安装Element Plus安装路由安装Vue Router配置Vue Router 测试 需求 开发一个后台管理系统 这里以CDN后台管理系统为例 设计 参照 CDN后台管理系统功能说明文档 环境搭建 确保已经安装了Node.js和npm 执行 npm install -g vu…

【极限、数学】 NOIP 2018 提高组初赛试题 第 7 题详解(线段长度期望)

在一条长度为 1 1 1 的线段上随机取两个点&#xff0c;则以这两个点为端点的线段的期望长度是&#xff08; &#xff09;。 考虑将一个线段上平均分布有 n ( n ≥ 2 ) n(n\geq 2) n(n≥2) 个节点&#xff0c;其中首尾均有一个节点&#xff0c;那么我们就将一个线段均分为 n…

SSMP+ajax实现广告系统的分页效果

文章目录 1.案例需求2.编程思路3.案例源码4.小结 1.案例需求 使用SSMPajax实现广告系统的分页效果&#xff0c;效果图如下&#xff1a; 2.编程思路 mapper层&#xff1a;定义一个接口&#xff0c;继承自BaseMapper&#xff0c;指定泛型为AdvInfo&#xff0c;这样MyBatis Pl…

Idea springboot项目热部署

使用 spring-boot-devtools spring-boot-devtools 是 Spring Boot 提供的开发工具模块&#xff0c;它可以自动检测到代码的变化并重启应用&#xff0c;实现热部署。 配置步骤&#xff1a; 添加依赖&#xff1a; 在项目的 pom.xml 中加入 spring-boot-devtools 依赖&#xff1…

SQL Server性能优化之读写分离

理论部分: 数据库读写分离&#xff1a; 主库&#xff1a;负责数据库操作增删改 20% 多个从库&#xff1a;负责数据库查询操作 80% 读写分离的四种模式 1.快照发布&#xff1a;发布服务器按照预定的时间间隔向订阅服务器发送已发布的数据快照 2.事务发布[比较主流常见]&#xf…

roctracer 的应用示例

1&#xff0c;不用 roctracer 的普通场景 mt.cpp /* Copyright (c) 2018-2022 Advanced Micro Devices, Inc.Permission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software")…

哪款宠物空气净化器是除浮毛王者?希喂、范罗士、霍尼韦尔宠物空气净化器实测

养宠人绕不过的痛——掉毛&#xff01;脱毛&#xff01;又到了掉毛季&#xff0c;就连空气中都有毛毛……不管遇到谁&#xff0c;都知道你养猫养狗了——只因T恤变身毛线衫、毛毛怎么都粘不干净。不止是衣服上&#xff0c;地板上、沙发上、桌面上&#xff0c;哪哪都是毛。开始养…

从头开始学MyBatis—02基于xml和注解分别实现的增删改查

首先介绍此次使用的数据库结构&#xff0c;然后引出注意事项。 通过基于xml和基于注解的方式分别实现了增删改查&#xff0c;还有获取参数值、返回值的不同类型对比&#xff0c;帮助大家一次性掌握两种代码编写能力。 目录 数据库 数据库表 实体类 对应的实体类如下&#x…

【目标检测】labelimg图像标注软件的使用流程

一、labelimg检测图片标注 1、下载labelimg.exe 链接&#xff1a;https://pan.baidu.com/s/1yk8ff56Xu40-ZLBghEQ5nw 提取码&#xff1a;vj8f 下载的文件是编译好的&#xff0c;可执行的labelImg.exe文件。直接将文件放在windows环境下&#xff0c;双击可执行。&#xff08;如果…

典型BUCK电路学习和设计

手把手教你设计12V3Abuck降压电路-2-相关输入参数讲解_哔哩哔哩_bilibili 这里是输入电容&#xff0c;先过大电容&#xff08;电解电容&#xff09;再过小电容&#xff08;陶瓷贴片电容&#xff0c;高频率波&#xff09; 输出也可以同理 开关电源不能带负载的原因&#xff0c…

uniapp vue3 梯形选项卡组件

实现的效果图&#xff1a; 切换选项卡显示不同的内容&#xff0c;把这个选项卡做成了一个组件&#xff0c;需要的自取。 // 组件名为 trapezoidalTab <template> <view class"pd24"><view class"nav"><!-- 左侧 --><view cla…

--链表--

一.链表的概述 二.逻辑图 三.代码详解 //1.定义关于链表的结构体 #include <iostream> #include <stdlib.h> #include <assert.h> using namespace std; typedef int SLTDateType;//适用于不同的数据类型 typedef struct SListNode {SLTDateType data;//数据…

【Day14-单例设计模式动态代理】

单例设计模式 什么是设计模式&#xff08;Design pattern&#xff09; ? 一个问题通常有n种解法&#xff0c;其中肯定有一种解法是最优的&#xff0c;这个最优的解法被人总结出来了&#xff0c;称之为设计模式。设计模式有20多种&#xff0c;对应20多种软件开发中会遇到的问题…

记录开发一个英语听力训练网站

背景 在当前全球经济衰退的背景下&#xff0c;IT相关的工作在国内的竞争也是越来越激烈&#xff0c;为了能够获得更多的可能性&#xff0c;英语的学习也许能为程序员打开一扇新的窗户&#xff0c;比如很多远程的工作尤其是国际化背景的工作团队&#xff0c;英语的协作沟通是必…

Modbus通信

Modbus是一种经典的工业通信协议&#xff0c;由Modicon&#xff08;现为施耐德电气&#xff09;在1979年首次发布。它广泛应用于各种工业自动化系统中&#xff0c;尤其是在PLC&#xff08;可编程逻辑控制器&#xff09;与其他设备之间的通信。Modbus的主要特点是其简单性和开放…