数据结构之各类排序算法代码及其详解
1. 排序的概念
排序是一种常见的算法概念,用于将一组数据按照特定的顺序进行排列。排序算法的目的是将一组数据按照递增或递减的顺序重新排列。常见的排序算法包括冒泡排序、插入排序、选择排序、快速排序、归并排序等。排序算法的选择通常取决于数据规模、数据分布和性能需求等因素。排序算法在计算机科学中具有非常重要的地位,被广泛应用于各种领域和问题的解决中。
1.1 稳定性的概念
在排序里面有一个非常重要的概念,那就是稳定性。
简单来说就是排序结束后,相同的数之间的相对位置关系不会发生改变。
比如,如果有一个包含学生信息的列表,需要按照学生的分数进行排序,但是分数相同的学生可能会有不同的其他信息(比如姓名、年龄等),如果排序算法是稳定的,那么相同分数的学生在排序后仍然保持着原本的顺序,这对于保持其他信息的相对顺序是非常有用的。
2. 常见的排序算法

以上这张图里面的就是我们接下来要实现的一些排序法。同时他们又因为不同的排序方法分为不同的类别比如说常见的插入排序(直接插入排序,希尔排序),选择排序(选择排序,堆排序)和交换排序(快速排序,冒泡排序)等等。
3. 插入排序
插入排序是一种简单直观的排序算法,其基本思想是将一个数据插入到已经排好序的数据序列中,从而逐步构建有序序列。
3.1直接插入排序
void InsertSort(int* a, int n)
{for (int i=1;i<n;++i){int end = i-1;int tmp = a[i];while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];end--;}else{break;}a[end + 1] = tmp;}}
}
函数接受两个参数,一个是整数指针 a ,表示要排序的数组,另一个是整数 n ,表示数组的长度。通过一个 for 循环从数组的第二个元素开始(索引为 1),依次将每个元素插入到已排序的部分。在每次循环中, end 表示当前要比较的位置,初始值为当前元素的前一个位置,还定义了一个变量 tmp 保存当前要插入的元素值。然后通过一个 while 循环,只要 end 大于等于 0 ,并且当前要插入的元素 tmp 小于 a[end] ,就将 a[end] 向后移动一位(a[end + 1] = a[end]),同时 end 向前移动一位(end--)。当找到合适的位置(tmp >= a[end])时,就将 tmp 插入到该位置后面(a[end + 1] = tmp)。(类似于抓了一把扑克牌,然后从左往右排序)
直接插入排序的特性总结:
3.2 希尔排序
希尔排序是一种插入排序的改进版本,也称为缩小增量排序。它是由美国计算机科学家希尔(Donald Shell)于1959年提出的。希尔排序的基本思想是将待排序的元素分成若干个子序列,对每个子序列进行插入排序,然后逐步缩小子序列的长度,最终整个序列变成有序。
void ShellSort(int* a, int n)
{int gap = n;while (gap > 1){gap /= 2;for (int i = 0; i < n - gap; i++){int end = i ;int tmp = a[i+gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}a[end+gap] = tmp;}}}
}
首先,通过一个 while 循环,只要 gap 大于 1 ,就将 gap 不断除以 2 来调整步长。对于每次确定的 gap 值,通过一个 for 循环,从索引 0 开始,对每隔 gap 个位置的元素进行插入排序的操作。在每次的插入排序过程中, end 为当前要比较的位置,初始值为 i , tmp 保存当前要插入的元素值(即 a[i + gap] )。通过一个 while 循环,只要 end 大于等于 0 ,并且当前要插入的元素 tmp 小于 a[end] ,就将 a[end] 向后移动 gap 个位置(a[end + gap] = a[end]),同时 end 向前移动 gap 个位置(end -= gap)。当找到合适的位置(tmp >= a[end])时,就将 tmp 插入到该位置后面(a[end + gap] = tmp)。

有点类似于这张图,然后通过减小gap来达到渐渐有序。这样做有一个好处就是大的数可以快速的跳到后面,不用慢慢的一个一个向后调整 ,希尔排序就是通过这样的方式来实现优化。
PS:我们可以把直接插入排序理解为gap恒定为1的希尔排序。
4. 选择排序
4.1 选择排序
void SelectSort(int* a, int n)
{int left = 0;int right = n - 1;while (left < right){int mymin = left;int mymax = left;for (int i = left + 1;i < n; ++i){if (a[i] <a[mymin]){mymin = i;}if (a[i] > a[mymax]){mymax = i;}}Swap(&a[left], &a[mymin]);if (left == mymax){mymax = mymin;}Swap(&a[right], &a[mymax]);left++;right--;}
}
首先,定义了两个变量 left 和 right ,分别表示数组的起始位置和结束位置。在 while 循环中,只要 left 小于 right ,就执行以下操作:在每次循环中,先假设 left 位置的元素是最小的(mymin = left)和最大的(mymax = left)。然后通过一个 for 循环,从 left + 1 位置开始遍历数组,找到当前最小元素的位置 mymin 和最大元素的位置 mymax 。接下来,交换 left 位置和 mymin 位置的元素。如果此时 left 等于之前的 mymax ,则更新 mymax 的值为 mymin 。再交换 right 位置和 mymax 位置的元素。最后,将 left 向右移动一位,right 向左移动一位。
PS:简单来说,我们可以想象现在我们手上有一把扑克牌,然后我们一眼扫过去,把最大和最小的依次放入左边和右边,这就是这个选择排序的逻辑。
4.2 堆排序
void HeapSort(int* a, int n)
{for (int i = (n - 1 - 1) / 2; i >=0; --i){AdjustDown(a, n, i);}int end = n - 1;while (end > 0){Swap(&a[end], &a[0]);AdjustDown(a, end, 0);end--;}
}
首先,通过一个 for 循环从数组中间位置开始向前(i = (n - 1 - 1) / 2),对每个非叶子节点调用 AdjustDown 函数进行调整,构建一个大根堆。然后,将堆顶元素(即最大元素)与最后一个未排序的元素交换位置,并对堆顶元素重新调用 AdjustDown 函数进行调整,使剩余元素仍保持大根堆的性质。这个过程一直持续到整个数组都被排序,通过 end 从数组末尾逐渐向前移动来控制。
PS:在i=(n-1-1)/2里面,第一个 -1是为了获取最后一个元素的索引,第二个-1是因为根据父节点公式 (子节点索引-1)/2,计算该元素的父节点索引,所以这里才会-1-1,同时这是为了区分(即更好的理解这段代码)。
PS:这里面的这个while我们可以理解为堆本身的一种缺陷,因为他不像AVL树,红黑树那样(即大的往右边,小的往左边),所以需要用到这里面的while。
5. 交换排序
5.1 冒泡排序
这个冒泡排序一般来说是我们学习的第一个排序算法。他非常的容易理解,同时他的代码也容易实现。简单来说这个代码写起来的逻辑就像是水里面的泡泡一样,小的上浮,大的下沉。
void BubbleSort(int* a, int n)
{for (int j = 0; j < n; ++j){int ex = 1;for (int i = 0; i < n - j-1; ++i){if (a[i] > a[i + 1]){Swap(&a[i], &a[i + 1]);ex = 0;}}if (ex == 1)break;}
}
外层的 for 循环控制排序的轮数,一共进行 n 轮。在每一轮中,初始化一个标志变量 ex 为 1,表示假定这一轮不需要交换元素,数组已经有序。内层的 for 循环用于比较相邻的元素,如果前一个元素大于后一个元素,就交换它们的位置,并将 ex 置为 0,表示这一轮进行了交换操作。每一轮结束后,如果 ex 仍然为 1,说明在这一轮中没有进行交换,即数组已经完全有序,此时通过 break 语句提前结束排序。
PS:ex是我加的一种优化,并不是必须的。
冒泡排序的时间复杂度是O(n^2),相对较低效,特别是在对大量数据进行排序时。然而,冒泡排序是一种容易实现和理解的算法,适用于小规模数据的排序。
5.2 快速排序
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
int ChooseMid(int* a, int left, int right)
{int mid = (left + right) / 2;if (a[left] < a[mid]){if (a[mid] < a[right])return mid;else if(a[left]>a[right]){return left;}else{return right;}}else{if (a[left] < a[right])return left;else if(a[mid]>a[right]){return mid;}else{return right;}}
}
首先,计算区间的中间位置 mid 。然后通过比较区间左右端点和中间点的值,来确定返回哪个位置的索引作为中间值的索引。如果 a[left]<a[mid] ,再判断 a[mid] 和 a[right] 的大小。如果 a[mid]<a[right],返回 mid ;如果 a[left]>a[right],返回left;否则返回right。如果 a[left]>=a[mid],同样再判断 a[left] 和 a[right] 的大小。如果 a[left]<a[right] ,返回 left ;如果 a[mid]>a[right],返回mid;否则返回right。
PS:这个函数是为了取出一个不是最大或者最小的值,因为max或者min的值会影响快速排序的效率。
5.2.1 Hoare版快速排序
void QuickSort1(int* a, int left, int right)
{if (left >= right)return;int begin = left;int end = right;int keyi = ChooseMid(a, left, right);Swap(&a[keyi], &a[left]);while (left < right){while(a[right] >= a[begin]&&left<right){--right;}while(a[left] <= a[begin]&&left<right){++left;}Swap(&a[left],& a[right]);}Swap(&a[begin],& a[left]);keyi = left;
QuickSort1(a, begin, keyi - 1);
QuickSort1(a, keyi + 1, end);
} 首先,检查区间的起始位置 left 是否大于等于结束位置 right ,如果是则直接返回,因为这种情况不需要排序。然后,初始化起始和结束位置的变量 begin 和 end ,通过 ChooseMid 函数选择一个 keyi ,并将keyI与起始位置的元素交换。接下来,通过两个 while 循环,从右向左找到小于keyi,从左向右找到大于keyi,然后交换它们的位置,直到左右指针相遇。相遇后,将keyi与当前指针位置的元素交换,确定keyi 的最终位置 。最后,对keyi左右两侧的子区间分别递归调用 QuickSort1 函数进行排序。
PS:我个人认为Hoare版本的快速排序挺简单的也好理解,个人比较推荐掌握这种。

我们把这个快速排序递归部分具体出来就是这个样子,通过这种方式来把它变的有序。
5.1.2 挖坑法版快速排序
void QuickSort2(int* a, int left, int right)
{if (left >= right)return;int begin = left;int end = right;int keyi = ChooseMid(a, left, right);Swap(&a[keyi], &a[left]);int hole = begin;int key = a[hole];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;}//Swap(&stay, &a[left]);//keyi = left;a[hole] = key;keyi = hole;QuickSort2(a, begin, keyi - 1);QuickSort2(a, keyi + 1, end);
} 首先,如果区间的起始位置 left 大于等于结束位置 right ,则直接返回,因为这种情况不需要排序。然后,初始化起始和结束位置的变量 begin 和 end ,通过 ChooseMid 函数选择一个基准元素的索引 keyi ,并将基准元素与起始位置的元素交换。接着,定义一个变量 hole 并初始化为起始位置,同时记录基准元素的值 key 。通过两个 while 循环,从右向左找到小于key的值,将其放到 hole 位置,更新 hole 为当前的右指针位置;从左向右找到大于基准元素的值,将其放到 hole 位置,更新 hole 为当前的左指针位置(简单来说就是先右边找比key小的,找到后给他放到key的位置,然后key变到右边,接着左边找比key小的,再换给已经到右边的key,最后循环往复)。循环结束后,将key放到 hole 位置,确定基准元素的最终位置 keyi 。最后,对key左右两侧的子区间分别递归调用 QuickSort2 函数进行排序。
5.1.3 前后指针版快速排序
void QuickSort3(int* a, int left, int right)
{if (left >= right)return;int begin = left;int end = right;int keyi = ChooseMid(a, left, right);//int keyi = left;Swap(&a[keyi], &a[left]);int first = left;int second = left + 1;while (second <= right){if (a[second] < a[keyi]&&++first!=second){//++first;Swap(&a[first], &a[second]);}++second;}Swap(&a[keyi], &a[first]);keyi = first;QuickSort3(a, begin, keyi - 1);QuickSort3(a, keyi + 1, end);
}
如果左边的位置大于等于右边,就不排序直接返回。先记录下开始和结束的位置,选一个基准元素并和左边的元素交换。然后设两个指针,一个从左边开始,另一个从左边下一个位置开始。在一个循环里,如果第二个指针(second)指向的元素小于基准元素,并且两个指针位置不同(因为同一位置交换没有意义),就交换它们指向的元素,两个指针再移动。如果second指向的元素比keyi大,那就不走if语句,直接加加second。循环完后,把基准元素和第一个指针指向的元素交换,确定基准位置。最后,对keyi两边的子区间再分别进行同样的排序操作。
5.3 快速排序(非递归版)
我们之所以要学习非递归版本的是因为递归版本的快速排序在面对大数量级的时候容易造成栈溢出,从而导致错误,所以我们要学习非递归版本的快速排序。
int PartSort3(int* a, int left, int right)
{int midi = GetMidNumi(a, left, right);if (midi != left)Swap(&a[midi], &a[left]);int keyi = left;int prev = left;int cur = left + 1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[cur], &a[prev]);++cur;}Swap(&a[prev], &a[keyi]);keyi = prev;return keyi;
}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);int keyi = PartSort3(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);
} PartSort3 函数前后指针版快速排序,只不过去掉了递归。
QuickSortNonR 函数实现非递归的快速排序。首先创建一个栈 st 并初始化,将排序区间的左右边界压入栈。在循环中,取出栈顶的两个值作为当前排序区间的起止位置,调用 PartSort3 进行部分排序得到基准索引。若基准右边区间可继续划分,将右边新的区间边界压入栈;若基准左边区间可继续划分,将左边新的区间边界压入栈。循环结束后销毁栈。
PS:其实本质这就是通过栈来进行一种对递归的模拟实现。
6. 归并排序
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; int end1 = mid;int begin2 = mid + 1;int 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++];}for (int i = 0; i < end-begin+1; ++i){a[i+begin] = tmp[i+begin];}
}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);
}
_MergeSort 函数用于对数组的指定区间进行归并排序的核心操作。如果起始位置大于等于结束位置,函数直接返回。通过计算中间位置,对左右子区间分别递归调用 _MergeSort 进行排序。然后,通过循环比较两个子区间的元素,将较小的元素依次放入临时数组 tmp 中。当其中一个子区间遍历完后,将另一个子区间剩余的元素放入 tmp 。最后,将 tmp 中的元素复制回原数组。MergeSort 函数用于对外提供归并排序的接口。首先分配一个与原数组大小相同的临时空间 tmp ,如果分配失败则报错并返回。然后调用 _MergeSort 对整个数组进行排序,排序完成后释放临时空间。
PS:其实他的本质就是分成一块一块然后进行小区间排序,然后小块合并再进行小区间排序,最后达到有序。
6.1 归并排序非递归版
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 += 2 * gap){int begin1 = i;int end1 = i + gap - 1;int begin2 = i + gap;int end2 = i + 2 * gap - 1;int j = i;if (end1 > n || begin2 > n){break;}if (end2 > n){end2 = n - 1;}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++];}int k = begin1;memcpy(a + i, tmp + i, sizeof(int)*(end2 - i + 1));}gap *= 2;}free(tmp);
}void MergeSort(int*a,int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}MergeSortNonR(a,n);free(tmp);
} 首先分配一个与原数组大小相同的临时数组tmp用于辅助排序。然后通过一个循环不断增加归并的子数组大小gap,每次循环对数组进行分组归并。对于每个分组,确定两个子数组的起始和结束位置,然后比较并合并两个子数组到tmp中,最后将tmp中的排序结果复制回原数组。当gap小于数组长度n时,不断重复这个过程。MergeSort函数主要用于分配临时空间,调用_MergeSortNonR进行排序,最后释放临时空间。
PS:非递归版的归并排序在代码实现的部分要格外注意边界处理。
7. 计数排序
void CountSort(int* a, int n)
{int max = a[0];int min = a[0];for (int i = 0; i < n; ++i){if (max < a[i]){max = a[i];}if (min > a[i]){min = a[i];}}int range = max - min + 1;int* cmp = (int*)malloc(sizeof(int*) * range);if (cmp == NULL){perror("malloc fail");return;}memset(cmp, 0, sizeof(int) * range);for (int i = 0; i < n; ++i){cmp[a[i] - min]++;}int j = 0;for (int i = 0; i < n; ++i){while (cmp[i]--){a[j++] = i + min;}}free(cmp);
} 首先,通过遍历数组找到数组中的最大值 max 和最小值 min 。然后计算数据的范围 range ,并分配一个与范围大小相同的辅助数组 cmp 。接着,再次遍历原数组,对每个元素在辅助数组中对应的位置进行计数。之后,通过另一个循环,根据辅助数组中的计数,将元素按顺序放回原数组。最后,释放辅助数组的内存。

PS:简单来说,他的原理就类似于这种(画的有点丑陋),通过记录出现的数的次数,最后把他们依次放入原始数组里面。从这张图我们也可以看出计数排序在原数组数字重复多并且数字间隔比较密的时候(max-min+1),计数排序会格外的好用。
6. 总结
在实际工程中,没有绝对的最优算法。当处理百万级数据时,快速排序往往表现优异;面对海量数据时,归并排序的稳定性优势凸显;在资源受限的嵌入式系统中,堆排序可能是更安全的选择。理解算法本质,分析数据特征,结合具体场景,才能做出最合适的决策。愿这些排序算法成为你解决实际问题的利剑,在编程之路上助你披荆斩棘!
建议读者在实践中尝试用不同算法处理同一数据集,直观感受性能差异。当面对特殊业务场景时,也可以考虑将多种算法组合使用,或者基于这些经典思想进行改良创新。排序的世界永远充满惊喜,期待你在实践中发现更多精妙之处!
相关文章:
数据结构之各类排序算法代码及其详解
1. 排序的概念 排序是一种常见的算法概念,用于将一组数据按照特定的顺序进行排列。排序算法的目的是将一组数据按照递增或递减的顺序重新排列。常见的排序算法包括冒泡排序、插入排序、选择排序、快速排序、归并排序等。排序算法的选择通常取决于数据规模、数据分布…...
【洛谷贪心算法】P1090合并果子
为了使消耗的体力最小,每次都应该选择当前重量最小的两堆果子进行合并。可以使用优先队列(小根堆)来实现这个过程,优先队列可以自动维护元素的顺序,每次取出堆顶的两个元素(即最小的两个元素)进…...
【告别双日期面板!一招实现el-date-picker智能联动日期选择】
告别双日期面板!一招实现el-date-picker智能联动日期选择 1.需求背景2.DateTimePicker 现状图3.日期选择器实现代码4.日期选择器实现效果图5.日期时间选择器实现代码6.日期时间选择器实现效果图 1.需求背景 在用户使用时间查询时,我们经常需要按月份筛选…...
现今大语言模型性能(准确率)比较
现今大语言模型性能(准确率)比较 表头信息:表的标题为“大语言模型性能比较结果”(英文:Table 1: Large Language Model Performance Comparison Results),表明该表是用于对比不同大语言模型的性能。列信息: 模型:列出参与比较的不同大语言模型名称,包括LLAMA3(70B)…...
程序诗篇里的灵动笔触:指针绘就数据的梦幻蓝图(水文,勿三)
大家好啊,我是小象٩(๑ω๑)۶ 我的博客:Xiao Xiangζั͡ޓއއ 很高兴见到大家,希望能够和大家一起交流学习,共同进步。 这一节我们来学习指针的相关知识,学习内存和地址,指针变量和地址,包…...
在 UniApp 中实现中间凸起 TabBar 的完整指南
如何在 UniApp 中设置中间 TabBar 凸起效果 在移动应用开发中,TabBar 是常见的导航组件,而中间凸起的 TabBar 按钮则是一种流行的设计风格,常用于突出重要功能(如发布、拍照等)。UniApp 提供了 midButton 属性&#x…...
Redis大key
Redis大key基本概念,影响 Redis 大 key 指在 Redis 中存储了大量数据的键,它会对 Redis 的性能和内存管理产生影响。 大key的定义与value的大小和元素数量有关,但这个定义并不是绝对的,而是相对的,具体取决于系统的使用…...
WPF高级 | WPF 与数据库交互:连接、查询与数据更新
WPF高级 | WPF 与数据库交互:连接、查询与数据更新 前言一、数据库交互基础概念1.1 数据库简介1.2 数据访问技术 二、WPF 与数据库连接2.1 连接字符串2.2 建立连接 三、WPF 中的数据查询3.1 使用ADO.NET进行数据查询3.2 使用 Entity Framework 进行数据查询3.3 使用…...
CogBlobTool工具
CogBlobTool是一款专用于图像斑点检测于分析的 工具,通过灰度值阈值分割和特征过滤,帮助在复杂背景中提取目标区域,并计算几何属性。 效果图 注意:在这里只有一张图像可以不使用模板匹配工具 CogBlobTool工具的功能 斑点检测于…...
C# WinForm程序中如何调试dll接口
公司的SF系统是自主开发的。不同的机种会有不同数据记录保存的需求,尤其是客户SQE更是各种奇思妙想......于是做了一个接口,实践之下效果还不错呢。 每每总是忘记怎么调试接口,特记录下备查。首先要将, 1 DLL项目与WinForms项目…...
自然语言处理:词频-逆文档频率
介绍 大家好,博主又来给大家分享知识了。本来博主计划完成稠密向量表示的内容分享后,就开启自然语言处理中文本表示的讲解。可在整理分享资料的时候,博主发现还有个知识点,必须得单独拎出来好好说道说道。 这就是TF-IDF…...
【银河麒麟高级服务器操作系统】服务器测试业务耗时问题分析及处理全流程分享
更多银河麒麟操作系统产品及技术讨论,欢迎加入银河麒麟操作系统官方论坛 https://forum.kylinos.cn 了解更多银河麒麟操作系统全新产品,请点击访问 麒麟软件产品专区:https://product.kylinos.cn 开发者专区:https://developer…...
基于大数据的民宿旅馆消费数据分析系统
【大数据】基于大数据的民宿旅馆消费数据分析系统(完整系统源码开发笔记详细部署教程)✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 该系统可以揭示民宿市场的消费模式和价格分布情况,帮助理解消费者偏好、价格走势及…...
Spring-AI搭建企业专属知识库 一
环境介绍:Spring3.3.2 JDK 21 POM文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation&…...
极简本地体验deepseek大模型教程
一 题外随感:时代之问 就像狄更斯在双城记中所述,“这是最好的时代,这是最坏的时代”。每一代人都有其所处的时代,每一个时代都有其所谓好的一面和不那么好的一面。很多时候随口的一句大环境不好,就似乎给了自己一个最…...
RabbitMQ系列(五)基本概念之Queue
在 RabbitMQ 中,Queue(队列) 是存储消息的容器,也是消息传递的核心载体。以下是其核心特性与作用的全方位解析: 一、Queue 的定义与核心作用 消息存储容器 Queue 是 RabbitMQ 中实际存储消息的实体,生产者…...
【记录】成为创作者的第 730 天(两年)
收获 还是总在感叹走到今天收获的一切,都是自己曾经不敢想的。 无论是靠自己努力拿到的 Offer,还是在 CSDN 网站上结交的网友和前辈们,都是我莫大的荣幸和财富,感恩一切、感恩自己。 过去一年的收获真的数不胜数,抛…...
深度剖析数据分析职业成长阶梯
一、数据分析岗位剖析 目前,数据分析领域主要有以下几类岗位:业务数据分析师、商业数据分析师、数据运营、数据产品经理、数据工程师、数据科学家等,按照工作侧重点不同,本文将上述岗位分为偏业务和偏技术两大类,并对…...
【XSS】DVWA靶场XSS攻击
一、XSS攻击 1.1. XSS 攻击简介 XSS(Cross-Site Scripting,跨站脚本攻击)是一种常见的Web安全漏洞,它允许攻击者在受害者的浏览器中执行恶意脚本。攻击者通常通过在Web应用程序中注入恶意脚本代码(如JavaScript&…...
Fiddler在Windows下抓包Https
文章目录 1.Fiddler Classic 配置2.配置浏览器代理自动代理手动配置浏览器代理 3.抓取移动端 HTTPS 流量(可选)解决抓取 HTTPS 失败问题1.Fiddler证书过期了 默认情况下,Fiddler 无法直接解密 HTTPS 流量。需要开启 HTTPS 解密: 1…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...
uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...
消防一体化安全管控平台:构建消防“一张图”和APP统一管理
在城市的某个角落,一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延,滚滚浓烟弥漫开来,周围群众的生命财产安全受到严重威胁。就在这千钧一发之际,消防救援队伍迅速行动,而豪越科技消防一体化安全管控平台构建的消防“…...
