【解密算法:时间与空间的博弈】

本章重点

  1. ​​什么是数据结构?

  2. 什么是算法?

  3. 算法效率

  4. 时间复杂度

  5. 空间复杂度

  6. 常见时间复杂度以及复杂度oj练习

1. 什么是数据结构?

        数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。

2.什么是算法?

        算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。比如:查找、排序等等。

3.算法效率

3.1 如何衡量一个算法的好坏

        我们能不能用一个程序运行的开始的时间到程序结束的时间去描述复杂度呢?

答案是不能,一个程序的运行还会受到很多因素的影响,比如电脑的配置、性能。就比如下面两台电脑,让两台电脑同时执行下面递归计算斐波那契数列40是多少的程序。很明显高性能的计算机器多核CPU,进程并发执行,速度高于左边的单核机器。

#include<stdio.h>
long long Fib(int N)
{if (N < 3)return 1;return Fib(N - 1) + Fib(N - 2);
}
int main()
{//使用斐波那契数列递归方法计算40long long result = Fib(40);printf("%lld", result);return 0;
}

斐波那契数列的递归实现方式非常简洁,但简洁一定好吗?那该如何衡量其好与坏呢?

        算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般是从时间空间两个维度来衡量的,即时间复杂度和空间复杂度

        时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。

        在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。

4.时间复杂度

        时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。

        一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。

        一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。

        即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。

 Func1 执行的基本操作次数 :

        我们发现当N的值越来越多的时候,2*N + 10这个表达式对结果造成的影响非常小,所有实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。

大O的渐进表示法

大O符号(Big O notation):是用于描述函数渐进行为的数学符号。

推导大O阶方法:

  1. 用常数1取代运行时间中的所有加法常数。
  2. 在修改后的运行次数函数中,只保留最高阶项。
  3. 如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。

使用大O的渐进表示法以后,Func1的时间复杂度为:O(N^2)

通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。另外有些算法的时间复杂度存在最好、平均和最坏情况:

  • 最坏情况:任意输入规模的最大运行次数(上界)
  • 平均情况:任意输入规模的期望运行次数
  • 最好情况:任意输入规模的最小运行次数(下界)

例如:在一个长度为N数组中搜索一个数据x

  • 最好情况:1次找到
  • 平均情况:N/2次找到
  • 最坏情况:N次找到

        在实际中一般情况关注的是算法的最坏运行情况,原因是为了保证算法在任何输入情况下都能保持一定的性能水平。最坏情况时间复杂度是对算法性能的一个保证,确保在任何可能的输入下,算法都不会执行得更差。所以数组中搜索数据时间复杂度为O(N).

常见时间复杂度计算举例

实例一:

// 计算Func2的时间复杂度?
void Func2(int N)
{int count = 0;for (int k = 0; k < 2 * N; ++k){++count;}int M = 10;while (M--){++count;}printf("%d\n", count);
}
  1. 第一个循环:for (int k = 0; k < 2 * N; ++k)

    • 这个循环执行了 2 * N 次,其中 N 是输入的参数。
    • 循环内部只有一个基本操作 ++count
  2. 第二个循环:while (M--)

    • 这个循环执行了固定的 10 次。
    • 循环内部只有一个基本操作 ++count

因此,总的基本操作数可以表示为:2 * N + 10

        在大O符号表示法中,我们通常关注最高阶的项,而忽略低阶项和常数系数。在这种情况下,最高阶项是 2 * N

        因此,Func2 函数的时间复杂度可以表示为 O(N)

实例二:

//计算Func3的时间复杂度?
void Func3(int N, int M)
{int count = 0;for (int k = 0; k < M; ++k){++count;}for (int k = 0; k < N; ++k){++count;}printf("%d\n", count);
}
  1. 第一个循环:for (int k = 0; k < M; ++k)

    • 这个循环执行了 M 次,其中 M 是第二个输入参数。
    • 循环内部只有一个基本操作 ++count
  2. 第二个循环:for (int k = 0; k < N; ++k)

    • 这个循环执行了 N 次,其中 N 是第一个输入参数。
    • 循环内部只有一个基本操作 ++count

因此,总的基本操作数可以表示为:M + N

        在大O符号表示法中,我们通常关注最高阶的项,而忽略低阶项和常数系数。在这种情况下,最高阶项是 M 和 N,同时取两者。

        因此,Func3 函数的时间复杂度可以表示为 O(M + N)

补充:

  • 若 M 和 N 的值相同,时间复杂度可以表示为 O(M) 或者 O(N)
  • 若 M >> N 的值,时间复杂度可以表示为 O(M)
  • 若 M << N 的值,时间复杂度可以表示为 O(N)

在这种情况下,最高阶项是 MN,取两者中较大的一个因此,Func3 函数的时间复杂度可以表示为 O(max(M, N))

实例三:

// 计算Func4的时间复杂度?
void Func4(int N)
{int count = 0;for (int k = 0; k < 100; ++k){++count;}printf("%d\n", count);
}
  1. 循环:for (int k = 0; k < 100; ++k)

    • 这个循环总共执行了 100 次。
    • 循环内部只有一个基本操作 ++count

因此,总的基本操作数可以表示为:100

        在大O符号表示法中,我们通常关注最高阶的项,而忽略低阶项和常数系数。在这种情况下,最高阶项是 100

        因此,Func4 函数的时间复杂度可以表示为 O(1)

实例四:

// 计算strchr的时间复杂度?
const char* strchr(const char* str, int character);

  strchr 函数用于在给定字符串中查找第一个出现的指定字符,并返回该字符后的部分字符串。

  • 最好情况:1次找到
  • 平均情况:N/2次找到
  • 最坏情况:N次找到

        在一般情况下,strchr 函数的时间复杂度可以被认为是 O(N),其中 N 是输入字符串的长度。这是因为 strchr 需要遍历字符串中的每个字符来查找指定字符。在最坏情况下,它可能需要遍历整个字符串才能找到目标字符。

 

实例五:

// 计算BubbleSort的时间复杂度?
void BubbleSort(int* a, int n)
{assert(a);for (size_t end = n; end > 0; --end){int exchange = 0;for (size_t i = 1; i < end; ++i){if (a[i - 1] > a[i]){Swap(&a[i - 1], &a[i]);exchange = 1;}}if (exchange == 0)break;}
}

        BubbleSort 是一种基本的排序算法,其时间复杂度取决于待排序数组的初始状态。让我们分析一下 BubbleSort 的时间复杂度。

        BubbleSort 的主要思想是在每一轮遍历中,比较相邻的两个元素,如果顺序不对就交换它们,直到最大(或最小)元素“冒泡”到正确的位置。在每一轮遍历中,至少会将一个元素放置在其最终的位置上。

        最坏情况下,数组完全逆序,需要执行 N-1 轮比较和交换操作。第一轮中,需要比较和可能的交换 N-1 次;第二轮中,需要比较和可能的交换 n-2次...

        所以,总的操作次数为:(N-1) * N / 2 = N * N / 2 - N / 2

在大O符号表示法中,我们通常关注最高阶的项,而忽略低阶项和常数系数。在这种情况下,最高阶项是 n^2。因此,BubbleSort 的时间复杂度可以表示为 O(n^2)

        需要注意的是,BubbleSort 的平均和最坏情况下的时间复杂度都是 O(N^2),即使在最好情况下,数组已经是有序的,BubbleSort 也需要进行 n-1 轮比较,时间复杂度都是 O(N)。

实例六:

// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{assert(a);int begin = 0;int end = n - 1;// [begin, end]:begin和end是左闭右闭区间,因此有=号while (begin <= end){int mid = begin + ((end - begin) >> 1);if (a[mid] < x)begin = mid + 1;else if (a[mid] > x)end = mid - 1;elsereturn mid;}return -1;
}

        BinarySearch(二分查找)是一种高效的查找算法,它的时间复杂度为 O(log N),其中 N 是待查找数组的长度。

        在每一轮循环中,BinarySearch 将当前搜索范围缩小为原来的一半。假设当前搜索范围的长度为 len,在下一轮循环中,搜索范围的长度将减半为 len/2。因此,每一轮循环都可以排除一半的元素。

        最坏情况下,当搜索范围缩小到只有一个元素时,算法结束。而要将 n 个元素缩小到 1 个元素,需要进行 log₂(n) 次操作。

        因此,BinarySearch 的时间复杂度是 O(log n)。

        BinarySearch 在已排序的数组中查找元素,通过不断缩小搜索范围,每次排除掉一半的元素,因此它的时间复杂度非常低。

图解:

 实例七:

// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{if (0 == N)return 1;return Fac(N - 1) * N;
}

阶乘递归函数 Fac 的时间复杂度可以通过递归关系来分析。在这种情况下,递归的深度与输入规模 N 相关,每一层递归都会进行一次乘法操作。让我们分析一下:

  1. 第一递归层(N = N):执行 1 次 Fac(N - 1) * N乘法。
  2. 第二递归层(N = N-1):执行 1 次 Fac(N - 2) * N乘法。
  3. 第三递归层(N = N-2):执行 1 次 Fac(N - 1) * N乘法。
  4. ...
  5. 最后一层递归(N = 0):执行 1 次 Fac(0) * N乘法。

        总的乘法操作次数为 N 次。

因此,阶乘递归函数 Fac 的时间复杂度可以表示为 O(N),其中 N 是输入的规模。

        尽管递归函数的每一层都只执行了一个乘法操作,但由于递归的深度与输入规模相关,最终的时间复杂度是线性的。

        总结:递归算法时间复杂度是多次调用的累加。

扩展:

//计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{if (0 == N)return 1;for (size_t i = 0; i < N; ++i){++count;}return Fac(N - 1) * N;
}

阶乘递归函数 Fac 的时间复杂度可以通过递归关系来分析。在这种情况下,递归的深度与输入规模 N 相关,每一层递归都会进行一次乘法操作。让我们分析一下:

  1. 第一递归层(N = N):执行 1 次 Fac(N - 1) * N 乘法 + 执行 N 次 ++count
  2. 第二递归层(N = N-1):执行 1 次 Fac(N - 2) * N 乘法 + 执行 N-1 次 ++count
  3. 第三递归层(N = N-2):执行 1 次 Fac(N - 1) * N 乘法 + 执行 N-2 次 ++count
  4. ...
  5. 最后一层递归(N = 0):执行 1 次 Fac(0) * N 乘法 + 执行0次 ++count

由于上面每次递归执行的乘法只有一次,对于总的操作次数影响较小,可以忽略不记,所以总的操作次数为 N * N /2 次。

因此,阶乘递归函数 Fac 的时间复杂度可以表示为 O(N^2),其中 N 是输入的规模。

        尽管递归函数的每一层都只执行了一个乘法操作,但由于递归的深度与输入规模相关,最终的时间复杂度是线性的。

        总结:递归算法时间复杂度是多次调用的累加。

实例八:

// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{if (N < 3)return 1;return Fib(N - 1) + Fib(N - 2);
}

斐波那契递归函数 Fib 的时间复杂度可以通过递归关系来分析。在这种情况下,递归的深度与输入规模 N 相关,每一层递归都会进行两次递归调用。让我们分析一下:

  1. 第一层递归(N = N):执行 2^0 次递归调用。
  2. 第二层递归(N = N-1):执行 2^2 次递归调用。
  3. 第三层递归(N = N-2):执行 2^3 次递归调用。
  4. ...

        每一层递归调用的次数是指数级别增长的, 总的乘法操作次数为 2^N-1 次。 

        因此,总的递归调用次数可以近似表示为 2^N 次。

        由于每次递归调用都需要一些操作(在这里是两次递归调用),在最坏情况下,每次递归调用的时间开销相对较小。因此,总的时间复杂度仍然可以表示为 O(2^N)

但是实际上右边会缺少,总的时间复杂度低于等于 O(2^N)。

5.空间复杂度 

        空间复杂度也是一个数学表达式,是对一个算法在运行过程中额外临时占用存储空间大小的量度

        空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。

        空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法。

        注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。

实例一:

// 计算BubbleSort的空间复杂度?
void BubbleSort(int* a, int n)
{assert(a);for (size_t end = n; end > 0; --end){int exchange = 0;for (size_t i = 1; i < end; ++i){if (a[i - 1] > a[i]){Swap(&a[i - 1], &a[i]);exchange = 1;}}if (exchange == 0)break;}
}

        在 BubbleSort 中,输入数组不是额外开辟的空间,不算入到空间复杂度上,其余只使用了很少的额外空间,主要是用来存储一些临时变量,如循环索引、交换标志等。这些额外空间的使用量不会随着输入规模 n 的增加而显著变化。无论输入数组的大小如何,BubbleSort 需要的额外空间是固定的。

        因此,BubbleSort 的空间复杂度为 O(1)

实例二:

// 计算Fibonacci的空间复杂度?
// 返回斐波那契数列的前n项
long long* Fibonacci(size_t n)
{if (n == 0)return NULL;long long* fibArray = (long long*)malloc((n + 1) * sizeof(long long));fibArray[0] = 0;fibArray[1] = 1;for (int i = 2; i <= n; ++i){fibArray[i] = fibArray[i - 1] + fibArray[i - 2];}return fibArray;
}
  1. 额外数组的空间:Fibonacci 函数内部,通过动态内存分配 malloc 来创建一个大小为 (n + 1) 的长整型数组 fibArray,用于存储斐波那契数列的前 n 项。这个数组的空间占用是与 n 相关的。

        所以,总的空间复杂度由额外数组的空间复杂度决定。

        在这种情况下,空间复杂度是 O(N)

实例三:

// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{if (N == 0)return 1;return Fac(N - 1) * N;
}

        阶乘递归函数 Fac 的空间复杂度是 O(N),因为每次递归调用都会在函数调用栈上创建一个新的递归帧,每个递归帧需要一些内存空间来存储局部变量、返回地址等。

        在最坏情况下,当递归深度达到 N 时,需要在栈上保留 N 层递归帧。因此,空间复杂度与递归的深度成正比,为 O(N)

实例四:

// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{if (N == 0)return 1;return Fac(N - 1) * N;
}

        这个斐波那契递归函数的空间复杂度是 O(N)。虽然函数本身没有显式地使用额外的数组或数据结构,但是在递归调用的过程中,每次调用都会在函数调用栈中创建一个新的函数调用帧。由于递归函数会多次调用自身,每次调用都需要分配一些内存来存储函数参数、局部变量以及返回地址等信息,这些内存会随着递归深度的增加而累积。递归深度直接影响了调用栈中需要分配的内存空间数量。

        我们拿 Fib(6) 举例,在这个递归实现中,当你调用 Fib(6) 时,它会依次调用 Fib(5)Fib(4),然而并不是右边那个Fib(4) ,是 Fib(5) 下面的 Fib(4) ,然后这些调用会进一步调用更低层次的函数,以此类推。当调完 Fib(2)后该函数栈帧就销毁了,然后生成 Fib(1)的栈帧, Fib(1)调完后也就释放了,以此类推。由于栈空间是可以复用的,一个栈帧释放空间就还给操作系统了,可以给后面的函数调用留下空闲的空间,整个过程中,递归最深的层数也就是Fib(6)到 Fib(2)了,其余调用的都是之前栈帧释放的空间,且深度也低。

        因此,递归的空间复杂度通常与递归的深度成正比,即 O(N)。这意味着在最坏情况下,调用栈的深度将达到 N 层,每一层都需要一些内存空间。这也是为什么递归在解决某些问题时可能会导致栈溢出或效率较低的原因之一。

下面这个例子就证明了栈空间是可复用滴。

#include<stdio.h>
void Func1()
{int a = 0;printf("%p\n", &a);
}
void Func2()
{int b = 0;printf("%p\n", &b);
}
int main()
{Func1();Func2();return 0;
}

运行结果:

        因为在许多编译器和操作系统中,函数调用时会使用相同的堆栈帧空间。这意味着在一个函数结束后,其分配给局部变量的堆栈空间可能会被重用给下一个函数的局部变量。

        因此,虽然在逻辑上 Func1Func2 是在不同的函数调用中,它们的局部变量 ab 分别被分配到了相同的堆栈空间(因为这两个函数的栈帧在调用时被重用),从而导致它们的局部变量的地址也相同。

结束啦!!!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker 数据管理

文章目录 前言1、Dcoker 文件体系2、volume挂载案例2.1、挂载运行一个容器实例方法1方法2 3、volumes-from 案例4、备份/恢复数据卷5、删除数据卷 前言 为什么要有数据管理&#xff1f; 因为&#xff1a; Docker 是不提供持久化的 &#xff0c;容器是不稳定的&#xff1b;一个…

TCP 三次握手,四次挥手

1、三次握手 第一次握手 SYN 等于1&#xff0c;SeqX 第二次握手 SYN等于1 ACK等于1&#xff0c;SeqY&#xff0c;AckX1 第三次SYN等于0 ACK等于1&#xff0c;SeqX1&#xff0c;AckY1 ackRow都是对应请求seqraw&#xff0c;三次握手后&#xff0c;Seq就是服务器前一个包中的ac…

【Git】—— 标签管理

目录 &#xff08;一&#xff09;理解标签 1、作用 &#xff08;二&#xff09;创建标签 &#xff08;三&#xff09;操作标签 1、删除标签 2、推送标签 3、删除远程标签 &#xff08;一&#xff09;理解标签 标签 tag &#xff0c;可以简单的理解为是对某次 commit 的…

Java基础篇--运算符

目录 算术运算符 赋值运算符 比较运算符 逻辑运算符 条件运算符&#xff08;?:&#xff09; instanceof 运算符 Java运算符优先级 在程序中经常出现一些特殊符号&#xff0c;如、-、*、、>等&#xff0c;这些特殊符号称作运算符。运算符用于对数据进行算术运算、赋值…

flowable-ui部署(6.80)

不使用tomcat直接看最后边 前置条件&#xff1a;Apache Tomcat/9.0.78版本及以下 https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.78/bin/apache-tomcat-9.0.78-windows-x64.zip 一、下载资源 https://github.com/flowable/flowable-engine/releases/download/flowable-6.…

vue3+ts使用antv/x6

使用 2.x 版本 x6.antv 新官网: 安装 npm install antv/x6 //"antv/x6": "^2.1.6",项目结构 1、初始化画布 index.vue <template><div id"container"></div> </template><script setup langts> import { onM…

TCP网络服务器设计

最近设计了一个网络服务器程序&#xff0c;对于4C8G的机器配置&#xff0c;TPS可以达到5W。业务处理逻辑是简单的字符串处理。服务器接收请求后对下游进行类似广播的发送。在此分享一下设计方式&#xff0c;如果有改进思路欢迎大家交流分享。 程序运行在CentOS7.9操作系统上&a…

yum 安装本地包 rpm

有时直接yum install 有几个包死活下不下来 根据网址&#xff0c;手动下载&#xff0c;下载后上传至 centos 然后运行 sudo yum localinstall xxx.rpm 即可安装 参考 https://blog.csdn.net/weiguang1017/article/details/52293244

利用状态监测和机器学习提高冷却塔性能的具体方法

在现代工业生产中&#xff0c;冷却塔扮演着至关重要的角色&#xff0c;它们的性能直接影响着工艺流程的稳定性和效率。为了确保冷却塔的正常运行和减少系统故障&#xff0c;状态监测和机器学习成为了关键技术。 图.冷却塔&#xff08;PreMaint&#xff09; 在前文《基于人工智…

开发工具IDEA的下载与初步使用【各种快捷键的设置,使你的开发事半功倍】

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于IDEA的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.IDEA的简介以及优势 二.IDEA的下载 1.下…

Android应用开发(35)SufaceView基本用法

Android应用开发学习笔记——目录索引 参考Android官网&#xff1a;https://developer.android.com/reference/android/view/SurfaceView 一、SurfaceView简介 SurfaceView派生自View&#xff0c;提供嵌入视图层次结构内部的专用绘图表面&#xff0c;SurfaceView可以在主线程之…

opencv基础60-用分水岭算法cv2.distanceTransform()实现图像分割与提取原理及示例

在图像处理的过程中&#xff0c;经常需要从图像中将前景对象作为目标图像分割或者提取出来。例如&#xff0c;在视频监控中&#xff0c;观测到的是固定背景下的视频内容&#xff0c;而我们对背景本身并无兴趣&#xff0c;感兴趣的是背景中出现的车辆、行人或者其他对象。我们希…

从安装 Seata 开始的分布式事务之旅 springboot集成seata

从安装 Seata 开始的分布式事务之旅 介绍什么是 Seata&#xff1f; 安装 Seata Server下载 Seata Server 发行版配置Seata解压文件配置Seata的yml文件把配置文件config.txt加载到nacos上修改config.txt文件加载到nacos上 启动Seata服务正常启动查看启动日志打开控制台页面 启动…

限流在不同场景的最佳实践

目录导读 限流在不同场景的最佳实践1. 前言2. 为什么要限流3. 有哪些限流场景3.1 限流场景分类3.2 限流与熔断降级之间的关系3.3 非业务限流3.4 业务限流 4. 有哪些限流算法4.1 计数器限流算法4.2 漏桶限流算法4.3 令牌桶限流算法4.4 滑动时间窗限流算法4.5 限流算法选型 5. 限…

NPM包的安装、更新、卸载

目录 1、下载安装全局包 2、解决全局安装包时的EACCES权限错误 2.1 重新安装NPM 2.2 手动更改npm的默认目录 3、更新从注册表下载的包 3.1 更新本地包 3.2 更新全局安装的软件包 3.3 确定哪些全局包需要更新 3.4 更新单个全局包 3.5 更新所有全局安装的软件包 4、在项…

VLAN监控及常见问题排查

局域网&#xff0c;我们通常称为LAN&#xff0c;是一种由基于同一地理位置的设备组成的网络&#xff0c;可实现它们之间的通信&#xff0c;局域网的虚拟对应物是虚拟局域网或 VLAN。VLAN 增强了 LAN&#xff0c;提供了进行更改的灵活性、更高的可扩展性和更好的安全性。 使用 …

使用 Gradio 构建生成式 AI 应用程序(一): 图片内容读取app

今天我们来学习DeepLearning.AI的在线课程&#xff1a;Building Generative AI Applications with Gradio&#xff0c;该课程主要讲述利用gradio来部署机器学习算法应用程序, 今天我们来学习第一课&#xff1a;Image captioning app&#xff0c;该课程主要讲述如何从图片中读取…

JavaScript 操作历史记录api怎样使用 JavaScript

JavaScript 操作历史记录api怎样使用 JavaScript History 是 window 对象中的一个 JavaScript 对象&#xff0c;它包含了关于浏览器会话历史的详细信息。你所访问过的 URL 列表将被像堆栈一样存储起来。浏览器上的返回和前进按钮使用的就是 history 的信息。 History 对象包含…

报错注入(主键重复)攻击原理

基本原理 利用数据表中主键不能重复的特点&#xff0c;通过构造重复的主键&#xff0c;使得数据库报错&#xff0c;并将报错结果返回到前端。 SQL说明函数 以pet数据表为例进行说明 rond(): 返回[0,1)区间内的任意浮点数。 count(): 返回每个组的列行数。 如&#xff0…

IDEA新建类时自动设置类注释信息,署名和日期

IDEA设置路径 File --> Settings --> Editor --> File and Code Templates --> Include --> File Header 官方模板 这里 ${USER} 会读取计算机的用户名 ${DATE}是日期 ${TIME}是时间 /*** Author ${USER}* Date ${DATE} ${TIME}* Version 1.0*/