当前位置: 首页 > news >正文

详解八大排序算法

文章目录

  • 前言
  • 排序算法
    • 插入排序
      • 直接插入排序:
      • 希尔排序(缩小增量排序)
    • 选择排序
      • 直接选择排序
      • 堆排序
    • 交换排序
      • 冒泡排序
      • 快速排序
        • hoare版本
        • 挖坑法
        • 前后指针版本
        • 快速排序的非递归
        • 快速排序总结
    • 归并排序
      • 归并排序的非递归实现:
    • 计数排序
  • 排序算法复杂度及稳定性分析
  • 总结

前言

本篇将讲述常用的排序算法,包括直接插入排序、希尔排序、直接选择排序、堆排序、冒泡排序、快速排序、归并排序、计数排序,使用动图解释,并且画图阐明了递归的写法,同时实现了与非递归的写法复杂度和稳定性分析等等,看完绝对不亏!!!

🕺作者: 迷茫的启明星

😘欢迎关注:👍点赞🙌收藏✍️留言

🏇家人们,码字不易,你的👍点赞🙌收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!

持续更新中~

排序算法

排序的概念及其运用

排序的概念

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

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

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

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

全文以升序为例,降序类似

常见排序:

在这里插入图片描述

插入排序

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

实际中我们玩扑克牌时,就用了插入排序的思想

在这里插入图片描述

直接插入排序:

当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移

其实就是将前面i个数视为一个有序数组,即使是一个也是有序数组,第i+1的数和前一个数比较,如果比它小就不变,比它大就让大的数往后移,一直到遇到比它小的数为止。这里指的是升序。

在这里插入图片描述

直接插入排序的特性总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:O(N2)
  3. 空间复杂度:O(1),它是一种稳定的排序算法
  4. 稳定性:稳定

代码实现:

void InsertSort(int* a, int n)
{for (int i = 0; i < n-1; ++i) {int end=i;int x=a[i+1];while(a[end]>x&&end>=0){a[end+1]=a[end];end--;}a[end+1]=x;}
}

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

  • 希尔排序法又称缩小增量法。

  • 希尔排序法的基本思想是:先选定一个整数gap,把待排序文件中所有记录分成个组,

    所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。

    然后,gap不断取小,重复上述分组和排序的工作。
    当到达=1时,所有记录在统一组内排好序。

在这里插入图片描述

希尔排序的特性总结:

  1. 希尔排序是对直接插入排序的优化。

  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。

    当gap == 1时,数组已经接近有序的了,再让gap等于1以后,

    就是前面未优化的直接插入排序方式排序一次,这样就会很快。

    这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

  3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间复杂度都不固定

《数据结构(C语言版)》— 严蔚敏

在这里插入图片描述

《数据结构-用面相对象方法与C++描述》— 殷人昆

在这里插入图片描述

  1. 稳定性:不稳定

代码实现:

void ShellSort(int* a, int n)
{int gap=n;while(gap>1){//gap=gap/2;//shell的方式gap=gap/3+1;//knuth的方式for (int i = 0; i < n-gap; i++) {//多次预排序+最后一次gap==1直接插入排序//预排序使得数组更加有序,后面直接排序便可加快效率int end=i;int x=a[i+gap];while(a[end]>x&&end>=0){a[end+gap]=a[end];end-=gap;}a[end+gap]=x;}}
}

选择排序

基本思想:
每一次从待排序的数据元素中选出最小(或最大)的一个元素,

存放在序列的起始位置,直到全部待排序的数据元素排完 。

直接选择排序

  • 在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素

  • 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换

  • 在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,

    直到集合剩余1个元素

也就是说,每次遍历都选出最大或者最小的值放到一端,这样到最后就是有序的了。

在这里插入图片描述

直接选择排序的特性总结:

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

代码实现:

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(N2)

堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

前面我们讲述二叉树的时候就已经讲过这个排序方式了

链接:http://t.csdn.cn/LcTKm

在这里插入图片描述

直接选择排序的特性总结:

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

skills:

在这里建堆最好使用向下调整来建堆,如果使用向上调整来建堆,

建堆的时间复杂度就要O(N*logN),而向下调整时间复杂度为O(N),

这些都在http://t.csdn.cn/LcTKm讲过了。

交换排序

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置

交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

冒泡排序

在这里插入图片描述

冒泡排序的特性总结:

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定
  • 简单来说:冒泡排序就是在[0,n-1]两两对比选出最大值放到a[n-1],

  • 再从[0,n-2]先选出最大值放到a[n-2],如此循环下去就排好序了。

  • 冒泡排序最好的情况是,数组本就是有序,此时经过一轮遍历后,

  • 没有发生交换,就可以判断是有序数组了,最好情况的时间复杂度为O(N),

  • 但是一般情况不会是这样,仍需要一个个选出最大值,时间复杂度为O(N2).

代码实现:

void BubbleSort(int* a, int n)
{int end = n;while (end > 0){int exchange = 0;for (int i = 1; i < end; ++i){if (a[i - 1] > a[i]){exchange = 1;Swap(&a[i - 1], &a[i]);}}--end;if (exchange == 0){break;}}
}

快速排序

  • 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法

  • 其基本思想为:任取待排序元素序列中的某元素作为基准值,

  • 按照该排序码将待排序集合分割成两子序列,

  • 左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,

  • 然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

// 假设按照升序对array数组中[left, right)区间中的元素进行排序
void QuickSort(int array[], int left, int right)
{if(right - left <= 1)return;// 按照基准值对array数组的 [left, right)区间中的元素进行划分int div = partion(array, left, right);// 划分成功后以div为边界形成了左右两部分 [left, div-1] 和 [div+1, right)// 递归排[left, div)QuickSort(array, left, div—1);// 递归排[div+1, right)QuickSort(array, div+1, right);
}

我们会发现,这很像二叉树的前序遍历,只不过要注意基准值的改变,以及边界的改变。

将其划分左右两部分的方式主要有两种:

hoare版本

在这里插入图片描述

思路:
取最左边的值为keyi,然后R往左走找到比a[keyi]小的值就停,

L往右走找到比a[keyi]大的值就停,然后交换两个值,

继续循环直到二者相遇值为x,把a[keyi]与a[x]交换,

自此keyi左边的值比a[keyi]小,keyi右边的值比a[keyi]大

代码实现:

int Partion1(int* a, int left, int right)
{int keyi=left;while(left<right){while(a[keyi]<=a[right]&&left<right)right--;while (a[keyi]>=a[left]&&left<right)left++;Swap(&a[left],&a[right]);}Swap(&a[keyi],&a[left]);return left;
}

挖坑法

在这里插入图片描述

思路:

与上面的思路相似,取最左边的值为keyi,

把a[keyi]备份,试想左边这个为坑

然后R往左走找到比a[keyi]小的值就把它放到坑中,

然后L往右走找到比a[keyi]大的值就把它放到坑中,

继续循环直到二者相遇值为x,把备份的a[keyi]放到坑中,

自此keyi左边的值比a[keyi]小,keyi右边的值比a[keyi]大

代码实现:

int Partion2(int* a, int left, int right)
{int key = a[left];int pivot = left;while (left < right){// 右边找小, 放到左边的坑里面while (left < right && a[right] >= key){--right;}a[pivot] = a[right];pivot = right;// 左边找大,放到右边的坑里面while (left < right && a[left] <= key){++left;}a[pivot] = a[left];pivot = left;}a[pivot] = key;return pivot;
}

前后指针版本

在这里插入图片描述

思路:

  • 设置最左值为key,再设置两个指针,一个在前为cur,一个在后为prev。

  • cur从第二个开始,prev从第一个开始,在前的cur开始往前寻找比key小的值。

  • 找到就停下来和prev+1位置的值交换,因为cur位置的值肯定比key大,

  • 而prev最初的位置是key,所以最后prev所在的位置的值必然是比key小的。

  • 停止的条件是什么呢?是cur指针超出了数组范围。

  • 就这样prev最后所在位置的值与key交换,这样就实现了key左边比key小,key右边比key大。

代码实现:

int Partion3(int* a, int left, int right)
{int keyi = left;int prev = left;int cur = prev + 1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur)//这个地方就比较巧妙,把++prev放在这里//为什么要判断++prev不等于cur呢?因为二者相同就没有交换的价值。{Swap(&a[cur], &a[prev]);}++cur;}Swap(&a[prev], &a[keyi]);return prev;
}

快速排序优化

  1. 三数取中法选key

三数指的是:a[0],a[(n-1)/2],a[n-1],由于a[key]的值是无法预料它的相对大小的,

当想要排升序时,如果是倒序有序的话,就是最坏的情况,

这时如果取首尾中间值为key则变成了最好的情况

代码实现:

int GetMidIndex(int* a, int left, int right)
{int middl=left+(right-left)/2;if(left>middl){if(right>left)return left;else if(right<middl)return middl;elsereturn right;}else//left<=middl{if(right>middl)return middl;else if(right<left)return left;elsereturn right;}
}

这个函数可以放在上述所说的三种方法中,只需向其中加以下代码

int mini = GetMidIndex(a, left, right);
Swap(&a[mini], &a[left]);
  1. 递归到小的子区间时,可以考虑使用插入排序

递归可以想象成满二叉树的结构,越到后面需要递归的次数越多,这样

就使我们萌生一个想法,把后面几次的递归排序换成直接插入排序。

想法是好的,那么怎么实现呢?

这时候就要考虑最后是什么情况了,我们需要它数组递归到个数少的时

候就使用直接插入排序,代码如下:

void QuickSort(int* a, int left, int right)
{if (left >= right)return;// 小区间优化,当分割到小区间时,不再用递归分割思路让这段子区间有序// 对于递归快排,减少递归次数if (right - left + 1 < 10){InsertSort(a + left, right - left + 1);}//要注意这里插入排序的左值是数组开始的位置,//右值是需要排序的个数,参考上文讲述的插入排序函数的描述。else{int keyi = Partion3(a, left, right);// [left, keyi-1] keyi [keyi+1, right]QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}  
}

就这样,我们实现了快速排序的递归实现方法,最主要的是怎么确定key值,
并且使key左边和右边比key小或者大,最后使用递归的方式,
一层层缩小数组使其有序,最后整个数组有序,那么非递归怎么写呢?

快速排序的非递归

在这里我们需要用到一个数据结构----栈

栈的实现我们之前在这里讲过,这里直接使用它的代码就好

栈的实现链接:http://t.csdn.cn/cZzJh

怎么使用?

思路是这样的:

  • 首先将原数组的首末位置按照末先首后的顺序传入栈中

  • (栈的作用主要是记录边界),然后原数组按照上文讲过的让左边右边

  • 比key大或者小的方法把key放到合适的位置,然后把栈内的已经找到

  • key合适位置的边界删掉,之后把key+1和end的值传入栈,

  • begin和key-1的值传入栈,把这个边界里key放到合适的位置就删掉边界,

  • 继续传入key右边的边界和左边的边界,

  • 到了最后第一次的key前面的都有序了,

  • 这时开始key后面的边界开始按照前面所说的

  • 开始把key放到合适的位置,最后栈空了就结束了。

代码实现:

这里我们就只把所用到栈的函数拿出来,如果需要复现的可以到http://t.csdn.cn/cZzJh里把Stack.c和Stack.h文件复制放到源文件下。

void QuickSortNonR(int* a, int left, int right)
{ST st;StackInit(&st);StackPush(&st, left);StackPush(&st, right);while (!StackEmpty(&st)){int end = StackTop(&st);StackPop(&st);int begin = StackTop(&st);StackPop(&st);int keyi = Partion3(a, begin, end);// [begin, keyi-1] keyi [keyi+1, end]if (keyi + 1 < end){StackPush(&st, keyi+1);StackPush(&st, end);}if (begin < keyi-1){StackPush(&st, begin);StackPush(&st, keyi-1);}}StackDestroy(&st);
}

快速排序总结

  1. 在大部分场景下,快速排序的性能都是比较好的,所以才叫快速排序

  2. 时间复杂度:O(N*logN)

    在这里插入图片描述

  3. 空间复杂度:O(logN)

  4. 稳定性:不稳定

归并排序

基本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,

该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

将已有序的子序列合并,得到完全有序的序列;

即先使每个子序列有序,再使子序列段间有序。

若将两个有序表合并成一个有序表,称为二路归并。

归并排序核心步骤:

在这里插入图片描述

在这里插入图片描述

思路:

  • 开辟一个新数组存重新排序的结果,原数组不断分半到最后只剩一个元素,
  • 再开始归并,两数比较,小的放前面,大的放后面,这一块就有序了,
  • 随着数组变大,数变多,依旧如此,大的放前面,小的放后面,
  • 一个数组没有数了就把另一个数组没有放的值按顺序放在后面即可。

图示:

由于代码过长不方便图示,故中间比较大小的就省去了,仅示例部分,后面类似

在这里插入图片描述

代码实现:

void _MergeSort(int* a, int left, int right, int* tmp)
{if (left >= right){return;}int mid = (left + right) / 2;// [left, mid] [mid+1, right] 有序_MergeSort(a, left, mid, tmp);_MergeSort(a, mid + 1, right, tmp);int begin1 = left, end1 = mid;int begin2 = mid+1, end2 = right;int i = left;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}// tmp 数组拷贝回afor (int j = left; j <= right; ++j){a[j] = tmp[j];}
}
void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int)*n);if (tmp == NULL){printf("malloc fail\n");exit(-1);}_MergeSort(a, 0, n - 1, tmp);free(tmp);tmp = NULL;
}

这就是归并排序的递归实现,其实还有一种非递归方法,

但是并不推荐使用,主要是使用时容易出错,

数组边界容易出问题,

但是还是在这里讲一下:

归并排序的非递归实现:

  • 通过观察数组可以发现,我们递归方法中分割的最后一步,

  • 把数组分成一个个单独的元素,

  • 但是数组里面我们可以把它看作是单个的元素,

  • 直接就是分的最后一步,开始归并就好了,

  • 那么该怎么归并呢?

  • 这里我们就要设置一个变量gap来控制,一次是几个元素进行归并,

  • 这一步是最主要的难点,然后就是这几个数进行归并,

  • 到了后面数不够了怎么办?比如说现在是八个数,

  • 先一一归,然后两两归,最后四四归,这是理想情况,

  • 但如果是九个数呢?最后还多了一个数怎么办?

  • 多的数和后面的归就越界了,排序结果肯定就不对了,

  • 这里所说的就是边界控制问题了,

  • 基于我的代码循环情况,越界情况有两种:

    1. 前一个右边界就越界,或者后一个的左边界就越界,那就不用再继续了,说明数组比较完了
    2. 后一个右边界越界,那就把它右边界设置为原来整个数组的右边界即可

代码实现:

void MergeSortNonR(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int)*n);if (tmp == NULL){printf("malloc fail\n");exit(-1);}int gap = 1;while (gap < n){for (int i = 0; i < n; i += 2 * gap){// [i,i+gap-1] [i+gap,i+2*gap-1]int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;// 核心思想:end1、begin2、end2都有可能越界// end1越界 或者 begin2 越界都不需要归并if (end1 >= n || begin2 >= n){break;}// end2 越界,需要归并,修正end2if (end2 >= n){end2 = n- 1;}int index = i;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 j = i; j <= end2; ++j){a[j] = tmp[j];}}gap *= 2;}free(tmp);tmp = NULL;
}

归并排序的特性总结:

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

计数排序

计数排序,顾名思义使用计数的方式进行排序,它是哈希直接定址法的变形应用。

步骤:

  1. 统计相同元素出现次数
  2. 根据统计的结果将序列回收到原来的序列中

图示:

在这里插入图片描述

这里有一个问题,一定要从0开始吗?

如果有一组数是这样的[1000,1111,1022,1073,1288],给它排序难道要浪费前面一千个空间来记录吗?

当然不需要,像这种集中在一定区域的数,就把它最小的数放在0那,其他的数就映射到x-min上,与从0开始不同的是,这是相对的映射,而那是绝对映射。我们怎么确定要开辟多大的空间来存放数据呢?循环一遍找出最大最小值即可1.

代码实现:

void CountSort(int* a, int n)
{int max = a[0], min = a[0];for (int i = 1; i < n; ++i){if (a[i] > max){max = a[i];}if (a[i] < min){min = a[i];}}int range = max - min + 1;int* count = (int*)malloc(sizeof(int)*range);memset(count, 0, sizeof(int)*range);if (count == NULL){printf("malloc fail\n");exit(-1);}// 统计次数for (int i = 0; i < n; ++i){count[a[i] - min]++;}// 根据次数,进行排序int j = 0;for (int i = 0; i < range; ++i){while (count[i]--){a[j++] = i + min;}}
}

计数排序总结:

因为计数排序的适用性不强,故不在后面总结中提及,此处仅作涉猎

  1. 在数据比较集中时效率很高,但是适用范围和场景有限,

    如果范围较大或者是浮点数就不适用了。

  2. 时间复杂度:O(max(N,range))

  3. 空间复杂度:O(范围)

  4. 稳定性:稳定

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

在这里插入图片描述

在这里插入图片描述

算法复杂度分析前文已经讲述过了,总结参考上图。

稳定性是什么呢?

  • 假定在待排序的记录序列中,存在多个相同的关键字的记录,
  • 若经过排序,这些记录的相对次序保持不变,
  • 就称这种算法是稳定的,否则是不稳定的。

那么前面讲过的排序方式哪些是稳定的、哪些是不稳定的呢.

插入排序:

  • 直接插入排序(稳定)

  • 希尔排序不稳定)

    在相同的值预排时可能分到不同的组里导致相对位置改变

选择排序:

  • 选择排序(不稳定)

    如果有两个相同的数,一个在首位,这时如果最小的数在另一个的右边,那么相对位置将会改变

  • 堆排序(不稳定)

    排升序时,如果最小的数在末尾,而最大数在前,不止一个,那么将最大的数替换后, 最大数的相对1位置也会发生改变

交换排序:

  • 冒泡排序(稳定)

  • 快速排序(不稳定)

    有相同元素时相对位置可能会发生变化

归并排序:

  • 归并排序(稳定)

  • 计数排序(不稳定)

    归并时,如果相同会先归左边的数,这样会导致相对位置改变

总结

这一篇我们讲述了八大排序算法,包括直接插入排序、希尔排序、直接选择排序、堆排序、冒泡排序、快速排序、归并排序、计数排序,并且进行了算法复杂度和稳定性的分析。可是知道理论和死板的重现是远远不够的,还需要刷题来巩固,主要掌握的是这种思想。

respect !

下篇见!
在这里插入图片描述

相关文章:

详解八大排序算法

文章目录前言排序算法插入排序直接插入排序:希尔排序(缩小增量排序)选择排序直接选择排序堆排序交换排序冒泡排序快速排序hoare版本挖坑法前后指针版本快速排序的非递归快速排序总结归并排序归并排序的非递归实现&#xff1a;计数排序排序算法复杂度及稳定性分析总结前言 本篇…...

python库streamlit学习笔记

什么是streamlit&#xff1f; Streamlit是一个免费的开源框架&#xff0c;用于快速构建和共享漂亮的机器学习和数据科学Web应用程序。它是一个基于Python的库&#xff0c;专为机器学习工程师设计。数据科学家或机器学习工程师不是网络开发人员&#xff0c;他们对花几周时间学习…...

C/C++开发,无可避免的内存管理(篇一)-约束好跳脱的内存

一、养成内存管理好习惯 1.1 养成动态对象创建、调用及释放好习惯 开发者手动接管内存分配时&#xff0c;必须处理这两个任务。分配原始内存时&#xff0c;必须在该内存中构造对象&#xff1b;在释放该内存之前&#xff0c;必须保证适当地撤销这些对象。如果你的项目是c项目&am…...

在React项目中引入字体文件并使用

一、背景 设计稿里某些文字所用的字体&#xff0c;系统默认不支持。 比如设计需要的这个字体&#xff1a;EmerlandRegular&#xff0c;即使在css里将文字字体设置为他们&#xff0c;实际效果也显示不出来。 二、现象及原因 1、样式 2、期待效果 3、实际效果 实际上是因为这个…...

STM32 CubeMX按键点灯

本文代码使用 HAL 库。 文章目录前言一、按键原理图二、CubeMX 创建工程三、代码讲解&#xff1a;1. GPIO的输入HAL库函数&#xff1a;2. 消抖&#xff1a;3. 详细代码四&#xff0c;实验现象&#xff1a;总结前言 我们继续讲解 stm32 f103&#xff0c;这篇文章将详细 为大家讲…...

2023链动2+1模式到底是什么?带你了解核心规则

2023链动21模式到底是什么&#xff1f;带你了解核心规则 2023-02-24 梦龙 大家好&#xff0c;我是你们熟悉而又陌生的好朋友梦龙&#xff0c;一个创业期的年轻人 传统的直销模式产品低价高卖&#xff0c;消费者难以接受。虽然直销省去了传统流通渠道的中间环节&#xff0c;但…...

【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day14

大家好&#xff0c;我是陶然同学&#xff0c;软件工程大三今年实习。认识我的朋友们知道&#xff0c;我是科班出身&#xff0c;学的还行&#xff0c;但是对面试掌握不够&#xff0c;所以我将用这100多天更新Java面试题&#x1f643;&#x1f643;。 不敢苟同&#xff0c;相信大…...

K8S篇-搭建kubenetes集群

安装环境 这里使用pve虚拟机搭建三台centos机器&#xff0c;搭建过程参考: Centos篇-Centos Minimal安装 此次安装硬件配置 CPU&#xff1a;2C 内存&#xff1a;2G 存储&#xff1a;64G 环境说明 操作系统&#xff1a;Centos 7.9 内核版本&#xff1a;6.2.0-1.el7.elrepo…...

文本生成图像简述4——扩散模型、自回归模型、生成对抗网络的对比调研

基于近年来图像处理和语言理解方面的技术突破&#xff0c;融合图像和文本处理的多模态任务获得了广泛的关注并取得了显著成功。 文本生成图像&#xff08;text-to-image&#xff09;是图像和文本处理的多模态任务的一项子任务&#xff0c;其根据给定文本生成符合描述的真实图像…...

财务共享建设,为什么需要电子影像系统?

某集团作为投资性集团公司&#xff0c;业务遍布全国20多个省市&#xff0c;控股公司200余家&#xff0c;业务范围涉及火电、供热、风电、天然气天然气、水务、铁路、港口、酒店、地产等20多个细分行业。 伴随着集团企业的快速发展&#xff0c;某集团在管理中面临“点多、面广、…...

「RISC-V Arch」SBI 规范解读(下)

第六章 定时器扩展&#xff08;EID #0x54494D45"TIME"&#xff09; 这个定时器扩展取代了遗留定时器扩展&#xff08;EID #0x00&#xff09;&#xff0c;并遵循 v0.2 中定义的调用规约。 6.1 函数&#xff1a;设置定时器&#xff08;FID #0&#xff09; struct sbi…...

Android framework socketpair

简述 在Linux中&#xff0c;socketpair函数可以用于创建一对相互连接的、通信域为AF_UNIX的套接字&#xff0c;其中一个套接字可用于读取&#xff0c;另一个套接字可用于写入。可以使用这对套接字在同一进程内进行进程间通信&#xff08;IPC&#xff09;。 以下是使用socketp…...

腾讯在海外游戏和短视频广告领域的新增长机会

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 腾讯(00700)的收入在过去几个季度一直在下降&#xff0c;部分原因是由于新冠疫情导致的经济放缓以及中国监管机构对大型科技公司的监管收紧导致游戏行业萎缩造成的。 然而&#xff0c;猛兽财经认为&#xff0c;这些不利因素…...

查找该学号学生的成绩。

从键盘输入某班学生某门课的成绩&#xff08;每班人数最多不超过40人&#xff09;&#xff0c;当输入为负值时&#xff0c;表示输入结束&#xff0c;试编程从键盘任意输入一个学号&#xff0c;查找该学号学生的成绩。**输入格式要求&#xff1a;"%ld"(学号) "%l…...

为Webpack5项目引入Buffer Polyfill

前言 最近在公司的一个项目中使用到了Webpack5&#xff0c; 然而在使用某个npm包的时候&#xff0c;出现了Buffer is not defined 这个问题&#xff0c;原因很明显了&#xff0c;因为浏览器运行时没有Buffer这个API&#xff0c;所以需要为浏览器引入Buffer Polyfill. Webpack5…...

【人工智能 AI 】您可以使用机器人流程自动化 (RPA) 实现自动化的 10 个业务流程:Robotic Process Automation (RPA)

摘:人类劳动正在被机器(例如在工业中)或计算机程序(适用于所有行业)所取代。 目录 10 processes you can robotise in your company您可以在公司中实现自动化的 10 个流程 Human employees or robotic workers?人类员工还是机器人工人? Robots take over headhunting…...

VMware ESXi 8.0b - 领先的裸机 Hypervisor (Dell HPE Custom Image update)

本站发布 Dell 和 HPE 定制版 ESXi 8.0b 镜像 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-esxi-8/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;www.sysin.org 产品简介 VMware ESXi&#xff1a;专门构建的裸机 Hyper…...

Java:SpringBoot 整合Spring-Retry实现错误重试

SpringBoot 整合Spring-Retry可以实现错误重试 目录引入依赖开启spring-retry使用重试注解Retryable 注解Backoff 注解测试参考引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactI…...

MyBatis学习笔记(二) —— 搭建MyBatis项目

2、搭建MyBatis 2.1、开发环境 IDE&#xff1a;idea 2019.2 构建工具&#xff1a;maven 3.5.4 MySQL版本&#xff1a;MySQL 8 MyBatis版本&#xff1a;MyBatis 3.5.7 MySQL不同版本的注意事项 1、驱动类 driver-class-name MySQL 5版本使用jdbc5驱动&#xff0c;驱动类使用…...

linux服务器上Docker中安装jenkins

前言 Jenkins是开源CI&CD软件领导者&#xff0c; 提供超过1000个插件来支持构建、部署、自动化&#xff0c; 满足任何项目的需要。 本文主要提供通过docker安装jenkins镜像&#xff0c;并配置nginx反向代理页面配置和使用。通过jenkins完成项目的自动部署。 我在安装之前…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)

2025年能源电力系统与流体力学国际会议&#xff08;EPSFD 2025&#xff09;将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会&#xff0c;EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…...

pam_env.so模块配置解析

在PAM&#xff08;Pluggable Authentication Modules&#xff09;配置中&#xff0c; /etc/pam.d/su 文件相关配置含义如下&#xff1a; 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块&#xff0c;负责验证用户身份&am…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配

AI3D视觉的工业赋能者 迁移科技成立于2017年&#xff0c;作为行业领先的3D工业相机及视觉系统供应商&#xff0c;累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成&#xff0c;通过稳定、易用、高回报的AI3D视觉系统&#xff0c;为汽车、新能源、金属制造等行…...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

MySQL 部分重点知识篇

一、数据库对象 1. 主键 定义 &#xff1a;主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 &#xff1a;确保数据的完整性&#xff0c;便于数据的查询和管理。 示例 &#xff1a;在学生信息表中&#xff0c;学号可以作为主键&#xff…...

前端中slice和splic的区别

1. slice slice 用于从数组中提取一部分元素&#xff0c;返回一个新的数组。 特点&#xff1a; 不修改原数组&#xff1a;slice 不会改变原数组&#xff0c;而是返回一个新的数组。提取数组的部分&#xff1a;slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...