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

本章重点

  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()
{
	//使用斐波那契数列递归方法计算40
	long 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;
		else
			return 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.mfbz.cn/a/69384.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【React学习】—类式组件(六)

【React学习】—类式组件&#xff08;六&#xff09; <script type"text/babel">//创建类式组件class MyComponent extends React.Component{render() {// render是放在哪里的&#xff1f;MyComponent的原型对象上&#xff0c;供实例使用// render中的this是谁…

Debian 12.1 正式发布

导读Debian 12.1 现已发布&#xff0c;这是对稳定发行版 Debian 12&#xff08;代号 Bookworm &#xff09;的首次更新。本次发布主要增加了安全问题的修正&#xff0c;并对严重问题进行了一些调整。 一些更新内容包括&#xff1a; 妥善处理系统用户的创建&#xff1b;修复 eq…

08-3_Qt 5.9 C++开发指南_Graphics View绘图架构

文章目录 1. 场景、视图与图形项1.1 场景1.2 视图1.3 图形项 2. Graphics View 的坐标系统2.1 图形项坐标2.2 视图坐标2.3 场景坐标2.4 坐标映射 3. Graphics View 相关的类3.1 QGraphicsView 类的主要接口函数3.2 QGraphicsScene 类的主要接口函数3.3 图形项 4. 实例介绍 1. 场…

OPENCV C++(八)HOG的实现

hog适合做行人的识别和车辆识别 对一定区域的形状描述方法 可以表示较大的形状 把图像分成一个一个小的区域的直方图 用cell做单位做直方图 计算各个像素的梯度强度和方向 用3*3的像素组成一个cell 3*3的cell组成一个block来归一化 提高亮度不变性 常用SVM分类器一起使用…

到 2030 年API 攻击预计将激增近 1000%

导读云原生应用程序编程接口管理公司 Kong 联合外部经济学家的最新研究预计&#xff0c;截至 2030 年 API 攻击将激增 996%&#xff0c;意味着与 API 相关的网络威胁的频率和强度都显着升级。 这项研究由 Kong 分析师和布朗大学副教授 Christopher Whaley 博士合作进行&#x…

ubuntu20.04 docker 下编译 tensorflow-gpu

ubuntu20.04 安装tensorflow-gpu 配置&#xff1a; 系统 ubuntu 20.04 LTS 显卡 GTX 1060 6G 1 安装cudatoolkit &#xff08;我选 CUDA Toolkit 12.2 &#xff09; NVIDIA CUDA Installation Guide for Linux https://docs.nvidia.com/cuda/cuda-installation-guide-linux/in…

Vue3 —— reactive 全家桶及源码学习

该文章是在学习 小满vue3 课程的随堂记录示例均采用 <script setup>&#xff0c;且包含 typescript 的基础用法 前言 上一篇学习了 ref 全家桶&#xff0c;在此基础上一起学习下 reactive 全家桶 一、reactive 对比 ref ref 可以接收 所有类型&#xff0c;reactive 只…

3分钟自建查分系统?现在每个人都可以实现了

学生成绩查询系统在现代教育管理中扮演着重要的角色&#xff0c;它不仅可以方便学生和家长查询成绩&#xff0c;也能帮助老师更好地管理和分析学生的学业表现。作为一名教师&#xff0c;了解如何制作学生成绩查询系统是提高教学效率和管理学生成绩便利性的关键。 在制作学生成…

数据结构笔记--链表经典高频题

目录 前言 1--反转单向链表 2--反转单向链表-II 3--反转双向链表 4--打印两个有序链表的公共部分 5--回文链表 6--链表调整 7--复制含有随机指针结点的链表 8--两个单链表相交问题 前言 面经&#xff1a; 针对链表的题目&#xff0c;对于笔试可以不太在乎空间复杂度&a…

深度学习之用PyTorch实现逻辑回归

0.1 学习视频源于&#xff1a;b站&#xff1a;刘二大人《PyTorch深度学习实践》 0.2 本章内容为自主学习总结内容&#xff0c;若有错误欢迎指正&#xff01; 代码&#xff08;类比线性回归&#xff09;&#xff1a; # 调用库 import torch import torch.nn.functional as F#…

手把手教你快速实现内网穿透

快速内网穿透教程 文章目录 快速内网穿透教程前言*cpolar内网穿透使用教程*1. 安装cpolar内网穿透工具1.1 Windows系统1.2 Linux系统1.2.1 安装1.2.2 向系统添加服务1.2.3 启动服务1.2.4 查看服务状态 2. 创建隧道映射内网端口3. 获取公网地址 前言 要想实现在公网访问到本地的…

【CSS】说说响应式布局

目录 一、是什么 二、怎么实现 1、媒体查询 2、百分比 3、vw/vh 4、小结 三、总结 一、是什么 响应式设计简而言之&#xff0c;就是一个网站能够兼容多个终端——而不是为每个终端做一个特定的版本。 响应式网站常见特点&#xff1a; 同时适配PC 平板 手机等…

MacOS创建NetworkExtension 【保姆级流程】

MacOS创建NetworkExtension (保姆级流程) 因为自己工作中的项目&#xff0c;是运行在macos系统上&#xff0c;其中的一部分功能是通过NetworkExtension来获取系统中的流量来做相应的处理&#xff0c;所以也想自己创建一个NetworkExtension&#xff0c;三天&#xff0c;不知道踩…

Linux系统USB转串口芯片 GPIO使用教程

一、简介 WCH的多款USB转单路/多路异步串口芯片&#xff0c;除串口接口以外&#xff0c;还提供独立的GPIO接口&#xff0c;各GPIO引脚支持独立的输出输入&#xff0c;GPIO功能的使用需要与计算机端厂商驱动程序和应用软件配合使用。各芯片的默认GPIO引脚状态有所区别&#xff…

深入理解机器学习与极大似然之间的联系

似然函数&#xff1a;事件A的发生含着有许多其它事件的发生。所以我就把这些其它事件发生的联合概率来作为事件A的概率&#xff0c;也就是似然函数。数据类型的不同&#xff08;离散型和连续性&#xff09;就有不同的似然函数 极大似然极大似然估计方法&#xff08;Maximum Li…

【逗老师的PMP学习笔记】10、项目沟通管理

目录 一、规划沟通管理1、【关键工具】沟通技术2、【关键工具】沟通模型&#xff08;沟通模式&#xff09;3、【关键工具】沟通方法4、【关键工具】文化意识5、【关键输出】沟通管理计划 二、管理沟通1、【关键工具】会议管理 三、监督沟通 一、规划沟通管理 规划沟通管理是基于…

Java集合知识回顾:从分类到工具类,掌握精髓

文章目录 1. 集合的分类2. Collection 接口3. Map 接口4. 泛型5. Collections 工具类总结 在Java编程世界中&#xff0c;集合是一项极为重要的知识&#xff0c;为我们的程序设计提供了强大的数据结构和处理手段。在本篇文章中&#xff0c;我们将回顾集合的分类以及相关的重要概…

dotNet 之网络TCP

**硬件支持型号 点击 查看 硬件支持 详情** DTU701 产品详情 DTU702 产品详情 DTU801 产品详情 DTU802 产品详情 DTU902 产品详情 G5501 产品详情 ARM dotnet 编程 dotNet使用TCP&#xff0c;可以使用Socket和TcpClient 、TcpListener类 2种&#xff0c;对于高级用户&…

nbcio-boot因升级mybatis-plus到3.5.3.1和JSQLParser 到4.6引起的online表单开发的数据库导入出错解决

更多功能看演示系统 gitee源代码地址 后端代码&#xff1a; https://gitee.com/nbacheng/nbcio-boot 前端代码&#xff1a;https://gitee.com/nbacheng/nbcio-vue.git 在线演示&#xff08;包括H5&#xff09; &#xff1a; http://122.227.135.243:9888 nbcio-boot因升级…

怎样学会单片机

0、学单片机首先要明白&#xff0c;一个单片机啥也干不了&#xff0c;学单片机的目的是学习怎么用单片机驱动外部设备&#xff0c;比如数码管&#xff0c;电机&#xff0c;液晶屏等&#xff0c;这个需要外围电路的配合&#xff0c;所以学习单片机在这个层面上可以等同为学习单片…
最新文章