归并排序 (递归实+非递归)

news2025/2/25 5:48:56

前言

归并排序是一种逻辑很简单,但是实现有点难度的排序,尤其是非递归,对于区间的把握更是需要一点逻辑的推导,但是没有关系,轻松拿捏

归并排序gif

归并排序单趟实现

1,创建tmp数组,

2,递归到最小值进行排序,同时拷贝到题目破数组里面

3,这里的关键逻辑实现是,递归回来的时候进行排序

4,最后我们把数值重新拷贝回去原数组

注意:这里的取值我们直接取中间值进行递归,一分为二

代码实现注意事项

关于创建tmp数组,我们需要创建的数组应该是等大的,要么就进行初始化。

  • 因为我们是malloc出来的空间,
  • 为什么malloc出来的空间必须是等大的,因为这里我们用的是Visual Studio 2022的编译器,这个编译器里面是不支持可变长数组的。所以我们需要用malloc,或者realloc来实现创建空间,也就是动态内存的分配
  • malloc创建空间的时候,空间里面是有随机值的
  • realloc创建的空间里面的数值是0
  • 所以一旦我们进行排序的时候,如果我们不进行覆盖的话,这里面的数值也会参与排序,从而导致数值出现偏差

这里对于这一点不清楚的可以看看之前写的文章,因为当时写这个文章的时候,刚接触编程,所以讲述的主要是以语法和一些简单的性质特点,希望可以起到帮助

  • 动态内存函数的使用和综合实践-malloc,free,realloc,calloc_内存申请函数-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/Jason_from_China/article/details/137075045

代码实现

//归并排序(递归实现)子函数(实现函数)
void _MergeSort01(int* a, int* tmp, int begin, int end)
{
	if (begin >= end)
		return;
	
	int mini = (begin + end) / 2;
	//注意,这里关键在于,区间的传递,
	//不应该传递【left,mini-1】【mini,right】
	//  应该传递【left,mini】【mini+1,right】
	//递归左右区间, 递归到最小个数进行对比
	_MergeSort01(a, tmp, begin, mini);
	_MergeSort01(a, tmp, mini + 1, end);

	int begin1 = begin, end1 = mini;
	int begin2 = mini + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	//数据从tmp拷贝回去数组a里面
	memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}
//归并排序(递归实现)(创建tmp函数)
void MergeSort01(int* a, int n)
{
	//这里n传递过来的是个数
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("MergeSort01:tmp:err:");
		exit(1);//正常退出
	}
	_MergeSort01(a, tmp, 0, n - 1);
	free(tmp);
	tmp = NULL;
}

解释:

  1. _MergeSort01 函数:这是归并排序的递归子函数,它接收三个参数:数组 a,临时数组 tmp,以及要排序的数组区间 [begin, end]

    • 如果 begin 大于或等于 end,则表示区间内没有元素或只有一个元素,不需要排序,直接返回。
    • 计算区间的中点 mini,将数组分为两部分:[begin, mini] 和 [mini + 1, end]
    • 对这两部分分别递归调用 _MergeSort01 函数进行排序,直到每个子区间只有一个元素。
  2. 合并过程

    • 初始化两个指针 begin1 和 end1 指向第一个子区间的开始和结束位置,begin2 和 end2 指向第二个子区间的开始和结束位置。
    • 使用一个索引 i 指向 tmp 数组的当前位置,用于存放合并后的有序元素。
    • 使用两个 while 循环来比较两个子区间的元素,将较小的元素复制到 tmp 数组中,并移动对应的指针。
    • 如果第一个子区间的所有元素已经被复制到 tmp 中,但第二个子区间还有剩余元素,将这些剩余元素复制到 tmp 数组的剩余位置。
    • 同理,如果第二个子区间的所有元素已经被复制,将第一个子区间的剩余元素复制到 tmp
  3. 拷贝回原数组

    • 使用 memcpy 函数将 tmp 数组中从索引 begin 开始的元素复制回原数组 a。这里计算了需要复制的元素数量为 end - begin + 1,乘以 sizeof(int) 来确定字节数。
  4. MergeSort01 函数:这是归并排序的初始化函数,接收数组 a 和数组的长度 n

    • 首先,使用 malloc 分配一个临时数组 tmp,大小为 n 个 int
    • 如果内存分配失败,使用 perror 打印错误信息,并调用 exit(1) 退出程序。
    • 调用 _MergeSort01 函数,传入数组 a、临时数组 tmp 和整个数组的区间 [0, n - 1] 进行排序。
    • 排序完成后,使用 free 释放 tmp 所占用的内存,并设置 tmp 为 NULL

归并排序的时间复杂度为 O(n log n),在大多数情况下表现良好,尤其是当数据量大且需要稳定排序时。不过,由于它需要额外的内存空间(如这里的 tmp 数组),在内存受限的情况下可能不是最佳选择。

递归排序(非递归解决)

前言

这里递归排序的非递归方式还是比较有难度的,所以需要多次观看两遍,我也会多次详细的讲解,促进知识的理解

非递归实现gif

非递归实现单趟实现

这里的关键的在于对于区间的理解

尤其是

            //但是此时可能产生越界行为,我们可以打印出来看一下
            //产生越界的
            //begin1, end1, begin2, end2 ->                       end2->[,][,15]
            //begin1, end1, begin2, end2 ->               begin2, end2->[,][12,15]
            //begin1, end1, begin2, end2 ->         end1, begin2, end2->[,11][12,15]
            //解决
            //begin2, end2区间越界的,我们直接下一次进行递归就可以
            //end2,我们重新设定新的end2的区间

所以我们需要对这个区间进行解决

代码的逐步实现

这里代码的实现,因为非递归实现是有难度的,所以这里我不会直接给出答案,而是逐步的写出步骤,促进理解

1,创建拷贝空间tmp,设定gap,一组假设初始1个数值,当然初始的时候,实际也是一个数值

//归并排序(非递归实现)(创建tmp函数)
void MergeSort02(int* a, int n)
{
	//这里n传递过来的是个数
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("MergeSort01:tmp:err:");
		exit(1);//正常退出
	}
	//假设初始化起始是一组
	int gap = 1;
}

2,区间的设定

这里依旧是两个区间,我们根据这个区间设定的数值

这里是很关键的一点

    int begin1 = 0, end1 = 0 + gap - 1;
    int begin2 = 0 + gap, end2 = 0 + 2 * gap - 1;

//假设此时就两个数值,一组是一个数值,也就是这两个数值进行最小对比

//begin1左区间的初始位置,肯定是0

//end1左区间的结束位置。

  • 也就是第一个区间的结束位置,也就是一组,
  • -1是因为一组是实际的个数
  • +0,这里因为加的是第一个区间起始位置
  • 因为我们也可能是2-4区间的,不可能只是这一个区间

//begin2第二个区间的起始位置,这里还是比较好理解的,只要理解了end1

//end2第二个区间的结束位置,这里同理,

  • 我们有可能是2-4区间的,不只是0-1或者0-2区间的。而且随着第一次排序的结束,第二次排序就变成两个合并了,继续和另外的对比,所以是变化的
  • 0也就是初始位置
  • 2*gap,因为这里是需要加上中间两组,
  • 最后-1,因为gap是个数,不是下标
//归并排序(非递归实现)(创建tmp函数)
void MergeSort02(int* a, int n)
{
	//这里n传递过来的是个数
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("MergeSort01:tmp:err:");
		exit(1);//正常退出
	}
	//假设初始化起始是一组
	int gap = 1;

	int begin1 = 0, end1 = 0 + gap - 1;
	int begin2 = 0 + gap, end2 = 0 + 2 * gap - 1;
}

3,排序的实现


//归并排序(非递归实现)(创建tmp函数)
void MergeSort02(int* a, int n)
{
	//这里n传递过来的是个数
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("MergeSort01:tmp:err:");
		exit(1);//正常退出
	}

	//假设初始化起始是一组
	int gap = 1;
	while (gap < n)
	{
		//递归到最小值,假设一组是一个,也就是此时是最小值进行比较并且入数组,关键是找出区间
		//这里一次跳过两组
		for (int i = 0; i < n - 1; i = i + gap * 2)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
		}
		gap *= 2;
	}
}

4,区间的拷贝

这里区间的拷贝我们需要注意的是我们拷贝的是区间,并且我们需要一边拷贝一边拷贝回去,为什么需要一边排序一边拷贝回去,在接下来的越界行为里面我们会进行讲解

最后需要关注的点是,关于拷贝空间的大小,一定是end2-begin1,因为这两个区间是排序成功然后进入到tmp里面

所以我们需要从tmp拷贝回去,不能弄错了


//归并排序(非递归实现)(创建tmp函数)
void MergeSort02(int* a, int n)
{
	//这里n传递过来的是个数
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("MergeSort01:tmp:err:");
		exit(1);//正常退出
	}

	//假设初始化起始是一组
	int gap = 1;
	while (gap < n)
	{
		//递归到最小值,假设一组是一个,也就是此时是最小值进行比较并且入数组,关键是找出区间
		//这里一次跳过两组
		for (int i = 0; i < n - 1; i = i + gap * 2)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
			//拷贝回a数组里面,这里的区间是end2-begin1之间的区间
			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}
}

5,解决越界行为

这里我们可以很清楚的看到哪些位置会产生越界行为

这里我们处理一下之后,我们发现就解决了


            //产生越界的区间有
            //begin1, end1, begin2, end2 ->                       end2->[,][,15]
            //begin1, end1, begin2, end2 ->               begin2, end2->[,][12,15]
            //begin1, end1, begin2, end2 ->         end1, begin2, end2->[,11][12,15]
            //解决
            //begin2, end2区间越界的,我们直接下一次进行递归就可以
            //end2,我们重新设定新的end2的区间


//归并排序(非递归实现)(创建tmp函数)
void MergeSort02(int* a, int n)
{
	//这里n传递过来的是个数
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("MergeSort01:tmp:err:");
		exit(1);//正常退出
	}

	//假设初始化起始是一组
	int gap = 1;
	while (gap < n)
	{
		//递归到最小值,假设一组是一个,也就是此时是最小值进行比较并且入数组,关键是找出区间
		//这里一次跳过两组
		for (int i = 0; i < n - 1; i = i + gap * 2)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//但是此时可能产生越界行为,我们可以打印出来看一下
			//产生越界的
			//begin1, end1, begin2, end2 ->                       end2->[,][,15]
			//begin1, end1, begin2, end2 ->               begin2, end2->[,][12,15]
			//begin1, end1, begin2, end2 ->         end1, begin2, end2->[,11][12,15]
			//解决
			//begin2, end2区间越界的,我们直接下一次进行递归就可以
			//end2,我们重新设定新的end2的区间
			printf("[begin1=%d,end1=%d][begin2=%d,end2=%d]\n", begin1, end1, begin2, end2);
			if (begin2 > n)
			{
				break;
			}
			if (end2 > n)
			{
				//记得这里需要是n-1不能是n,因为end2是下标
				end2 = n-1;
			}
			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
			//拷贝回a数组里面,这里的区间是end2-begin1之间的区间
			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}
}

时间复杂度

时间复杂度分析如下:

  1. 分解阶段:归并排序首先将原始数组分解成多个子数组,每个子数组的大小为1。这个过程是线性的,时间复杂度为 O(n),其中 n 是原始数组的大小。

  2. 合并阶段:在合并阶段,算法将这些有序的子数组逐步合并成更大的有序数组。每次合并操作都需要将两个有序数组合并成一个更大的有序数组,这个过程的时间复杂度是 O(n)。

  3. 重复合并:归并排序的合并阶段会重复进行,直到所有的元素都被合并成一个有序数组。合并的次数可以看作是对数级的,具体来说,是 log2(n) 次。

综合以上两点,归并排序的总时间复杂度主要由合并阶段决定,因为分解阶段是线性的,而合并阶段虽然每次都是线性的,但由于重复了对数级次数,所以总的时间复杂度是:

𝑂(𝑛)+𝑂(𝑛log⁡𝑛)

由于在大 O 符号中,我们只关注最高次项和系数,所以归并排序的时间复杂度通常被简化为:𝑂(𝑛log⁡𝑛)

这意味着归并排序的时间效率与数组的大小成对数关系,是相当高效的排序算法。此外,归并排序是一种稳定的排序算法,它保持了相等元素的原始顺序。然而,它需要与原始数组同样大小的额外空间来存储临时数组,这可能会在空间受限的情况下成为一个问题。

代码实现


//归并排序(非递归实现)(创建tmp函数)
void MergeSort02(int* a, int n)
{
	//这里n传递过来的是个数
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("MergeSort01:tmp:err:");
		exit(1);//正常退出
	}

	//假设初始化起始是一组
	int gap = 1;
	while (gap < n)
	{
		//递归到最小值,假设一组是一个,也就是此时是最小值进行比较并且入数组,关键是找出区间
		//这里一次跳过两组
		for (int i = 0; i < n - 1; i = i + gap * 2)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//但是此时可能产生越界行为,我们可以打印出来看一下
			//产生越界的
			//begin1, end1, begin2, end2 ->                       end2->[,][,15]
			//begin1, end1, begin2, end2 ->               begin2, end2->[,][12,15]
			//begin1, end1, begin2, end2 ->         end1, begin2, end2->[,11][12,15]
			//解决
			//begin2, end2区间越界的,我们直接下一次进行递归就可以
			//end2,我们重新设定新的end2的区间
			printf("[begin1=%d,end1=%d][begin2=%d,end2=%d]\n", begin1, end1, begin2, end2);
			if (begin2 > n)
			{
				break;
			}
			if (end2 > n)
			{
				//记得这里需要是n-1不能是n,因为end2是下标
				end2 = n-1;
			}
			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
			//拷贝回a数组里面,这里的区间是end2-begin1之间的区间
			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}
}

解释:

  1. 内存分配:首先,函数使用 malloc 分配一个与原数组 a 同样大小的临时数组 tmp。如果内存分配失败,使用 perror 打印错误信息,并调用 exit 退出程序。

  2. 初始化间隔gap 变量初始化为 1,表示每个元素自身作为一个有序的子数组。

  3. 外层循环:使用 while 循环来逐步增加 gap 的值,每次迭代将 gap 乘以 2。这个循环继续进行,直到 gap 大于或等于数组的长度 n

  4. 内层循环:对于每个 gap 值,使用 for 循环遍历数组,计算每次合并操作的起始索引 i。每次迭代,i 增加 gap * 2,以确保每次处理的子数组间隔是 gap

  5. 计算子数组边界:在每次迭代中,计算两个子数组的起始和结束索引:begin1end1begin2end2。这两个子数组的间隔是 gap

  6. 处理数组越界:如果 begin2 大于数组长度 n,循环将终止。如果 end2 超出了数组的界限,将其调整为 n-1

  7. 合并子数组:使用 while 循环来合并两个子数组。比较 a[begin1]a[begin2],将较小的元素复制到 tmp 数组中,并相应地移动索引。当一个子数组的所有元素都被复制后,使用另外两个 while 循环复制剩余子数组中的元素。

  8. 拷贝回原数组:使用 memcpytmp 中的有序子数组复制回原数组 a。这里,拷贝的区间是从索引 iend2,拷贝的元素数量是 end2 - i + 1

  9. 更新间隔:外层循环的 gap 每次迭代后翻倍,这样在每次迭代中,处理的子数组的大小逐渐增加,直到整个数组被排序。

非递归的归并排序在某些情况下可能比递归版本更有效,因为它避免了递归调用的开销,尤其是在深度很大的递归树中。然而,它需要更多的迭代来逐步构建有序数组,因此在实际应用中,选择哪种实现取决于具体的需求和场景。

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

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

相关文章

《Windows API每日一练》4.6 矩形、区域和裁剪

在前面的4.3节中我们讲述了绘制矩形的API函数Rectangle和RoundRect。本节我们将介绍另外一组使用RECT矩形结构和区域的绘图函数。 本节必须掌握的知识点&#xff1a; 矩形 第28练&#xff1a;绘制随机矩形 矩形与区域的裁剪 第29练&#xff1a;区域裁剪 4.6.1 矩形 ■FillRe…

HarmonyOS开发知识 :扩展修饰器,实现节流、防抖、权限申请

引言 防重复点击&#xff0c;利用装饰器面向切面&#xff08;AOP&#xff09;的特性结合闭包&#xff0c;实现节流、防抖和封装权限申请。 节流 节流是忽略操作&#xff0c;在触发事件时&#xff0c;立即执行目标操作&#xff0c;如果在指定的时间区间内再次触发了事件&…

C语言中的内存动态管理

1.为什么有动态内存管理 int a20;//开辟4个字节 int arr[10]{0};//开辟40个字节 上述的代码有两个特点 1.开辟空间的大小是固定的。 2.数组在申明的时候已经固定了大小&#xff0c;无法更改。 这样写代码不够灵活&#xff0c;所以c语言中引入了动态内存管理&#xff0c;让程序…

Java | Leetcode Java题解之第165题比较版本号

题目&#xff1a; 题解&#xff1a; class Solution {public int compareVersion(String version1, String version2) {int n version1.length(), m version2.length();int i 0, j 0;while (i < n || j < m) {int x 0;for (; i < n && version1.charAt(…

Flat Ads:全球领先的移动广告营销平台

Flat Ads是全球领先的移动广告营销平台,凭借独家开发者矩阵网络和程序化广告交易平台,帮助广告主在全球范围内精准获取目标用户,提升品牌知名度,实现业务增长目标。同时,Flat Ads也致力于为全球开发者提供高效变现、最大化收益的一站式解决方案,助力开发者实现商业价值最大化。…

怎么给软件做数字证书签名?

要想让软件获得身份并让计算机识别软件发布者就需要申请数字证书&#xff0c;CA机构严格实名认证后签发的数字证书就可以对指定的软件签名使用。 代码签名是使用数字证书对软件、固件、脚本和可执行文件进行签名的过程&#xff0c;旨在向最终用户和客户保证代码的完整性和真实…

【论文阅读】-- Attribute-Aware RBFs:使用 RT Core 范围查询交互式可视化时间序列颗粒体积

Attribute-Aware RBFs: Interactive Visualization of Time Series Particle Volumes Using RT Core Range Queries 1 引言2 相关工作2.1 粒子体渲染2.2 RT核心方法 3 渲染彩色时间序列粒子体积3.1 场重构3.1.1 密度场 Φ3.1.2 属性字段 θ3.1.3 优化场重建 3.2 树结构构建与调…

最新ios苹果手机版植物大战僵尸杂交版下载安装2024六月最新

嘿&#xff0c;亲爱的小可爱们~ &#x1f31f; 今天给你们带来一款超炫的游戏——植物大战僵尸杂交版2024&#xff01;这可不是普通的植物大战僵尸哦&#xff0c;它融合了最新的科技元素和创新玩法&#xff0c;让你在打僵尸的战场上大呼过瘾。快跟着我一起探索这个神奇的游戏世…

在PHP项目中使用阿里云消息队列MQ集成RabbitMQ的完整指南与问题解决

在现代Web应用程序中&#xff0c;消息队列系统扮演着至关重要的角色&#xff0c;帮助开发者实现异步处理、削峰填谷、任务调度等功能。阿里云消息队列MQ作为一种高可用、可伸缩的消息队列服务&#xff0c;为开发者提供了可靠的消息投递和处理能力。而RabbitMQ则是一种广泛使用的…

yolov9-pytorch 深度学习目标检测算法模型

YOLOv9 论文 https://arxiv.org/abs/2402.13616 模型结构 YOLOv9将可编程梯度信息 (PGI) 概念与通用 ELAN (GELAN)架构相结合而开发&#xff0c;代表了准确性、速度和效率方面的重大飞跃。 算法原理 Yolov9将可编程梯度信息&#xff08;PGI&#xff09;和GLEAN&#xff08…

时隔一年,SSD大涨价?

同样产品&#xff0c;2T&#xff0c;去年400多到手&#xff0c;今年700。 去年 今年

comfyui虚拟试衣、ai换装、电商换装源码

一、AI换装技术博客 1. 项目介绍 IDM-VTON 是一个虚拟试衣模型&#xff0c;可以在 ComfyUI 中进行部署。相比于其他虚拟试衣模型&#xff0c;如 OOTDiffusion&#xff0c;IDM-VTON 提升了图像保真度和细节保留&#xff0c;更强调真实感,而且就算是侧面的模特或者背面的模特都…

数据库:与红黑树不同的延迟序列

在内存里维护一个序列&#xff0c;可能第一个想到的就是红黑树。但是&#xff0c;红黑树算法复杂&#xff0c;这还不是主要的&#xff0c;主要的问题是&#xff1a;红黑树的空间利用率低。 红黑树的空间利用率 一个红黑树的节点&#xff0c;包括父节点指针、两个子节点指针、…

智慧校园发展趋势:2024年及未来教育科技展望

展望2024年及未来的教育科技领域&#xff0c;智慧校园的发展正引领着一场教育模式的深刻变革&#xff0c;其核心在于更深层次地融合技术与教育实践。随着人工智能技术的不断成熟&#xff0c;个性化学习将不再停留于表面&#xff0c;而是深入到每个学生的个性化需求之中。通过精…

SpringMVC系列七: 手动实现SpringMVC底层机制-上

手动实现SpringMVC底层机制 博客的技术栈分析 &#x1f6e0;️具体实现细节总结 &#x1f41f;准备工作&#x1f34d;搭建SpringMVC底层机制开发环境 实现任务阶段一&#x1f34d;开发ZzwDispatcherServlet&#x1f966;说明: 编写ZzwDispatcherServlet充当原生的DispatcherSer…

电脑怎么录音?分享2种音频录制方法

在日常生活和工作中&#xff0c;我们经常需要录制电脑上的音频&#xff0c;无论是为了记录会议内容、保存网络课程&#xff0c;还是为了制作自己的音频素材&#xff0c;录音功能都显得尤为重要。那么电脑怎么录音&#xff1f;本文将详细介绍2种方法教你如何在电脑上进行录音&am…

如何避免接口重复请求(axios推荐使用AbortController)

前言&#xff1a; 我们日常开发中&#xff0c;经常会遇到点击一个按钮或者进行搜索时&#xff0c;请求接口的需求。 如果我们不做优化&#xff0c;连续点击按钮或者进行搜索&#xff0c;接口会重复请求。 以axios为例&#xff0c;我们一般以以下几种方法为主&#xff1a; 1…

二进制数转字符串

题目链接 二进制数转字符串 题目描述 注意点 32位包括输出中的 “0.” 这两位题目保证输入用例的小数位数最多只有 6 位 解答思路 将小数转为二进制的思路是将小数乘2&#xff0c;如果整数部分为1&#xff0c;则说明第i位是1&#xff08;第i位则乘了2的几次方&#xff09;…

STM32(七)———TIM定时器(基本and通用)

文章目录 前言一、通用定时器TIM简介1.STM32F10X系列总共最多有八个定时器&#xff1a;2.三种STM32定时器的区别&#xff1a;3.STM32 的通用定时器功能&#xff1a;4.计数器模式 二、基本定时器1.基本定时器的结构框图2.定时时间的计算3.定时器的结构体和库函数 总结 前言 一个…

无线备网,保障连锁零售数字化运营

为了提升运营效率、改进客户体验&#xff0c;零售商们不断引入新的数字化工具和平台&#xff0c;包括数字化收银、客流统计、客户关系管理系统等。现代化智慧零售的运营更加依赖于稳定、高效的网络连接&#xff0c;数字化网络不仅是提升运营效率和客户体验的关键&#xff0c;还…