【初阶数据结构】星河中的光影 “排” 象:排序(下)
文章目录
- 4.交换排序
- 4.1 冒泡排序(BubbleSort)
- 4.2 快速排序(QuickSort)
- 4.2.1 hoare版本
- 4.2.2 挖坑法
- 4.2.3 前后指针法
- 4.2.4 非递归实现
- 5.归并排序(MergeSort)
- 5.1 递归实现
- 5.2 非递归实现
- 5.2.1 一次性全部拷贝回去:memcpy(a, tmp, sizeof(int) * n)
- 5.2.2 分批次拷贝回去:memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1))
- 6.排序复杂度及效率对比
- 希望读者们多多三连支持
- 小编会继续更新
- 你们的鼓励就是我前进的动力!
接上一篇,解决掉剩余的排序方法,本篇有些许难度,建议巩固好上一篇再来进行本篇的学习
传送门:【初阶数据结构】星河中的光影 “排” 象:排序(上)
4.交换排序
4.1 冒泡排序(BubbleSort)
基本思想: 所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大
的记录向序列的尾部移动
,键值较小
的记录向序列的前部移动
冒泡排序
在C语言部分进行过详细的解析,这里就不过多赘述
传送门:关于我、重生到500年前凭借C语言改变世界科技vlog.14——常见C语言算法
4.2 快速排序(QuickSort)
快速排序
是 Hoare 于 1962 年提出的一种二叉树结构的交换排序方法,其基本思想为: 任取待排序元素序列中的某元素作为基准值
,按照该排序码将待排序集合分割成两子序列
,左子序列
中所有元素均小于基准值
,右子序列
中所有元素均大于基准值
,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止
4.2.1 hoare版本
动图理解:
💻排序实现:
void QuickSort1(int* a, int left, int right)
{if (left >= right)return;int begin = left, end = right;//随机选keyint randi = left + (rand() % (right - left + 1));Swap(&a[left], &a[randi]);//三数取中//int midi = GetMidNumi(a, left, right);//Swap(&a[midi], &a[left]);int keyi = left;while (left < right){//右边找小while (left < right && a[right] >= a[keyi])--right;//左边找大while (left < right && a[left] <= a[keyi])++left;Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[left]);keyi = left;QuickSort1(a, begin, keyi - 1);QuickSort1(a, keyi + 1, end);
}
⌨️代码解读:
if (left >= right)
, 如果左边界大于等于右边界,说明子数组已经有序或为空,直接返回- 随机选
key
- 当左边界小于右边界时,继续分区操作。
右边找小
,从右向左找到第一个小于基准元素的元素;左边找大
,从左向右找到第一个大于基准元素的元素。交换左右找到的元素 - 最后将基准元素放到正确的位置,更新基准元素的索引
- 此时基准元素所放置的位置就是正确的排序位置,基准元素左右两边任然无序,所以对左右两边进行循环操作,
每循环一次就确定一个数的位置
🔥值得注意的是:
选取 key
的方式有三种
- 选第一个数为
key
- 三数取中值
int GetMidNumi(int* a, int left, int right)
{int mid = (left + right) / 2;if (a[left] < a[mid]){if (a[left] > a[right])return left;else if (a[mid] < a[right])return mid;elsereturn right;}else //a[left] > a[mid]{if (a[mid] > a[right])return mid;else if (a[right] > a[left])return left;elsereturn right;}
}
- 随机选
key
,经过算法研究发现该选key
方式是最有效的
4.2.2 挖坑法
动图理解:
💻排序实现:
void QuickSort2(int* a, int left, int right)
{if (left >= right)return;int begin = left, end = right;//随机选keyint randi = left + (rand() % (right - left + 1));Swap(&a[left], &a[randi]);//三数取中//int midi = GetMidNumi(a, left, right);//Swap(&a[midi], &a[left]);int key = a[left];int hole = left;while (left < right){//右边找小while (left < right && a[right] >= key)--right;a[hole] = a[right];hole = right;//左边找大while (left < right && a[left] <= key)++left;a[hole] = a[left];hole = left;}a[hole] = key;QuickSort2(a, begin, hole - 1);QuickSort2(a, hole + 1, end);
}
⌨️代码解读:
- 选择左边界元素作为基准元素
key
,并将其位置标记为hole
(坑) - 从右向左扫描,找到第一个小于
key
的元素,将其填入hole
位置,并更新hole
为该元素的位置。 - 从左向右扫描,找到第一个大于
key
的元素,将其填入hole
位置,并更新hole
为该元素的位置。 - 不断重复上述两个步骤,直到
left
和right
相遇。 - 最后将基准元素
key
填入最终的hole
位置
🔥值得注意的是: 在从右向左查找小于 key
的元素时,right
指针会不断向左移动。如果不添加 left < right
这个条件,right
指针可能会一直向左移动,越过 left
指针,导致访问到数组范围之外的元素,从而引发越界错误。例如,当数组中的元素都大于等于 key
时,如果没有 left < right
的限制,right
指针会一直向左移动,最终可能访问到数组的负索引位置,这是不合法的。从左向右查找大于 key 的元素也是同理
4.2.3 前后指针法
动图理解:
💻排序实现:
void QuickSort3(int* a, int left, int right)
{if (left >= right)return;int randi = left + (rand() % (right - left + 1));Swap(&a[left], &a[randi]);//定义基准值以及cur和destint keyi = left;int dest = left, cur = left + 1;while (cur <= right){if (a[cur] < a[keyi] && ++dest != cur){Swap(&a[cur], &a[dest]);}//无论cur位置的值是否小于基准值,cur都要++,所以留到这里做调整cur++;}Swap(&a[keyi], &a[dest]);QuickSort3(a, left, dest - 1);QuickSort3(a, dest + 1, right);
}
⌨️代码解读:
keyi
是基准元素的索引,初始化为左边界left
,dest
指针用于标记小于基准元素的元素应该放置的位置,初始化为左边界left
,cur
指针用于遍历数组,初始化为left + 1
- 如果
cur
位置的值小于基准值,要和dest
后面的元素进行交换,但是有可能dest
后面就是cur
,所以我们可以让dest
先++
,再和cur
比较,如果++dest == cur
,说明它们暂时指向同一个元素,无需交换;如果++dest != cur
,说明它们不指向同一个元素,直接交换
🖱️复杂度分析:
• 时间复杂度:
○ 最好情况: 当每次选择的基准元素都能将数组均匀地分成两部分时,递归树的深度为 log n
,每层需要处理的元素总数为 n
,因此总的时间复杂度为 O(n * log n)
○ 最坏情况: 最坏情况下,快速排序的时间复杂度会退化为 O(n²)
○ 平均情况: 时间复杂度为 O(n * log n)
• 空间复杂度: 时间复杂度为 O(log n)
总结: 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
4.2.4 非递归实现
因为上面的三种方法均涉及递归,考虑到递归太多数据会导致栈溢出的风险
,所以非递归实现快排
也很重要
那么根据我们学过的栈
发现其原理:先进后出
,比较符合我们排序的效果
这里我们引入栈的头文件和源文件
传送门:【初阶数据结构】先来后到的秩序:栈和队列
💻排序实现:
int PartSort(int* a, int left, int right)
{if (left >= right)return;int randi = left + (rand() % (right - left + 1));Swap(&a[left], &a[randi]);int keyi = left;int dest = left, cur = left + 1;while (cur <= right){if (a[cur] < a[keyi] && ++dest != cur){Swap(&a[cur], &a[dest]);}cur++;}Swap(&a[keyi], &a[dest]);return dest;
}void QuickSortNonR(int* a, int left, int right)
{ST st;STInit(&st);STPush(&st, right);STPush(&st, left);while (STEmpty(&st)){int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);int keyi = PartSort(a, begin, end);if (keyi + 1 < end){STPush(&st, end);STPush(&st, keyi + 1);}if (begin < keyi - 1){STPush(&st, keyi - 1);STPush(&st, begin);}}STDestroy(&st);
}
⌨️代码解读:
- 将初始的排序区间
[left, right]
压入栈中。注意,先压入右边界right
,再压入左边界left
,后续出栈时会先得到左边界,符合后续处理逻辑 - 从栈中弹出一个区间
[begin, end]
,先弹出的是左边界begin
,再弹出右边界end
- 调用
PartSort
函数对当前区间[begin, end]
进行分区操作,得到基准元素的最终位置keyi
- 根据基准元素的位置
keyi
,将左右子区间压入栈中。如果基准元素右边还有元素(keyi + 1 < end)
,则将右子区间[keyi + 1, end]
压入栈;如果基准元素左边还有元素(begin < keyi - 1)
,则将左子区间[begin, keyi - 1]
压入栈。这样后续会继续对这些子区间进行排序
🔥值得注意的是: 非递归的方式本质上就是借助栈来存区间,然后 PartSort(a, begin, end)
会对数进行交换排序,进行实质的交换
5.归并排序(MergeSort)
5.1 递归实现
动图理解:
假设有这么个区间,两个有序区间,就是不断对两个移动指针比较,把较小的数插入到新空间,形成新的有序序列,然后再赋值回原来的空间
💻排序实现:
void _MergeSort(int* a, int begin, int end, int* tmp)
{if (begin >= end)return;int mid = (begin + end) / 2;_MergeSort(a, begin, mid, tmp);_MergeSort(a, mid + 1, end, tmp);int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;int i = begin;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++];}memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}_MergeSort(a, 0, n - 1, tmp);free(tmp);
}
⌨️代码解读:
- 计算中间索引
mid
,将当前数组分成两个子数组,分别递归调用_MergeSort
函数对左右子数组进行排序,从最下面的子数组依次往上归并
- 初始化两个指针
begin1
和begin2
分别指向左右子数组的起始位置,end1
和end2
指向左右子数组的结束位置 - 比较
a[begin1]
和a[begin2]
的大小,将较小的元素放入临时数组tmp
中,并将相应的指针后移 - 当其中一个子数组遍历完后,将另一个子数组中剩余的元素依次放入临时数组
tmp
中 - 使用
memcpy
函数将临时数组tmp
中排好序的元素复制回原数组a
中
🔥值得注意的是: memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1))
,而不是 memcpy(a, tmp, sizeof(int) * (end - begin + 1))
,为了在临时数组中准确找到当前子数组合并结果的起始位置,以便将排好序的数据正确地复制回原数组
5.2 非递归实现
同理,为了避免栈溢出
,归并排序
也有非递归实现
为什么不用栈处理?
栈的非递归实现
其实是类似于前序遍历
的,而归并的非递归实现
类似于后序遍历
💻排序实现:
5.2.1 一次性全部拷贝回去:memcpy(a, tmp, sizeof(int) * n)
void MergeSortNonR1(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 += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;if (end1 >= n){end1 = n - 1;begin2 = n;end2 = n - 1;}else if (begin2 >= n){begin2 = n;end2 = n - 1;}else if (end2 >= n){end2 = n - 1;}int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}while (begin1 <= end1){tmp[j++] = a[begin1++];}while (begin2 <= end2){tmp[j++] = a[begin2++];}memcpy(a, tmp, sizeof(int) * n);}gap *= 2;}free(tmp);
}
⌨️代码解读:
gap
表示一组有多少个数据,gap = 1
时一个一个归并,gap = 2
时两个两个归并,依次向后推,不断扩大 gap
实现有序
根据规律写出 int begin1 = i, end1 = i + gap - 1
;int begin2 = i + gap, end2 = i + 2 * gap - 1
;
但是我们发现这套规律只适用于偶数个数的归并
,当为奇数时会出现`多出来一个数无法进行归并的情况,所以我们要针对这种情况进行修正
- end1 越界示例
假设我们有一个长度n = 5
的数组arr = [1, 2, 3, 4, 5]
,当gap = 4
时,开始进行合并操作
计算过程:
当 i = 0
时:begin1 = i = 0
,end1 = i + gap - 1 = 0 + 4 - 1 = 3
,这是正常的,因为数组索引 3
在有效范围内(数组索引范围是 0
到 4
)
当 i = 4
时:begin1 = i = 4
,end1 = i + gap - 1 = 4 + 4 - 1 = 7
,而数组的最大有效索引是 4
,此时 end1
超出了数组的边界,出现越界情况
修正方法: end1 = n - 1
,begin2 = n
,end2 = n - 1
,因为是整体赋值,让有效的数据按原路返回,后面的修正为一个不存在的区间,也就不会归并了,那么就有个问题,后面的数不就无序了吗?其实前面的排序保证了其有序性,在 gap 进一步扩大的过程中会将其逐步变为有序
- begin2 越界示例
同样使用长度n = 5
的数组arr = [1, 2, 3, 4, 5]
,当gap = 3
时进行分析
计算过程:
当 i = 2
时:begin1 = i = 2
,end1 = i + gap - 1 = 2 + 3 - 1 = 4
,这是正常的,begin2 = i + gap = 2 + 3 = 5
,数组的最大有效索引是 4
,所以 begin2
超出了数组的边界,出现越界情况
修正方法: 同 end1
越界处理情况相同
- end2 越界示例
还是以长度 n = 5
的数组 arr = [1, 2, 3, 4, 5]
为例,当 gap = 3
时进行分析
计算过程:
当 i = 0
时:begin1 = i = 0
,end1 = i + gap - 1 = 0 + 3 - 1 = 2
,begin2 = i + gap = 0 + 3 = 3
,end2 = i + 2 * gap - 1 = 0 + 2 * 3 - 1 = 5
,数组的最大有效索引是 4
,所以 end2
超出了数组的边界,出现越界情况
修正方法: end2 = n - 1
,将有效数据纳入进行排序,剩余的数在 gap
扩大时会得到处理
5.2.2 分批次拷贝回去:memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1))
void MergeSortNonR2(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 += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;if (end1 >= n || begin2 >= n){break;}else if (end2 >= n){end2 = n - 1;}int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}while (begin1 <= end1){tmp[j++] = a[begin1++];}while (begin2 <= end2){tmp[j++] = a[begin2++];}memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));}gap *= 2;}free(tmp);
}
⌨️代码解读:
一次性拷贝回去需要调整 end1、begin1 等边界:
一次性拷贝回去意味着我们要把合并好的结果一次性从临时数组
tmp
复制回原数组a
。在这个过程中,如果出现越界情况,比如end1
或end2
超出了数组长度,我们需要调整这些边界,以确保只处理有效的数组元素,避免访问非法内存
分批次拷贝回去直接 break:
分批次拷贝回去通常是指在发现越界情况时,由于后续没有有效的子数组可供合并,直接终止当前的合并操作,等待下一轮更大的
gap
再处理剩余元素。这种方式简化了越界处理逻辑,因为不需要对越界的边界进行复杂的调整
end1、begin2 越界: break
,等待 gap
增大时处理未处理的数据
end2 越界: 和一次性拷贝的处理相同
🔥值得注意的是: memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1))
不能写成 memcpy(a + begin1, tmp + begin1, sizeof(int) * (end2 - begin1 + 1))
,因为 begin1
被移动了,i
才是原来的初始位置
6.排序复杂度及效率对比
💻测试代码:
void TestOP()
{srand(time(NULL));const int N = 100000;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);int* a7 = (int*)malloc(sizeof(int) * N);int* a8 = (int*)malloc(sizeof(int) * N);int* a9 = (int*)malloc(sizeof(int) * N);int* a10 = (int*)malloc(sizeof(int) * N);int* a11 = (int*)malloc(sizeof(int) * N);int* a12 = (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];a7[i] = a1[i];a8[i] = a1[i];a9[i] = a1[i];a10[i] = a1[i];a11[i] = a1[i];a12[i] = a1[i];}int begin1 = clock();InsertSort(a1, N);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();QuickSort1(a5, 0, N - 1);int end5 = clock();int begin8 = clock();QuickSort2(a8, 0, N - 1);int end8 = clock();int begin9 = clock();QuickSort3(a9, 0, N - 1);int end9 = clock();int begin10 = clock();QuickSortNonR(a9, 0, N - 1);int end10 = clock();int begin6 = clock();MergeSort(a6, N);int end6 = clock();int begin11 = clock();MergeSortNonR1(a11, N);int end11 = clock();int begin12 = clock();MergeSortNonR1(a12, N);int end12 = clock();int begin7 = clock();BubbleSort(a7, N);int end7 = 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("BubbleSort:%d\n", end7 - begin7);printf("QuickSort1:%d\n", end5 - begin5);printf("QuickSort2:%d\n", end8 - begin8);printf("QuickSort3:%d\n", end9 - begin9);printf("QuickSortNonR:%d\n", end10 - begin10);printf("MergeSort:%d\n", end6 - begin6); printf("MergeSortNonR1:%d\n", end11 - begin11);printf("MergeSortNonR2:%d\n", end12 - begin12);free(a1);free(a2);free(a3);free(a4);free(a5);free(a6);free(a7);free(a8);free(a9);free(a10);free(a11);free(a12);
}
⌨️测试结果:
希望读者们多多三连支持
小编会继续更新
你们的鼓励就是我前进的动力!
相关文章:
【初阶数据结构】星河中的光影 “排” 象:排序(下)
文章目录 4.交换排序4.1 冒泡排序(BubbleSort)4.2 快速排序(QuickSort)4.2.1 hoare版本4.2.2 挖坑法4.2.3 前后指针法4.2.4 非递归实现 5.归并排序(MergeSort)5.1 递归实现5.2 非递归实现5.2.1 一次性全部拷…...
C++ 练习1
阐述g 有哪些常用的选项,该选项有什么作用 选项作用-o <file>指定输出文件名(默认生成 a.out)-c仅编译生成目标文件(.o 文件),不链接-E只进行预处理,输出预处理后的代码(展开…...
Ajax数据采集与分析详解
文章目录 1. 什么是 Ajax?2. Ajax 的工作原理3. Ajax 在网页中的应用场景4. 爬取 Ajax 数据的方法4.1 分析网络请求4.2 模拟 Ajax 请求4.3 使用 Selenium 模拟浏览器4.4 使用 Headless 浏览器 5. 处理动态参数6. 处理分页和滚动加载7. 处理反爬虫机制8. 数据存储9. …...
协方差(Covariance)与得分函数:从Fisher信息矩阵看统计关联
协方差与得分函数:从Fisher信息矩阵看统计关联 协方差(Covariance)是统计学中一个基础但强大的概念,它描述了两个随机变量之间的关系。在Fisher信息矩阵中,协方差以一种特别的形式出现:得分函数的协方差。…...
【CSS 选择器的特异度 CSS 继承 CSS 求值过程解析 CSS 布局方式及相关技术】
以下是关于 CSS 选择器特异度、继承、求值过程及布局技术 的详细解析,结合核心概念和实际应用场景: 一、CSS 选择器特异度(Specificity) 1. 特异度规则 特异度用于决定当多个选择器作用于同一元素时,哪个样式优先级更…...
Vue+ElementPlus的一些问题修复汇总
目录 一、ElementPlusVue-router做侧边栏问题 二、 组件样式问题 2.1修改文字颜色、大小、粗细、边框的颜色 2.2修改聚焦后文字的颜色、边框的颜色 2.3修改鼠标悬浮时文字的颜色、边框的颜色 三、 组件样式问题 3.1修改文字颜色、大小、粗细 四、 样式问题 4.1当数据为空…...
单链表删除算法(p=L; j=0;与p=p->next;j=1的辨析)
算法描述 Status ListDelete(LinkList &L,int i) { //在带头结点的单链表 L 中,删除第 i 个元素 pL; j0; while ((p->next) && (j<i-1)) {pp->next; j;} if (!(p->next)||(j>i-1)) return ERROR; qp->nex…...
从单片机的启动说起一个单片机到点灯发生了什么下——使用GPIO点一个灯
目录 前言 HAL库对GPIO的抽象 核心分析:HAL_GPIO_Init 前言 我们终于到达了熟悉的地方,对GPIO的初始化。经过漫长的铺垫,我们终于历经千辛万苦,来到了这里。关于GPIO的八种模式等更加详细的细节,由于只是点个灯&am…...
vue2项目打包后js文件过大, 首次加载缓慢
vue2项目打包后js文件过大, 首次加载缓慢 安装插件 npm i compression-webpack-plugin6.1.1 -D配置vue.config.js const CompressionWebpackPlugin require(compression-webpack-plugin)module.exports {configureWebpack: {plugins:[new CompressionWebpackPlugin({filen…...
llama.cpp 一键运行本地大模型 - Windows
文章目录 llama.cpp 一键运行本地大模型 - Windows嘿,咱来唠唠 llama.cpp 这玩意儿!gguf 格式是啥?咱得好好说道说道基座模型咋选?所需物料,咱得准备齐全咯核心命令,得记牢啦运行方式咋选?测试应…...
Android 老项目 jcenter 库失效
最近重新维护了一些老项目发现大部分jcenter库失效了, Could not resolve com.xx:2.1.3. 如果你也遇到了,不妨试试 替换为 aliyun的jcenter服务,就不用一个个找代替库了。 project 下的 build.gradle 文件添加: maven { url htt…...
MyBatis简明教程
MyBatis 是一个用于简化数据库操作的持久层框架,它的核心思想是 将 SQL 与 Java 代码解耦,让开发者专注于 SQL 的编写,同时自动处理重复的数据库操作步骤。 一、核心思想:SQL 与 Java 解耦 传统 JDBC 需要开发者手动管理数据库连…...
【Golang 面试题】每日 3 题(六十八)
✍个人博客:Pandaconda-CSDN博客 📣专栏地址:http://t.csdnimg.cn/UWz06 📚专栏简介:在这个专栏中,我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话,欢迎点赞👍收藏…...
DeepSeek个人知识库
deepseek构建个人知识库 安装软件链接 : 安装链接 先在本地把deepseek跑起来,本地部署deepseek见前文链接: 本地部署ollama # 目前软件只支持1.5b小模型,将就着用 ollama run deepseek-r1:1.5b等服务器启动后开启软件 上传文件 输入消息 (…...
力扣练习之字符串的最大公因子
使用语言:c 题目: 对于字符串 s 和 t,只有在 s t t t ... t t(t 自身连接 1 次或多次)时,我们才认定 “t 能除尽 s”。 给定两个字符串 str1 和 str2 。返回 最长字符串 x,要求满足 x 能…...
姿态矩阵/旋转矩阵/反对称阵
物理意义,端点矢量角速率叉乘本身向量; 负号是动系b看固定系i是相反的; 一个固定 在惯性导航解算中,旋转矢量的叉乘用于描述姿态矩阵的微分方程。你提到的公式中, ω i b b \boldsymbol{\omega}_{ib}^b \times ωibb…...
项目一 - 任务3:搭建Java集成开发环境IntelliJ IDEA
通过本次实战,我们成功搭建了Java集成开发环境IntelliJ IDEA,并完成了多个任务。首先,安装了IntelliJ IDEA并进行了个性化设置,如选择主题、调整字体和编码等。接着,创建了Java项目、包和类,编写并运行了简…...
C++的类型转换
目录 一、隐式类型转换的触发场景 1.基本数据类型间的转换 i.提升转换 ii.截断转换 2.类与对象的转换 i.单参数构造函数 ii.类型转换运算符 3.继承体系中的指针/引用转换 向上转型 二、隐式转换的风险与问题 1.意外行为 2.二义性错误 3.性能损耗 三、C强制类型转…...
嵌入式项目:STM32刷卡指纹智能门禁系统
本文详细介绍基于STM32的刷卡指纹智能门禁系统。 获取资料/指导答疑/技术交流/选题/帮助,请点链接: https://gitee.com/zengzhaorong/share_contact/blob/master/stm32.txt 1 系统功能 1.1 功能概述 本系统由STM32硬件端(下位机)…...
DeepSeek基础之机器学习
文章目录 一、核心概念总结(一)机器学习基本定义(二)基本术语(三)假设空间(四)归纳偏好(五)“没有免费的午餐”定理(NFL 定理) 二、重…...
Docker 搭建 Nginx 服务器
系列文章目录 Docker 搭建 Nginx 服务器 系列文章目录前言一、准备工作二、设置 Nginx 容器的目录结构三、启动一个临时的 Nginx 容器来复制配置文件四、复制 Nginx 配置文件到本地目录五、删除临时 Nginx 容器六、创建并运行 Nginx 容器,挂载本地目录七、修改 ngin…...
【Docker基础】理解 Docker:本质、性质、架构与核心组件
文章目录 Docker 本质Docker 的引擎迭代Docker 和虚拟机的区别Docker 为什么比虚拟机资源利用率高,速度快?Docker 和 JVM 虚拟化的区别Docker 版本1. LXC (Linux Containers)2. libcontainer3. Moby4. docker-ce5. docker-ee总结: Docker 架构…...
QT:QLinearGradient、QRadialGradient、QConicalGradient
QLinearGradient QLinearGradient 是 Qt 框架中用于创建线性渐变的类,它允许在图形绘制中实现颜色沿着一条直线的平滑过渡效果。以下是关于 QLinearGradient 的详细介绍: 基本概念:线性渐变是指颜色从一个点(起始点)沿…...
MySql:Authentication plugin ‘caching sha2 password‘ cannot be loaded
报错问题解释 在 MySQL 数据库中,如果你尝试使用 caching_sha2_password 插件进行认证,但是遇到错误信息 "Authentication plugin caching sha2 password cannot be loaded",这通常意味着客户端库或者连接器不兼容或者没有正确配置…...
c++类知识点复习与总结
类 c 是一种人机交互的面向对象的编程语言,面向对象思想主要体现在 类 上。 类是具有相同属性和相同行为的对象的集合, 具有封装,继承,多态的特性。 类的定义 class 类名 { }; 封装 例如:人就是一种类…...
Redis快速入门
一、Redis介绍 Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings),散列(has…...
嵌入式开发:傅里叶变换(5):STM32和Matlab联调验证FFT
目录 1. MATLAB获取 STM32 的原始数据 2. 将数据上传到电脑 3. MATLAB 接收数据并验证 STM32进行傅里叶代码 结果分析 STM32 和 MATLAB 联调是嵌入式开发中常见的工作流程,通常目的是将 STM32 采集的数据或控制信号传输到 MATLAB 中进行实时处理、分析和可视化…...
LLM/VLM进行票据识别工作
票据识别任务的需求是给定不同类型的票据图像,提取出指定的字段值,以json格式给出结构化信息。 目前的范式包括OCR,OCRLLM, OCRVLM,VLM四种方法。 一、OCR 利用OCR技术进行图像文字识别。 例如:https://github.c…...
AWS SDK for Java 1.x 403问题解决方法和原因
问题表现 使用AWS SDK for Java 1.x访问S3,已经确认文件存在,且具有权限,仍然出现403 Forbidden应答。 解决方法 升级到AWS SDK for Java 2.x。 问题原因 AWS签名机制严格依赖请求的精确路径格式,任何URI的差异(如…...
Spring Boot 项目中,JDK 动态代理和 CGLIB 动态代理的使用
在 Spring Boot 项目中,JDK 动态代理和 CGLIB 动态代理都是实现 AOP (面向切面编程) 的重要技术。 它们的主要区别在于代理对象的生成方式和适用范围。 下面详细介绍它们的使用场景: 1. JDK 动态代理 (JDK Dynamic Proxy) 原理: JDK 动态代理…...
蓝桥杯备赛-精卫填海-DP
精卫终于快把东海填平了!只剩下了最后的一小片区域了。同时,西山上的木石也已经不多了。精卫能把东海填平吗? 事实上,东海未填平的区域还需要至少体积为 v 的木石才可以填平,而西山上的木石还剩下 n 块,每块…...
萌新学 Python 之闭包函数
闭包函数:在一个函数体内嵌套函数,是一个函数对象,允许在内部函数中修改或引用外部函数的变量 闭包函数对数据有封存功能 闭包函数需要满足以下几个条件: 1.函数必须有一个嵌套函数,在定义函数时,内部再…...
AI创作教程:用deepseek和猫箱做互动故事游戏
年轻的时候我看过典型的玛丽苏文学、小妞文学,老了虽然识破这是给女孩编织的琉璃般的梦,看起来梦幻美丽其实一击就碎,会伤人的碎渣渣。【叠甲完毕】 本来我想用橙光的,但是橙光的话,最好把剧本和立绘都多打磨一下。快…...
【Linux探索学习】第三十一弹——线程互斥与同步(下):深入理解确保线程安全的机制
线程互斥与同步(上):【Linux探索学习】第三十弹——线程互斥与同步(上):深入理解线程保证安全的机制-CSDN博客 Linux探索学习: https://blog.csdn.net/2301_80220607/category_12805278.html?…...
博客系统完整开发流程
前言 通过前⾯课程的学习, 我们掌握了Spring框架和MyBatis的基本使用, 并完成了图书管理系统的常规功能开发, 接下来我们系统的从0到1完成⼀个项⽬的开发. 企业开发的流程 1. 需求评审(产品经理(PM)会和运营(想口号),UI,测试,开发等沟通) ,会涉及到背景/目标/怎么做,可能会有多…...
【C++】面试常问八股
5、内存管理 野指针 野指针指的是未进行初始化或未清零的指针,不是NULL指针野指针产生原因及解决方案: 指针变量未初始化:指针变量定义时若未初始化,则其指向的地址是随机的,不为NULL;定义时初始化为NULL…...
自定义提交按钮触发avue-form绑定的submit事件
场景 使用avue-form时,提交按钮会绑定至form区域下方,如果想自定义按钮位置,需要通过dialog的footer位置进行编写,例如: <avue-form ref"form" v-model"dataInfo" :option"dataInfoOpti…...
HarmonyOS 无线调试
下载sdk 找到hdc位置> C:\Users\27638\AppData\Local\OpenHarmony\Sdk\14\toolchains 不要去DevEco Studio的窗口不知道为什么调不动 hdc tconn IP:PORT...
Android之APP更新(通过接口更新)
文章目录 前言一、效果图二、实现步骤1.AndroidManifest权限申请2.activity实现3.有版本更新弹框UpdateappUtilDialog4.下载弹框DownloadAppUtils5.弹框背景图 总结 前言 对于做Android的朋友来说,APP更新功能再常见不过了,因为平台更新审核时间较长&am…...
二、大模型微调技术栈全解析
大模型微调技术栈全解析:从微调方法到算力支撑 在大模型的领域中,微调(Fine-tuning)就像是为模型量身定制的高级裁缝服务,能够让通用的大模型更好地适应特定的任务和场景。而要完成这项精细的工作,需要一整…...
设置 C++ 开发环境
设置 C++ 开发环境 C++ 是一种通用编程语言,现在广泛用于竞争性编程。它具有命令式、面向对象的和通用编程功能。 C++ 可以在许多平台上运行,如 Windows、Linux、Unix、Mac 等。在我们开始使用 C++ 编程之前。我们需要在本地计算机上设置一个环境,以便成功编译和运行我们的…...
计算机基础知识
1、RAM和ROM RAM:随机存取存储器,也叫做主存。是与CPU直接交换数据的内部存储器。这种存储器在断电时将丢失其数据,故主要用于短时间使用的程序。 ROM:即只读存储,是一种只能读出事先所存数据的固态半导体存储器 2、…...
蓝桥杯——按键
一:按键得原理图 二:按键的代码配置 step1 按键原理图对应引脚配置为输入状态 step2 在GPIO中将对应引脚设置为上拉模式 step3 在fun.c中写按键扫描函数 写完后的扫描函数需放在主函数中不断扫描 扫描函数主要通过两个定义变量的值来判断…...
Zemax OpticStudio 中的扩散器建模
在 Zemax OpticStudio 中构建漫射器涉及创建散射或漫射光的表面或物体。以下是有关如何在 Zemax OpticStudio 中创建漫射器的一般指南: 转到非序列模式 (NSC) 选项卡。NSC 对于模拟与物体而非表面相互作用的非序列射线很有用。 在需要散光器的位置创建新对象。对象…...
网络安全防御:蓝队重保备战与应急溯源深度解析
课程目标 本课程旨在培养专业的网络安全蓝队成员,通过系统化的学习和实战演练,使学员能够掌握网络安全防御的核心技能,包括资产测绘、应急响应、系统安全应急溯源分析、网络层溯源分析以及综合攻防演练等。学员将能够熟练运用各种工具和技术…...
MySQL 和 Elasticsearch 之间的数据同步
MySQL 和 Elasticsearch 之间的数据同步是常见的需求,通常用于将结构化数据从关系型数据库同步到 Elasticsearch 以实现高效的全文搜索、聚合分析和实时查询。以下是几种常用的同步方案及其实现方法: 1. 应用层双写(双写模式) 原…...
【深度学习】矩阵的核心问题解析
一、基础问题 1. 如何实现两个矩阵的乘法? 问题描述:给定两个矩阵 A A A和 B B B,编写代码实现矩阵乘法。 解法: 使用三重循环实现标准矩阵乘法。 或者使用 NumPy 的 dot 方法进行高效计算。 def matrix_multiply(A, B):m, n …...
汽车开放系统架构(AUTOSAR)中运行时环境(RTE)生成过程剖析
一、引言 在当今高度智能化的汽车电子领域,软件系统的复杂性呈指数级增长。为了应对这一挑战,汽车开放系统架构(AUTOSAR)应运而生,它为汽车电子软件开发提供了标准化的分层架构和开发方法。其中,运行时环境…...
VC++零基础入门之系列教程 【附录E MFC快速参考指南】
附录E MFC快速参考指南 E.1 创建窗口 使用M F C CWnd wnd; W n d . C r e a t e E x ( E xSt y l e , C l a s s N a m e , Wi n d o w N a m e , S t y l e , x , y, Wi d t h , H e i g h t , P a r e n t , M e n u , P a r a m ) ; 使用A P I HWND hwnd=::CreateWi n d …...
Holoens2开发报错记录02_通过主机获取彩色和深度数据流常见错误
01.E1696 E1696 无法打开源文件 “stdio.h” 解决方法: 更新一下SDK 1)打开Visual Studio Installer,点击修改 2)安装详细信息中自己系统对应的SDK,点击修改即可 02.WinError 10060 方法来源 解决方法:…...