01数据结构-交换排序

01数据结构-交换排序

  • 1.冒泡排序
    • 1.1基础冒泡排序
      • 1.1.1基础冒泡排序代码实现
    • 1.2冒泡排序的一次优化
      • 1.2.1冒泡排序的第一次优化代码实现
    • 1.3冒泡排序的二次优化
      • 1.3.1 冒泡排序的二次优化代码实现
  • 2.快速排序
    • 2.1双边循环法
      • 2.1.1双边循环法的代码实现
    • 2.2单边循环法
      • 2.2.1单边循环法代码实现

1.冒泡排序

1.1基础冒泡排序

算法思想

冒泡排序是最简单的排序算法了。冒泡排序通过不断地比较两个相邻元素,将较大的元素交换到右边(升序),从而实现排序。那我们直接看例子。

在这里插入图片描述
我们对数组 [5,1,4,2,8,4] ,采用冒泡排序进行排序,注意这里的两个 4 的颜色是不同
的,主要是为了区分两个不同的 4 ,进而解释冒泡排序算法的稳定性问题。

第一轮冒泡排序:第一步:比较 5 和 1 ,5 > 1,则交换 5 和 1 的位置:
在这里插入图片描述
第二步,比较 5 和 4,5 > 4,交换 5 和 4 的位置:
在这里插入图片描述
第三步:比较 5 和 2 ,5 > 2,交换 5 和 2 的位置:
在这里插入图片描述
第四步:比较 5 和 8 ,5 < 8 ,不交换
在这里插入图片描述
第五步:比较 8 和 4 , 8 > 4,交换 8 和 4 :
在这里插入图片描述
此刻我们获得数组当中最大的元素 8 ,使用橘⻩色进行标记:

第一轮冒泡结束,最大的元素8到了最后,然后对于前面5个元素,进行第二轮冒泡将第二大的数据放在右边。

最终结果:在这里插入图片描述
事实上第二阶段结束,整个数组已经有序了,但是对于冒泡排序而言并不知道,她还需要通过第三阶段的比较操作进行判断。

对于冒泡排序算法而言,她是通过判断整个第三阶段的比较过程中是否发生了交换来确定数组是否有序的,显然上面的过程中没有交换操作,冒泡排序也就知道了数组有序,整个算法执行结束。

1.1.1基础冒泡排序代码实现

void bubbleSortV1(SortTable *table) {for (int i = 0; i < table->length-1; ++i) {for (int j = 0; j < table->length-1-i; ++j) {if (table->data[j].key > table->data[j+1].key) {swapElement(&table->data[j+1],&table->data[j]);}}}
}

这里面的swapElement是我测试框架里的代码,作用是交换两个元素的位置,外层循环用于把最大的元素冒到右边去,内层循环从0开始length-1-i,才能完整扫描未排好区间,实现“冒泡”效果。注意第二层循环的上界,最右边 i 个元素已经是有序区,不需要再碰,所以上界写成 table->length-1-i。

来测一下:

#include"bubbleSort.h"
void test01() {int n=100000;SortTable *table1=generateRandomArray(n,0,1+5000);testSort("bubbleSortV1",bubbleSortV1,table1);releaseSortTable(table1);
}int main() {test01();return 0;
}

结果:

D:\work\DataStruct\cmake-build-debug\04_Sort\SwapSort.exe
bubbleSortV1 cost time: 20.063000s.进程已结束,退出代码为 0

可以看到同样是十万个元素上一节课中的插入排序几秒钟就能完成,但是这里的冒泡排序需要20几秒,原因在于无论数据是否已经有序,都要跑完全部 n-1 趟且交换次数往往也很高,我们就在想能不能优化一下呢?

1.2冒泡排序的一次优化

这里我们增加了一个标识数组是否有序 ,当冒泡排序过程中没有交换操作时,swapped = false ,也意味着数组有序;否则数组无序继续进行冒泡排序。不要小看这个变量奥,因为这个变量,当数组有序的时候,冒泡排序的时间复杂度将降至 O(n)(因为其只需要执行一遍内层的 for 循环就可以结束冒泡排序),没有这个变量,数组有序也需要O(n2)的时间复杂度。讲直白点因为冒泡排序始终把大的元素冒到右边,可以当作右边始终是有序的,当发现某一轮不需要交换,那么就说明已经有序,退出循环。

1.2.1冒泡排序的第一次优化代码实现

void bubbleSortV2(SortTable* table) {for (int i = 0; i < table->length - 1; ++i) {int isSorted = 1;for (int j = 0; j < table->length - 1 - i; ++j) {if (table->data[j].key > table->data[j + 1].key) {swapElement(&table->data[j + 1], &table->data[j]);isSorted = 0;}}if (isSorted) {break;}}
}

来测一下:

#include"bubbleSort.h"
void test01() {int n=10000;SortTable *table1=generateRandomArray(n,0,1+5000);SortTable *table2=copySortTable(table1);testSort("bubbleSortV1",bubbleSortV1,table1);testSort("bubbleSortV2",bubbleSortV2,table2);releaseSortTable(table1);releaseSortTable(table2);
}int main() {test01();return 0;
}

结果:

D:\work\DataStruct\cmake-build-debug\04_Sort\SwapSort.exe
bubbleSortV1 cost time: 0.130000s.
bubbleSortV2 cost time: 0.130000s.进程已结束,退出代码为 0

这里看不出来精度差是因为我们产生的数据太随机了,意义不太大,但是这种思想要学会。

1.3冒泡排序的二次优化

一次优化是为了避免数组有序的情况下,继续进行判断操作的。那么二次优化又为了什么呢 ?

我们看下面的例子。
在这里插入图片描述
经过一次冒泡后,我们会注意到一个问题,但是我们注意到,数组数组中的 [5,6,8] 本身已经有序,而对于有序的部分进行比较是没有意义的,相当于在白白浪费资源,有没有什么办法减少这样的比较次数呢?

换句话说,是否能够确定出已经有序部分和无序部分的边界呢?

答案当然是肯定的,这个边界就是第一趟冒泡排序的过程中最后一次发生交换的位置 j :也就是 1 和 4 发生交换之后,4 和 5 没有发生交换,此时 1 之后的元素为有序。

第一步:4 和 2比较,4 > 2 ,交换 4 和 2 ,将 LastSwappedIndex = 0;

第二步:4 和 1 比较,4 > 1,交换 4 和 1, LastSwappedIndex = 1 ;

第三步:比较 4 和 5 , 4 < 5,不交换, lastSwappedIndex 也不更新;

第四步:比较 5 和 6 ,不交换, lastSwappedIndex 也不更新;

第五步:比较 6 和 8 ,不交换, lastSwappedIndex 也不更新;

第一趟冒泡排序结束了,我们把 LastSwappedIndex放在了4这里,相当于是一个挡板
在这里插入图片描述
来看第二趟冒泡排序,此时 j 的 取值将从 j = 0 到 j = lastSwappedIndex ,第一步:比较 2 和 1 ,2 > 1,交换,lastSwappedIndex = 0 ,并且第二趟冒泡也就结束了,也就说我们节省了 从 2 到 6的比较操作;

最后再来一趟冒泡排序,发现没有任何交换,所以冒泡排序结束。

相比于一次优化的实现方式,二次优化的实现方式进一步减少了不必要的执行次数,两种优化后的实现方式需要冒泡排序的趟数是一样的,本质上没有什么区别。所以即使对于一个有序的数组,两种方式的时间复杂度都是O(n)

1.3.1 冒泡排序的二次优化代码实现

/* 引入newIndex标记交换的索引位置,下次冒泡的时候结束位置就是newIndex */
void bubbleSortV3(SortTable* table) {int newIndex;int n = table->length;do {newIndex = 0;for (int i = 0; i < n - 1; ++i) {if (table->data[i].key > table->data[i + 1].key) {swapElement(&table->data[i + 1], &table->data[i]);newIndex = i + 1;}}//更新挡板位置n = newIndex;} while (newIndex > 0);
}

注意要把i+1赋给newIndex,如果赋的是i由于我们for循环中循环条件是n-1就会少一个数的排序,如图假设newIndex在6,本来该是2,1,4,5冒泡排序,但是由于newIndex-1赋值给n,n-1为循环条件就会导致5没有参与冒泡排序。
在这里插入图片描述

来测一下:

#include"bubbleSort.h"
void test01() {int n=10000;SortTable *table1=generateRandomArray(n,0,1+5000);SortTable *table2=copySortTable(table1);SortTable *table3=copySortTable(table1);testSort("bubbleSortV1",bubbleSortV1,table1);testSort("bubbleSortV2",bubbleSortV2,table2);testSort("bubbleSortV3",bubbleSortV3,table3);releaseSortTable(table1);releaseSortTable(table2);
}int main() {test01();return 0;
}

结果:

D:\work\DataStruct\cmake-build-debug\04_Sort\SwapSort.exe
bubbleSortV1 cost time: 0.129000s.
bubbleSortV2 cost time: 0.131000s.
bubbleSortV3 cost time: 0.125000s.进程已结束,退出代码为 0

看的出来第二次优化会比前两次是要好一点的

2.快速排序

找pos犄点,pos是索引号,pos对应的值的左边都是比pos值小的,pos对应的值的右边都是比pos值大的,这样咱们就把要排序的序列分成两部分,我们拿排序的最差时间复杂度来说,假设左边部分有x个元素,右边部分为y个,那么分别排序这两个序列的时间复杂度为x2+y2而没有拆分前的时间复杂度是x2+y2+2xy,同理在左边部分的序列又可以找一个犄点,右边部分的序列也可以找一个犄点。如图所示:
如果我们每次的犄点都是均分,一直分下去最后的时间复杂度为nlogn。找犄点的方法有两种:双边循环法,单边循环法。
在这里插入图片描述

2.1双边循环法

所谓双边就是两个指针在左右,用这两个指针从数组两端向中间“夹击”,随机初始化一个犄点把小于犄点的元素换到左边、大于犄点的元素换到右边,直到两指针相遇。初始时的犄点是随机取得,这里我就把它放在数组得最左边,如图:
在这里插入图片描述
来看右边right,看右边的指针是否比初始时犄点值大,发现是49没问题,right往左边走一步来到27,发现27比38小,说明应该在犄点的左边;left最初指向38,可以认为大于等于初始时犄点的值,left往右走一步来到49,49比38大,说明应该在犄点的右边,我们应该把两个值交换位置,注意一定要先操作右边,找到第一个比我们最初设定的犄点值小的数,如果暂时没有,就先一直操作右边right–,保证右边先找到,再去操作左边,找到第一个比我们最初设定的犄点值大的数,如果暂时没有,就再一直操作右移,两者都找到了就交换位置如图
在这里插入图片描述
right往右一直走到13发现比38小了,left往左边走,相碰了,我们找到了犄点所在位置,我们就交换38和13的位置。如图,犄点左边的值确实比犄点的值小,犄点右边的值确实比犄点的值大,同理在新分出来的左,右边依然可以采取这种方法直到left和right相碰,很明显这是一种递归的是方法,下面我们来看怎么实现代码
在这里插入图片描述

2.1.1双边循环法的代码实现

static int partitionDouble(SortTable *table, int startIndex, int endIndex) {int pivot = startIndex;int left = startIndex;int right = endIndex;// 随机将startIndex和后续的一个随机索引指向的元素进行交换while (left != right) {while (left < right && table->data[right].key > table->data[pivot].key) { right--; }while (left < right && table->data[left].key <= table->data[pivot].key) { left++; }if (left < right) {swapElement(&table->data[right], &table->data[left]);}}swapElement(&table->data[pivot], &table->data[left]);return left;
}// 用递归思想实现[start, end]区间的排序
static void quickSort1(SortTable *table, int startIndex, int endIndex) {if (startIndex >= endIndex) {return;}// 找到犄点int pivot = partitionDouble(table, startIndex, endIndex);quickSort1(table, startIndex, pivot - 1);quickSort1(table, pivot + 1, endIndex);
}void quickSortV1(SortTable* table) {quickSort1(table, 0, table->length - 1);
}

我们先来看大框架:static void quickSort1(SortTable *table, int startIndex, int endIndex);我们需要通过找犄点把整个序列按左边小右边大的思路一直分下去,所以我们需要写一个找犄点的函数,找到后开始递归,把新的左右两边的序列再次通过犄点分成左右两份,但是我们不能无限递归下去,所以我们需要写一个递归终止条件,只要子区间长度 ≤ 1,即 startIndex >= endIndex,就已经有序,无需再排,然后我们在
void quickSortV1(SortTable *table)调用这个函数,并赋值starIndex和endIndex。

接下来看找犄点的代码,我们把最初的基准pivot放到startIndex上,当左边left小于等于右边right的时候,开始先对右边处理逻辑,再对左边处理逻辑。有人可能会问,为什么外层循环已经有left<=right,内层循环为什么还要加呢?当出现如下图所示情况时如果在内层while循环中没有left<right的话,right就会一直减。同理处理左边的时候也需要加上left<right这个条件。外层循环只是控制分区过程是否继续,内层循环防止单指针移动时越界/交叉,确保每次移动后仍满足指针有效性。前面这两是必须的,if中的left<right避免指针重合时的无效交换,推荐使用
在这里插入图片描述
来测试一下:

#include"bubbleSort.h"
#include"quickSort.h"
void test02() {int n = 10000;SortTable *table1 = generateRandomArray(n, 0, n + 5000);SortTable *table2 = copySortTable(table1);testSort("bubbleSortV3", bubbleSortV3, table1);testSort("quick SortV1", quickSortV1, table2);releaseSortTable(table1);}int main() {test02();return 0;
}

结果:

D:\work\DataStruct\cmake-build-debug\04_Sort\SwapSort.exe
bubbleSortV3 cost time: 0.131000s.
quick SortV1 cost time: 0.000000s.进程已结束,退出代码为 0

能够看出快速排序的时间比冒泡排序的时间快的多,接下来看单边循环法

2.2单边循环法

我们依旧需要一个犄点,犄点的左值小于犄点,犄点的右值大于犄点,只不过我们用一个指针来处理结构,我们把这个犄点重新起个名字叫mark,帮忙维护整个序列的指针我们先称为i,依旧先把序列的最左边当作我们的犄点。如下图初始时
在这里插入图片描述
我们的i不断地往右边走,当走到i对应的值大于基础值38的时候我们就不管,当发现我们的i对应的值小于了38时,我们mark往右边走一位,然后交换mark指向的值和i指向的值,为什么mark要先往右边走一位呢,因为最终我们38会和我们最终确定的mark交换位置,我们需要保证最后交换后的mark的左边全部小于mark对应的值。反过来说,我们找到了对应i的值小于了基础值38后,mark要往后面走一位给我们找到的数据空一格位置出来,我们走到13的时候发现13比38小,mark往右边移动一位,和i指向的值交换

在这里插入图片描述
下一次找到比38小的数是27,我们依旧采用这样的思路如图所示:
在这里插入图片描述
i继续往后面走,发现mark的右边已经全部比38大了,这时我们交换38和mark指向的27的位置即可。
在这里插入图片描述

2.2.1单边循环法代码实现

static int partitionSingle(SortTable *table, int startIndex, int endIndex) {keyType tmpValue = table->data[startIndex].key;//备份一下第一个基准值int mark = startIndex;//假设第一个是犄点for (int i = startIndex + 1; i <= endIndex; i++) {if (table->data[i].key < tmpValue) {mark++;swapElement(&table->data[i], &table->data[mark]);}}swapElement(&table->data[startIndex], &table->data[mark]);return mark;
}static void quickSort2(SortTable *table, int startIndex, int endIndex) {if (startIndex >= endIndex) {return;}// 找到犄点int pivot = partitionSingle(table, startIndex, endIndex);quickSort2(table, startIndex, pivot - 1);quickSort2(table, pivot + 1, endIndex);
}void quickSortV2(SortTable* table) {quickSort2(table, 0, table->length - 1);
}

这里的逻辑就是我上述说的逻辑。

#include"bubbleSort.h"
#include"quickSort.h"void test02() {int n = 10000;SortTable *table1 = generateRandomArray(n, 0, n + 5000);SortTable *table2 = copySortTable(table1);SortTable *table3 = copySortTable(table1);testSort("bubbleSortV3", bubbleSortV3, table1);testSort("quick SortV1", quickSortV1, table2);testSort("quick SortV2", quickSortV2, table3);releaseSortTable(table1);releaseSortTable(table2);releaseSortTable(table3);
}int main() {test02();return 0;
}

结果:

D:\work\DataStruct\cmake-build-debug\04_Sort\SwapSort.exe
bubbleSortV3 cost time: 0.123000s.
quick SortV1 cost time: 0.001000s.
quick SortV2 cost time: 0.001000s.进程已结束,退出代码为 0

大概先写这些吧,今天的博客就先写到这,谢谢您的观看。

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

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

相关文章

MySQL架构和储存引擎

MySQL服务器整体架构如下&#xff1a;连接层&#xff1a;连接层的作用是处理客户端的连接&#xff0c;如何管理连接的。网络端口和连接管理线程&#xff1a;网络端口&#xff1a;一台服务器可以连接网络上多个端口的客户连接&#xff0c;只需要开放多个端口&#xff0c;只需要在…

详解flink java基础(一)

文章目录1.流式处理flink介绍2.Flink SQL介绍3. Flink Runtime4.使用flink集成kafka5.使用Flink SQL进行有状态流处理6.Event time & Watermarks7. flink Checkpoints & recovery1.流式处理flink介绍 实时服务依赖流式处理: flink优点: 高性能丰富的特性&#xff1a…

Day119 持续集成docker+jenkins

Day119 dockerjenkins 1.Dockerfile Dockerfile 可以用于项目部署。通过编写 Dockerfile&#xff0c;可以将整个项目及其所需的依赖项打包到一个 Docker 镜像中&#xff0c;然后在任何支持 Docker 的环境中部署和运行该镜像 Dockerfile 是用于构建 Docker 镜像的文本文件。它包…

Vue3+Vite MPA多页面应用开发完整指南 – 从零搭建到部署优化

什么是 MPA 多页面应用 MPA&#xff08;Multi-Page Application&#xff09;是由多个独立的 HTML 页面组成的应用&#xff0c;每个页面都有独立的入口文件。与 SPA 不同&#xff0c;MPA 的每个页面都是独立的&#xff0c;页面间通过链接跳转&#xff0c;适合大型项目或需要 SE…

【企业级架构】企业战略到技术落地的全流程【第一篇】

目录 一、人生蓝图与企业罗盘&#xff1a;战略视角下的成长架构 1. 大学毕业迷茫期 → 企业未制定战略前&#xff1a;无方向、无目标​ 2. 制定职业规划 → 企业战略制定&#xff1a;明确 “去哪” 和 “分几步走”​ 3. 盘点自身能力差距 → 业务架构梳理&#xff1a;搞清…

(二) Python + 地球信息科学与技术 = 经典案例分析

目录 四、农业精准施肥与产量预测&#xff08;植被指数 机器学习&#xff09; 五、公共场所踩踏事故预警系统&#xff08;时空大数据 Web 开发&#xff09; 六、森林火灾智能识别与救援路径规划&#xff08;遥感 路径优化&#xff09; 七、海岸线侵蚀动态监测与防护&…

从需求到部署全套方案:餐饮服务许可证数据可视化分析系统的大数据技术实战

&#x1f393; 作者&#xff1a;计算机毕设小月哥 | 软件开发专家 &#x1f5a5;️ 简介&#xff1a;8年计算机软件程序开发经验。精通Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等技术栈。 &#x1f6e0;️ 专业服务 &#x1f6e0;️ 需求定制化开发源码提…

Qt 关于QString和std::string数据截断的问题- 遇到\0或者0x00如何处理?

Qt 关于QString和std::string数据截断的问题- 遇到\0或者0x00如何处理&#xff1f;引言一、解决方案二、使用QByteArray注意事项引言 在Qt开发中&#xff0c;使用QString或std::string获取、发送字符串时&#xff0c;遇到\0(空字符)或者0x00(十六进制表示)可能导致数据截断&am…

Spring Cloud LoadBalancer 最佳实践

Ribbon 曾经是 Spring Cloud 家族默认的客户端负载均衡工具&#xff0c;而 Spring Cloud LoadBalancer (SCLB) 是官方替换 Ribbon 的新实现。表面上它们都解决 “服务调用时选哪个实例” 的问题&#xff0c;但在理念、架构和生态上差异不小。一、Ribbon vs SCLB1. 定位和生态…

【STM32】SPI 与 Flash 笔记

1️⃣ SPI&#xff08;Serial Peripheral Interface&#xff0c;串行外设接口&#xff09;英文解释&#xff1a; Serial&#xff1a;串行Peripheral&#xff1a;外设Interface&#xff1a;接口用途&#xff1a;MCU 与外部设备&#xff08;Flash、传感器等&#xff09;高速数据通…

抽象工厂设计模式 Abstract Factory

抽象工厂抽象工厂设计模式是一种创建模式&#xff0c;它提供了一个用于创建相关或从属对象族的接口&#xff0c;而无需指定其具体类。 它在以下情况下特别有用&#xff1a; 您需要创建必须一起使用并且是一致系列的一部分的对象&#xff08;例如&#xff0c;按钮、复选框和菜单…

WSL 下的虚拟网卡配置

第一部分&#xff1a;Windows 虚拟网卡创建指南 1. 原理 在 Windows 里&#xff0c;“虚拟网卡”本质是由网络驱动在系统网络栈中创建的一个 软件网卡接口。它的作用和物理网卡类似&#xff0c;只不过不直接连接到物理硬件&#xff0c;而是通过内核网络驱动与宿主机网络进行交换…

Dify web前端源码本地部署详细教程

目录 1. 先启动API 2. 启动worker服务 3. 启动web 4. 访问登陆地址 在前面的文章中&#xff0c;Dify源码部署&#xff0c;搭建二次开发环境&#xff08;一&#xff09; 已经记录了如何在本地启动API、work、中间件。在本篇文章中&#xff0c;将概述如何启动dify web源码项…

CVPR 2025|英伟达联合牛津大学提出面向3D医学成像的统一分割基础模型

在 2D 自然图像和视频的交互式分割领域&#xff0c;基础模型已引发广泛关注&#xff0c;这也促使人们开始构建用于医学成像的 3D 基础模型。然而&#xff0c;3D 医学成像存在的领域差异以及临床应用场景&#xff0c;要求开发一种有别于现有 2D 解决方案的专用模型。具体而言&am…

解决“Win7共享文件夹其他电脑网络无法发现共享电脑名称”的问题

要让运行 Windows 7 的电脑被局域网中其他设备&#xff08;包括另一台电脑、手机、NAS 等&#xff09;“发现”&#xff0c;必须同时满足三个条件&#xff1a; 网络发现功能已启用&#xff1b;对应的后台服务已启动&#xff1b;防火墙规则放行。 下面给出最简、最稳妥的 3 步设…

Python pyzmq 库详解:从入门到高性能分布式通信

一、前言 在现代软件开发中&#xff0c;进程间通信&#xff08;IPC&#xff09;与分布式系统通信已经成为基础能力。无论是构建一个微服务架构的后端&#xff0c;还是实现大规模并行计算任务&#xff0c;如何让不同的进程或节点之间高效地传递消息&#xff0c;都是核心问题。 传…

CentOS 7更换国内镜像源

第一步&#xff1a;检查系统版本 在修改任何配置之前&#xff0c;先确定你的 CentOS 版本&#xff0c;因为不同版本的镜像源配置文件不同。 cat /etc/redhat-release这个命令会显示你的 CentOS 版本信息&#xff0c;例如 CentOS Linux release 7.9.2009 (Core)。从你的错误日志…

详解 doclayout_yolo:Python 文档布局检测

目录一、doclayout_yolo 核心功能二、安装方法1. 直接安装2. 通过 PDF-Extract-Kit 安装三、使用示例1. 快速体验&#xff08;HuggingFace Demo&#xff09;2. 本地推理代码3. 批量处理四、技术亮点五、应用场景六、其他说明1.相关资源2. 注意事项doclayout_yolo 是一个基于 Y…

猫头虎AI分享|一款Coze、Dify类开源AI应用超级智能体Agent快速构建工具:FastbuildAI

猫头虎AI分享&#xff5c;一款 Coze、Dify 类开源 AI 应用超级智能体快速构建工具&#xff1a;FastbuildAI 区别在于它的易用度和商业闭环功能 摘要&#xff1a;FastbuildAI 是一个开源的 AI 应用“快速构建 商业化闭环”工具。它让个人开发者与小团队用 可视化 零代码 的方…

GitLab 安全漏洞 CVE-2025-6186 解决方案

本分分享极狐GitLab 补丁版本 18.2.2, 18.1.4, 18.0.6 的详细内容。这几个版本包含重要的缺陷和安全修复代码&#xff0c;我们强烈建议所有私有化部署用户应该立即升级到上述的某一个版本。对于极狐GitLab SaaS&#xff0c;技术团队已经进行了升级&#xff0c;无需用户采取任何…