排序(四)——归并排序 + 外排序

news2024/11/27 10:26:15

目录

1.归并排序递归实现

代码

2.归并排序非递归

代码

3.比较快排、归并和堆排序

4.归并排序实现外排序


1.归并排序递归实现

我们之前对两个有序数组进行排序就用到了归并的思想,对于两个有序数组,我们分别取他们首元素比较大小,取小的插入到一个新开辟的数组中,

归并排序的前提就是要有两个有序序列,而且还要开辟一块新的空间来存放新的有序的数组。

那么对于一个无序的数组该如何使用归并排序呢

这一整个数组我们可以把它分成左右两个区间来看,一个是 [0,4] ,一个是 [5,9] ,而只要这两个区间有序了,我们就有办法通过控制下标来进行归并排序。

怎么使两个子区间有序呢?这就又回到了二叉树的结构,这不就是相当于二叉树的后序遍历吗?我们要先使两个字区间有序,然后再对这两个字区间进行归并排序。

递推到最后的子问题就是 只有一个数据时(begin==end),

递推到最后就开始返回了,

代码

最终左右区间都有序之后,再归并就能够将整个数组都排成有序了。我们首先将归并的逻辑写出来,当一个序列左右区间都有序了之后我们就要进行归并,合成一个新的有序序列。

归并逻辑
void _MergePartSort(int* a, int begin, int end,int*tmp)
{
	int mid = begin + (end - begin) / 2;
	int begin1 = begin;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = end;
	int index = begin;
	//将两个有序区间归并到一起
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[index++] = a[begin1++];
		}
		else
		{
			tmp[index++] = a[begin2++];
		}
	}
	//剩余的数据放进去
	while (begin1 <= end1)
		tmp[index++] = a[begin1++];

	while (begin2 <= end2)
		tmp[index++] = a[begin2++];

	//最后将新序列拷贝回原数组
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

归并的逻辑我们写完了之后,就要考虑如何实现后序遍历了,后序遍历无非就是先递归排序子区间,最后将这两个子区间归并成一个有序的序列

//归并排序实现
void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin == end)
		return;

	//先处理子区间
	int mid = begin + (end - begin) / 2;
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid+1, end, tmp);

	//归并
	_MergePartSort(a, begin, end,tmp);
}

最后,我们的归并排序的主函数中还创建了一个动态开辟数组,我们要记得释放掉。为什么我们要单独拿一层逻辑逻辑来创建一个数组呢?我们每一次归并都需要用到一个额外的数组,与其每一次都开辟一次,不如直接开辟一个完整的能够对所有数据归并临时存放的数组,这样更好管理,方便释放。

//归并排序 创建新数组
void MergeSort(int* a, int begin, int end)
{
	assert(a);
	int* tmp = (int*)malloc(sizeof(int) * (end - begin+1));
	assert(tmp);

	_MergeSort(a, begin, end,tmp);
	free(tmp);
}

归并排序不会像快排的递归那样可能会栈溢出,归并排序由于每一次区间都是均分的,最多调用logN层栈帧,而他还创建了一个额外的数组来归并数据,所以他的总的空间复杂度就是O(N)。

2.归并排序非递归

归并排序的递归实现其实不难,难的是他的非递归实现。对于归并排序,我们能使用栈或者队列来实现非递归吗?栈和队列实现起来十分的复杂,后序遍历不适合栈和队列的特点,那么我们就得想其他的的办法来解决了。

其实,对于归并排序,我们只需要两层循环就能够实现他的非递归。首先数组的每一个数都可以看成是有序的,那么我们是不是可以直接对相邻的两个数进行递归呢?这样一来每两个数为一组就都是有序的了。

这样就成了 n/2 个有序序列,然后对这些数据 相邻两组 进行归并

 这时候我们就发现了一个要注意的问题,就是在分组的时候,有可能并不是偶数组,可能是奇数组或者有些组可能数据并不满 ,比如当原数组是十一个元素时,在两两归并之后最后一组只有一个数据,我们在用代码实现时要考虑到这些点。

我们要怎么控制这些分组呢?我们知道,当数据总个数为n时,第一次分组都是 一个数与一个数进行归并,而第二次就变成了 两个数的有序序列 与 两个数的有序序列进行归并 ,然后就是 四个数的有序序列和四个数的有序序列进行归并,以此类推,最后一次 就是 n/2 个数据的有序序列和 n/2个数据的有序序列进行归并,这是在每一次都是偶数个组的时候,也就是数据总数为 2 的次方。

首先我们假设是 gap 个有序数与 gap 个有序数进行归并,先把单趟的归并逻辑理清楚。非递归的实现也要依赖于一个额外的数组。

	//gap个数为一组,两组进行归并
	int begin1 = ;//第一组的开始
	int end1 = begin1 + gap-1;
	int begin2 = begin1 + gap;
	int end2 = begin1 + 2 * gap - 1;
	int i = 0;
	for (i = 0; i <= end; i += 2 * gap)
	{
		//gap个数为一组,两组进行归并
		int begin1 = i;//第一组的开始
		int end1 = begin1 + gap - 1;
		int begin2 = begin1 + gap;
		int end2 = begin1 + 2 * gap - 1;
		int index = begin1;
		//一次归并
		while (begin1 <= end1 && begin2 <= end2)
		{
			if (a[begin1] < a[begin2])
				tmp[index++] = a[begin1++];
			else
				tmp[index++] = a[begin2++];
		}
		while (begin1 <= end1)
		{
			tmp[index++] = a[begin1++];
		}
		while (begin2 <= end2)
		{
			tmp[index++] = a[begin2++];
		}
	}

这边是一轮归并的逻辑,而我们除了归并之外,还需要拷贝数据回到原数组,我们要如何拷贝呢?

可以一次将一轮归并之后的tmp数组全部拷贝到原数组,也可以每一次两两归并就拷贝回去。我们先用一次性拷贝。 然后我们要开始处理边界的问题了,也就是前面说的数据不是完整的偶数组。

首先第一种情况就是,最后的 end1刚好是 end,而 beign2 和 end2 越界了,这种情况怎么处理呢?我们直接将 begin1 到end1 的数据写到tmp就行了

 

而第二种情况就是,最后递归的 begin1合法,而end1 就已经越界了,更不用谈begin2和end2了,这时候我们则是将 beign1 到 n-1 的数据写到tmp中。

第一种和第二种情况的处理是一样的

		if (end1 >= end)//第一组就越界了或者第一组的 end1 就是数组尾
		{
			while (begin1 <= end)
			{
				tmp[begin1] = a[begin1];
				begin1++;
			}
			break;
		}

第三种越界就是:第二组数据部分越界,也就是 begin2 <=end,而end2>end,这时候这两组还要进行归并。

这时候我们只需要把end2 修改为 end 就行了。

这样一来我们就能写出第一轮的相邻两个数的的归并的代码

//非递归归并
void MergeSortNonR(int* a, int begin, int end)
{
	int* tmp = (int*)malloc(sizeof(int)*(end - begin + 1));
	assert(tmp);
	int gap=1;
	int i = 0;
	for (i = 0; i <= end; i += 2 * gap)
	{
		//gap个数为一组,两组进行归并
		int begin1 = i;//第一组的开始
		int end1 = begin1 + gap - 1;
		if (end1 >= end)//第一组就越界了或者第一组的 end1 就是数组尾
		{
			while (begin1 <= end)
			{
				tmp[begin1] = a[begin1];
				begin1++;
			}
			break;
		}
		int begin2 = begin1 + gap;
		int end2 = begin1 + 2 * gap - 1;
		if (end2 > end)
		{
			end2 = end;
		}
		int index = begin1;
		//一次归并
		while (begin1 <= end1 && begin2 <= end2)
		{
			if (a[begin1] < a[begin2])
				tmp[index++] = a[begin1++];
			else
				tmp[index++] = a[begin2++];
		}
		while (begin1 <= end1)
		{
			tmp[index++] = a[begin1++];
		}
		while (begin2 <= end2)
		{
			tmp[index++] = a[begin2++];
		}
	}
	memcpy(a, tmp, sizeof(int) * (end-begin+1));

}

这一层逻辑写完就简单了,我们在写一层循环来控制 gap 就行了,gap每一次都是上次的二倍,而因为数据个数可能不是 2 的次方个,所以最后一次的 gap < n 。而数据个数 n 我们用传过来的参数表示就是 end-begin+1。

最后在把tmp数组释放掉,我们就完成了归并的非递归实现。

代码
//非递归归并
void MergeSortNonR(int* a, int begin, int end)
{
	int* tmp = (int*)malloc(sizeof(int)*(end - begin + 1));
	assert(tmp);
	int gap=1;
	for (; gap < end - begin + 1; gap *= 2)
	{
		int i = 0;
		for (i = 0; i <= end; i += 2 * gap)
		{
			//gap个数为一组,两组进行归并
			int begin1 = i;//第一组的开始
			int end1 = begin1 + gap - 1;
			if (end1 >= end)//第一组就越界了或者第一组的 end1 就是数组尾
			{
				while (begin1 <= end)
				{
					tmp[begin1] = a[begin1];
					begin1++;
				}
				break;
			}
			int begin2 = begin1 + gap;
			int end2 = begin1 + 2 * gap - 1;
			if (end2 > end)
			{
				end2 = end;
			}
			int index = begin1;
			//一次归并
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
					tmp[index++] = a[begin1++];
				else
					tmp[index++] = a[begin2++];
			}
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
		}
		memcpy(a, tmp, sizeof(int) * (end - begin + 1));
	}
	free(tmp);
}

我们可以观察每一轮归并的结果来分析程序逻辑是否正确

gap为1时

gap为2时

gap为4时

gap为8时

这时候就已经完全排完了。

3.比较快排、归并和堆排序

void test()
{
	int N = 1000000;
	int* a1 = (int*)malloc(sizeof(int) * N);
	assert(a1);
	int* a2 = (int*)malloc(sizeof(int) * N);
	assert(a2);
	int* a3 = (int*)malloc(sizeof(int) * N);
	assert(a3);
	
	srand((unsigned int)time(NULL));

	for (int i = 0; i < N; i++)
	{
		a1[i] = rand();
		a2[i] = a1[i];
		a3[i] = a1[i];
	}

	int begin1 = clock();
	HeapSort(a1, N);
	int end1 = clock();
	printf("HeapSort:%d\n", end1 - begin1);

	int begin2 = clock();
	QuickSort(a1,0, N-1);
	int end2 = clock();
	printf("QuickSort:%d\n", end2 - begin2);

	int begin3 = clock();
	MergeSort(a1,0, N-1);
	int end3 = clock();
	printf("MergeSort:%d\n", end1 - begin1);

	free(a1);
	free(a2);
	free(a3);
}

当要排序一百万个数据时

而排序十万个数据时

这里我们就能发现虽然我们解析他们的时间复杂度都是NlogN,但是其实快排是要比另外两个排序快的,首先是因为,堆排序要建堆,建堆的时间复杂度为N,而选数的时间复杂度时NlogN,所以他要比快排慢,而归并排序则是因为,他要开辟额外的空间,同时我们拷贝数据回原数组时,memset的时间复杂度也很高,影响了我们的效率。

4.归并排序实现外排序

什么是外排序?

我们前面讲的排序都是在内存中去排序数据,因为他们的数据量都不是很大,在内存中能放下,他们都是内排序算法。

而当我们的数据量十分大时,无法一次性放进内存中进行排序,数据存在磁盘中,也就是外存中,这时候我们就需要改进前面学过的排序来实现对外存中的数据进行排序,而其他的排序算法都只能在内存中排序,只有归并排序能实现外排序,所以归并排序既是一种内排序算法,也是外排序算法。

为什么不直接在磁盘文件中进行排序呢?第一,文件是不支持随机读写的,只能做一个串行的直接访问,虽然我们可以用 fseek 函数来修改文件指针的位置,但是当数据量很大的时候我们是顾不过来对文件指针的操作的。第二,磁盘的访问速度以及数据挪动等操作速度都是远慢于内存的,在实际应用中不适合在磁盘中操作数据。

外排序的思路是怎么样的呢?

首先我们有一个放了数据的文件,数据很大,无法一次性放到内存中

虽然我们无法一次性全加载到内存中,但是我们可以先将一部分数据放到内存中去排序,数据的个数取决于内存的大小。 

这时候内存中的数据我们就能够通过内排序算法进行排序了,最好用快排,因为堆排效率比不上快排,而归并则还需要额外的空间,内存本来就不够了,所以对内存中的数据排序不能用归并。 我们将排序好的数据从内存中输出到一个小文件中

然后重复上述过程,直到把数据都读完,排好序之后都写到小文件中

这时候我们就能得到几个数据有序的小文件,对这些有序的小文件,我们就能进行归并排序了。

这样一来,我们就得到了一个排好序的文件。

归并排序实现外排序的思想其实并不难,难的是在操作的过程中的文件操作,以及给文件取名的细节,还有最后归并小文件时的迭代。

我们首先创建一个文件,我们在实现代码的时候没必要用海量数据来实现,也做不到。这就假设内存的空间就够存储一个 二十个数据的数组,因为我们要用数组来接受文件中的数据,我们还要考虑内排序算法调用堆栈的消耗,我们在文件中存进100个随机数据,范围在100~999之间,然后再放上几个二位数与四位数

int main()
{
	FILE* fout = fopen("data", "w");
	srand((unsigned)time(NULL));
	for (int i = 0; i < 100; i++)
	{
		int data = rand() % 900 + 100;
		fprintf(fout, "%d\n", data);
	}
	fclose(fout);
	return 0;
}

我们先生成一个data文件,然后手动添加几个数据进去便于我们最后结果的检验。

有一点要注意,我们在这个示例中的文件名都不加后缀了,因为加了后缀之后太过复杂(在后面部分给文件取名的时候),而C语言对文件名的操作并不是这么方便,所以我们就不加文件后缀了。

假如这就是我们要排序的文件。

我们假设内存中最多就只能放二十个整形数据(除去排序所需要的空间),这时候我们每次要用一个数组来接受20个数据,对他们进行排序之后再分别写入一个小文件中保存


int main()
{
	//FILE* fout = fopen("data", "w");
	//srand((unsigned)time(NULL));
	//for (int i = 0; i < 100; i++)
	//{
	//	int data = rand() % 900 + 100;
	//	fprintf(fout, "%d\n", data);
	//}
	//fclose(fout);

	char filename[20] = "data";
    //测试小文件的个数
	int filenum=Read_Sort(filename);
	printf("%d", filenum);
	return 0;
}


读取数据并排序放到小文件的函数

//读取数据排序写入小文件
int Read_Sort(char* filename)
{
	//内存中最多存储个数
	int N = 20;
	int* a = (int*)malloc(sizeof(int) * N);
	assert(a);

	FILE* pf = fopen(filename, "r");
	assert(pf);

	//记录小文件个数并用于小文件命名
	int n = 0;
	char sfile[20] = "sorted";

	int data;//用于读取保存数据
	int i = 0;//a数组下标
	//读取数据
	while (fscanf(pf,"%d", &data) != EOF)
	{
		if (i < N-1)//先放19个数据
		{
			a[i++] = data;
		}
		else//放第20个数据
		{
			a[i++] = data;//把第20个数据放进数组
			//对这一组数据排序
			QuickSort(a, 0, N - 1);

			//小文件命名
			char file[20];
			n++;
			sprintf(file, "%s%d", sfile, n);//将小文件命名为sorted1  sorted2 ... ...
			FILE* fout=fopen(file, "w");
			assert(fout);
			for (int j = 0; j < i; j++)
			{
				fprintf(fout, "%d\n", a[j]);//存数据的时候要注意格式,到时候取数据的格式要与存数据的格式一样
			}
			//写完之后关闭文件
			fclose(fout);
			//i复原,重新读取
			i = 0;
		}
	}
	//最后要判断a数组中还有没有数据
	if (i != 0)
	{
		n++;
		char file[20];
		sprintf(file, "%s%d", sfile, n);
		FILE* fout = fopen(file, "w");
		assert(fout);
		for (int j = 0; j < i; j++)
			fprintf(fout,"%d\n", a[j]);

		fclose(fout);
	}


	fclose(pf);
	//返回小文件个数

	free(a);
	return n;
}

我们可以看到一共生成了6个小文件,

我们的data总数是110个,前五个文件都有20个数据,第六个文件右10个数据,说明这一步是没问题的。

这一步完成之后,下一步就是对着6个小文件进行归并了。

文件归并的过程中我们每次都只操作三个文件,file1 和 file2 是要读取数据进行归并的文件,而mfile适用于归并后数据存储的文件。 

一次归并完之后,将 file1 改成 mfile(sorted12)去管理生成的归并之后的文件 ,file2则修改为sorted3,然后再创建一个mfile(sorted123)文件,来进行新的归并。

这个过程是一个循环与迭代的过程


//文件归并
void Merge_File(int n)
{
	//初始条件
	char filename[20] = "sorted";
	char file1[20]="sorted1";
	char file2[20];
	char mfile[20] = "sorted12";
	int i = 0;
	for (i = 1; i < n; i++)
	{
		sprintf(file2, "%s%d", filename, i+1);//对file2修改
		FILE* fin1 = fopen(file1, "r");
		assert(fin1);
		FILE* fin2 = fopen(file2, "r");
		assert(fin2);
		FILE* fout = fopen(mfile, "w");
		assert(fout);

		//读取file1和file2数据
		int num1;
		int ret1=fscanf(fin1, "%d\n", &num1);
		int num2;
		int ret2= fscanf(fin2, "%d\n", &num2);

		while (ret1!=EOF&&ret2!=EOF)
		{
			if (num1 < num2)
			{
				fprintf(fout, "%d\n", num1);
				ret1 = fscanf(fin1, "%d\n", &num1);
			}
			else
			{
				fprintf(fout, "%d\n", num2);
				ret2 = fscanf(fin2, "%d\n", &num2);
			}
		}
		//其中一个文件读取完了
		while (ret1 != EOF)
		{
			fprintf(fout, "%d\n", num1);
			ret1 = fscanf(fin1, "%d\n", &num1);
		}
		while (ret2 != EOF)
		{
			fprintf(fout, "%d\n", num2);
			ret2 = fscanf(fin2, "%d\n", &num2);
		}
		//都读取完之后关闭文件
		fclose(fin1);
		fclose(fin2);
		fclose(fout);

		//迭代
		memcpy(file1, mfile, 20);//将file修改为 sorted12
		sprintf(mfile, "%s%d", mfile, i + 2);//将mfile修改为 sorted123
	}

}

这里我们注意用num1和num2,还有ret1和ret2来分别保存读取的数据和fscanf的返回值,如果不用ret1和ret2的话,要特别注意不要多读或者漏读数据,yaoqingchuwenjianzhizhendedingwei

当我们把程序运行完之后,我们去工程目录底下就能找到这一些列文件

我们可以打开最终的文件 sorted123456来验证一下程序的正确性,

首先数据的个数是没问题的,还是110个数据,而我们手动添加的十个数据也都在文件的开头和结尾,这说明排序也没问题。

外排序的实现其实最主要的不是排序的算法,而是对文件的操作的熟悉,外排序我们可能造成写程序用不到,但是在以后处理大数据的排序时我们就可以借鉴这个思路。

总的来说,归并排序不适合常规场景,因为他有额外的空间消耗,但是适合这种外排序。

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

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

相关文章

2011年认证杯SPSSPRO杯数学建模C题(第二阶段)你的爱车入保险了吗全过程文档及程序

2011年认证杯SPSSPRO杯数学建模 C题 你的爱车入保险了吗 原题再现&#xff1a; 近几年&#xff0c;国内汽车销售市场异常火爆&#xff0c;销售量屡创新高。车轮上的世界&#xff0c;保险已经与我们如影随形。汽车保险&#xff0c;简称车险&#xff0c;是指对机动车辆由于自然…

嵌入式工程师有哪些必备技能,和电子爱好者有很大区别!

要掌握的技能实际上是非常多的。在这里&#xff0c;我来结合自己亲身经历&#xff0c;从技术、思维、项目管理等方面来谈一下我认为嵌入式开发需要掌握的技能。 技术方面 C语言和汇编语言能力 C语言是嵌入式开发最核心的编程语言。在我的初学阶段&#xff0c;我花费了很多时间…

微服务项目实战-黑马头条(三):APP端文章详情

文章目录 一、文章详情-实现思路1.1 传统实现方式1.2 静态模版分布式文件系统 二、FreeMaker模板引擎2.1 FreeMaker 介绍2.2 环境搭建&&快速入门2.2.1 创建测试工程2.2.2 配置文件2.2.3 创建模型类2.2.4 创建模板2.2.5 创建controller2.2.6 创建启动类2.2.7 测试 2.3 F…

常用的数据结构及算法

一、数据结构 &#xff08;一&#xff09;线性结构&#xff1a;一对一。 1.可以使用数组、链表来表示。数组又分为静态数组和动态数组两种。链表常用的是单链表。 2.两种特殊的线性结构&#xff1a;队列和栈。其中队列是先进先出&#xff08;排队&#xff09;&#xff0c;栈…

OCR识别图片的字体与正常的字不同怎么调回来呢?

一般来说&#xff0c;OCR的任务是将图片文字转化成计算机可编辑的文字&#xff0c;一般不识别字体&#xff0c;当然&#xff0c;也不排除某些OCR软件可以识别字体的&#xff0c;具体来说&#xff0c;造成这种现象的可能原因如下&#xff1a; 1. **字体匹配问题**&#xff1a;OC…

IPFS分布式存储系统

一、 引言 IPFS是InterPlanetary File System的缩写。它是一个分布式的网络传输协议&#xff0c;它可以把文件分成很多小块放到服务器的不同地方&#xff0c;然后用一种特别的方式来寻找和传输这些小块。这样&#xff0c;我们就可以更快、更安全、更抗容错了的存储文件了。 可能…

FinalShell 远程连接 Linux(Ubuntu)系统

Linux 系列教程&#xff1a; VMware 安装配置 Ubuntu&#xff08;最新版、超详细&#xff09;FinalShell 远程连接 Linux&#xff08;Ubuntu&#xff09;系统Ubuntu 系统安装 VS Code 并配置 C 环境 ➡️➡️➡️提出一个问题&#xff1a;为什么使用 FinalShell 连接&#xff0…

文件msvcr120.dll丢失怎样修复?这三种方法能准确修复msvcr120.dll

小编为大家总结了解决msvcr120.dll文件缺失问题的三种方法&#xff0c;以帮助你快速解决这一难题。首先&#xff0c;我们来看看msvcr120.dll文件为何会出现丢失的情形。 一.msvcr120.dll丢失问题的常见原因包括 病毒感染&#xff1a;病毒或恶意软件侵入电脑有可能会损毁或删除…

NPL预训练模型-GPT-3

简介及特点 GPT-3是一个由OpenAI开发的自然语言处理&#xff08;NLP&#xff09;预训练模型&#xff0c;它是生成式预训练变换器&#xff08;Generative Pretrained Transformer&#xff09;系列的第三代模型。GPT-3以其巨大的规模和强大的语言处理能力而闻名&#xff0c;具有…

LLM学习笔记-1

过往历史 ​​ 大体框架 手戳GPT2-small 一些概念 pytorch注意力机制(Transformer)LLM 过程 模型参数 GPT_CONFIG_124M {"vocab_size": 50257, # 词表大小"ctx_len": 1024, # 上下文长度"emb_dim": 768, # 嵌入维度"n…

python笔记 | 哥德巴赫猜想

哥德巴赫猜想&#xff1a;每个不小于6的偶数都可以表示成两个素数之和。 素数&#xff1a;只能被1和自身整除的正整数。就是大于1且除了1和它本身之外没有其他因数的数。例如&#xff0c;2、3、5、7、11等都是素数&#xff0c;而4、6、8、9等则不是素数。 下面这段Python代码…

vue实现文字转语音的组件,class类封装,实现项目介绍文字播放,不需安装任何包和插件(2024-04-17)

1、项目界面截图 2、封装class类方法&#xff08;实例化调用&#xff09; // 语音播报的函数 export default class SpeakVoice {constructor(vm, config) {let that thisthat._vm vmthat.config {text: 春江潮水连海平&#xff0c;海上明月共潮生。滟滟随波千万里&#xf…

力扣哈哈哈哈

public class MyStack {int top;Queue<Integer> q1;Queue<Integer> q2;public MyStack() {q1new LinkedList<Integer>();q2new LinkedList<Integer>();}public void push(int x) {q2.offer(x);//offer是入队方法while (!q1.isEmpty()){q2.offer(q1.pol…

内网代理技术总结

代理技术就是解决外网和内网的通信问题&#xff0c;例如&#xff0c;我的一个外网主机想要找到另外一个网段下的一个内网主机&#xff0c;理论上是无法找到的。如果我们想要进行通信的话就要使用代理技术。我们可以找到一个与目标内网主机在容易网段下可以通信的外网主机&#…

华为OD机试 - 分披萨 - 动态规划(Java 2024 C卷 200分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

golang map总结

目录 概述 一、哈希表原理 哈希函数 哈希表和哈希函数的关系 哈希表的优势 哈希冲突 什么是哈希冲突 如何处理哈希冲突 链表法 开放寻址法 哈希表常见操作过程 存储数据 检索数据 删除数据 常用的哈希算法 哈希表的应用场景 二、golang map map的内部结构 h…

学习笔记------时序约束之时钟周期约束

本文摘自《VIVADO从此开始》高亚军 主时钟周期约束 主时钟&#xff0c;即从FPGA的全局时钟引脚进入的时钟或者由高速收发器输出的时钟。 对于时钟约束&#xff0c;有三个要素描述&#xff1a;时钟源&#xff0c;占空比和时钟周期。 单端时钟输入 这里我们新建一个工程&#x…

爬楼梯(c)

文章目录 描述分析思路关键代码运行结果 描述 给定一个整数数组 cost &#xff0c;其中 cost[i]是从楼梯第i 个台阶向上爬需要支付的费用&#xff0c;下标从0开始。-旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶 要求&#xff1a;请你计算并返回达到楼梯顶部的…

每天五分钟计算机视觉:基于卷积操作完成滑动窗口的图片分类?

本文重点 我们前面学习了使用不同大小的滑动窗口来滑动图片,然后切分成许多小的图片,然后依次应用到我们已经训练好的图像分类模型中,但是这种方式效率太低了,本节课程我们学习一种新的方式,来看一下如何并行识别这些剪切的图片。 原始结构 首先我们先来看一下,如何把…