排序(9.17)

1.排序的概念及其运用

1.1排序的概念

排序 :所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性 :假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次 序保持不变,即在原序列中,r[i]=r[j] ,且 r[i] r[j] 之前,而在排序后的序列中, r[i] 仍在 r[j] 之前,则称这种排 序算法是稳定的;否则称为不稳定的。
内部排序 :数据元素全部放在内存中的排序。
外部排序 :数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

1.2排序运用

商品排序
学校排序

1.3 常见的排序算法

2.常见排序算法的实现

2.1 插入排序

2.1.1基本思想

把待排序的目标按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为 止,得到一个新的有序序列
实际中我们玩扑克牌时,就用了插入排序的思想

2.1.2直接插入排序

(假设原数组为当插入第 i(i>=1) 个元素时,前面的 array[0],array[1],…,array[i-1] 已经排好序,此时用 array[i] 的排序码与 array[i-1],array[i-2],…的排序码顺序进行(逆序)比较,找到插入位置即将 array[i] 插入,原来位置上的元素顺序后移
简单实现:先实现单趟排序,也就是插入一个值,将它与原数组的值依次相比较,找到合适的位置插入;再实现整体排序,也就是从空数组开始插入,到插满数组,插入依次就单趟排序一次,次数为 n 次。
插入时,用一个指针指向要比较的数组成员(从数组尾开始),如果数组成员大于要插入的数,就将该数组成员向后移动一位,如果直到指针走到-1要插入的数都是最小的,就将数插入到数组头,如果中途遇到了小于要插入的数的数组成员,就插入到该数后面。
//直接插入排序
void InsertSort(int* a, int n)
{//完整排序for (int i = 0; i < n - 1; i++){//单趟排序int end=i;//将待插入数据保存下来int tmp = a[end + 1];while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];}else{break;}end--;}//走到这里有两种情况//1.end走到-1都没找到比tmp大的值//2.找到了比tmp小的值a[end + 1] = tmp;//单趟}
}
int main()
{int arr[] = { 5,8,6,7,3,1,9 };InsertSort(arr,7);return 0;
}

直接插入排序的时间复杂度最差情况O(N^2),最好O(N)。
和冒泡排序相比,时间复杂度是基本相同的,细节一些,如果数组局部有序,则插入排序更优。

2.1.3 希尔排序( 缩小增量排序 )

希尔排序法又称缩小增量法。希尔排序法的基本思想是: 选定一个间隔,把待排序文件中所有数据分成几个 组,所有间隔一定分在同一组内,并对每一组内的数据进行排序。然后,不断缩小这个间隔,重复上述分组和排序的工 作。当间隔到达 =1 时,所有数据就完成了排序。

gap>1的时候都是预排序,gap==1就是插入排序

希尔排序中预排序的意义是:大的数更快跳到后面去,小的数更快跳到前面来(常规的排序需要一个个挪动,效率低)。gap越大,越不接近有序,gap越小,越接近有序,gap==1,直接变有序。

简单实现:

//希尔排序
void ShellSort(int* a, int n)
{//数据间距int gap = n;while (gap >= 1){ //间隔每次缩小一半gap = gap / 2;//整个排序//gap由大到小最终实现完全有序for (int j = 0; j < gap; j++){//一趟排序(将间隔为gap的一组数据排序)for (int i = j; i < n - gap; i += gap){//单次排序int end = i;//保存下一个(待比较的数据)int tmp = a[end + gap];while (end >= 0){//如果右小于左if (tmp < a[end]){//右边的就等于左边的a[end + gap] = a[end];//end左移一个gap,以便于给左赋值end -= gap;}else{break;}}//走到这里有两种情况// 1.end为负// 2.end左移之后,a[end]<tmp//给左赋值a[end + gap] = tmp;//完成一次排序,一组内的两个数变成左小右大}}}
}

用一个函数来测试对同一个数组进行不同种类的排序的时间花费

// 测试排序的性能(时间)对比
void TestOP()
{srand(time(0));const int N = 100000;//开辟6个数组空间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);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];}//记录排序开始时间(ms)int begin1 = clock();InsertSort(a1, N);//记录排序结束时间(ms)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();QuickSort(a5, 0, N - 1);int end5 = clock();int begin6 = clock();MergeSort(a6, N);int end6 = 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("MergeSort:%d\n", end6 - begin6);*/free(a1);free(a2);free(a3);free(a4);free(a5);free(a6);
}

比较直接插入排序和希尔排序

容易发现两者的差距是巨大的,说明希尔排序的效率更高。

顺便回顾一下之前的排序并比较它们的性能差异

冒泡排序:

//冒泡排序
void BubbleSort(int* a,int n)
{//整趟for (size_t i = 0; i < n; i++){int exchange = 0;//一趟for (size_t j = 1; j < n - i; j++){if (a[j - 1] > a[j]){Swap(&a[j - 1], &a[j]);exchange = 1;}}//如果一趟排序结束后一次交换都没有发生,说明数组有序if (exchange == 0){break;}}
}

再比较一下它们的效率:

显而易见冒泡排序表现是最差的。

希尔排序的特点:

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

堆排序:

//数据向下调整(找较小的孩子往上推)
void AdjustDown(int* a, int n, int parent)
{//假设较小孩子为左孩子int child = parent * 2 + 1;while (child < n){//如果右孩子更小//child+1必须小于n,否则当child=n-1,child+1就越界if (child + 1 < n && a[child + 1] < a[child]){child++;}if (a[child] < a[parent]){//较小的子节点向上挪Swap(&a[child], &a[parent]);//父节点向下移动继续判断parent = child;child = parent * 2 + 1;}else{break;}}
}
// 排升序(建大堆)
void HeapSort(int* a, int n)
{向上调整建堆// //O(N*logN)//for (int i = 1; i < n; i++)//{//	AdjustUp(a, i);//}//向下调整建堆// //O(N)(效率更高)for (int i = (n - 1 - 1) / 2; i >= 0; i--){//从最后一个父节点开始往第一个节点向下调整AdjustDown(a, n, i);}int end = n - 1;while (end > 0){//交换最最大值到堆尾Swap(&a[0], &a[end]);//小的往上,大的往下调整,选出次大的AdjustDown(a, end, 0);//缩小调整范围 --end;}
}

2.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 + 1; i <= end; i++){//找最小的if (a[i] < a[mini]){mini = i;}//找最大的if (a[i] > a[maxi]){maxi = i;}}//将最小值交换到数组头Swap(&a[begin], &a[mini]);//如果最大值在开头,数值会被当成begin的值交换走//我们需要让maxi跟随最大值if (maxi == begin){maxi = mini;}//将最大值交换到数组尾Swap(&a[end], &a[maxi]);//一趟循环结束缩小一次范围begin++;end--;}
}

 比较一下以上排序的效率:

2.3 交换排序

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排 序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

 2.3.2 快速排序

思路为: 任取待排序元素序列中 的某元素作为基准值(一般取数组头元素),按照该排序码将待排序集合分割成左右两子序列,左子序列中所有元素均 小于 基准值,右 子序列中所有元素均 大于 基准值,然后在左右子序列重复该过程(可用递归实现),直到所有元素都排列在相应位置上为止
2.3.2.1霍尔法

先将第一个数据存放在临时下标 key 位置的值中,形成一个坑位

该排序由许多趟排序组成,一趟排序完成将一个数放到其相应位置。其他趟为对左右子数递归组重复这个过程;一趟排序中,先从数组尾开始遍历比目标数小的,找到之后在从数组头开始遍历比目标数大的,找到之后交换,最终使得数组的左边都小于目标数,右边都大于目标数,当左右遍历到同意位置时结束遍历,将此位置的值与key位置的值交换,一个数就完成了排序。

单趟排序过程如图

 

递归过程如图

//快速排序单趟
int PartQuick(int* a,int left,int right)
{//记录第一个数下标作为目标位int key = left;while (left < right){//找小while(a[right] >= a[key] && left < right){right--;}//找大while(a[left] <= a[key] && left < right){left++;}//找到左右后调整使小在左,大在右Swap(&a[left], &a[right]);}//将目标位放置到合适位置Swap(&a[key], &a[left]);return left;
}//快速排序
void QuickSort(int* a, int begin,int end)
{if (begin >= end){return;}//将中间位找出来int key = PartQuick(a, begin, end);//分别对左右区间递归QuickSort(a,begin, key-1);QuickSort(a,key+1, end);
}

对比一下效率较高的几个排序,容易发现快速排序依然领先。

排序存在的疑问:如何保证单趟排序中相遇位置的值是小于key位值的?

情况1:R动L不动,R去相遇L,L和R在上一轮交换过,交换后L位置的值一定比R位置的值小

情况2:L动R不动,L去相遇R,R先走,找小找到了,停下来,L找大找不到根R相遇,相遇点的值比key小。

快速排序的时间复杂度:

最好的情况:

key刚好为中位数,时间复杂度为N*logN

最坏的情况:

数列增序且key为最小数,R找小直接从尾找到头,遇到L并交换(自己和自己交换),就完成了本趟排序,然后递归右子数列一直重复此过程(左子数列为空),时间复杂度为N^2

如何避免这样的情况发生呢?

我们需要尽量让下标key的成员值为中位数而非最小值,这里可以采用三数取中的方法,取出有序数列中间位置的值作为key坑位值,即可让有序数列情况下的的时间复杂度接近理想情况

//三数取中(中指中位数)
int GetMid(int* a, int left, int right)
{int mid = (left + right) / 2;if (a[left] < a[mid]){//mid为中位数if (a[mid] < a[right]){return mid;}//left为中位数else if (a[left] > a[right]){return left;}//right为中位数else{return right;}}else // a[left] > a[mid]{//mid为中位数if (a[mid] > a[right]){return mid;}//left为中位数else if (a[left] < a[right]) {return left;}//right为中位数else{return right;}}
}

此时来比较一下有序数列下和希尔排序的时间复杂度,容易看出三数取中让有序数列下的快速排序的时间复杂度有了质的飞跃,在采用了三数取中的优化以后,有序数列情况下不会再出现最坏情况。

2.3.2.2挖坑法

先将第一个数据存在临时变量key中,形成一个坑位,然后从右开始找小于key的位置,找到小于key的就将其填到坑位中,然后该位置形成新的坑位,再从左边找大,以此类推。

//快速排序挖坑法单趟
int PartQuickDig(int* a, int left, int right)
{//找出中位数的下标int mid = GetMid(a, left, right);//将中位数交换到数列头作为keySwap(&a[left], &a[mid]);int key = a[left];int hole = left;while (left < right){//找小while (a[right] >= key && left < right){right--;}a[hole] = a[right];hole = right;//找大while (a[left] <= key && left < right){left++;}a[hole] = a[left];hole = left;}a[hole] = key;return hole;
}

整趟排序

单趟排序一次找出一个已经排序好的位置,然后对其左右数组进行递归,直到完全排序。

//快速排序
void QuickSort(int* a, int begin,int end)
{if (begin >= end){return;}//将已经放置合适的位置找出来// 霍尔//int key = PartQuick(a, begin, end);//挖坑int key = PartQuickDig(a, begin, end);//分别对左右区间递归QuickSort(a,begin, key-1);QuickSort(a,key+1, end);
}

挖坑法可以算作希尔法的小优化,不用思考为什么要先从右边开始找小。

2.3.2.3前后指针法

思路:用临时变量 key 保存数组第一个值,一前一后指针从头开始,cur 指针在前面找大于 key 的值,在找到之前,prev紧跟cur,找到一个大之后,prev 停下等待 cur 找完连续的大,然后从大后面第一个开始交换 cur 和prev 的数组值(大的到后面,小的到前面),交换一次 cur 和prev 向前移动一次,直到大交换完毕,cur 继续往前走找大。最后交换 key 和 prev 的数组值,返回 prev,即完成了单趟排序,对左右子数组递归即可完成排序。

//快速排序前后指针单趟
int PartQuickPointer(int* a, int left, int right)
{int key = left;int prev = left, cur = prev + 1;while (cur<=right){//找大if (a[cur] < a[key]){//prev仅在cur小于key的时候++//如果此时prev和cur相邻,++后相等prev++;//如果prev和cur相等,相当于没交换//如果不相等,就将小于key的换到前面,大于key的换到后面Swap(&a[prev], &a[cur]);}//无论cur比key大还是小,均要++cur++;}//key和它的值挪到prevSwap(&a[key], &a[prev]);return prev;
}

对快速排序的优化:

当待排序的数为10个及以下时,使用递归排序将会造成资源浪费,造成效率的下降,因此在快速排序时,当待排序数为10个以下时,改用直接插入排序。

//快速排序
void QuickSort(int* a, int begin,int end)
{if (begin >= end){return;}//待排序数大于10用递归if (end - begin + 1 > 10){//将已经放置合适的位置找出来// 霍尔//int key = PartQuickHoare(a, begin, end);//挖坑//int key = PartQuickDig(a, begin, end);//前后指针int key = PartQuickPointer(a, begin, end);//分别对左右区间递归QuickSort(a, begin, key - 1);QuickSort(a, key + 1, end);}//小于10个用直接插入排序else{InsertSort(a+begin,end-begin+1);}
}

优化前后效率对比:可以看出优化之后的效率是有小幅提升的。

非递归快排: 

学习了使用递归的排序方法后,我们也有必要掌握非递归的排序方法,因为递归算法所使用的空间是在栈区上开辟的,而栈区的空间是有限的,如果递归数据过多,就会造成栈溢出;而非递归算法使用的空间是在堆区上开辟的,就不存在这个问题。这里用栈来实现非递归,但还是运用递归的思想

//非递归快排
void QuickSortNonR(int* a, int begin, int end)
{//栈声明ST st;//栈初始化STInit(&st);//先入尾再入头(栈后进先出,以便出的时候先出头再出尾)STPush(&st, end);STPush(&st, begin);while (!STEmpty(&st)){//获取头int left = STTop(&st);//获取一个出一次STPop(&st);//int right = STTop(&st);STPop(&st);//将一个值排序到合适位置int key = PartQuickHoare(a, left, right);//入尾和头,向左右数组区间“递归”//左区间if (key + 1 < right){STPush(&st, right);STPush(&st, key + 1);}//右区间if (left < key - 1){STPush(&st, key-1);STPush(&st, left);}}//销毁栈STDestroy(&st);
}

2.4 归并排序

基本思想: 类似于树的后序遍历
归并排序 MERGE-SORT )是建立在归并操作上的一种有效的排序算法 , 该算法是采用分治法( Divide and Conquer)的一个非常典型的应用。 将已有序的子序列合并(取同位置较小的数依次尾插到新数组),得到完全有序的序列; 如果子序列无序 ,即先使每个子序列有序(递归并分割左右子序列到最小区间(1和1归并),使子数列一定有序),再使子序列段间有序。将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

在分割子序列的过程中,不用每次都开辟新的空间来存放这个序列,而是在原数组上通过控制下标来实现对子序列的操作(分割和归并),这里额外用一个函数来实现。归并的时候再用新开辟的空间用来存放归并的结果(如果在原数组上归并,会覆盖原数据),每次归并完将结果拷贝到原数组,就完成了对原数组的归并排序。

递归实现:

//子序列递归归并排序
void _MergeSort(int* a, int* tmp, int begin, int end)
{//序列只剩一个数返回if (end <= begin){return;}//找出中界限int mid = (begin + end) / 2;//递归左右子序列//类似后序遍历的思想:先左,后右,再根_MergeSort(a, tmp, begin, mid);_MergeSort(a, tmp, mid + 1, end);//归并到tmpint begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;//定义一个下标管理tmp数组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, (end - begin + 1) * sizeof(int));
}
//归并排序
void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc");return;}_MergeSort(a,tmp,0,n-1);
}

递归展开图:

非递归实现:

与递归不同的是非递归用一个 gap 来控制每轮归并数据的个数。gap为几,就为几个数据和几个数据归并,到新申请的数组空间,归并一次,gap就翻倍一次,从开始的 1 1 归并到最后 n/2|n/2 归并。每轮归并内,用一个 for 循环来控制进度 ,每完成一组(两个区间)归并,就往后移动 gap*2 位。

但是这种方法仅适用于需要递归的目标数组的成员为 二的次方倍数个(因为数据的归并为两两(组)归并),如果不满足,就可能造成如图的两种越界。

测试一下9个数数列(下标0-8)归并的范围,就容易看出 end1, begin2, end2 越界了

此时就需要分情况处理越界:

如果是 end1 越界,那么这一组就没必要归并了;

如果是 begin2 越界,同样这一组不需要归并;

而 begin2 越界,end1必定越界,因此可以将它们归为一类;

如果是 end2 越界,那么这一组就变成了 [begin1,end1],[begin1] 的归并,需要修正右边的边界。

 

一组归并完毕后将其拷贝到数组 tmp。

需要注意将拷贝放在循环内,归并一组,拷贝一组,这样未归并的数据就不会被拷贝

memcpy(a + i, tmp + i, ( end2 - i + 1 ) * sizeof(int));
//子序列归并排序(非递归)
void MergeSortNonR(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}//控制几几归并int gap = 1;while (gap < n){//控制归并进度(每次前进一组)for (int i = 0; i < n; i += gap * 2){//归并到tmp// [begin1,end1],[begin2,end2]//  gap个数据      gap个数据//第一个区间int begin1 = i, end1 = i + gap - 1;//第二个区间int begin2 = i + gap, end2 = i + gap * 2 - 1;//定义一个下标管理tmp数组int index = i;//判断溢出的情况分别处理//1.第一组的尾越界或二组的头越界(第二组不存在)//这一组就不用归并了if (begin2 >= n){break;}//2.第二组的尾溢出if (end2 >= n){end2 = n - 1;}//比较左子列和右子列//按顺序归并到新建数组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 + i, tmp + i, (end2-i+1) * sizeof(int));}//归并每组的个数翻倍gap *= 2;}free(tmp);
}

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

常见排序算法复杂度及稳定性分析

稳定性的意义

相同的数在数组中排序前后,相对位置是否发生变化,不变为稳,变则不稳。

例如:

考试排名取前三,先交卷的成绩先进入数组

记忆的时候切忌背结论,而是要搞清楚不同排序的原理,明白为什么不稳。

希尔排序之前先分组进行预排序,预排序中相同的数可能分到不同的组,导致相对位置发生改变。

选择排序将最小值交换到最左边,出现如图情况两个3的相对次序发生改变

堆排序一调整相对次序就变乱了

快速排序当 key 位为5时,多个5的前后次序发生改变。

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

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

相关文章

OSI参考模型和TCP/IP网络参考模型

1、OSI参考模型 1.1 产生背景 为了解决网络之间的兼容性问题,实现网络设备间的相互通讯,国际标准化组织ISO于1984年提出了OSIRM(Open System Interconnection Reference Model,开放系统互连参考模型)。OSI参考模型很快成为计算机网络通信的基础模型。由于种种原因,并没有…

Redis高可用三主三从集群部署

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容使用宝塔面板搭建集群规划配置验证 使用docker搭建使用脚本搭建&#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介绍 &#x1f31f;我是廖志伟&#xff0c;一名Java开发工程师、Java领域优质创作者、CSDN博…

聊一聊bpmn-js中的elementFactory模块

上一篇文章里我们了解了bpmn-js使用palette模块进行左侧小工具区域(也可以理解为调色板区域)的功能扩展,今天这个话题则是延续上期的palette进行开展的。 从上篇文章《聊一聊bpmn-js中的Palette》我们知道,PaletteProvider通过getPaletteEntries方法提供小工具Map对象,而单…

Python 实现 CR 指标计算 (带状能量线):股票技术分析的利器系列(13)

Python 实现 CR 指标计算 (带状能量线&#xff09;&#xff1a;股票技术分析的利器系列&#xff08;13&#xff09; 介绍算法公式 代码rolling函数介绍核心代码计算MID计算CR计算移动平均 完整代码 介绍 CR指标&#xff0c;又称带状能量线&#xff0c;通过计算股价的动能和变化…

MySQL的21个SQL经验

1. 写完SQL先explain查看执行计划(SQL性能优化) 日常开发写SQL的时候,尽量养成这个好习惯呀:写完SQL后,用explain分析一下,尤其注意走不走索引。 explain select userid,name,age from user where userid =10086 or age =18;2、操作delete或者update语句,加个limit(S…

BTC网络 vs ETH网络

设计理念 BTC 网络 比特币是一种数字货币&#xff0c;旨在作为一种去中心化的、不受政府或金融机构控制的电子货币。其主要目标是实现安全的价值传输和储存&#xff0c;比特币的设计强调去中心化和抗审查。 ETH 网络 以太坊是一个智能合约平台&#xff0c;旨在支持分散的应…

计网Lesson15 - TCP可靠传输

文章目录 1. 停止等待ARQ协议2. 连续ARQ协议与滑动窗口协议 1. 停止等待ARQ协议 ARQ&#xff08;Automatic Repeat–reQuest&#xff09;自动重传请求 几种重传情况 发送端丢失 发送方过久没有接收到接收方的确认报&#xff0c;这种情况会触发超时重传机制&#xff0c;发送方…

瑞_Redis_Redis客户端

文章目录 1 Redis客户端1.1 Redis命令行客户端1.2 图形化桌面客户端1.2.1 资源准备1.2.2 安装1.2.3 建立连接 &#x1f64a; 前言&#xff1a;本文章为瑞_系列专栏之《Redis》的基础篇的Redis客户端章节。由于博主是从B站黑马程序员的《Redis》学习其相关知识&#xff0c;所以本…

适配器模式(Adapter Pattern) C++

上一节&#xff1a;原型模式&#xff08;Prototype Pattern&#xff09; C 文章目录 0.理论1.组件2.类型3.什么时候使用 1.实践1.基础接口和类2.类适配器实现3.对象适配器实现 0.理论 适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允…

通过Power automate上传文件到SharePoint

上传文件到SharePoint 背景过程Apps开发准备Flow开发SharePoint数据库测试 背景 我们通常都可以通过一些form表单直接上传文件&#xff0c;但是有些时候我们需要对文件名字等信息做一些处理。这个时候我们就需要将文件的内容json流化&#xff0c;然后在流中还原回来。 过程 …

OpenHarmony分布式购物车案例展示~

简介 分布式购物车demo 模拟的是我们购物时参加满减活动&#xff0c;进行拼单的场景&#xff1b;实现两人拼单时&#xff0c;其他一人添加商品到购物车&#xff0c;另外一人购物车列表能同步更新&#xff0c;且在购物车列表页面结算时&#xff0c;某一人结算对方也能实时知道结…

STM32存储左右互搏 QSPI总线FATS文件读写FLASH W25QXX

STM32存储左右互搏 QSPI总线FATS文件读写FLASH W25QXX FLASH是常用的一种非易失存储单元&#xff0c;W25QXX系列Flash有不同容量的型号&#xff0c;如W25Q64的容量为64Mbit&#xff0c;也就是8MByte。这里介绍STM32CUBEIDE开发平台HAL库Quad SPI总线实现FATS文件操作W25Q各型号…

第12章-生成树协议

1. STP产生背景 1.1 桥接网络&#xff1a;网桥 1.2 交换机网络 1.3 解决方案 2. STP生成树协议 2.1 概念 2.2 BPDU(Bridge Protocol Data Unit) 2.3 选举机制&#xff08;网桥&#xff1a;一进一出&#xff09; 2.4 例题 2.5 端口状态 2.6 STP计时器 2.7 STP的问题 …

家装服务管理:Java技术的创新应用

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

C++ //练习 9.16 重写上一题的程序,比较一个list<int>中的元素和一个vector<int>中的元素。

C Primer&#xff08;第5版&#xff09; 练习 9.16 练习 9.16 重写上一题的程序&#xff0c;比较一个list中的元素和一个vector中的元素。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /**********************************…

C++ //练习 9.14 编写程序,将一个list中的char*指针(指向C风格字符串)元素赋值给一个vector中的string。

C Primer&#xff08;第5版&#xff09; 练习 9.14 练习 9.14 编写程序&#xff0c;将一个list中的char*指针&#xff08;指向C风格字符串&#xff09;元素赋值给一个vector中的string。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim…

基于 ResNet50和 SVM + 决策树的人脸口罩检测

欢迎收看&#xff0c;这篇文章是关于我如何使用 ResNet50作为特征提取器来构建掩码检测&#xff0c;然后使用支持向量机(SVM) 决策树和叠加集成方法作为分类器的一个快速解释。 为了向研究人员致敬&#xff0c;这个应用程序是基于研究论文&#xff0c;题目是“在2019冠状病毒…

智慧公厕:让城市更智慧、更环保

在现代社会&#xff0c;智慧公厕作为城市管理的重要一环&#xff0c;是智慧城市的重要组成部分&#xff0c;其建设的价值十出突出&#xff0c;是公共厕所信息化升级改造的核心方案。如智慧公厕源头厂家广州中期科技有限公司&#xff0c;所自主研发的智慧公厕整体解决方案&#…

最新Sora人工智能视频资源网址分享

1&#xff0c;了解什么是Sora * 什么是 OpenAI Sora&#xff1f; Sora 是由 OpenAI 开发的文本到视频模型。它可以按照用户的提示生成长达一分钟的高质量和一致的视频。 * 如何使用 OpenAI Sora 模型&#xff1f; 目前&#xff0c;OpenAI Sora 模型处于内测阶段&#xff0c;并将…

继电器测试中需要注意的安全事项有哪些?

继电器广泛应用于电气控制系统中的开关元件&#xff0c;其主要功能是在输入信号的控制下实现输出电路的断开或闭合。在继电器测试过程中&#xff0c;为了确保测试的准确性和安全性&#xff0c;需要遵循一定的安全事项。以下是在进行继电器测试时需要注意的安全事项&#xff1a;…