【八大排序算法】插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序、计数排序
文章目录
- 一、排序的相关概念
- 二、排序类型
- 三、排序算法实现
- 插入排序
- 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
值,使用双指针i
和j
分别从区间左右两边往中间走,将>=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问题;例:找出一千万个数中最小的前一百个数;
算法的时间复杂度与初始序列初态无关的算法有:选择排序、堆排序、归并排序。
完整代码:八大排序算法
相关文章:

【八大排序算法】插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序、计数排序
文章目录 一、排序的相关概念二、排序类型三、排序算法实现插入排序1.直接插入排序2.希尔排序 选择排序3.简单选择排序4.堆排序 交换排序5.冒泡排序6.快速排序递归实现非递归实现 7.归并排序递归实现非递归实现 8.计数排序 四、总结 一、排序的相关概念 排序:根据数…...
Flutter 中的 CupertinoActionSheet 小部件:全面指南
Flutter 中的 CupertinoActionSheet 小部件:全面指南 在Flutter中,CupertinoActionSheet是用于在iOS风格的应用中显示动作面板的组件。它提供了一个简洁的界面,让用户可以快速从一组选项中做出选择。CupertinoActionSheet通常伴随着一个或多…...

IDEA 好用的插件
图标插件:Atom Material Icons 此插件的作用就是更好的显示各种文件的类别,使之一目了然 汉化包 Chinese (Simplified) Language Pack / 中文语言包 作用就是 汉化 AI编码助手 GitHub Copilot AI编码助手:提示代码很好用 缺点:…...

leetcode——链表的中间节点
876. 链表的中间结点 - 力扣(LeetCode) 链表的中间节点是一个简单的链表OJ。我们要返回中间节点有两种情况:节点数为奇数和节点数是偶数。如果是奇数则直接返回中间节点,如果是偶数则返回第二个中间节点。 这道题的解题思路是&a…...

稳定网络的诀窍:静态住宅代理解决方案
在数字化时代,网络稳定性对于个人和企业都至关重要。然而,由于多种因素的影响,如地理位置、网络拥堵或网络安全问题等,网络稳定性常常受到挑战。为了应对这些挑战,静态住宅代理作为一种高效且可靠的网络解决方案&#…...

VACode 创建Vue项目完整过程
一、软件下载 VSCode官网下载地址:https://code.visualstudio.com/ 二、下载开发环境 1. 安装 [Node.js](https://nodejs.org/); 2. 安装 [npm](https://www.npmjs.com/) 依赖管理工具; 注:node.js安装完后会同步安装npm,一般…...

【C++】详解C++的模板
目录 概念 编辑 语法 函数模板 类模板 非类型模板参数 模板的特化 函数模板特化 类模板特化 全特化 偏特化 分离编译 概念 模板是C中非常厉害的设计,模板把通用的逻辑剥离出来,让不同的数据类型可以复用同一种模板的逻辑,甚至可以…...

1146 -Table ‘performance schema.session variables‘ doesn‘t exist的错误解决
一、问题出现 今天在本地连数据库的时候,发现这个问题,哎呦我擦,差点吓死了 二、解决办法 1)找文件 用everything搜一下MySQL Server 5.7 然后去Windows服务找一下MySQL配置文件的具体路径 如果知道那最好,不知道那…...

练习题(2024/5/13)
1移除链表元素 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val val 的节点,并返回 新的头节点 。 示例 1: 输入:head [1,2,6,3,4,5,6], val 6 输出:[1,2,3,4,5]示例 2: …...

LeetCode—设计循环队列(两种方法)
1.题目 2.思路一(数组) 通过数组进行模拟,通过操作数组的索引构建一个虚拟的首尾相连的环。再循环队列结构中,设置一个队首head和队尾tail,数组的大小固定为k。 初步分析:存在缺陷 改善假溢出问题&#…...

python “名称空间和作用域” 以及 “模块的导入和使用”
七、名称空间和作用域 可以简单理解为存放变量名和变量值之间绑定关系的地方。 1、名称空间 在 Python 中有各种各样的名称空间: 全局名称空间:每个程序的主要部分定义了全局的变量名和变量值的对应关系,这样就叫做全局名称空间 局部名称…...

Pycharm导入自定义模块报红
文章目录 Pycharm导入自定义模块报红1.问题描述2.解决办法 Pycharm导入自定义模块报红 1.问题描述 Pycharm 导入自定义模块报红,出现红色下划线。 2.解决办法 打开【File】->【Setting】->【Build,Execution,Deployment】->【Console】->【Python Con…...

LLMs之KG-RAG:KG-RAG(基于知识图谱的RAG系统)的简介(可以解决多跳问题/同时支持结构化和非结构化数据查询)、经验技巧、案例应用之详细攻略
LLMs之KG-RAG:KG-RAG(基于知识图谱的RAG系统)的简介(可以解决多跳问题/同时支持结构化和非结构化数据查询)、经验技巧、案例应用之详细攻略 背景痛点:传统的基于向量相似度检索的RAG模型回答多环(多步)问题的能力有限,无法同时处理来自多个文…...

综合模型及应用(图论学习总结部分内容)
文章目录 前言六、综合模型及应用(以题目总结为主)分层图思想(包括拆点建图) e g 1 : 通信线路 eg1:通信线路 eg1:通信线路[A-Telephone Lines](https://ac.nowcoder.com/acm/contest/1055/A)(蓝书例题) e g 2 : 小雨坐地铁 eg2:小雨坐地铁 eg2:小雨坐地铁 [1012-小雨坐…...

2025考研专业课、英语、数学、政治视频大全,整理全了!
考研季又到了,备考的小伙伴们,你们准备好了吗? 时间管理 考研是一场与时间的赛跑,合理安排时间,让复习更高效! - 制定详细的学习计划,每天、每周、每月都有明确目标 - ♂️ 保持一定的学习…...
设计模式之策略模式(一)
背景: 下单时有很多情况,有的是用户下单,有的是卡密下单,有的是下游下单,有的是需要唤起支付,有的不需要支付,这样就需要写很多下单接口,下面使用策略模式优化这种情况 代码结构 com.example.order ├── controller │ └── OrderController.java ├── service │ …...
常见网络攻击及解决方案
网络安全是开发中常常会遇到的情况,为什么会遇到网络攻击,网络攻击是如何进行的,如何抵御网络攻击,都是我们需要思考的问题。 为什么会遇到网络攻击? 以下是一些主要的因素: 技术漏洞:软件或操…...

【挑战30天首通《谷粒商城》】-【第一天】【10 番外篇】 解决docker 仓库无法访问 + MobaXterm连接VirtualBox虚拟机
文章目录 课程介绍 1、解决docker 仓库无法访问 2、 MobaXterm连接VirtualBox虚拟机 Stage 1:下载MobaXterm选择适合你的版本 Stage 2:vagrant ssh 连接,开启ssh访问 Stage 2-1:su获取root账号权限,输入密码(默认vagra…...

【C++】每日一题 17 电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 可以使用回溯法来解决这个问题。首先定义一个映射关系将数字与字母对应起来…...
vue预览PDF文件的几种方法
1.使用iframe标签预览PDF文件 1.1页面结构 html <iframe:src"fileUrl"id"iframeBox"ref"iframeRef"frameborder"0"style"width: 100%; height: 800px"></iframe>1.2 js代码 export default {data() {return {…...

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...

MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...

Ubuntu系统复制(U盘-电脑硬盘)
所需环境 电脑自带硬盘:1块 (1T) U盘1:Ubuntu系统引导盘(用于“U盘2”复制到“电脑自带硬盘”) U盘2:Ubuntu系统盘(1T,用于被复制) !!!建议“电脑…...
k8s从入门到放弃之HPA控制器
k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率(或其他自定义指标)来调整这些对象的规模,从而帮助应用程序在负…...

图解JavaScript原型:原型链及其分析 | JavaScript图解
忽略该图的细节(如内存地址值没有用二进制) 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么:保存在堆中一块区域,同时在栈中有一块区域保存其在堆中的地址(也就是我们通常说的该变量指向谁&…...