常见排序算法详解
一、插入排序插入排序的核心思想是把一个数据插入已经排好序的一组数据中的正确位置。当运用插入排序来排序一组数据时先把第一个数看作有序把第二个数插入正确位置再把前两个数看作有序把第三个数插入正确位置以此类推直到所有数据按照正确顺序排列。以升序为例假设[0-end]有序。每一趟排序需要将end1位置的数据与前面的数据依次比较。如果小于当前位置的数据需要将当前位置的数据向前移动一位否则就将这个数据插入到当前数据的后面。将移动一位的操作放在while循环外面来写有这样的考虑如果end1位置的数据是最小的那么最后end会减小至-1导致跳出循环。所以把移动数据的逻辑写在循环外面就不用写两次了。void InsertSort(int* a, int n) { for (int i 0; i n - 1; i) { int end i;//认为0-end有序end1位置的值插入[0,end]保持有序 int tmp a[end 1]; while (end 0) { if (a[end] tmp) { a[end 1] a[end]; --end; } else { break; } } a[end 1] tmp; } }二、希尔排序希尔排序是对插入排序的优化。它的核心思想是通过预排序使数组接近有序然后再进行插入排序。我们将整组数据分为gap组每一组中每gap个数据取一个数据然后将每一组插入排序再整体插入排序。我们首先实现每一组插入排序的代码void ShellSort(int* a, int n) { int gap 3; for (int j 0; j gap; j) { for (size_t i j; i n - gap; i gap) { int end i; int tmp a[end gap]; while (end 0) { if (tmp a[end]) { a[end gap] a[end]; end - gap; } else { break; } } a[end gap] tmp; } } }这是一组一组排用了三层循环来实现。我们也可以多组一起排用两个循环就可以实现看上去简洁一点但是效率并没有改变。void ShellSort(int* a, int n) { int gap 3; for (size_t i 0; i n - gap; i) { int end i; int tmp a[end gap]; while (end 0) { if (tmp a[end]) { a[end gap] a[end]; end - gap; } else { break; } } a[end gap] tmp; } }预排序过程gap越大大的数据越快调到后面小的数据越快跳到前面但预排序后的结果越不接近有序。gap越小跳得越慢但预排序后的结果越接近有序。一般会走多组预排序每次预排序gap的值发生变化。下一次预排序的gap值与这一次预排序的gap值满足gap gap/3 1比较合适可以保证最后一次gap1也就是最后一次进行的是整体的插入排序。void ShellSort(int* a, int n) { int gap n; while (gap 1) { gap gap/3 1; for (size_t i 0; i n - gap; i) { int end i; int tmp a[end gap]; while (end 0) { if (tmp a[end]) { a[end gap] a[end]; end - gap; } else { break; } } a[end gap] tmp; } } }希尔排序的时间复杂度约为O(n^1.3)其证明涉及一些复杂的数学知识。三、选择排序选择排序的核心思想是遍历一次所有数据选出最大和最小的数据放在两端再遍历一次剩下的数据选出其中最大和最小的数据放在第二和倒数第二的位置......直到数组有序。void SelectSort(int* a, int n) { int begin 0, end n-1; while (begin end) { int mini begin, maxi end; for (size_t i begin; i end; i) { if (a[i] a[mini]) mini i; if (a[i] a[maxi]) maxi i; } Swap(a[begin], a[mini]); if (maxi begin) maxi mini; Swap(a[end], a[maxi]); begin; end--; } }这里有一个需要注意的地方如果maxi恰好与begin重合那么交换begin与mini位置的数据后最大的数据就移动到mini的位置了。所以要写一个判断语句来专门处理这种情况。四、快速排序4.1 hoare版快速排序快速排序的核心思想是将数组中的一个数据作为“关键值”两个指针分别从数组的左端和右端开始遍历数组遍历的过程中将所有比“关键值”小的数据放到左边把所有比“关键值”大的数据都放到右边当两个指针相遇时将相遇位置的数据与“关键值”互换这样关键值就把数组分成了两半左边数据都小于关键值右边数据都大于关键值。然后在分出的两个数组中再进行一次这样的遍历......整个过程类似二叉树的递归遍历终止条件是分出的数组中只剩1个值或没有值递归遍历结束时整个数组就有序了。void QuickSort(int* a, int left, int right) { if (left right) return; int keyi left; int begin left, end right; while (begin end) { //右边找小的 while (begin end a[end] a[keyi]) { --end; } //左边找大的 while (begin end a[begin] a[keyi]) { begin; } Swap(a[begin], a[end]); } Swap(a[keyi], a[begin]); keyi begin; QuickSort(a, left, keyi - 1); QuickSort(a, keyi 1, right); }此处有一个可能的疑点为什么begin和end相遇的位置数据一定比关键值小分两种情况分析如果是begin遇到了endend先走停下的条件是遇到比关键值小的数据所以end停下的位置的数据一定比关键值小。begin没有找到比关键值更大的数据所以遇到end停下了。如果是end遇到了beginend先走找比关键值更小的数据没有找到直接和begin相遇了。此时begin停留的位置是上一轮交换过后的位置上一轮交换把比关键值小的数据移动到begin的位置了。不管是那种情况相遇位置的数据一定比关键值小。4.2 快速排序算法的优化我们已经写出了一个简单版的快速排序算法。然而这个算法现在有很大的缺陷。首先这个算法面对已经有序的数组时每趟排序只能解决一个数据此时它就退化成一个O(N^2)的算法递归深度太深会有栈溢出的风险。为了避免有序情况下的效率退化我们可以对算法进行一些优化。我们可以不要总是选择数组的第一个数据作为关键值可以采取“三数取中”的方法在最前面、最后面和最中间这三个数据中选择一个中间的数据作为关键值。此时我们需要实现一个三数取中的函数。int GetMidi(int* a, int left, int right) { int midi (left right) / 2; if (a[left] a[midi]) { if (a[midi] a[right]) { return midi; } else if (a[left] a[right]) { return right; } else { return left; } } else { if (a[midi] a[right]) { return midi; } else if (a[right] a[left]) { return right; } else { return left; } } }然后修改一下我们写好的快速排序算法void QuickSort(int* a, int left, int right) { if (left right) return; int midi GetMidi(a, left, right); Swap(a[left], a[midi]); int keyi left; int begin left, end right; while (begin end) { //右边找小的 while (begin end a[end] a[keyi]) { --end; } //左边找大的 while (begin end a[begin] a[keyi]) { begin; } Swap(a[begin], a[end]); } Swap(a[keyi], a[begin]); keyi begin; QuickSort(a, left, keyi - 1); QuickSort(a, keyi 1, right); }此时仍然有优化的空间。现在的快速排序算法用的是递归遍历递归到接近结束的时候假设一个小区间只有五个数那么仍然任然需要用6次递归使之有序非常浪费。刚才提快速排序的过程类似二叉树的递归遍历而二叉树仅最后一层的递归次数就占整体的50%最后三层的递归次数占整体的80%多。所以我们可以通过“小区间优化”在分出的区间足够小时改用插入排序就可以大幅减少递归的次数。void QuickSort(int* a, int left, int right) { if ((right - left 1) 10) { InsertSort(a left, right - left 1); //注意此处调用插入排序第一个参数应为a left } else { if (left right) return; int midi GetMidi(a, left, right); Swap(a[left], a[midi]); int keyi left; int begin left, end right; while (begin end) { //右边找小的 while (begin end a[end] a[keyi]) { --end; } //左边找大的 while (begin end a[begin] a[keyi]) { begin; } Swap(a[begin], a[end]); } Swap(a[keyi], a[begin]); keyi begin; QuickSort(a, left, keyi - 1); QuickSort(a, keyi 1, right); } }4.3 双指针版快速排序hoare版本的单趟排序不太好理解而双指针法好理解写起来也更简单效率并没有区别。用双指针法来实现快速排序的单趟排序就是定义两个指针prev和cur让prev从左端开始走cur从左端数的第二个位置开始走cur向前移动寻找比关键值小的数据没有找到就继续向前移动找到了就让prev然后交换prev和cur位置的数据一直到cur走到最右端为止。然后交换prev和最左端位置的数据就完成了单趟排序。把单趟排序的过程拎出来就是这样的int PartSort2(int* a, int left, int right) { //三数取中 int midi GetMidi(a, left, right); Swap(a[left], a[midi]); int keyi left; int prev left; int cur prev 1; while (cur right) { if (a[cur] a[keyi] prev ! cur) { Swap(a[prev], a[cur]); } cur; } Swap(a[prev], a[left]); return prev; }4.4 非递归版快速排序快速排序中每个递归调用的栈帧实际上主要用来记录区间。递归有栈溢出风险我们可以改用栈这种数据结构来存储区间的值实现非递归版本的快速排序。每次从栈顶取出一个区间单趟排序后将分出来的右、左区间压栈一直重复到栈为空为止就完成了排序。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 PartSort2(a, begin, end); if (end keyi 1) { STPush(st, end); STPush(st, keyi 1); } if (keyi - 1 begin) { STPush(st, keyi - 1); STPush(st, begin); } } STDestroy(st); }4.5 三路划分版快速排序当数据中有大量重复数据时快速排序的性能会急剧下降。为了解决这个问题可以用三路划分的思路将数组分为严格小于关键值、等于关键值和大于关键值三部分。接下来只需要递归遍历严格小于和严格大于关键值的部分即可。具体的实现思路是用三个指针left从左开始移动right从右开始移动cur从左开始遍历数组。cur所在位置的值比关键值小时交换left和cur位置的值然后让left向右移动一位让cur向后移动一位如果cur所在位置的值等于关键值不进行交换操作直接让cur向后移动如果cur所在位置的值大于关键值交换cur和right位置的值让right向左移动一位。此时cur不必移动因为不确定新换到cur位置的值与关键值的大小关系需要重新进入判断语句进行判断。void QuickSort2(int* a, int begin, int end) { if (begin end) { return; } int key a[begin]; int left begin; int right end; int cur begin; while (cur right) { if (a[cur] key) { cur; } else if (a[cur] key) { Swap(a[cur], a[left]); left; cur; } else { Swap(a[cur], a[right]); right--; } } QuickSort2(a, begin, left - 1); QuickSort2(a, right 1, end); }五、归并排序5.1 递归版归并排序我们已经做过归并两个有序链表的问题当时我们用了两个指针分别从两个有序链表的第一个节点开始移动将两个指针指向的两个数据中小的那个插入新链表直到其中一个链表中的所有数据全部插入新链表中再把另一个链表中剩下的所有节点插入到新链表中。假设一个数组由两个已经有序的数组拼接而成我们就可以用类似的思路进行排序。然而数组是无序的。此时我们就可以用递归的方法将无序的数组继续拆成两个数组直到拆出的数组中只有一个数据就可以视为有序的了。每次拆分我们以 mid 左端下标右端下标/2 作为分界点将数组拆成[begin,mid] 和[mid 1, end]两部分。递归的终止条件是数组只有一个元素也就是左端点等于右端点。void _MergeSort(int* a, int* tmp, int begin, int end) { if (begin end) { return; } int mid (begin end) / 2; //[begin, mid][mid 1,end] _MergeSort(a, tmp, begin, mid); _MergeSort(a, tmp, mid 1, end); //归并 int begin1 begin, end1 mid; int begin2 mid 1, end2 end; int i begin; while (begin1end1 begin2end2) { if (a[begin1] a[begin2]) { tmp[i] a[begin1]; } else { tmp[i] a[begin2]; } } while (begin2 end2) { tmp[i] a[begin2]; } while (begin1 end1) { tmp[i] a[begin1]; } memcpy(abegin, tmpbegin, (end - begin 1) * sizeof(int)); } void MergeSort(int* a, int n) { int* tmp (int*)malloc(sizeof(int) * n); if (tmp NULL) { perror(malloc failed); return; } _MergeSort(a, tmp, 0, n - 1); free(tmp); tmp NULL; }归并的时间复杂度是O(N*logN)空间复杂度是O(N)。5.2 非递归版归并排序可以像快速排序一样用栈来模拟递归也可以直接用循环先让数据两个两个归并再四个四个归并再八个八个归并直到所有数据有序为止。我们将每次归并的数据个数设为gap。gap从1开始每次乘2。归并的逻辑和刚才一样只需要注意每个数组的起末位置不要写错即可。void MergeSortNonR(int* a, int n) { int* tmp (int*)malloc(sizeof(int) * n); if (tmp NULL) { perror(malloc failed); return; } int gap 1; while (gap n) { for (int i 0; i n; i gap * 2) { int cur i; int begin1 i, end1 i gap - 1; int begin2 i gap, end2 i 2 * gap - 1; while (begin1 end1 begin2 end2) { if (a[begin1] a[begin2]) { tmp[cur] a[begin1]; } else { tmp[cur] a[begin2]; } } while (begin2 end2) { tmp[cur] a[begin2]; } while (begin1 end1) { tmp[cur] a[begin1]; } memcpy(a i, tmp i, gap * 2 * sizeof(int)); } gap gap * 2; } free(tmp); tmp NULL; }此时我们还要解决一个越界的问题如果数组中数据个数不是2的幂次就会越界访问。越界分成三种情况1、只有end2越界2、begin2和end2越界3、end1、begin2、end2都越界。由于begin1和end2之间的数据一定是有序的begin2和end2之间的数据也一定是有序的所以上述的第2、3种情况都不需要处理而第一种情况只需要调整end2的值为n-1即可。void MergeSortNonR(int* a, int n) { int* tmp (int*)malloc(sizeof(int) * n); if (tmp NULL) { perror(malloc failed); return; } int gap 1; while (gap n) { for (int i 0; i n; i gap * 2) { int cur i; int begin1 i, end1 i gap - 1; int begin2 i gap, end2 i 2 * gap - 1; if (begin2 n) { break; } else if (end1 n) { end2 n - 1; } while (begin1 end1 begin2 end2) { if (a[begin1] a[begin2]) { tmp[cur] a[begin1]; } else { tmp[cur] a[begin2]; } } while (begin2 end2) { tmp[cur] a[begin2]; } while (begin1 end1) { tmp[cur] a[begin1]; } memcpy(a i, tmp i, gap * 2 * sizeof(int)); } gap gap * 2; } free(tmp); tmp NULL; }5.3 归并排序实现外排序当要排序的数据非常多而内存有限时就可以采用外排序。比如我们现在创建一个有一千万个随机数的数据文件void CreateData() { int n 10000000; srand(time(0)); const char* file data.txt; FILE* fin fopen(file, w); if (fin NULL) { perror(fopen failed); return; } for (int i 0; i n; i) { int x rand() 1; fprintf(fin, %d\n, x); } fclose(fin); }我们可以用归并排序的思路来实现外排序。我们可以先从从文件中读取100000个数用C语言库中的qsort排序后放入file1文件中。再读取100000个数排序后放入file2文件中。用归并排序将这200000个数据归并到mfile中。然后删除file1和file2将mfile重命名为file1然后再从数据文件中读取100000个数据放入新的file2中再次进行归并......直到所有数据都被排序此时的排序结果就存在file1中。void FileSort() { CreateData(); const char* file data.txt; const char* file1 file1.txt; const char* file2 file2.txt; const char* mfile mfile.txt; FILE* fout fopen(file, r); if (fout NULL) { perror(fopen failed); return; } int m 1000000; ReadNDataSortToFile(fout, m, file1); ReadNDataSortToFile(fout, m, file2); while (true) { MergeFile(file1, file2, mfile); remove(file1); remove(file2); rename(mfile, file1); int n 0; n ReadNDataSortToFile(fout, m, file2); if (n 0) break; if (n 100) { int x 0; } } }整体的框架有了。接下来我们需要实现ReadNDataSortToFile和MergeFile的具体逻辑。先看ReadNDataSortToFile传数据文件的句柄作为参数将数据文件中的100000个数据读取后放到malloc开辟的数组中用qsort进行排序需要自己写比较函数然后放入指定文件中。因为要处理数据个数不是100000的整数倍的情况所以用变量j记录成功读取的数据个数排序时排j个数据。如果刚开始就读到了EOF说明数据文件已经读完了直接返回0。外部得到返回值为0时也终止循环。int compare(const void* a, const void* b) { return (*(int*)a - *(int*)b); } int ReadNDataSortToFile(FILE* fout, int n, const char* file1) { int x 0; int* a malloc(sizeof(int) * n); if (a NULL) { perror(malloc failed); return; } int j 0; for (int i 0; i n; i) { if (fscanf(fout, %d, x) EOF) break; a[j] x; } if (j 0) { free(a); return 0; } qsort(a, j, sizeof(int), compare); FILE* fin fopen(file1, w); if (fin NULL) { perror(fopen failed); return; } for (int i 0; i j; i) { fprintf(fin, %d\n, a[i]); } fclose(fin); free(a); return j; }MergeFile的逻辑就是归并排序。void MergeFile(const char* file1, const char* file2, const char* mfile) { FILE* fout1 fopen(file1, r); if (fout1 NULL) { perror(fopen failed); return; } FILE* fout2 fopen(file2, r); if (fout2 NULL) { perror(fopen failed); return; } FILE* mfin fopen(mfile, w); if (mfin NULL) { perror(fopen failed); return; } int x1 0; int x2 0; int ret1 fscanf(fout1, %d, x1); int ret2 fscanf(fout2, %d, x2); while (ret1 ! EOF ret2 ! EOF) { if (x1 x2) { fprintf(mfin, %d\n, x1); ret1 fscanf(fout1, %d, x1); } else { fprintf(mfin, %d\n, x2); ret2 fscanf(fout2, %d, x2); } } while (ret1 ! EOF) { fprintf(mfin, %d\n, x1); ret1 fscanf(fout1, %d, x1); } while (ret2 ! EOF) { fprintf(mfin, %d\n, x2); ret2 fscanf(fout2, %d, x2); } fclose(fout1); fclose(fout2); fclose(mfin); }六、计数排序计数排序是一种非比较排序它利用了数组下标天然有序的特点进行排序。它的核心思想是进行一次遍历找出数组中最大和最小的数进而算出所有数据的范围然后开辟一个大小恰好等于这个范围的count数组这样所有的数据都和count数组的下标有了映射关系。再次遍历数组每读取一个数据就让count数组中对应下标位置的数据1。最后遍历count数组每个下标位置的数据是几就往原来数组中放入几个对应数据。void CountSort(int* a, int n) { int min a[0]; int max a[0]; for (int i 0; i n; i) { if (a[i] min) { min a[i]; } if (a[i] max) { max a[i]; } } int range max - min 1; int* count (int*)calloc(range, sizeof(int)); if (count NULL) { perror(calloc failed); return; } //统计次数 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; } } }计数排序只适用于整数的排序适合处理范围比较集中的数据的排序。时间复杂度是O(Nrange)。七、排序算法的性能比较7.1 稳定性稳定性指的是排序后相同的值相对顺序不变的性能。在结构体排序中这个性能就很重要。直接插入排序稳定不改变相同值的相对位置希尔排序不稳定相同的数据在预排序时可能被分到不同的组里选择排序不稳定排序过程中有交换数据位置的操作堆排序不稳定排序过程中有交换数据位置的操作冒泡排序稳定不改变相同值的相对位置快速排序不稳定会交换数据位置归并排序稳定不改变相同值的相对位置7.2 时间复杂度和空间复杂度算法时间复杂度空间复杂度直接插入排序O(N^2)O(1)希尔排序O(N^1.3)O(1)选择排序O(N^2)O(1)堆排序O(N*logN)O(1)冒泡排序O(N^2)O(1)快速排序O(N*logN)O(logN)归并排序O(N*logN)O(N)END

相关新闻

网络安全人才缺口327万!应急响应工程师薪资涨幅领跑IT行业,你上车了吗

网络安全人才缺口327万!应急响应工程师薪资涨幅领跑IT行业,你上车了吗

327万缺口,安全行业的人才荒2026年,中国网络安全行业面临着一个令人既兴奋又焦虑的数字:327万。这是教育部、工业和信息化部联合发布的《网络安全人才发展报告》中披露的最新人才缺口数字。与此同时,全国高校每年网络安全相关专业…

2026/7/5 14:12:18 阅读更多 →
【信息科学与工程学】【制造工程】第八十二篇 半导体芯片集成电路集成制造01

【信息科学与工程学】【制造工程】第八十二篇 半导体芯片集成电路集成制造01

半导体芯片集成制造 编号 类型 领域 子领域 / 内容 问题 步骤拆解 参数列表及参数的数值范围及数值分析及常量/常数 1 物理制造 光刻 Lithography 光学成像 + OPC 掩模图案经投影物镜后在光刻胶上形成畸变,如何预补偿? 电磁(亥姆霍兹/波动光学)+ 傅里叶光学 +…

2026/7/5 14:12:18 阅读更多 →
Windows系统优化新选择:Winhance中文版如何让电脑重获新生?

Windows系统优化新选择:Winhance中文版如何让电脑重获新生?

Windows系统优化新选择:Winhance中文版如何让电脑重获新生? 【免费下载链接】Winhance-zh_CN A Chinese version of Winhance. C# application designed to optimize and customize your Windows experience. 项目地址: https://gitcode.com/gh_mirror…

2026/7/5 14:08:17 阅读更多 →

最新新闻

告别格式障碍:SketchUp STL插件让你的3D设计轻松走进现实世界

告别格式障碍:SketchUp STL插件让你的3D设计轻松走进现实世界

告别格式障碍:SketchUp STL插件让你的3D设计轻松走进现实世界 【免费下载链接】sketchup-stl A SketchUp Ruby Extension that adds STL (STereoLithography) file format import and export. 项目地址: https://gitcode.com/gh_mirrors/sk/sketchup-stl 你是…

2026/7/5 14:58:26 阅读更多 →
4-20mA电流环检测与PIC单片机信号处理方案

4-20mA电流环检测与PIC单片机信号处理方案

1. 4-20mA电流环基础与行业应用工业现场最可靠的信号传输方式莫过于4-20mA电流环,这个看似简单的标准已经统治过程控制领域半个多世纪。电流信号相比电压信号具有显著优势:抗干扰能力强,可长距离传输(理论可达数公里)&…

2026/7/5 14:56:26 阅读更多 →
6. 【C语言】格式化输入输出:和程序说说话

6. 【C语言】格式化输入输出:和程序说说话

前面五篇文章,我们熟悉了变量、常量、数据类型,但程序还像个闷葫芦——要么沉默不语,要么只喊一句固定的“Hello, World”。要让程序真正和人互动,就得学会两样本事: 输出:把数据展示给用户看(…

2026/7/5 14:56:25 阅读更多 →
MWC26 上海开幕,人形机器人点球大战、Agentic AI 成主角——智能体从概念走向赛场

MWC26 上海开幕,人形机器人点球大战、Agentic AI 成主角——智能体从概念走向赛场

MWC26 上海开幕,人形机器人点球大战、Agentic AI 成主角——智能体从概念走向赛场 6 月 24 日,MWC26 上海世界移动通信大会开幕。今年最大的看点不是 5G,不是 6G,而是人工智能。 人形机器人点球大战 MWC26 上海首次举办了"人…

2026/7/5 14:52:25 阅读更多 →
2026 AI 开发者生存指南(10):AI 开发者职业发展与学习路线图——从入门到精通

2026 AI 开发者生存指南(10):AI 开发者职业发展与学习路线图——从入门到精通

AI 开发者职业发展与学习路线图 2026 版:从入门到精通怎么走? 2026 年的 AI 行业,招聘需求在变、技能要求在变、薪资结构在变。不管是刚入行还是想转型,都需要一张清晰的路线图。 这篇文章整理 AI 开发者的职业发展路径和学习方向…

2026/7/5 14:52:25 阅读更多 →
Unreal Engine 5体积渲染架构深度解析:OpenVDB与NanoVDB集成技术实现

Unreal Engine 5体积渲染架构深度解析:OpenVDB与NanoVDB集成技术实现

Unreal Engine 5体积渲染架构深度解析:OpenVDB与NanoVDB集成技术实现 【免费下载链接】unreal-vdb This repo is a non-official Unreal plugin that can read OpenVDB and NanoVDB files in Unreal. 项目地址: https://gitcode.com/gh_mirrors/un/unreal-vdb …

2026/7/5 14:52:25 阅读更多 →

日新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/5 0:03:34 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/5 0:03:34 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/5 0:07:38 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/5 0:03:34 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/5 0:03:34 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/5 0:07:38 阅读更多 →

月新闻