【排序算法】六大比较类排序算法——插入排序、选择排序、冒泡排序、希尔排序、快速排序、归并排序【详解】

文章目录

  • 六大比较类排序算法(插入排序、选择排序、冒泡排序、希尔排序、快速排序、归并排序)
  • 前言
  • 1. 插入排序
    • 算法描述
    • 代码示例
    • 算法分析
  • 2. 选择排序
    • 算法描述
    • 优化
    • 代码示例
    • 算法分析
  • 3. 冒泡排序
    • 算法描述
    • 代码示例
    • 算法分析
    • 与插入排序对比
  • 4. 希尔排序
    • 算法描述
    • 代码示例
    • 算法分析
  • 5. 快速排序
    • 5.1 挖坑法
      • 算法描述
      • 单轮操作
      • 代码示例
      • 算法分析
      • 优化(三数取中,小区间优化)
    • 5.2 左右指针法
      • 算法描述
      • 代码示例
    • 5.3 前后指针法
      • 算法描述
      • 代码示例
  • 6. 归并排序
    • 算法描述
    • 代码示例
  • 总结对比

六大比较类排序算法(插入排序、选择排序、冒泡排序、希尔排序、快速排序、归并排序)

前言

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减地排列起来地操作。

稳定性:假定在待排序地记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i] = r[j],且 r[i] 在 r[j] 之前,而在排序后的序列中。r[i] 仍在 r[j] 之前。则称这种排序算法是稳定的。否则称为不稳定的。

内部排序:数据元素全部放在内存中的排序。(我们下面讲的排序都是属于内部排序)

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

1. 插入排序

  • 算法描述

直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。

插入排序的原理很好理解,打过扑克牌的人应该都能懂这种思想。

当我们在插入第 i (i>=1) 个元素时,前面的 array[0], array[1],…, array[i-1] 已经排好序(初始时,已排序部分只包含第一个元素,未排序部分包含剩余元素),此时用 array[i] 的排序码依次与它前面的 array[i-1], array[i-2],… 的排序码顺序进行比较,找到插入位置时就就将 array[i] 插入。原来位置上的元素顺序后移一位。

请添加图片描述

下面是动图演示:

请添加图片描述

  • 代码示例

// 插入排序
void InsertSort(int* a, int n) {  // n表示数组的大小
	for (int i = 0; i < n - 1; i++) {
		int end = i;  
		int tmp = a[end + 1];  // 待排序元素
		while (end >= 0) {
			if (a[end] > tmp) {  // 依次向前比较
				a[end + 1] = a[end];  // 向后移移位
				--end;
			}
			else {  
				break;
			}
		}
		a[end + 1] = tmp;  // 插入操作
	}
}
  • 算法分析

  1. 元素集合越接近有序,直接插入排序算法的时间复杂度效率越高

  2. 时间复杂度 O ( N 2 ) O(N^2) O(N2)

    最坏情况下为 O ( N 2 ) O(N^2) O(N2),此时待排序列为逆序,或者说接近逆序
    最好情况下为 O ( N ) O(N) O(N),此时待排序列为升序,或者说接近升序。

  3. 空间复杂度 O ( 1 ) O(1) O(1)

  4. 稳定性:稳定

2. 选择排序

  • 算法描述

选择排序的基本思想是每次从待排序列中选出一个最小值(或最大值),然后放在序列的起始(或末尾)位置,直到全部待排数据排完即可。

下面是动图演示:

请添加图片描述

  • 优化

实际上,我们可以一趟选出两个值,一个最大值一个最小值,然后将其放在序列开头和末尾,这样可以使选择排序的效率快一倍。

  • 代码示例

void SelectSort(int* a, int n) {
	int begin = 0, end = n - 1;  // 保存开头和末尾的下标
	while (begin < end) {
		int mini = begin, maxi = begin;  // 记录区间内最小值和最大值的下标
		for (int i = begin; i <= end; ++i) {  // 该循环用于找到区间内的最大和最小值
			if (a[i] < a[mini]) {
				mini = i; 
			}
			if (a[i] > a[maxi]) {
				maxi = i;
			}
		}
        // Swap函数在外部需要自己写一下
		Swap(&a[begin], &a[mini]);  // 把最小的放在开头
		Swap(&a[end], &a[maxi]);  // 把最大的放在末尾
		++begin;
		--end;
	}
}

这就完了吗?当然没有,还有一点小瑕疵,我们来看这两行:

Swap(&a[begin], &a[mini]);  // 把最小的放在开头
Swap(&a[end], &a[maxi]);  // 把最大的放在末尾

这里万一我最大的数就是开头的数呢?就有可能在第一次交换的时候和最小值交换走,那么下一行交换的时候 a[maxi] 就不是最大值了,所以在第一次交换的时候我们需要判断一下 beginmaxi 的位置是否重叠。完整代码如下:

void SelectSort(int* a, int n) {
	int begin = 0, end = n - 1;
	while (begin < end) {
		int mini = begin, maxi = begin;
		for (int i = begin; i <= end; ++i) {
			if (a[i] < a[mini]) {
				mini = i;
			}
			if (a[i] > a[maxi]) {
				maxi = i;
			}
		}
		Swap(&a[begin], &a[mini]);  // 把最小的放在开头
		// 如果 begin 和 maxi 的位置重叠,maxi 的位置要修正一下
		if (begin == maxi) {
			maxi = mini;  // 也就是说值换了那么对应的下标也得改
		}
		Swap(&a[end], &a[maxi]);  // 把最大的放在末尾
		++begin;
		--end;
	}
}
  • 算法分析

时间复杂度:最坏情况: O ( N 2 ) O(N^2) O(N2)
      最好情况: O ( N 2 ) O(N^2) O(N2)
空间复杂度: O ( 1 ) O(1) O(1)

稳定性:不稳定

3. 冒泡排序

  • 算法描述

冒泡排序通过比较相邻的元素并交换它们的位置来实现排序。即从列表的第一个元素开始,比较相邻的两个元素。如果前一个元素比后一个元素大,则交换它们的位置。对列表中的每一对相邻元素重复上述步骤,直到列表的末尾。这样,最大的元素会被 “冒泡” 到列表的最后。忽略已经排序好的最后一个元素,重复上述步骤,直到整个列表排序完成。

下面是动图演示:

请添加图片描述

  • 代码示例

void BubbleSort(int* a, int n) {
	for (int i = 0; i < n - 1; i++) {
		int flag = 0;
		for (int j = 1; j < n - i; j++) {
			if (a[j - 1] > a[j]) {
				Swap(&a[j - 1], &a[j]);  // 前面比后面大就交换
				flag = 1;
			}
		}
		// 没有发生交换说明已经有序,不需要再比较了
		if (flag == 0) {
			break;
		}
	}
}
  • 算法分析

时间复杂度:最坏情况: O ( N 2 ) O(N^2) O(N2)
      最好情况: O ( N ) O(N) O(N)(通过设置一个变量 flag 来实现)
空间复杂度: O ( 1 ) O(1) O(1)

稳定性:稳定

  • 与插入排序对比

当一个数组很接近有序的时候,比如 [1, 2, 3, 5, 4, 6] ,这个时候采用插入排序循环的执行次数只需要 N N N 次, 而冒泡排序需要 ( N − 1 ) + ( N − 2 ) (N-1)+(N-2) (N1)+(N2) 次。可见,对于接近有序的数组,插入排序比冒泡排序的局部适应性更强

4. 希尔排序

  • 算法描述

希尔排序又称缩小增量法,它的基本思想是:将待排序的元素分为多个子序列,然后对每个子序列进行插入排序。这些子序列是原始序列中相隔一定增量的元素组成的。然后逐渐减小增量,重复这个过程,最终将增量减小到 1,完成最后一轮的插入排序,此时序列已经基本有序,只需进行少量的比较和交换操作,大大提高了排序效率。

请添加图片描述

也就是说,我们需要指定一个 gap(间隔),从第一个数开始每各一个这个 gap 的距离直到最后的这些数为一组,在同一个组内的数它们进行插入排序的操作。然后进行了这一轮 “预排序” 之后再逐渐缩小这个 gap,直到 gap 为 1,最后这个数组就有序了。而我们会发现:

  • gap 越大,大的数可以越快地到后面,小的数可以越快地到前面。但是这样预排序完数组也越不接近有序。
  • gap 越小,排完之后地数组越接近有序。
  • 特别地,当 gap 为 1 的时候,这就变成了一个普通的插入排序。

而刚刚我们也提到过说插入排序对接近有序的数组它的效率就越高,那么这个希尔排序就可以看作是插入排序的升级版,它通过设置一个 gap 先预排序,让数组接近有序,然后再插入排序,这样就能提高效率。

还有一个问题:这个 gap 到底该怎么设计呢?通常我们可以让初始的 gap 设置为数组长度的一半这样会更好,然后每次预排序完之后让 gap 整除 2(或者 3 也不是不行),但最后都要保证 gap 能到 1,因为这样最后排完的数组才能够是有序的。

下面是它的动图演示:

请添加图片描述

  • 代码示例

void ShellSort(int* a, int n) {
	int gap = n;  // 一开始设置成n,这样一进循环就是长度的一半了
	while (gap > 1) {
		gap /= 2;        // 可以保证 gap 最后一次为 1
		// gap /= 3 + 1;  // gap 整除 3 不一定能到 1,所以要加 1

		// 把间隔为 gap 的同时排
		for (int i = 0; i < n - gap; ++i) {
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0) {
				if (a[end] > tmp) {
					a[end + gap] = a[end];
					end -= gap;
				}
				else {
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}
  • 算法分析

  • 希尔排序实际上是直接插入排序的优化:
  1. 先进行预排序,让数组接近有序
  2. 直接插入插入排序
  • gap > 1​ 时,都是预排序,让数组接近有序。

    gap == 1​ 时,就是直接插入排序。

  • 时间复杂度:

第一层的 while 循环,时间复杂度: O ( l o g 2 N ) O(log_2N) O(log2N) 或者 O ( l o g 3 N ) O(log_3N) O(log3N)

第二层的 for 循环,当 gap​ 很大时,下面的预排序时间复杂度: O ( N ) O(N) O(N),当 gap​ 很小时,数组已经接近有序了,这时候差不多也是 O ( n ) O(n) O(n)

综合计算,有数据得出平均的时间复杂度为 O ( N 1.3 ) O(N^{1.3}) O(N1.3)

  • 空间复杂度: O ( 1 ) O(1) O(1)
  • 稳定性:不稳定

5. 快速排序

快速排序的基本思想是:选择一个基准元素,将数组划分为两个子数组,比基准元素小的元素放在左边,比基准元素大的元素放在右边,然后分别对左右子数组进行排序,最终实现整个数组的有序排列。

而实现上面所说的操作,我们可以有以下几种方法:

5.1 挖坑法

  • 算法描述

  1. 我们把数组的最左边的数看作为一个 “坑位”,把这个值保存在一个变量 key 中。

  2. 定义两个变量 leftright 起始位置分别是数组的开头和结尾,这个时候最左边的 left 就是 “坑位”。让 right 先往左走,每次走一个单位去寻找比 key 更小的单位,找到了就停下。

  3. 然后把这个值赋给 “坑位” 上,也就是 left 对应的位置,此时 right 变成新的 “坑位”。

  4. 这时再让 left 往右走去寻找比 key 更大的值,找到了就停下。

  5. 然后把这个值赋给 “坑位”,也就是 right 所对应的位置,此时 left 形成新的 “坑位”。

  6. 再让 right 往左走…,就这样循环往复直到 leftright 相遇,此时相遇点一定是 “坑位”,最后把 key 的值赋给坑位上就算完成了第一趟排序。

注意:如果将最左边的数看作是 “坑位” 的话,那么需要让 right 先走;如果最右边的数是 “坑位” 的话,那么需要最左边的数先走。

下面是动图演示:

请添加图片描述

  • 单轮操作

void PartQuickSort(int* a, int n) {
	int begin = 0, end = n - 1;
	int pivot = begin;  // 最左边的作为坑位
	int key = a[begin];  // 将坑位上的值保存在 key 中
	while (begin < end) {
		//右边找小,放到左边
		while (begin < end && a[end] >= key) {
			--end;
		}
		//小的放到左边的坑,自己形成了新的坑位
		a[pivot] = a[end];
		pivot = end;

		//左边找大,放到右边
		while (begin < end && a[begin] <= key) {
			++begin;
		}
		//大的放到右边的坑,自己形成了新的坑位
		a[pivot] = a[begin];
		pivot = begin;
	}
	pivot = begin;
	a[pivot] = key;
}

在完成第一轮操作之后,我们会发现 key 的左边全是比 key 小的值,而 key 的右边全是比 key 大的值。而我们要让整个数组有序,我们希望 key 的左右两部分都是有序的。

这就可以用到分治的思想——去递归地调用自身,让 key 的左右两个子区间进行同样的操作直到区间只有一个数或者没有了为止。这样一来 key 左右两个区间就是有序的,那么整个数组就有序了。

  • 代码示例

void QuickSort(int* a, int left, int right) {  // 由于之后需要递归调用子区间,所以要改一下形参
	if (left >= right) {
		return;
	}  // 递归结束条件
	int begin = left, end = right;
	int pivot = begin;
	int key = a[begin];
	while (begin < end) {
		//右边找小,放到左边
		while (begin < end && a[end] >= key) {  // 注意这里左边的条件是为了防止两变量错过
			--end;
		}
		//小的放到左边的坑,自己形成了新的坑位
		a[pivot] = a[end];
		pivot = end;

		//左边找大,放到右边
		while (begin < end && a[begin] <= key) {
			++begin;
		}
		//大的放到右边的坑,自己形成了新的坑位
		a[pivot] = a[begin];
		pivot = begin;
	}
	pivot = begin;
	a[pivot] = key;

	//[left, pivot - 1] pivot [pivot + 1, right]
	QuickSort(a, left, pivot - 1);  // 递归左边
	QuickSort(a, pivot + 1, right);  // 递归右边
}
  • 算法分析

  • 时间复杂度

快速排序的时间复杂度为 O ( N l o g 2 N ) O(Nlog_2N) O(Nlog2N),其中 N N N 为待排序数组的长度。最坏情况下,快速排序的时间复杂度为 O ( N 2 ) O(N^2) O(N2),但这种情况出现的概率很小,可以通过一些优化措施来避免。

  • 空间复杂度

快速排序的空间复杂度取决于递归栈的深度,在最坏情况下,递归栈的深度为 O ( N ) O(N) O(N),因此快速排序的空间复杂度为 O ( N ) O(N) O(N)。但是,在一些实现中,可以使用非递归的方式来实现快速排序,从而避免递归栈带来的空间开销。

请添加图片描述

  • 稳定性

快速排序是一种不稳定的排序算法。因为在排序过程中,可能会交换相同元素的位置,从而导致相同元素的相对顺序被改变。例如,对于数组 [3, 2, 2, 1],如果选择第一个元素 3 作为基准元素,那么经过第一次划分后,数组变成了 [1, 2, 2, 3],其中两个 2 的相对顺序被改变了,因此是不稳定的。

  • 优化(三数取中,小区间优化)

上面说到快速排序在最坏的情况下时间复杂度会下降到 O ( N 2 ) O(N^2) O(N2),那是什么时候是最坏的呢?

结论:快速排序在**有序(不论顺序还是逆序)**的情况下最坏,时间复杂度为 O ( N 2 ) O(N^2) O(N2)

因为在有序的情况下,这个所谓的 “坑位” 并不会移动至靠近中间的位置,也就是说这样的话每次做完单趟排序的时候也只会让一个数变得有序。如下图所示:

请添加图片描述

为了解决这个问题,提出了一个解决方法——三数取中

即比较左中右三个数的大小,让值最中间的数作为坑,这样的话就避免了坑在最左边或者最右边的极端情况。

int GetMidIndex(int* a, int left, int right) {
	int mid = (left + right) >> 1;  // 这里是右移运算符,也相当于整除2
	if (a[left] < a[mid]) {
		if (a[mid] < a[right]) {
			return mid;
		}
		else if (a[left] > a[left]) {
			return left;
		}
		else return{
			return right;
		}
	}
	else {
		if (a[mid] > a[right]) {
			return mid;
		}
		else if (a[left] < a[right]) {
			return left;
		}
		else {
			return right;
		}
	}
}

void QuickSort(int* a, int left, int right) {
	if (left >= right) {
		return;
	}

	int index = GetMidIndex(a, left, right);
	Swap(&a[left], a[index]);
    
    //...下同
}

还有一种优化——小区间优化

就是说当递归的时候这个区间已经很小了,这个时候再去递归调用的效率就可能比不上其他的排序方法了。这里以 10 10 10 为例,当区间小于 10 10 10 的时候,我们就采用直接插入排序,如果区间大于 10 10 10 我们就正常递归即可。

void QuickSort(int* a, int left, int right) {
    //...上同

	//[left, pivot - 1] pivot [pivot + 1, right]
	//QuickSort(a, left, pivot - 1);
	//QuickSort(a, pivot + 1, right);

	if (pivot - 1 - left > 10) {
		QuickSort(a, left, pivot - 1);
	}
	else {
		InsertSort(a + left, pivot - 1 - left + 1);
	}
	if (pivot - 1 - left > 10) {
		QuickSort(a, pivot + 1, right);
	}
	else {
		InsertSort(a + pivot + 1, right - (pivot + 1) + 1);
	}
}

5.2 左右指针法

  • 算法描述

同样我们可以定义数组最左边的值为 key,再定义两个变量 leftright 起始位置分别是数组的开头和结尾,右边的 right 先走去找比 key 更小的值,找到了就停下。然后左边的 left 去找比 key 更大的值,找到了然后停下。

这时交换 leftright 对应的值。

然后 right 再走…,直到 leftright 相遇,此时将相遇的位置对应的值与 key 位置的值交换。这样一来,key 左边的值也都是比 key 小的,右边的是比 key 大的。、

注意:同样的,如果定义对左边的数为 key,那么右边的 right 先走,反之 left 先走。

下面是动图演示:

请添加图片描述

  • 代码示例

void QuickSort2(int* a, int left, int right) {
	if (left >= right) {
		return;
	}

	int index = GetMidIndex(a, left, right);
	Swap(&a[left], &a[index]);

	int begin = left, end = right;
	int keyi = begin;

	while (begin < end) {
		//找小
		while (begin < end && a[end] >= a[keyi]) {
			--end;
		}
		//找大
		while (begin < end && a[begin] <= a[keyi]) {
			++begin;
		}
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], &a[keyi]);

	QuickSort2(a, begin, keyi - 1);
	QuickSort2(a, keyi + 1, end);
}

这个方法和挖坑法的区别就在于第一趟排序排完之后左右序列的顺序不同

时间复杂度: O ( N l o g 2 N ) O(Nlog_2N) O(Nlog2N)

5.3 前后指针法

  • 算法描述

  1. 还是选出一个最左边或最右边的数作为 key
  2. 定义两个变量 prevcur。起始时, prev 指向开头,cur 指向 prev+1,也就是开头的后一位。
  3. cur 指向的内容小于 key,则 prev 向后移动一位,然后交换 prevcur 所指向的内容,然后 cur++;若 cur 所指向的内容大于 key,则 cur 直接 ++。如此进行下去,直到 cur 到达 end 位置。此时 key++prev 所指向的内容交换即可。

这样也能使得 key 左序列的值小于 key,右边序列的值大于 key。之后再递归调用即可。

下面时动图演示:

请添加图片描述

  • 代码示例

void QuickSort3(int* a, int left, int right) {
	if (left >= right) {
		return;
	}

	int index = GetMidIndex(a, left, right);
	Swap(&a[left], &a[index]);

	int keyi = left;
	int prev = left, cur = left + 1;
	while (cur <= right) {
		if (a[cur] < a[keyi] && ++prev != cur){
			Swap(&a[prev], &a[cur]);
		}
		++cur;
	}
	Swap(&a[prev], &a[keyi]);

	QuickSort3(a, left, prev - 1);
	QuickSort3(a, prev + 1, right);
}

时间复杂度: O ( N l o g 2 N ) O(Nlog_2N) O(Nlog2N)

6. 归并排序

  • 算法描述

归并排序的核心思想是将一个大问题分解成若干个小问题,分别解决这些小问题,然后将结果合并起来,最终得到整个问题的解。具体到排序问题,归并排序的步骤如下:

  1. 分解:将待排序的数组分成两个子数组,每个子数组包含大约一半的元素。
  2. 解决:递归地对每个子数组进行排序。
  3. 合并:将两个已排序的子数组合并成一个有序的数组。

通过不断地分解和合并,最终整个数组将被排序。

请添加图片描述

这样看可能不直观,下面是动图演示:

请添加图片描述

  • 代码示例

注意这里需要用到两个函数,因为要用到开辟新的内存,所以不能用在递归中,需要单独提出来。

void _MergeSort(int* a, int left, int right, int* tmp) {  // 此函数用来递归
	if (left >= right) {
		return;
	}
	int mid = (left + right) >> 1;
	
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);

	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int index = left;
	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++];
	}

	for (int i = left; i <= right; i++) {
		a[i] = tmp[i];
	}
}

void MergeSort(int* a, int n) {
	int* tmp = (int*)malloc(sizeof(int) * n);
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
}

时间复杂度: O ( N l o g 2 N ) O(Nlog_2N) O(Nlog2N)

稳定性:稳定

总结对比

排序方法平均情况最好情况最坏情况辅助空间稳定性
插入排序 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)稳定
选择排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)不稳定
冒泡排序 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)稳定
希尔排序 O ( n log ⁡ n ) O(n\operatorname{log}n) O(nlogn) ~ O ( n 2 ) O(n^2) O(n2) O ( n 1.3 ) O(n^{1.3}) O(n1.3) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)不稳定
快速排序 O ( n log ⁡ n ) O(n\operatorname{log}n) O(nlogn) O ( n log ⁡ n ) O(n\operatorname{log}n) O(nlogn) O ( n 2 ) O(n^2) O(n2) O ( log ⁡ n ) O(\operatorname{log}n) O(logn) ~ O ( n ) O(n) O(n)不稳定
归并排序 O ( n log ⁡ n ) O(n\operatorname{log}n) O(nlogn) O ( n log ⁡ n ) O(n\operatorname{log}n) O(nlogn) O ( n log ⁡ n ) O(n\operatorname{log}n) O(nlogn) O ( n ) O(n) O(n)稳定

注:快速排序加了三数取中之后基本不会出现最坏的情况。

有任何问题,还请大佬们指出!

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

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

相关文章

OpenHarmony分布式数据管理子系统

OpenHarmony分布式数据管理子系统 简介 目录 组件说明 分布式数据对象数据共享分布式数据服务Key-Value数据库首选项关系型数据库标准数据化通路 相关仓 简介 子系统介绍 分布式数据管理子系统支持单设备的各种结构化数据的持久化&#xff0c;以及跨设备之间数据的同步、…

Linux系统使用Docker部署Geoserver并做数据挂载进行地图服务的发布和游览

文章目录 1、前提环境2、拉取geoserver镜像3、创建数据挂载目录4、 运行容器5、 测试使用&#xff08;发布shp数据为服务&#xff09;5.1、创建工作区5.2、添加数据存储5.3、发布图层5.4、服务游览 1、前提环境 部署环境&#xff1a;Linux&#xff0c;Centos7 &#xff0c;Doc…

MySql数据库运维学习笔记

数据库运维常识 DQL、DML、DCL 和 DDL 是 SQL&#xff08;结构化查询语言&#xff09;中的四个重要类别&#xff0c;它们分别用于不同类型的数据库操作&#xff0c;下面为你简单明了地解释这四类语句&#xff1a; 1. DQL&#xff08;数据查询语言&#xff0c;Data Query Langu…

企业内容中台搭建实战手册

内容概要 在数字化转型浪潮中&#xff0c;内容中台作为企业信息资产的核心枢纽&#xff0c;承担着统一管理、智能分发与持续迭代的战略职能。本手册聚焦于构建企业级内容基础设施的完整生命周期&#xff0c;系统梳理从战略规划到技术落地的全链路方法论。通过对内容生产、存储…

VUE四:Vue-cli

什么是Vue-cli vue-cli是官方提供的一个脚手架,用于快速生成一个vue的项目模板; 预先定义好的目录结构及基础代码&#xff0c;就好比咱们在创建 Maven项目时可以选择创建一个骨架项目&#xff0c;这个骨架项目就是脚手架,我们的开发更加的快速; 什么是web pack 本质上&#…

如何解决‘找不到vcruntime140_1.dll 无法执行’的问题,vcruntime140_1.dll 的解析

当你满心欢喜地点开游戏或专业软件&#xff0c;却被"找不到vcruntime140_1.dll"的报错弹窗打断&#xff0c;这种突如其来的系统警告总让人措手不及。这个微软Visual C运行库的核心组件缺失&#xff0c;可能导致Adobe全家桶、Steam游戏等各类程序集体罢工。不过别急着…

将产品照片(form.productPhotos)转为 JSON 字符串发送给后端

文章目录 1. 前端 form.productPhotos 的当前处理a. 组件绑定b. 当前发送逻辑 2. 如何将 form.productPhotos 转为 JSON 字符串发送给后端a. 修改前端 save() 方法b. 确保 esave API 支持接收字符串 基于你提供的 identify-form.vue 代码&#xff0c;我将分析如何将产品照片&a…

分布式事务-本地消息表学习与落地方案

本文参考&#xff1a; 数据库事务系列04-本地消息表实现分布式事务 基础概念 本地消息表实现分布式事务最终一致性的核心&#xff1a;是通过上游本地事务的原子性持久性&#xff0c;配合中间件的重试机制&#xff0c;从而实现调用下游的最终一致性。 这里有几个要点可以解析一…

java常见面试场景题

1. 如何定位线上OOM 造成OOM的原因 如何快速定位OOM 2. 如何防止重复下单 方案一&#xff1a;前端提交订单按钮置灰 用户点击下单按钮后置灰&#xff0c;防止用户无意点击多次 方案二: 后端Redis setnx 用户token 商品URL KEY 用setnx 命令并设置过期时间3-5秒防止重复下单…

【微服务】深入解析spring aop原理

目录 一、前言 二、AOP 概述 2.1 什么是AOP 2.2 AOP中的一些概念 2.2.1 aop通知类型 2.3 AOP实现原理 2.3.1 aop中的代理实现 2.4 静态代理与动态代理 2.4.1 静态代理实现 三、 jdk动态代理与cglib代理 3.1 jdk动态代理 3.1.1 jdk动态代理模拟实现 3.2 CGLIB 代理…

【JT/T 808协议】808 协议开发笔记 ② ( 终端注册 | 终端注册应答 | 字符编码转换网站 )

文章目录 一、消息头 数据1、消息头拼接2、消息 ID 字段3、消息体属性 字段4、终端手机号 字段5、终端流水号 字段 二、消息体 数据三、校验码计算四、最终计算结果五、终端注册应答1、分解终端应答数据2、终端应答 消息体 数据 六、字符编码转换网站 一、消息头 数据 1、消息头…

go 环境准备

配置路径&#xff1a; GOROOT&#xff1a;D:\GoGOPATH&#xff1a;go的工作目录 D:\workspacego 验证版本&#xff1a;go version 配置第三方仓库&#xff1a; GO111MODULE&#xff1a;开启mod模式GOPROXY&#xff1a;go语言三方库地址GOSUMDB&#xff1a;go语言软件包的M…

嵌入式硬件篇---数字电子技术中的触发器

文章目录 前言简介1. SR触发器&#xff08;Set-Reset Flip-Flop&#xff09;工作原理1.基本结构2.输入信号3.真值表4.缺点5.应用示例 2. 钟控SR触发器&#xff08;Clocked SR Flip-Flop&#xff09;工作原理1.改进点2.触发条件3.问题4.应用示例 3. D触发器&#xff08;Data Fli…

安卓/鸿蒙模拟位置信息-Fake Location模拟虚拟定位打卡

一、软件下载安装 需要用到的软件就一个即&#xff1a;FakeLocation虚拟打卡定位 下载地址&#xff1a;FakeLocation虚拟打卡定位.app 二、手机端设置 打开手机设置-关于手机-版本信息-版本号&#xff0c;连续点击版本号直到出现已进入开发者模式字样&#xff0c;此时打开手…

中文Build a Large Language Model (From Scratch) 免费获取全文

中文pdf下载地址&#xff1a;https://pan.baidu.com/s/1aq2aBcWt9vYagT2-HuxdWA?pwdlshj 提取码&#xff1a;lshj 原文、代码、视频项目地址&#xff1a;https://github.com/rasbt/LLMs-from-scratch 翻译工具&#xff1a;沉浸式翻译&#xff08;https://app.immersivetrans…

20250221 NLP

1.向量和嵌入 https://zhuanlan.zhihu.com/p/634237861 encoder的输入就是向量&#xff0c;提前嵌入为向量 二.多模态文本嵌入向量过程 1.文本预处理 文本tokenizer之前需要预处理吗&#xff1f; 是的&#xff0c;文本tokenizer之前通常需要对文本进行预处理。预处理步骤可…

【HeadFirst系列之HeadFirst设计模式】第7天之命令模式:封装请求,轻松实现解耦!

命令模式&#xff1a;封装请求&#xff0c;轻松实现解耦&#xff01; 大家好&#xff01;今天我们来聊聊设计模式中的命令模式&#xff08;Command Pattern&#xff09;。如果你曾经需要将请求封装成对象&#xff0c;或者希望实现请求的撤销、重做等功能&#xff0c;那么命令模…

为Eclipse IDE安装插件IBM编程助手watsonx Code Assistant

从Eclipse IDE 安装 从Eclipse IDE 安装插件&#xff1a; _1、在Eclipse IDE 中&#xff0c;单击帮助菜单&#xff0c;然后选择EclipseMarketplace。 _2、根据您计划进行的工作类型选择安装方式&#xff1a; 有关代码建议、代码解释、代码文档和单元测试的集成生成式人工智能&a…

23. AI-大语言模型-DeepSeek简介

文章目录 前言一、DeepSeek是什么1. 简介2. 产品版本1. 类型2. 版本3. 参数规模与模型能力 3. 特征4. 三种访问方式1. 网页端和APP2. DeepSeek API 二、DeepSeek可以做什么1. 应用场景2. 文本生成1. 文本创作2. 摘要与改写3. 结构化生成 3. 自然语言理解与分析1. 语义分析2. 文…

基于WebRTC与AI大模型接入EasyRTC:打造轻量级、高实时、强互动的嵌入式音视频解决方案

随着物联网和嵌入式技术的快速发展&#xff0c;嵌入式设备对实时音视频通信的需求日益增长。然而&#xff0c;传统的音视频解决方案往往存在体积庞大、实时性差、互动体验不佳等问题&#xff0c;难以满足嵌入式设备的资源限制和应用场景需求。 针对以上痛点&#xff0c;本文将介…