【排序算法(四)】归并排序计数排序(非比较排序)以及八大排序算法的总结

news2024/10/5 18:32:53

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:数据结构
🎯长路漫漫浩浩,万事皆有期待

文章目录

  • 1、归并排序
    • 1.1 算法思想
    • 1.2 两个有序子序的归并(排升序)
    • 1.3 归并递归版本
    • 1.4 归并排序非递归版本
      • 修正区间 :
      • 不修正区间 :
    • 1.5 特性及复杂度
  • 2、计数排序
    • 2.1 算法思想
    • 2.2 代码实现
    • 2.3 特性及复杂度
  • 3、八大排序算法总结
    • 排序的**稳定性**:
  • 4、排序性能测试
  • 5.总结:

上一篇博客:【排序算法(三)】交换排序(冒泡排序&&快速排序)

1、归并排序

1.1 算法思想

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并

如果说快速排序是前序遍历,那么归并恰巧就是它的对立面,归并排序相当于是二叉树的后序遍历

归并排序算法思想(排升序为例):
1.假设数组有N个元素,先将数组不断地二分,直到将数组划分为N个由单个元素构成的子数组,整个划分过程中所有子数组构成满二叉树(或接近满二叉树)的逻辑结构,如图:

在这里插入图片描述
2.数组划分完后再逐层向上将二叉树兄弟结点子数组(具有相同前驱结构)两两进行归并操作完成排序:
在这里插入图片描述

归并排序是将区间逐个分解为一个个小区间,直到不能分割为止,然后一步步 归并起来 ,逐层返回。而这一过程需要借助一个辅助数组 tmp 来完成归并过程。
eg.两个有序数组归并:依次比小,小的尾插到新空间

在这里插入图片描述

1.2 两个有序子序的归并(排升序)

arr是被分割的原数组,tmp是用于归并操作的临时数组,begin是arr的左端下标,end是arr的右端下标

假设数组arr被二等分为两个子序列(两个子序列都是有序的):
在这里插入图片描述

接下来我们将上图中的[begin,(begin+end)/2)和[(begin+end)/2),end)两个子序列(有序)合并到一个tmp数组中构成一个新的有序序列(通过三指针完成)
在这里插入图片描述

代码:

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)//不存在返回
	{
		return;
	}
	
	int mid = (begin + end) >> 1;// /2

  
	//[begin,mid] [mid+1,end]归并
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	
	/*
	* 第一种拷贝回原数组的方式 - memset
	* 此种做法 cnt 从 begin 开始
	* memset 从 begin 位置开始,一共拷贝 end - begin + 1 个元素
	* 和下面做法道理相同
	*/
	// int cnt = begin;
	
	/*
	* 第二种拷贝回原数组的方式 - 循环拷贝
	* 此种做法 cnt 从 0 开始
	* 开始拷贝的位置从 begin 开始
	* cnt 最终的长度就是 [begin, end] 之间的长度
	* 没问题
	*/
	int cnt = 0; 

	while (begin1 <= end1 && begin2 <= end2)
	{
		// 保持稳定性
		if (a[begin1] <= a[begin2])
		{
			tmp[cnt++] = a[begin1++];
		}
		else
		{
			tmp[cnt++] = a[begin2++];
		}
	}

	while (begin1 <= end1) 
	{
		tmp[cnt++] = a[begin1++];
	}
	while (begin2 <= end2) 
	{
		tmp[cnt++] = a[begin2++];
	}
	// 方法1
	// memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
	//目标 源 大小
	
    // 方法2
    for (int i = begin, j = 0; i <= end; i++, j++)
	{
		a[i] = tmp[j];
	}
}

1.3 归并递归版本

完成arr数组[left,right)区间序列排序的过程可以拆分为如下三个步骤:
1.先完成左子区间[left,left + (right - left) / 2)的排序
2.再完成右子区间[left + (right - left) / 2,right)的排序
3.最后将左右子区间进行归并完成[left,right)区间序列的排序

数组二分的递归框架:

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)//不存在返回
	{
		return;
	}
	
	int mid = (begin + end) >> 1;// /2
	//[begin,mid] [mid+1,end] 子区间递归排序
    // 递归到底部
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);
}

思路:
1.对于归并排序来说,首先开辟一个辅助数组 tmp 。我们每一次取一个中间点 mid=(begin + end)/2 。
2.按照后序遍历的方式,分别递归左右区间:[begin,mid] ,[mid+1,end] 一直递归到底部,递归的返回条件为begin>=end 。
3.然后归并,设定相关变量,将两区间内对应元素由小到大放置到tmp 数组对应位置处。
4.如果放置过程结束,一个数组没有放置完,则需要在循环结束后,将数组的数据全部倒入 tmp 数组中。
5.在上面的过程完毕之后,再把tmp 数组中的数据拷贝回原数组。
6.最终,递归逐层返回后,就完成了归并过程。

代码:

//子函数
void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)//不存在返回
	{
		return;
	}
	
	int mid = (begin + end) >> 1;// /2
	//[begin,mid] [mid+1,end] 子区间递归排序
    // 递归到底部
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);
	//[begin,mid] [mid+1,end]归并
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	
	/*
	* 第一种拷贝回原数组的方式 - memset
	* 此种做法 cnt 从 begin 开始
	* memset 从 begin 位置开始,一共拷贝 end - begin + 1 个元素
	* 和下面做法道理相同
	*/
	// int cnt = begin;
	
	/*
	* 第二种拷贝回原数组的方式 - 循环拷贝
	* 此种做法 cnt 从 0 开始
	* 开始拷贝的位置从 begin 开始
	* cnt 最终的长度就是 [begin, end] 之间的长度
	* 没问题
	*/
	int cnt = 0; 

	while (begin1 <= end1 && begin2 <= end2)
	{
		// 保持稳定性
		if (a[begin1] <= a[begin2])
		{
			tmp[cnt++] = a[begin1++];
		}
		else
		{
			tmp[cnt++] = a[begin2++];
		}
	}

	while (begin1 <= end1) 
	{
		tmp[cnt++] = a[begin1++];
	}
	while (begin2 <= end2) 
	{
		tmp[cnt++] = a[begin2++];
	}
	// 方法1
	// memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
	//目标 源 大小
	
    // 方法2
    for (int i = begin, j = 0; i <= end; i++, j++)
	{
		a[i] = tmp[j];
	}
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("mallol fail");
		return;
	}

	_MergeSort(a, 0, n - 1, tmp);
}

1.4 归并排序非递归版本

归并排序的非递归版本在这一块是一个难点,因为它本身就很难想到。

首先想一下,对于归并排序来说,能不能像快速排序那样借助数据结构-栈来实现?

如果用栈,是不太行的。因为归并是一种类似二叉树后序遍历的排序,当将区间入栈后,把区间拿出来处理,之后要继续分割时,一段区间可能就不见了,所以借助数据结构-栈时不太行的。

所以我们可以不借助数据结构,用一种相对简单的方法完成。

我们可以设定一个 gap ,控制我们的区间大小,gap 就是归并时每组的数据个数。由于我们是类似二叉树后序遍历的方式,所以我们一开始的归并实际上就是 gap 为 1 情况。

如下图:
在这里插入图片描述
arr代表待排序的数组,size为待排序数组的元素个数

通过每次改变gap 实际上也就是改变了区间大小,就模拟除了归并递归到底,从小区间合并逐渐到大区间合并的过程。所以我们就让gap 每次 × 2,这样子就是归并每次扩大区间的过程。

使用一个变量i来遍历每一个gap情形下的各个进行归并的序列组(每个序列组由两个子数组构成):

for (int gap = 1; gap < size; gap *= 2)      //完成logN个层次的子数组的归并
	{
		for (int i = 0; i < size; i += 2 * gap)  //i每次跳过一个归并序列组(每个序列组有两个子数组)
		{
			//对子数组[i,i+gap-1]和子数组[i+gap,i+2*gap-1]进行归并操作
		}
	}

在这里插入图片描述

但是上面的方法只能解决数组长度恰巧被整除的情况,对于无法被整除的情况可能就会造成越界。比如 n=17 。在gap=4 时,最后一段区间 [ 17 , 20 ] 越界,所以这里需要调整

我们设置四个点begin1 = i, end1 = i + gap - 1, begin2 = i + gap, end2 = i + 2 * gap - 1四个点来规定两段区间。

列举一下,这四个点的越界情况,我们可以分为三种情况:
1 .end1, begin2, end2越界
在这里插入图片描述

2.end1没有越界,begin2,end2 越界
在这里插入图片描述

3.end2 越界
在这里插入图片描述

以上是三种越界情况,需要分别处理,处理方式分为 修正区间不修正区间

修正区间 :

第一种越界情况,实际上就是 end1≥n ,那么这种情况修正区间的话,这时将end1=n−1 ,之后将没有越界的部分拷贝到 tmp 数组中,然后将[begin2,end2] 修正为一个不存在的区间,这样就不会进入后面循环,也就不会拷贝进数据。
反例:如果begin2 =end2 = n - 1,就会出现有一个数据重复归并的bug

if (end1 >= n)
{
	end1 = n - 1;
	// begin2 和 end2 修正为不存在的区间
	begin2 = n;
	end2 = n - 1;
}

第二种越界情况,就是 begin2≥n ,这种情况下直接将[begin2,end2] 修正为不存在的区间即可。

else if (begin2 >= n)
{
	begin2 = n;
	end2 = n - 1;
}

第三种越界情况,就是end2≥n,这种情况将end2=n−1 ,让两端区间正常归并。

else if (end2 >= n)
{
	end2 = n - 1;
}

这种情况可以边归并边拷贝,也可以一组归并完了拷贝。

循环外拷贝,修正区间后,直接一把全部拷贝:

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	int gap = 1;
	while (gap < n)
	{
		// 一组归并的跨距为 2 * gap
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			int j = i;
			// 修正区间
			if (end1 >= n)
			{
				end1 = n - 1;
				// begin2 和 end2 修正为不存在的区间
				begin2 = n;
				end2 = n - 1;
			}
			else if (begin2 >= n)
			{
				begin2 = n;
				end2 = n - 1;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}
			//归并
			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++];
			}
			// 可以局部拷贝
			//memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		memcpy(a, tmp, sizeof(int) * n);
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}

不修正区间 :

第一种越界情况,修正区间之后由于后面的数据不归并了,实际上也就是拷贝了原数组的数据到tmp,然后又拷贝回原数组,所以没必要修正, 直接break 掉。

if (end1 >= n)
{
	break;
}

第二种越界情况,同第一种,实际上也是拷贝原数组的数据,也可以 break 。

else if (begin2 >= n)
{
	break;
}

但是第三种越界情况,就需要修正一下,否则这次归并无法完成,之后的归并也都错误了,让 end2=n−1 。

else if (end2 >= n)
{
	end2 = n - 1;//修正end2边界,以完成数组尾部剩余子数组的归并
	//break;
}

而这种情况只能边归并边拷贝,因为有些区间是未处理的,如果贸然进行拷贝会把随机值,或者错误数据拷贝进来。

memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));

循环内拷贝,不修正区间,归并一部分,拷贝一部分:

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			int j = i;
			if (end1 >= n)
			{
				break;
			}
			else if (begin2 >= n)
			{
				break;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
				//break;
			}
			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++];
			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		// 这里不能外部拷贝,因为有些情况是直接 break 出来的,tmp 中不是正确数据
		// memcpy(a, tmp, sizeof(int) * n); // 会把错误数据拷入
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}

1.5 特性及复杂度

由于归并排序的数组划分每次都是严格地二分,每次排序子数组划分结构都是稳定的满二叉树(或接近满二叉树)结构,因此归并排序的时间复杂度在各种情况下都不会有变化(不会像快排,希尔排那样由于所处理的序列的逆序数的差异而导致算法时间复杂度有所变化)。然而由于有序序列归并操作需要额外开辟数组来完成,因此归并排序有较大的空间消耗,这是归并排序的一个缺陷

对于归并递归版本,每次都是区间二分,然后开始递归的。所以递归层数是严格logN ,每次递归中时间复杂度为O(N) ,所以总体时间复杂度为O(N*logN) ;对于非递归,gap每次乘 2 ,每次 gap 处理的时间复杂度为 O(N) ,时间复杂度也是 O(N *logN)。

对于归并排序的空间复杂度,递归和非递归有一些计算上的区别,但是结果不影响。
归并排序首先需要一个 tmp 数组,空间复杂度为 O(N) 。如果对于递归,还会开 logN 层栈帧,所以递归版本消耗的总空间大约为O(N+logN) ,当 N 足够大时,logN 省略,所以为 O(N);对于非递归,那么就仅仅只有tmp 的消耗。

所以综上所述,归并的空间复杂度为 O(N)。

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

2、计数排序

2.1 算法思想

思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。

计数排序的动图:
在这里插入图片描述

计数排序实际上就是将数组中对应数据出现的次数,将数据出现次数映射到一个新数组中。在与数据相等值的下标处,将这个下标位置的元素自增。每出现一个数字就自增一次。

平常的映射就是直接在其相等下标位置处理,叫做 绝对映射 ;还有一种映射方式叫 相对映射

绝对映射
所谓绝对映射,就是开辟一个辅助数组count ,数组大小为待排序数组的最大元素的大小 max ,然后遍历数组,将数据映射到辅助数组 count 中
在这里插入图片描述

然后根据count 数组中的元素,根据元素对应的下标,将下标的值填入 a 数组中,如果 count 数组中该位置为 0 , 则不需要填。
在这里插入图片描述

最后 a 数组中的元素就已经被排序好了。

绝对映射 的缺点:当最大元素很大,或者是出现负数时,就无法映射了。因为空间开大了浪费空间,并且无法在负数下标自增。所以这就引出了 相对映射 。

相对映射
相对映射是根据数据之间的相对情况来开辟数组大小,并在转换后的相对位置执行映射。
比如有这样一组数据:{328,325,323,321} ,对于这组数据我们开 329 个空间肯定是浪费的。

我们相对映射的思路就是遍历序列,找到序列最大值 max 和最小值 min ,然后开辟max−min+1 个空间,让空间尽可能充分利用。

之后映射自增时,也使用相对位置,这个相对位置就是数组元素减去数组元素的最小值:a[i] - min 。

在最后将元素放到原数组中时,也需要将数组下标加上最小值:i + min 放回去就可以。

通过相对映射,对于元素有负数,和空间浪费的情况都可以解决。(ps:元素有负数的情况,无需特殊处理,因为相对映射的原因,这些步骤都可以正确进行,不信可以试验一下)。

2.2 代码实现

接口实现步骤:
1.找出待排序的数组中最大和最小的元素。
2.统计数组中每个值为 i 的元素出现的次数,存入数组 count 的第 i 项。
3.对所有的计数累加(从 count 中的第一个元素开始,每一项和前一项相加)。
4.反向填充目标数组:将每个元素 i 放在新数组的第 count(i) 项,每放一个元素就将count(i) 减1。

接下来使用相对映射的思路:

// 计数排序 正负数都可以排
void CountSort(int* a, int n)
{
	// 1. 找最小值和最大值
	int max = a[0], min = a[0];
	for (int i = 0; i < n; i++)
	{
		if (a[i] > max)
		{
			max = a[i];
		}
		if (a[i] < min)
		{
			min = a[i];
		}
	}

	// 2. 根据差值构建 count 数组
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);
	if (count == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
    // 初始化
	memset(count, 0, sizeof(int) * range);

	// 3. 将值映射到count数组中
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++; // 映射到相对位置
	}

	int cnt = 0;
	for (int i = 0; i < range; i++)
	{
		while (count[i]--)
		{
			a[cnt++] = i + min;
		}
	}
	
	free(count);
}

2.3 特性及复杂度

计数排序的时间复杂度其实是由 range 和 N 的关系来衡量的,当我们不确定range和 N 的大小时,我们可以认为 计数排序的时间复杂度取O(max(N,range)) 较大的一个。

空间复杂度则是 O(range)。

实际上通过时空复杂度上看,我们发现计数排序在数据集中的情况下是很强的,能达到几乎 O(N) 的时间复杂度,并且空间复杂度也不会太大。但是对于范围分散,跨度大的序列就不适合,不仅时间没啥优势,空间占比也是个大问题。所以计数排序的适用范围是有限的,如:字符串、浮点数等就不适合

特性:计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
时间复杂度:O(MAX(N,范围)) 。
空间复杂度:O(范围) 。
稳定性:稳定。

3、八大排序算法总结

在这里插入图片描述

排序的稳定性

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且 r[i] 在r[j] 之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
简单来说,在排相等的两个数时,这两个数不交换,比如遇到 5 5 时,不发生交换。
在这里插入图片描述

稳定性是排序算法一种额外的优点。如果一种排序可以通过某种措施,达到数据相对次序不变的效果,则称该排序是稳定的。

4、排序性能测试

void TestOP()
{
	srand(time(0));
	const int N = 10000000;

	int* a1 = (int*)malloc(sizeof(int) * N);
	int* a2 = (int*)malloc(sizeof(int) * N);
	int* a3 = (int*)malloc(sizeof(int) * N);
	int* a4 = (int*)malloc(sizeof(int) * N);
	int* a5 = (int*)malloc(sizeof(int) * N);
	int* a6 = (int*)malloc(sizeof(int) * N);
	int* a7 = (int*)malloc(sizeof(int) * N);

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

	// clock 获取程序运行到这块的时间
	// end1 - begin1 = 排序时间
	// 获取的是毫秒
	// 时间过小时,计算不出来
	int begin1 = clock();
	InsertSort(a1, N);
	int end1 = clock();

	int begin2 = clock();
	ShellSort(a2, N);
	int end2 = clock();

	int begin3 = clock();
	SelectSort(a3, N);
	int end3 = clock();

	int begin4 = clock();
	HeapSort(a4, N);
	int end4 = clock();

	int begin5 = clock();
	QuickSortT(a5, 0, N - 1);
	int end5 = clock();

	int begin6 = clock();
	BubbleSort(a6, N);
	int end6 = clock();

	int begin7 = clock();
	MergeSort(a7, N);
	MergeSortNonR(a7, N);
	int end7 = clock();

	printf("InsertSort:%d\n", end1 - begin1);
	printf("ShellSort:%d\n", end2 - begin2);
	printf("SelectSort:%d\n", end3 - begin3);
	printf("HeapSort:%d\n", end4 - begin4);
	printf("QuickSort:%d\n", end5 - begin5);
	printf("BubbleSort:%d\n", end6 - begin6);
	printf("MergeSort:%d\n", end7 - begin7);

	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
	free(a7);
}

5.总结:

今天我们认识并具体学习了归并排序和非比较排序中的计数排序,以及对八大排序算法进行了总结,到这里我们的排序算法学习就暂告一段落啦。接下来,我们将开始学习C++的相关知识。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

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

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

相关文章

图像处理数据集

BSDS500 Berkeley Segmentation Dataset 500 是第一个用于评估超像素算法的数据集。对于参数优化&#xff0c;使用了验证集。 500张数据集200训练集train100验证集val200测试集test 每张图像有 5 个不同的高质量地面真值分割&#xff08;groundTruth,是.mat文件&#xff09; …

Android 中的混音器 AudioMixer 实现分析

Android framework 的音频处理模库 libaudioprocessing (位于 frameworks/av/media/libaudioprocessing) 提供了混音器组件 AudioMixer&#xff0c;它主要用在 audioflinger 里&#xff0c;用来将多路音频源数据混音&#xff0c;以方便送进音频设备播放出来。 音频混音操作本身…

MyBatis(九)MyBatis小技巧

一、#{}和${} #{}&#xff1a;先编译sql语句&#xff0c;再给占位符传值&#xff0c;底层是PreparedStatement实现。可以防止sql注入&#xff0c;比较常用。 ${}&#xff1a;先进行sql语句拼接&#xff0c;然后再编译sql语句&#xff0c;底层是Statement实现。存在sql注入现象。…

第09章_异常处理

第09章_异常处理 讲师&#xff1a;尚硅谷-宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a;http://www.atguigu.com 本章专题与脉络 1. 异常概述 1.1 什么是生活的异常 男主角小明每天开车上班&#xff0c;正常车程1小时。但是&#xff0c;不出…

计网第五章.运输层—TCP流量控制与可靠传输

以下来自湖科大计算机网络公开课笔记及个人所搜集资料 目录一、流量控制死锁死锁的解决&#xff1a;二、超时重传时间的选择解决方案Karn算法三、可靠传输补充&#xff1a;其实TCP的流量控制&#xff0c;可靠传输&#xff0c;拥塞控制&#xff0c;都是围绕滑动窗口机制来实现的…

SpringBoot的统一功能处理

目录 1.统一用户的的登录权限校验 最开始的用户登录 Spring拦截器 2.统一数据返回格式 统一数据的返回格式意义 统一数据返回格式的实现 3.统一异常处理 在上篇博客中我介绍了Spring AOP的基础知识,这篇博客则是AOP的实践练习,通过借助AOP实现三个目标 1.统一用户登录权…

VContainer 初体验

IOC 控制反转 IOC 提供一个对象生成容器&#xff0c;在我们需要取得某个对象时&#xff0c;不再使用New关键字进行对象生成操作&#xff0c;而是通过IOC容器内部控制来获得对象。 使用这种思想方式&#xff0c;可以让我们无需关心对象的生成方式&#xff0c;只需要告诉容器我需…

xmanager连接linux桌面教程 xmanager连接之后黑屏

xmanager 是一款专业的远程服务器管理软件&#xff0c;但习惯了使用Windows系统下的桌面&#xff0c;一时会无法适应linux服务器的命令行界面。下面我就为大家介绍xmanager连接linux桌面教程&#xff0c;xmanager连接之后黑屏的相关内容&#xff0c;让大家在使用linux更加方便。…

node.js详解

文章目录1.Node.js1.1 Node.js 模块化1.2 模块暴露数据1.2.1 模块初体验1.2.2 暴露数据1.3.导入(引入)模块1.4 JavaScript引擎1.5 什么是Node.js1.6 BFF2.包管理工具2.1 npm2.1.1 npm 的安装2.1.2 npm 基本使用2.1.2.1 初始化2.1.2.2 搜索包2.1.2.2 下载安装包2.1.2.3 require …

java遍历字符串的方法

在 java中&#xff0c;我们需要遍历字符串&#xff0c;如何遍历呢&#xff1f;首先我们先了解一下遍历的概念&#xff1a; 在我们的计算机中&#xff0c;存储的都是二进制数据&#xff0c;为了方便存储和管理&#xff0c;我们把一段数据分成多个字符串。在 java中&#xff0c;遍…

BM36-判断是不是平衡二叉树

题目 输入一棵节点数为 n 二叉树&#xff0c;判断该二叉树是否是平衡二叉树。 在这里&#xff0c;我们只需要考虑其平衡性&#xff0c;不需要考虑其是不是排序二叉树 平衡二叉树&#xff08;Balanced Binary Tree&#xff09;&#xff0c;具有以下性质&#xff1a;它是一棵空…

LabVIEW-簇数据类型

簇数据类似于 C 语言的结构体&#xff0c;创建时&#xff0c;首先将“簇”放置到前面板上&#xff1a; 然后放置簇内的元素&#xff0c;比如“数值输入控件”&#xff0c;当“簇框架”内边沿出现虚线框时&#xff0c;单击“数值输入控件”即可添加到簇中:在簇中也可以修改“数值…

常见网络协议汇总(一)

“网络协议”是指为完成特定的任务而制定的一套规则。网络协议通常用来表示数据传输中一组用于实现一个或多个OT模型级别的规则或规范。在通信时&#xff0c;网络协议定义了在通信时如何进行通信。今天海翎光电的小编就汇总了常见的网络协议&#xff0c;来一起看看。我们先回顾…

Linux基础IO(下)

Linux基础IO&#xff08;下&#xff09;FILE自己模拟实现fopen/fclose、fread/fwrite理解文件系统OS如何看待磁盘管理磁盘硬链接软连接ACM时间动态库和静态库见一见Linux下的库为什么要有库写一写库制作一个静态库制作一个动态库关于动静态库的一点小实验FILE 通过前面学习我们…

【SSM】Spring6(十二.Spring6集成MyBatis3.5)

文章目录1. 实现步骤2.具体实现2.1 准备数据库2.2 创建模块&#xff0c;引入依赖2.3 创建包2.4 创建Pojo类2.5 编写mapper接口2.6 编写Mapper配置文件2.7 编写service接口和service接口实现类2.8 编写jdbc.properties配置文件2.9 编写mybatis-config.xml配置文件2.10编写spring…

什么是数字“指纹”?

今天的网站收集有关访问者的大量信息&#xff0c;不仅用于广告、业务优化和用户体验&#xff0c;还用于安全目的。 除了 cookie 之外&#xff0c;网站还使用“指纹识别”来收集有关用户网络浏览器、硬件、设备配置、时区甚至行为模式的信息&#xff0c;以授权合法用户或取消对…

考虑可再生能源消纳的电热综合能源系统日前经济调度模型

目录 1 主要内容 模型示意图 目标函数 程序亮点 2 部分程序 3 程序结果 4 程序链接 1 主要内容 本程序参考文献《考虑可再生能源消纳的建筑综合能源系统日前经济调度模型》模型&#xff0c;建立了电热综合能源系统优化调度模型&#xff0c;包括燃气轮机、燃气锅炉、余热…

飞腾D2000 UOS下安装KVM虚拟机

其他的和x86环境都差不多&#xff0c;开了开发者模式后&#xff0c;virt-manager qemu-efi-aarch64 qemu-system 几个包补齐&#xff0c;启动libvirtd服务&#xff0c;查看日志&#xff0c;报以下日志&#xff0c; 4月 09 21:13:34 actionchen-PC systemd[1]: Starting Virtu…

SQL select总结(基于选课系统)

表详情&#xff1a; 学生表&#xff1a; 学院表&#xff1a; 学生选课记录表&#xff1a; 课程表&#xff1a; 教师表&#xff1a; 查询&#xff1a; 1. 查全表 -- 01. 查询所有学生的所有信息 -- 方法一&#xff1a;会更复杂&#xff0c;进行了两次查询&#xff0c;第一…

C语言实现扫雷教学

本篇博客会讲解&#xff0c;如何使用C语言实现扫雷小游戏。 0.思路及准备工作 使用2个二维数组mine和show&#xff0c;分别来存储雷的位置信息和排查出来的雷的信息&#xff0c;前者隐藏&#xff0c;后者展示给玩家。假设盘面大小是99&#xff0c;这2个二维数组都要开大一圈…