【八大排序算法】插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序、计数排序

文章目录

  • 一、排序的相关概念
  • 二、排序类型
  • 三、排序算法实现
    • 插入排序
      • 1.直接插入排序
      • 2.希尔排序
    • 选择排序
      • 3.简单选择排序
      • 4.堆排序
    • 交换排序
      • 5.冒泡排序
      • 6.快速排序
        • 递归实现
        • 非递归实现
    • 7.归并排序
      • 递归实现
      • 非递归实现
    • 8.计数排序
  • 四、总结

一、排序的相关概念

排序:根据数据中关键字的大小,来进行升序或者降序排列。比如按照英文字母顺序排序,按照商品价格或者销量来排序等。

稳定性:如果存在两个或多个相同的数据,若经过排序后,它们的相对次序保持不变,则称这种排序算法是稳定的;否则就是不稳定的。

内部排序:数据元素全部放在内存中的排序。

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

本篇介绍的是内部排序

二、排序类型

在这里插入图片描述

三、排序算法实现

可用各种排序算法跑这个OJ—>排序OJ链接

插入排序

1.直接插入排序

基本思想:将待排序的元素逐个插入到一个已经排好序的有序序列中,直到所有元素插入完为止,得到一个新的有序序列 。

初始序列是无序的,怎么将待排元素插入有序序列呢?

第一个元素本身可以看做一个有序序列,所以从第二个元素开始,从后往前遍历比较,插入前面已经排好的有序序列中。将大于该元素的所有元素位置向后挪动一位,在比较的过程中边比较边挪动。

以升序为例
在这里插入图片描述

可以发现,假设待排序的序列长度为n,插入排序需要进行n-1趟。

时间复杂度:O(N2)趟数为n-1,每趟待排序元素需要在前面的有序序列中从后往前比较挪动数据。

最好情况:O(N);有序,判断一次直接break出来,总共n-1趟,每趟不用挪动数据,量级为。
最坏情况:O(N2);逆序,每趟都头插,次数1+2+3+…N-1,所以时间复杂度量级是。

元素集合越接近有序,直接插入排序算法的效率越高。

空间复杂度:O(1)。 没有使用额外空间。

稳定性:稳定。 如果是大于等于某个位置的元素,则在其后面插入,相对位置不变。

//插入排序 稳定 时间复杂度O(N^2) 空间复杂度O(1)
void InsertSort(int* a, int n)
{for (int i = 1; i < n; i++){int end = i - 1;//有序序列的末尾位置int x = a[i];//待排序元素//将x插入到[0, end]区间中,并保持有序while (end >= 0){if (x < a[end]){a[end + 1] = a[end];//往后挪一个位置end--;}else{break;}}//while循环结束有两种情况://1.end = -1也即x比前面所有数都小;//2.x >= a[end] 这两种都是在end后面插入a[end + 1] = x;}
}

2.希尔排序

希尔排序法又称缩小增量法。是对插入排序的优化方案,效率比直接插入排序要高。

基本思想:将待排序列以间隔gap划分为gap个不同的组,对同组内的元素进行直接插入排序,然后缩小gap,重复上述分组和排序,直到gap=1,也就是直接插入排序,就会得到一个有序序列。

在这里插入图片描述

gap取多少合适?

gap初始值一般取n / 2,每趟排序完gap / 2,直到gap=1,即直接插入排序,此时序列接近有序,直接插入效率O(N)。gap也可以每次 ÷ 3,只要保证gap最后一次能取到1。

排升序,gap越大,大的数更快到后面,小的数更快到前面,但是越不接近有序;gap越小,越接近有序,gap=1时,就是直接插入排序。

时间复杂度:平均为O(N1.3)希尔排序的时间复杂度至今还没有精确的分析结果,经过大量的实验推出,n在某个特定范围内,希尔排序的比较和移动次数约为N1.3

空间复杂度:O(1)。 没有使用额外空间。

稳定性:不稳定。 希尔排序是分组排序的,且每个组内的数据不连续,无法保证相同数据的相对位置不被改变。比如上图中紫色4和橙色4在第一趟排序后,相对位置就发生了变化,所以希尔排序是不稳定排序。

//希尔排序 不稳定  效率比直接插入排序高 
void ShellSort(int* a, int n)
{//gap > 1 预排序//gap == 1 直接插入排序int gap = n;while (gap > 1){//gap = gap / 3 + 1;//也可以,除3后别忘了+1,否则gap为2时除3结果为0gap /= 2;for (int i = gap; i < n; i++){int end = i - gap;int tmp = a[i];//将x插入到[0, end]区间中,保持有序while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];//往后挪一个位置,方便x插入end -= gap;}else{break;}}a[end + gap] = tmp;}}
}

选择排序

3.简单选择排序

基本思想:每次从剩下的待排序列中标记最小(最大)值的下标位置,遍历完后存放到未排序列的起始位置(也是已排好的序列的下一个位置),直到全部待排序的数据排完。

实际上,我们每趟可以选出两个值,一个最大值,一个最小值,然后分别与序列的起始和末尾元素交换。这样排序的趟数可以减少一半,但比较和交换的次数会×2,整体效率没有太大变化。
在这里插入图片描述
时间复杂度:O(N2)一共选择n趟,每趟与n-1个数进行比较;如果是每次同时选出最大值和最小值,一共需要n/2趟,每趟比较2×(n-1)次,量级都是O(N2)。

选择排序没有最好情况和最坏情况,不管有序还是逆序,都需要进行O(N2)次比较。所以选择排序的性能很差。因为不管是有序还是逆序,选择排序的效率都是最差的O(N2)。

空间复杂度:O(1)。 没有使用额外空间。

稳定性:不稳定。 因为每次是将待排序列的起始位置与序列中最小值(或最大值)进行交换,只保证了最小值的稳定性,但起始位置的稳定性可能会被破坏。(如果同时找出最大值并交换,例如图中一样,最小值表示的元素的稳定性也可能会被破坏,比如上图中第二趟排序4的相对位置就发生了变化)

//选择排序 不稳定 时间复杂度O(N^2) 空间复杂度O(1)
void SelectSort(int* a, int n)
{int l = 0, r = n - 1;while (l < r){int minPos = l, maxPos = l;//标记最小值和最大值的下标for (int i = l + 1; i <= r; i++){if (a[i] < a[minPos])minPos = i;if (a[i] > a[maxPos])maxPos = i;}//C语言没有库函数,交换函数需要自己写Swap(&a[l], &a[minPos]);//如果最大值maxPos与左边界l重合,在l与minPos交换后,最大值转移到了minPos位置if (l == maxPos)maxPos = minPos;Swap(&a[r], &a[maxPos]);l++;r--;}
}

4.堆排序

堆排序在数据结构——堆中已经详细讲过了,这里可能讲的没有那么细致。想详细了解堆的具体原理和使用可以看看这篇博客。

堆排序是利用数据结构堆(Heap)的特性所设计的一种算法,是选择排序的一种。它是通过堆来选择数据。

基本思想: 依次将将堆顶的数据与堆尾的数据交换,然后向下调整成堆,重复上述步骤直到数据完全有序。比如一个大根堆,我们取出堆顶元素(最大数)与最后一个数交换,交换后的最大数不看作在堆里面,那么堆顶元素的左右子树仍满足堆的性质,堆的结构并没有被破坏,然后堆顶元素向下调整成堆,即可选出第二大的数,以此类推到最后一个元素,就可以成功实现堆排序了。

升序:建大堆
降序:建小堆

时间复杂度:O(N*logN)向下调整建堆的时间复杂度为O(N)(过程在数据结构堆篇),堆顶元素一共进行n-1次交换,每次交换后向下调整为O(logN),所以最大量级是O(N*logN)。

堆排序也没有最好情况和最坏情况,堆排序不受初始序列顺序的影响,有序或者逆序时间复杂度都是O(N*logN)。

空间复杂度:O(1)。 没有使用额外空间。

稳定性:不稳定。 建堆时数据的相对顺序就可能被改变,在选数时堆顶与堆尾数据交换也可能导致稳定性被破坏。

//C语言没有库函数,交换函数需要自己写
void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}
//堆排序向下调整(排升序建大堆)
void AdjustDown(int* a, int n, int parent)
{int child = 2 * parent + 1;//先赋值左孩子while (child < n){//右孩子存在且右孩子比左孩子更大if (child + 1 < n && a[child + 1] > a[child]){child++;}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = 2 * parent + 1;}else{break;}}
}//堆排序 不稳定 时间复杂度O(N*logN)
void HeapSort(int* a, int n)
{//倒着调整,从最后一个非叶子结点开始向下调整建堆 效率O(N)for (int i = (n - 2) / 2; i >= 0; i--){AdjustDown(a, n, i);}//O(N*logN)int end = n - 1;//堆的有效长度while (end > 0){Swap(&a[0], &a[end]);//堆顶与堆尾元素交换AdjustDown(a, end, 0);//堆顶再向下调整,范围[1,end]end--;}
}

交换排序

5.冒泡排序

基本思想:从前往后遍历序列,每相邻两个数比较大小,前一个比后一个大就交换,直到将最大元素交换到末尾位置,继续从前往后遍历,重复上述操作,直到所有元素有序。

冒泡排序和选择排序一样都非常容易理解,也不上图演示了(画图太费时间),一切都在代码中。

时间复杂度:O(N2)一共n趟,每趟比较交换次数递减,总共比较1+2+3+…+n-1次,量级为O(N2)。

最好情况:O(N) ;有序情况下只用进行一趟比较(flag标记),就退出循环。
最坏情况:O(N2);每趟都进行交换。

空间复杂度:O(1)。 没有使用额外空间。

稳定性:稳定。 前后两个元素相同则不用交换,相对位置没有改变。

//冒泡(交换)排序 稳定 时间复杂度O(N^2)
void BubbleSort(int* a, int n)
{for (int i = 0; i < n; i++){bool flag = false;//判断是否交换过for (int j = 0; j < n - 1 - i; j++){if (a[j] > a[j + 1]){Swap(&a[j], &a[j + 1]);flag = true;}}if (flag == false)//一趟下来没有交换说明已经有序{break;}}
}

6.快速排序

快速排序整体的综合性能和使用场景都是比较好的,这也是大多数人使用快排的原因。
快速排序算法应该算是排序算法中的重点了。

快速排序是一种二叉树结构的交换排序方法。

基本思想:任取待排序的序列中的某元素作为key值,按照key值将待排序集合分割成两个子序列,左子序列中所有元素均小于key,右子序列中所有元素均大于key,然后最左右子序列重复该过程,直到所有元素有序。

快速排序的过程与二叉树的前序遍历相似,因此可以采用递归的方式。但在某些极端情况下可能会出现栈溢出,因此有时也会使用非递归的形式实现。

递归实现

快速排序的细节问题有很多,区间问题、key值的选取、交换细节等等,这些细节不注意很容易会造成超时(无限循环)、排序出错等问题。因此也衍生了很多版本:hoare版本、挖坑法、前后指针版本等,还有一些优化key值选取的方法例如“三数取中法”,这些代码有些地方不是那么容易理解,并且冗长,有的版本在排序OJ会跑不过。

上述所有版本这里不再总结,其他博主有更详细的介绍。下面介绍一种AcWing上大佬总结的快排模板,非常厉害,代码简洁易懂,在排序OJ也能跑过。

思路

我们选取一个key值,使用双指针ij分别从区间左右两边往中间走,将>=key的数换到右区间,将<=key的数换到左边界,当i>=j时,j(或i)的左边区间都<=key,右边区间都>=key,然后再递归左区间和右区间按照此方法排序。

void QuickSort(int* a, int l, int r)
{//递归的终止情况if (l >= r) return;//最好不选左右端点作key,否则有序或逆序情况下效率很差int key = a[(l + r) / 2];int i = l - 1, j = r + 1;while (i < j){while (a[++i] < key);//左边找大while (a[--j] > key);//右边找小if (i < j)Swap(&a[i], &a[j]);}QuickSort(a, l, j);QuickSort(a, j + 1 , r);
}

下面需要证明两个问题:无限循环和无限递归的边界问题。

在这里插入图片描述

key的取值会影响后面递归的区间,下面四种写法都是正确的。

上面代码是右边第一种写法。

在这里插入图片描述

我们先分析递归区间的取法

在这里插入图片描述

再来分析key的取法
虽然下面这两种区间划分都可以,但具体哪种要看key的取法:

在这里插入图片描述

这四种写法取一种即可

但建议写左边两种,因为key取到边界的话,有序或逆序情况下效率很差。
在这里插入图片描述总结

1.key取a[l]或者a[(l+r)/2]递归区间为QuickSort(a,l,j)QuickSort(a,j+1,r);
2.key取a[r]或者a[(l+r+1)/2]递归区间为QuickSort(a,l,i-1)QuickSort(a,i,r);
3.key不建议取边界,最好从中间选取,否则有序或者逆序情况下,效率很差。

在这里插入图片描述
时间复杂度:O(N*logN)时间复杂度=每层的操作次数×树的深度=nlogn

最好情况:O(N*logN) ;有序,只判断不进行交换。
最坏情况:O(N2);并非逆序,每次划分,key都是最小(最大)的。

空间复杂度:O(logN)。 递归的栈帧开销。

稳定性:不稳定。 快排前后交换数据会导致相对位置 发生改变。

非递归实现

快排的递归过程是将大区间划分为两个小区间,这两个小区间再分别划分成两个更小区间,直到递归到边界条件,结束然后回退到上一层。

所以快排的非递归可以借助队列或者的方式来实现。

队列的话可以参考二叉树的层序遍历。

思路:先取队头区间排序处理,再将队头的左右子区间入队,再取队头区间,再将左右区间入队,重复直到队列为空。

下面介绍快排非递归采用栈的实现方法。类似二叉树的前序遍历。

思路:每次取栈顶区间进行快排的单趟排序,然后将该区间的左右子区间入栈,再取栈顶区间处理,重复直到栈空。

//非递归快排
void QuickSortNonRe(int* a, int left, int right)
{ST st;STInit(&st);STPush(&st, left);//左端点入栈STPush(&st, right);//右端点入栈while (!STEmpty(&st)){//注意顺序,和入栈顺序相反int r = STTop(&st);STPop(&st);int l = STTop(&st);STPop(&st);//单趟排序int key = a[(l + r) / 2];int i = l - 1, j = r + 1;while (i < j){while (a[++i] < key);//左边找大while (a[--j] > key);//右边找小if (i < j)Swap(&a[i], &a[j]);}//分为[l, j]和[j + 1, r]区间if (l < j)//l == j 说明该区间只有一个值,无需入栈{STPush(&st, l);STPush(&st, j);}if (j + 1 < r){STPush(&st, j + 1);STPush(&st, r);}}STDestroy(&st);
}

7.归并排序

递归实现

归并排序的递归实现属于分治算法,分治算法都有三步:

1.分成子问题;
2.递归处理子问题;
3.子问题合并。

归并排序是不断将区间分解成子区间,一分二,二分四…,直到每个区间只有一个元素,然后开始向上合并有序区间,分而治之。实现过程与二叉树的后序遍历类似,先递归再合并处理元素。

在这里插入图片描述

时间复杂度:O(N*logN)与快排一样,时间复杂度=每层的操作次数×树的深度=nlogn

归并排序不受初始数据顺序的影响,不管有序还是无序,时间复杂度都是O(N*logN)。因为每次都递归到最小区间开始合并区间。

空间复杂度:O(N)。 需要额外开辟数组空间。

稳定性:稳定。 归并过程左右区间有两个数相同时,先从左区间取数据。

//归并排序 稳定 时间复杂度O(N*logN) 空间复杂度O(N)
void Merge(int* a, int l, int r, int* tmp)//tmp临时存放合并好的数据
{//递归的终止情况if (l >= r)return;//第一步:分成子问题int mid = (l + r) / 2;//第二步:递归处理子问题Merge(a, l, mid, tmp);Merge(a, mid + 1, r, tmp);//第三步:合并子问题;将[l, mid]和[mid + 1, r]区间归并int i = l, j = mid + 1;int k = 0;while (i <= mid && j <= r){if (a[i] <= a[j])//可见稳定性tmp[k++] = a[i++];elsetmp[k++] = a[j++];}//左区间剩余while (i <= mid)tmp[k++] = a[i++];//右区间剩余while (j <= r)tmp[k++] = a[j++];//将归并好的tmp数组的内容交给数组a的[l,r]区间memcpy(a + l, tmp, sizeof(int) * k);// k == r - l + 1//i = l, k = 0;//while (i <= r)//	a[i++] = tmp[k++];
}//归并排序
void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (NULL == tmp){perror("malloc");return;}Merge(a, 0, n - 1, tmp);free(tmp);
}

非递归实现

归并排序的非递归是可以直接从最小区间开始合并的,所以省去了递归划分子区间的过程,使用一个gap值来实现区间的跨度,gap从1开始每循环一次乘2,;区间的跨度从1到2,再到4…以2的指数增长。如果数据个数不满足2n个,则左右区间可能会发生越界,这就是需要处理的细节问题。

修正区间边界:(左区间的左端点一定不会越界,不用考虑)当左区间的右端点越界,或者右区间的左端点越界,则后面的元素(有序)不再合并,等待下一轮。
否则如果右区间的右端点越界,则使右端点=n-1,进行修正。
在这里插入图片描述

//非递归
void MergeSortNonRe(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (NULL == tmp){perror("malloc");return;}int gap = 1;while (gap < n){for (int i = 0; i < n; i += 2 * gap){int left1 = i, right1 = i + gap - 1;int left2 = i + gap, right2 = i + 2 * gap - 1;if (right1 >= n || left2 >= n)//不再归并{break;}if (right2 >= n)//第二区间右端点越界{right2 = n - 1;}//合并子区间int k = i;while (left1 <= right1 && left2 <= right2){if (a[left1] <= a[left2])tmp[k++] = a[left1++];elsetmp[k++] = a[left2++];}while (left1 <= right1)tmp[k++] = a[left1++];while (left2 <= right2)tmp[k++] = a[left2++];//合并一次,拷贝一次memcpy(a + i, tmp + i, sizeof(int) * (right2 - i));}gap *= 2;}free(tmp);
}

8.计数排序

前面七种排序算法都属于比较排序,而计数排序是一种非比较排序。并不是通过两个数相比来进行排序的。

计数排序本质就是用数组存储一个映射关系把它的位置保存起来,然后再遍历原先的数组从位置数组中把它拿出来进行排序。

基本思想:统计相同元素出现次数,然后根据统计的结果将序列回收到原来的序列中。

遍历一边序列,元素每出现一次,就将以该元素为下标的数组的值+1,类似哈希表。

但是,如果序列中只有几个数,但这几个数并不算很小,例如{100, 100,101};我们就需要开辟101个空间来存储,会造成空间的大量浪费。所以对此可以进行优化,利用序列中最大值和最小值的差值来开辟空间。这样只需开辟2个整型空间即可。

时间复杂度:O(N+range)只需要遍历几遍数组。

归并排序不受初始数据顺序的影响,不管有序还是无序,时间复杂度都是O(N*logN)。因为每次都递归到最小区间开始合并区间。

空间复杂度:O(range)。 需要额外开辟空间。range=最大值-最小值+1

稳定性:不稳定。 没有进行数据交换,本质上是修改了原始数据。

计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。

//计数排序 时间复杂度O(N+range) 空间复杂度O(range)
void CountSort(int* a, int n)
{//第一遍,找出最大值和最小值int maxVal = a[0], minVal = a[0];for (int i = 1; i < n; i++){if (a[i] > maxVal)maxVal = a[i];if (a[i] < minVal)minVal = a[i];}//开辟数组空间int range = maxVal - minVal + 1;//calloc自动初始化为0int* count = (int*)calloc(sizeof(int), range);if (NULL == count){perror("malloc");return;}//第二遍,计数for (int i = 0; i < n; i++)count[a[i] - minVal]++;//排序int i = 0;for (int x = minVal; x <= maxVal; x++){while (count[x - minVal]-- > 0)a[i++] = x;}free(count);
}

四、总结

对这八种算法进行性能测试,随机生成10万个数据,统计排序所消耗时间如下:

在这里插入图片描述可以看出,三种基本排序:插入排序、选择排序、冒泡排序所花费的时间明显更多。它们的时间效率都是O(N2),但是它们的效率却有明显差别。

插入排序最快;选择排序扫描一遍数组,只需要换两次位置;而冒泡排序需要不断交换相邻的元素。因此,选择排序在大型数据集中的性能比冒泡排序更好。

剩下五种排序算法跟它们不是一个量级,数据量太小看不出明显差别,我们单独用100万个数据来测试这五种排序算法的性能。

在这里插入图片描述
计数排序最快,但是计数排序的使用场景也有限,本质是拿空间换时间,而且当空间效率O(range)>O(nlog(n))的时候其效率反而不如基于比较的排序;所以计数排序不归在常见的排序算法中。

剩下四种常见算法中,希尔排序、堆排序、归并排序是相差不大的;快速排序是比较突出的,要比其余算法(除了计数排序)都快,快速排序整体的综合性能和使用场景都是比较好的。

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


排序方法时间复杂度空间复杂度稳定性
平均情况最好情况最坏情况
插入排序插入排序O(n²)O(n)O(n²)O(1)稳定
希尔排序平均 O(n1.3)O(1)不稳定
选择排序选择排序O(n²)O(n²)O(n²)O(1)不稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
交换排序冒泡排序O(n²)O(n)O(n²)O(1)稳定
快速排序O(nlogn)O(nlogn)O(n²)O(nlogn)不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定
计数排序O(n+range)O(n+range)O(n+range)O(range)不稳定

快速排序:一般情况时排序速度最块,但是不稳定,当有序时,反而不好;
归并排序:效率非常不错,在数据规模较大的情况下,比希尔排序和堆排序要好;
堆排序:适合Tok-K问题;例:找出一千万个数中最小的前一百个数;

算法的时间复杂度与初始序列初态无关的算法有:选择排序、堆排序、归并排序。

完整代码:八大排序算法

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MapReduce代码

WordCount 数据准备&#xff1a; a.txt lxy lxy lxy zhang wsoossj liagn guui liang liagn代码&#xff08;在idea中创建一个Maven工程&#xff09;&#xff1a; mapper&#xff1a; package com.lxy.mr.wordcount.thi;import org.apache.hadoop.io.LongWritable; import…

【经验总结】超算互联网服务器 transformers 加载本地模型

1. 背景 使用 超算互联网 的云服务&#xff0c;不能连接外网&#xff0c;只能把模型下载到本地&#xff0c;再上传上去到云服务。 2. 模型下载 在 模型中 https://huggingface.co/models 找到所需的模型后 点击下载 config.json pytorch_model.bin vocab.txt 3. 上传模型文…

矩阵相关运算1

矩阵运算是线性代数中的一个核心部分&#xff0c;它包含了许多不同类型的操作&#xff0c;可以应用于各种科学和工程问题中。 矩阵加法和减法 矩阵加法和减法需要两个矩阵具有相同的维度。操作是逐元素进行的&#xff1a; CAB or CA−B其中 A,B 和 C 是矩阵&#xff0c;且 C…

idea控制台日志控制

1.清除控制台log日志 测试的时候&#xff0c;控制台打印的日志比较多&#xff0c;速度有点慢而且不利于查看运行结果&#xff0c;所以接下来我们把这个日志处理下: 取消初始化spring日志打印&#xff0c;resources目录下添加logback.xml&#xff0c;名称固定&#xff0c;内容如…

智能自助终端主板RK3288/RK3568在酒店前台自助机方案的应用,支持鸿蒙,支持免费定制

酒店前台自助机解决方案是一款基于自助服务终端&#xff0c;能够让客人通过简单的操作完成入住登记/退房的解决方案&#xff0c;大幅提高酒店的工作效率&#xff0c;提升客人体验&#xff0c;降低人力成本。 该方案解决了以下传统前台登记入住方式的痛点&#xff1a; 1、人流量…

过滤器Filter和拦截器Interceptor实现登录校验

一.过滤器 Filter过滤器可以把对资源的请求拦截下来&#xff0c;从而实现一些登录验证的功能 1.Filter的快速入门 1.定义Filter:定义一个类&#xff0c;实现Filter接口&#xff0c;并重写其所有方法。2.配置 public class dofilter implements Filter {Override //初始化只…

Git使用(3):版本管理

一、查看历史 编写一个java类进行测试 选择Git -> Show Git Log查看日志。 第一次修改推送到远程仓库了&#xff0c;所以有origin&#xff08;远程仓库地址&#xff09;&#xff0c;第二次修改只提交到本地仓库所以没有。 二、版本回退 1、本地回退 在要回退的版本上右键&a…

线程纵横:C++并发编程的深度解析与实践

hello &#xff01;大家好呀&#xff01; 欢迎大家来到我的Linux高性能服务器编程系列之《线程纵横&#xff1a;C并发编程的深度解析与实践》&#xff0c;在这篇文章中&#xff0c;你将会学习到C新特性&#xff0c;并发编程&#xff0c;以及其如何带来的高性能的魅力&#xff0…

linux系统修改网卡名称

说明&#xff1a; 因操作过程需要停用网卡&#xff0c;导致ssh远程连接不上&#xff0c;需要控制台登录操作。 测试环境&#xff1a; CentOS7.9、8.2虚拟机 Suse15 SP4虚拟机 操作步骤&#xff1a; 方法一&#xff1a; 1、 查看网卡当前名称及状态 ip a2、 将网卡状态从启用…

uni-app(三):离线打包与插件引用(Android)

离线打包与插件引用 1.下载Android离线SDK2.使用Android Studio打开离线打包项目并更新Gradle3.解决报错4.构建5.配置AppKeya.查看证书b.申请AppKeyc.配置AppKey 6.生成本地打包App资源7.拷贝App资源到Android项目中8.修改 appid9.修改Android项目配置文件10.下载证书并配置11.…

如何将一个流固耦合的爆炸案例修改成一个没有固体的爆炸案例(类似于blastfoam的twochargeDetonation案例,可以重点模仿这个来)

t技巧总结&#xff1a;~/myapp/OpenFOAM-7/blastfoam_2_0/tutorials/twoChargeDetonation案例对比&#xff0c;发现确实这个案例也没有固体文件夹和precice-config文件夹&#xff0c;只需要用到openfoam7与blastfoam2.0.0。&#xff08;这个案例可以当做一个很好的爆炸案例的入…

51. UE5 RPG 自定义FGameplayEffectContext

我们期望能够通过FGameplayEffectContext将此次技能造成的伤害是否触发格挡和暴击的参数传递到AttributeSet中&#xff0c;所以需要实现自定义一个FGameplayEffectContext类&#xff0c;来增加对应的配置。 创建自定义类文件 首先在Public目录上右键&#xff0c;选择添加一个…

【Flutter】极光推送配置流程(VIVO/OPPO/荣耀厂商通道) 章三

相关文章 推送配置共三篇(如下链接) 【Flutter】极光推送配置流程(极光通道/华为厂商/IOS) 章一 【Flutter】极光推送配置流程(小米厂商通道) 章二 【Flutter】极光推送配置流程(VIVO/OPPO/荣耀厂商通道) 章三 前言 很高兴大家来看小编写的文章&#xff5e;&#xff5e; 继【…

NSSCTF中的web学习(md5())

目录 MD5的学习 [BJDCTF 2020]easy_md5 [LitCTF 2023]Follow me and hack me [LitCTF 2023]Ping [SWPUCTF 2021 新生赛]easyupload3.0 [NSSCTF 2022 Spring Recruit]babyphp MD5的学习 md5()函数&#xff1a; md5($a)&#xff1a;返回a字符串的散列值 md5($a,TRUE)&…

使用XxlCrawler抓取全球航空公司ICAO三字码

目录 前言 一、数据源介绍 1、目标网站 2、页面渲染结构 二、XxlCrawler信息获取 1、创建XxlCrawler对象 2、定义PageVo对象 3、直接PageVO解析 4、自定义解析 总结 前言 长距离旅行或者出差&#xff0c;飞机一定是出行的必备方式。对于旅行达人或者出差人员而言&…

MySQL、JDBC复盘及规划

数据库仍有习题尚未做完&#xff0c;策略从一天做完改为每天5到10题&#xff0c;以此达到掌握和复习的效果&#xff0c;JDBC的六部仍需每天练习&#xff0c;从明天开始正式进行JavaWeb的学习&#xff0c;预计持续到七月中旬&#xff0c;还会完成一个书城项目&#xff0c;六月底…

API低代码平台介绍3-异构数据源的数据查询功能

异构数据源的数据查询功能 在上一篇文章中我们通过API平台定义了一个最基本的数据查询接口&#xff0c;本篇文章我们将上升难度&#xff0c;在原有接口的基础上&#xff0c;实现在MySQL数据库和Oracle数据库同时进行数据查询。   什么场景会需要同时对异构数据源进行查询&…

【C++】string类的使用③(修改器Modifiers || 非成员函数重载Non-member function overloads)

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; STL || C 目录 前言&#x1f525;修改器&#xff08;Modifiers&#xff09;**operator**appendpush_back和pop_backassigninserterasereplaceswap &#x1f525;非成员函数重载&#xff…

二叉树专题(有关二叉树的相关学习)

二叉树 1.数概念及结构 1.1树的结构 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因 为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 有一个特殊的结…

springcloud简单了解及上手

springcloud微服务框架简单上手 文章目录 springcloud微服务框架简单上手一、SpringCloud简单介绍1.1 单体架构1.2 分布式架构1.3 微服务 二、SpringCloud与SpringBoot的版本对应关系2022.x 分支2021.x 分支2.2.x 分支 三、Nacos注册中心3.1 认识和安装Nacos3.2 配置Nacos3.3 n…