数据结构八大常见的排序

news2024/11/28 22:56:49

数据结构八大常见的排序

  • 常见排序算法分类
  • 1.插入排序
  • 2.希尔排序(缩小增量排序)
  • 3.选择排序
  • 4.堆排序
  • 5.冒泡排序
  • 6.快速排序
  • 7.归并排序
  • 归并排序非递归的实现
  • 8.计数排序

常见排序算法分类

在这里插入图片描述

1.插入排序

基本思想:把待排序的数组按大小逐个插入到一个已经排好序的有序序列中,直到所有的数据插入完为止,得到一个新的有序序列 。
动图解析:
在这里插入图片描述

代码实现思路:
定义一个临时变量tmp来遍历待排数组,定义一个end作为有序数组的最后一个下标,然后end遍历有序数组。在排序中有以下几种情况:
1.最初开始的时候第一趟排序tmp直接从第二个数据开始遍历即可。
在这里插入图片描述
2.tmp<a[end]
在这里插入图片描述
3.tmp>a[end]
在这里插入图片描述
代码实现:

void InsertSort(int* a, int n)//插入排序 最好为O(N) O(N2)
{
	//思路:把数组中一个一个的插入,然后进行比较,把它们放到自己的位置
	for (int i = 1; i < n; i++)
	{
		int end = i - 1;//end为控制数组的下标,使他放在temp的前一个
		int temp = a[i];
		//一趟插入一个数组,如果temp比a[end]小就放在a[end]左边,如果temp比a[end]大的话就放在,a[end]的右边
		while (end >= 0)
		{
			if (temp < a[end])//如果temp小于a[end],那么tempd肯定插入到a[end],之前,那么就让a[end]向后移动一位
				//接下来就拿temp和前面的比较,看是否小于a[end]前面的数
			{
				a[end + 1] = a[end];
				end--;
			}
			else {
				break;//如果temp>a[end]的话就跳出循环,将temp放在a[end]后面,插入的精髓就在于此temp永远在a[end]的后面
			}
		}
		a[end + 1] = temp;//这种写法temp大于或小于a[end]两种情况都符合,temp放在左(小)右(大)边都符合
	}
}

直接插入排序的特性总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1),它是一种稳定的排序算法
  4. 稳定性:稳定

2.希尔排序(缩小增量排序)

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数gap,把待排序文件中所有数据分成个gap组,所有距离为gap的数据分在同一组内,并对每一组内的数据进行排序,单趟排序的思路和直接插入排序一样。然后取重复上述分组和排序的工作。当到达gap=1时,所有数据在同一组内排序,gap=1是也就相当于直接插入排序。
在这里插入图片描述

代码实现:

void ShellSort(int* a, int n)//希尔排序
{
	int gap = n;//gap在这里是一个不断变化的值
		while (gap > 1)
	
		{
			gap = gap / 3 + 1;//保证gap最后一次为1,当gap等于1的时候相当于简单插入排序,就可以直接排序出来了
			for (int i = 0; i < n - gap; i++)
			{
				int end = i;
				int temp = a[end + gap];
				//将数组分为gap组,当gap较大的时候,也就是第一循环gap为n/3+1,将数组分成gap(大约n/3)组,那么一组就有3个数据,
				// 最差的情况while循环也只需要1+2次,外循环n,所以gap较大的时候的时间复杂度为O(N)
				//当gap较小的时候,即gap为1的时候,这时候就是插入排序,但是因为gap越小数组就越接近有序,
				//所以这时候的时间复杂度为O(N)
				//所以希尔排序的时间复杂度的量级为 logN*N   大约为n^1.3
	
				while (end >= 0)
				{
					if (temp < a[end])
					{
						a[end + gap] = a[end];
						end -= gap;
					}
					else
					{
						break;
					}
				}
				a[end + gap] = temp;
			}
		}
}

希尔排序的特性总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就
    会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
  3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些书中给出的
    希尔排序的时间复杂度都不固定:
    在这里插入图片描述
    在这里插入图片描述
  4. 稳定性:不稳定

3.选择排序

基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据排完 。我们实现的是单趟排序中选出最大值和最小值,分别放在数组的最右边和最左边。
动图实现的是将数组中最小的放在最左边:
在这里插入图片描述
代码实现:

void SelectSort(int* a, int n)//选择排序(升序)   最坏为O(N2) 最好O(N2)
{
	int left = 0;
	int right = n - 1;
	while (left < right)
	{
		//一趟选出一个最大和最小的值然后放在数组的两边
		int mini = left;//最小数的下标
		int maxi = left;//最大数的下标
		for (int i = left+1; i <= right; i++)
		{
			if (a[i] < a[mini])
			{
				mini = i;
			}
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
		}
		Swap(&a[left], &a[mini]);//最小的放在左边
		if (maxi == left)//如果刚好最大值再最左边,则会导致最大值交换到mini下标的位置
		{
			maxi = mini;
		}
		Swap(&a[right], &a[maxi]);
		left++;
		right--;

	}
}

直接选择排序的特性总结:

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

4.堆排序

升序堆排序的一般思路就是将给定的一组数据放在堆的数据结构当中去,然后进行不断被的取堆顶元素放在数组当中,不断地pop(删除)。但是这种方法太麻烦了,自己还要手写一个堆的数据结构以及一些接口函数,还要创建数组等,显然不是最优解。
接上文的写的两种调整方式,向上调整和向下调整。 详细见数据结构二叉树顺序结构——堆的实现
思路:
①可以用向上调整或者向下对原数组进行调整,也就是建一个大堆(排升序大堆后面讲为啥)
②接下来利用堆删除的原理,将堆顶的数据和数组最后一个交换(也就是将堆顶和堆尾的数据进行交换),然后就相当于最大的数放在了最后一个位置,所以最后一个位置的数据确定了,接下来对剩下的数据进行向下调整,再重复以上操作。
在这里插入图片描述

ps:排升序建大堆而不是小堆的原因,反证思路来看,若建小堆的话,最小的数据在第一个,第一个数据确定了,但是剩下的数据很暖再重新调整成为一个新的小堆的数据结构,所以排升序建小堆很难实现。

向上调整和向下调整都可以完成建堆的操作,但是他们的时间复杂度有所不同,接下来讲一下他们的取舍。
向上调整建堆(时间复杂度:O(N * logN) )

for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):
在这里插入图片描述

向下调整建堆(时间复杂度:O(N) )

for (int i = (n - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);//a是堆的数组,n是防止数组越界的数组数据个数,i是开始向下调整的位置
	}

向下调整建堆得思路:从第一个非叶子结点开始从数组得后面向前面一个一个进行调整
在这里插入图片描述

复杂度证明:
在这里插入图片描述

看到这里很容易发现向下调整方法建堆得时间复杂度更加合适
代码实现:


void AdjustDown(int* a, int parent, int n)//向下调整
{
	int child = parent * 2 + 1;
	while (child < n)//这里child会先越界到达临界点,所以不用判断parent
	{
		//默认child是左右孩子中较大的值
		if (child + 1 < n && a[child] < a[child + 1])
		{
			child = child + 1;
		}
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;//符合堆的性质则跳出循环
		}
	}
}

// 对数组进行堆排序
void HeapSort(int* a, int n)
{
	//思路:向上调整方法建堆建堆
	//>这里排升序要建大堆,因为建小堆的话,接下来排升序第一个数据好处理,
	//但是剩下的数据重新排成堆的话关系全都乱了
	//所以这时候建大堆,和删除的思路一样,首先交换堆顶和堆尾,这时候最大的数据放到了最后一个位置
	//然后将前面n-1个数据看成新的堆进行向下调整,然后再找次大的数放在倒数第二的位置
	
	//建堆有两种方式 向上调整建堆和向下调整建堆,时间复杂度分别为O(N*logN)和O(N)
	
	//向上调整建堆 建堆时间复杂度 O(N*logN)
	/*for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}*/

	//向下调整建堆  时间复杂度为O(N)
	//向下调整的条件是调整节点的左右子树都为大堆或者小堆
	//所以从下边开始进行向下调整(大堆),这时候大的数会慢慢上浮,最先调整的位置不能是叶子节点,那样会重复很多操作
	//应该从最后一个父亲节点开始进行向下调整 最后一个父亲节点的下标为 (n-1)/2,然后按数组下标的顺序递减进行调整
	for (int i = (n - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);//a是堆的数组,n是防止数组越界的数组数据个数,i是开始向下调整的位置
	}
	while (--n)  //排序的时间复杂度为O(N*logN)
	{
		//此处为--n的原因:一共有n个数据,循环一次确定一个数据的位置,循环n-1次之后就可以确定n-1个数据的位置,
		// 前面已经确定了n-1个位置,最后一个数据的位置也已经确定,所以当n=0的时候的那一次循环可以不需要进行就可以
		// 
		//此时n已经自减1,所以此时n就为堆尾数据,且n为下一个新堆的数据个数,所以后面向下调整直接传参n
		Swap(&a[0], &a[n]);//交换堆顶和堆尾的数据
		AdjustDown(a, n, 0);//n为新堆的数据个数
	}

	//总结:堆排序的时间复杂度为O(N*logN),因为堆排序有两个步骤①建堆②排序
	//建堆向上调整建堆的时间复杂度为O(N*logN),向下调整建堆的时间复杂度为O(N),相对较快,
	//但是排序的时间复杂度都为O(N*logN),所以决定了堆排序的时间复杂度为O(N*logN)。
}

堆排序的特性总结:

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

5.冒泡排序

基本思路:两两数据相比,大的元素向后移,也就是前一个数据大于后一个数据就交换,一趟能保证最大的数据放在数组的最右边。
在这里插入图片描述
代码实现:

void BubbleSort(int* a, int n)//冒泡排序   最坏O(N2)  最好O(N)
{
	//这里起始位置如果i=0;那么就是a[i]和a[i+1]进行比较,i作为下标最后的位置落在n-2的位置,和n-1去比较
	//这里起始位置如果i=1;那么就是a[i-1]和a[i]进行比较,i作为下标最后的位置落在n-1的0位置,和n-1-1去比较
	while (n)
	{
		int i = 0;
		bool exchange = false;
		//这里定义一个记号,记录一下单趟循环中是否有交换,若单趟循环中没有交换就证明,已经有序了
		//不需要接下来的排序
		for (i = 1; i < n; i++)
		{
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				exchange = true;
			}
		}
		if (exchange == false)
		{
			break;
		}
		n--;
	}
}

6.快速排序

详细见数据结构——快速排序的三种方法和非递归实现快速排序

7.归并排序

基本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
在这里插入图片描述
动图详解:
在这里插入图片描述
代码实现思路:

子规模:
将数组分为两部分,然后分别把这两部分数据排成有序部分,然后将两组有序数组分别进行遍历比较,较小的数据放在临时数组tmp中,其中一个数组遍历结束后将另一组未比较的数据直接赋值到临时数组tmp,最后用memcpy函数将临时数组tmp的值拷贝放到指定数组中;
递归:
先将给定数组不断递归,直到数组中只有一个数据,也就代表有序,不需要进行排序,然后返回。
代码实现:

void MergeSort(int* a, int n)//归并排序
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("tmp malloc failed\n");
		return;
	}
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
}

void _MergeSort(int* a, int left, int right, int* tmp)//归并排序的排序部分
{
	//思路:将数组分为左右两部分,并且每组内是有序的,然后再将有序的两组分别遍历相比较,
	//较小的放在临时数组中,一个数组遍历结束后,另一个数组的所有值直接赋值给临时数组
	//最后用memcpy将临时数组的值拷贝到原数组
	if (left >= right)
	{
		return;
	}
	int mid = (left + right) / 2;
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	//递归到一组为一个值的时候开始比较
	_MergeSort(a, begin1, end1, tmp);
	_MergeSort(a, begin2, end2, tmp);
	//比较
	int i = begin1;//这里i的值必须是begin1,当递归到右半边的数据的时候,给tmp赋值的时候必须对应的也应该是右半边的空间
	while (begin1 <= end1 && begin2 <= end2)//两组数据都没遍历结束的时候都可以进循环比较
	{
		if (a[begin1] <= a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}
	//将没有遍历完的数组赋值到临时数组tmp中
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	//这里memcpy中数组的起始地址要加上left的,因为我们每次只需要将递归中重新排序的那个组的数值拷贝到原来的数组即可
	memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}

归并排序非递归的实现

归并排序的思路就是将数组分为左右两个有序数组然后进行排序,通过不断递归到子规模数组中只有一个元素的时候开始排序,然后一步一步的返回。那么我们可以直接模拟递归的子规模然后一步一步的的返回,也就是定义一个gap,初始值为1,gap的意义就是将数组分为每组为gap个数组的组,然后进行排序:
在这里插入图片描述
每两组然后开始进行单趟归并排序,排序后为6,10,1,7,3,9,2,4。接下来将数组分为每组数组为2的数组,也就是gap=2:
在这里插入图片描述
接下来进行单趟归并,归并后数组为1,6,7,10,2,3,4,9,接下来gap=4:
在这里插入图片描述
然后进行单趟归并排序后为1,2,3,4,6,7,9,10。完成递归。当然看到这里肯定有疑问,如果数组元素个数不是2的n次方怎么办呢,肯定会越界访问,先不急一步一步来,先实现这种理想状态下的代码
代码实现

void MergeSortNonR(int* a, int n)//归并排序(非递归)
{
	int* tmp = (int*)malloc(sizeof(int) * n);//创建临时数组tmp用来存放归并后的数据然后最后再拷贝回去
	if (tmp == NULL)
	{
		perror("tmp malloc failed\n");
		return;
	}
	int gap = 1;
	while(gap<n)
	{
		int i = 0;
		int j = 0;//控制tmp数组
		for (i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
		
			while (begin1 <= end1 && begin2 <= end2)//两组数据都没遍历结束的时候都可以进循环比较
			{
				if (a[begin1] <= a[begin2])
				{
					//这两种写法一样不过我的编译器第一种会有点报错,有点强迫症哈哈
					//tmp[j++] = a[begin1++];
					*(tmp + j++) = a[begin1++];
				}
				else
				{
					*(tmp + j++) = a[begin2++];
				}
			}
			//将没有遍历完的数组赋值到临时数组tmp中
			while (begin1 <= end1)
			{
				*(tmp + j++) = a[begin1++];
			}
			while (begin2 <= end2)
			{
				*(tmp + j++) = a[begin2++];
			}
			//这里memcpy中数组的起始地址要加上i的,因为我们每次只需要将递归中重新排序的那个组的数值拷贝到原来的数组即可
			memcpy(a + i, tmp + i, sizeof(int) * (end2-i+1));
		}
		gap *= 2;
	}
	free(tmp);
}

运行截图:
在这里插入图片描述
这里发现我们的思路暂时是没问题的,但是我们将数组改为11个数据的时候,会发现有越界的情况。
在这里插入图片描述

在这里插入图片描述
这时候我们可以加几行代码,把每次进行单趟排序的去加打印出来。
在这里插入图片描述

在这里插入图片描述
这时候我们发现划线部分都是越界部分,我们分析一下,begin1,end1,begin2,end2,谁有可能越界,因为begin1=i,i<n,所以begin1不会越界,那么也就是end1,begin2,end2会越界,这时候对这三种情况分情况讨论即可。
在这里插入图片描述
修改后代码:

void MergeSortNonR(int* a, int n)//归并排序(非递归)
{
	int* tmp = (int*)malloc(sizeof(int) * n);//创建临时数组tmp用来存放归并后的数据然后最后再拷贝回去
	if (tmp == NULL)
	{
		perror("tmp malloc failed\n");
		return;
	}
	int gap = 1;
	while(gap<n)
	{
		int i = 0;
		int j = 0;//控制tmp数组
		for (i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			
			if (end1 >= n || begin2 >= n)
			{
				break;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}

			//printf("[%d,%d][%d,%d]  ", begin1, end1, begin2, end2);
			while (begin1 <= end1 && begin2 <= end2)//两组数据都没遍历结束的时候都可以进循环比较
			{
				if (a[begin1] <= a[begin2])
				{
					//这两种写法一样不过我的编译器第一种会有点报错,有点强迫症哈哈
					//tmp[j++] = a[begin1++];
					*(tmp + j++) = a[begin1++];
				}
				else
				{
					*(tmp + j++) = a[begin2++];
				}
			}
			//将没有遍历完的数组赋值到临时数组tmp中
			while (begin1 <= end1)
			{
				*(tmp + j++) = a[begin1++];
			}
			while (begin2 <= end2)
			{
				*(tmp + j++) = a[begin2++];
			}
			//这里memcpy中数组的起始地址要加上left的,因为我们每次只需要将递归中重新排序的那个组的数值拷贝到原来的数组即可
			memcpy(a + i, tmp + i, sizeof(int) * (end2-i+1));
		}
		gap *= 2;
		//printf("\n");
	}
	free(tmp);
}

测试截图:
在这里插入图片描述

归并排序的特性总结:

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

8.计数排序

计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。
实现思路:
1.开辟一个数组CountA
首先遍历一遍数组找出数组中的最大值MAX,最小值MIN,然后开辟一个RANGE大小的新的数组CountA,其中RANGE=MAX-MIN+1。CountA数组每个元素分别用来记录待排数组中对应数据的出现次数。
2.统计次数
在这里插入图片描述
在以上例子中可以发现待排数组的最小值为1,最大值为9,RANGE=9,所以开辟9个空间,将CountA计数数组初始化为0,然后开始遍历待排数组,待排数组中1出现了一次,2出现了2次,3出现了1次,4出现了0次,5出现了2次,6出现了1次,7出现了0次,8出现了0次,9出现了1次。
这里计数数组CountA中第一个数据也就是下标0的位置映射的是待排数组中的MIN,下标1映射的就是MIN+1,···,最后一个数据RANGE-1映射的就是最大值MAX。
3.赋值到原数组
直接按照顺序将原数组的数据覆盖就好。
代码实现:

void CountSort(int* a, int n)
{
	int max = a[0], min = a[0];
	for (int i = 1; i < n; ++i)
	{
		if (a[i] > max)
		{
			max = a[i];
		}

		if (a[i] < min)
		{
			min = a[i];
		}
	}

	int range = max - min + 1;
	int* countA = (int*)malloc(sizeof(int) * range);
	if (countA == NULL)
	{
		perror("malloc fail\n");
		return;
	}
	memset(countA, 0, sizeof(int) * range);

	// 计数
	for (int i = 0; i < n; i++)
	{
		countA[a[i] - min]++;
	}

	// 排序
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (countA[i]--)
		{
			a[j++] = i + min;
		}
	}

	free(countA);
}

总结:基数排序适合范围集中,且范围不大的整型数组排序。不适合范围分散过着非整形的排序,如字符串、浮点数等

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

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

相关文章

【Java EE】多线程(一)

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 |《MySQL探索之旅》 |《Web世界探险家》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更…

物联网实战--入门篇之(六)嵌入式-WIFI驱动(ESP8266)

目录 一、WIFI简介 二、基础网络知识 三、思路讲解 四、代码分析 4.1 状态机制 4.2 客户端连接 4.3 应用数据接收处理 4.4 数据发送 4.5 主函数调用 4.6 网络连接ID分配 五、总结 一、WIFI简介 WIFI在我们生活中太常见了&#xff0c;手机电脑都可以用WiFi连接路由器进行上…

MIT最新研究成果 机器人能够从错误中纠偏 无需编程介入和重复演示

目前科学家们正在努力让机器人变得更加智能&#xff0c;教会他们完成诸如擦拭桌面&#xff0c;端盘子等复杂技能。以往机器人要在非结构化环境执行这样的任务&#xff0c;需要依靠固定编程进行&#xff0c;缺乏场景通用性&#xff0c;而现在机器人的学习过程主要在于模仿&#…

开关恒流源简介

目录 工作原理 设计要点 应用场景 初步想法&#xff0c;为参加活动先占贴&#xff08;带家人出去玩没时间搞~~&#xff09;&#xff0c;后面优化 开关恒流源是一种基于开关电源技术的恒流输出电源设备。它采用开关管进行高速的开关动作&#xff0c;通过控制开关管的导通和截…

linux 一些命令

文章目录 linux 一些命令fdisk 磁盘分区parted 分区文件系统mkfs 格式化文件系统fsck 修复文件系统 mount 挂载swap 交换分区清除linux缓存df du 命令raid 命令基本原理硬raid 和 软raid案例raid 10 故障修复&#xff0c;重启与卸载 lvm逻辑卷技术LVM的使用方式LVM 常见名词解析…

数据库---------完全备份和增量备份的数据恢复,以及断点恢复

目录 一、在数据库表中&#xff0c;分三次录入学生考试成绩 1.1先创建库&#xff0c;创建表&#xff0c;完成三次数据的录入 1.2首次录入成绩后&#xff0c;做该表的完全备份 1.3第二次插入后 做增量备份 1.4第三次插入后 做增量备份 二、模拟数据丢失&#xff0c;并使用…

大文件上传做断点续传(有详细的代码内容)

文章目录 一、是什么分片上传断点续传 二、实现思路三、使用场景小结参考文献 一、是什么 不管怎样简单的需求&#xff0c;在量级达到一定层次时&#xff0c;都会变得异常复杂 文件上传简单&#xff0c;文件变大就复杂 上传大文件时&#xff0c;以下几个变量会影响我们的用户…

标题:Vue3 中父组件向子组件通信的方式

标题&#xff1a;Vue3 中父组件向子组件通信的方式 在 Vue3 中&#xff0c;父组件和子组件之间可以通过一些方式进行通信。其中&#xff0c;父组件向子组件通信主要有两种方式&#xff1a;传值和调用子组件的方法。 一、父组件向子组件传值 当父组件需要向子组件传递数据时&a…

快速排序---算法

1、算法概念 快速排序&#xff1a;通过一趟排序将待排记录分隔成独立的两部分&#xff0c;其中一部分记录的数据均比另一部分的数据小&#xff0c;则可分别对这两部分记录继续进行排序&#xff0c;以达到震哥哥序列有序。 快速排序的最坏运行情况是O()&#xff0c;比如说顺序数…

设计一个动物声音“模拟器”,希望模拟器可以模拟许多动物的叫声。

设计一个动物声音“模拟器”&#xff0c;希望模拟器可以模拟许多动物的叫声。要求如下&#xff1a; &#xff08;1&#xff09;编写接口Animal Animal接口有2个抽象方法cry()和getAnimaName()&#xff0c;即要求实现该接口的各种具体动物类给出自己的叫声和种类名称。 &…

设计模式 - 中介器模式

中介者模式使得组件通过一个中心点——中介者进行交互。组件不需要直接进行通信&#xff0c;而是将请求发送给中介者&#xff0c;由中介者进行转发&#xff01;在JavaScript中&#xff0c;中介者往往只是一个对象字面量或一个函数。 你可以将这种模式与空中交通管制员和飞行员…

科技革新,OTG充电新纪元!

在科技日新月异的今天&#xff0c;数据交互已经渗透到我们生活的每个角落&#xff0c;无论是工作还是娱乐&#xff0c;它都发挥着不可替代的作用。OTG技术的出现&#xff0c;极大地简化了设备间的联接与数据交换过程&#xff0c;但随之而来的接口有限和续航问题&#xff0c;也让…

产品经理的进阶之路

点击下载《产品经理的进阶之路》 1. 前言 本文深入剖析了产品经理这一职业从产品专员起步,逐步晋升为产品经理、高级产品经理,直至产品总监的整个职业发展路径。在每个阶段,产品经理都需承担不同的工作职责,展现出独特的职业特点。 2. 产品专员 关键词【产品需求/原型/文…

计算机服务器中了rmallox勒索病毒怎么办?rmallox勒索病毒解密数据恢复

网络技术的不断发展与应用&#xff0c;大大提高了企业的生产运营效率&#xff0c;越来越多的企业开始网络开展各项工作业务&#xff0c;网络在为人们提供便利的同时&#xff0c;也会存在潜在威胁。近日&#xff0c;云天数据恢复中心接到多家企业的求助&#xff0c;企业的计算机…

设计模式-概述篇

1. 掌握设计模式的层次 第1层&#xff1a;刚开始学编程不久&#xff0c;听说过什么是设计模式第2层&#xff1a;有很长时间的编程经验&#xff0c;自己写了很多代码&#xff0c;其中用到了设计模式&#xff0c;但是自己却不知道第3层&#xff1a;学习过了设计模式&#xff0c;…

Vue 3.0生命周期:深入理解与用法

Vue 3.0生命周期&#xff1a;深入理解与用法 摘要&#xff1a; 本文将深入探讨Vue 3.0的生命周期&#xff0c;解释每个生命周期钩子的含义和用法&#xff0c;以及它们在开发过程中的重要性。我们将详细解析每个钩子的工作原理&#xff0c;并提供一些示例和最佳实践&#xff0c…

python爬取B站视频

参考&#xff1a;https://cloud.tencent.com/developer/article/1768680 参考的代码有点问题&#xff0c;请求头需要修改&#xff0c;上代码&#xff1a; import requests import re # 正则表达式 import pprint import json from moviepy.editor import AudioFileClip, Vid…

常见贪心问题详解

目录 贪心算法应用条件 常见贪心问题 活动安排问题&#xff08;区间调度问题&#xff09; 区间覆盖问题 最优装载问题1 最优装载问题2 多机调度问题 例题&#xff1a;翻硬币 例题&#xff1a;快乐司机 例题&#xff1a;防御力 例题&#xff1a;答疑 贪心算法应用条件…

【通信原理笔记】【三】模拟信号调制——3.1 模拟信号调制基本模型与思路

文章目录 前言一、模拟信号二、模拟调制系统模型三、模拟调制的三种方式四、调制的评价指标总结 前言 一般常见的信号的频带均集中在基带附近&#xff0c;如果要通过无线地方式传输&#xff0c;其较长的波长需要大型的天线才能传输&#xff0c;难以实现。另一方面基带的带宽资…

ruoyi-nbcio-plus基于vue3的flowable执行监听器的升级修改

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a…