news 2026/10/1 10:54:46

排序算法体系化解析:从原理到手写实现与面试避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
排序算法体系化解析:从原理到手写实现与面试避坑

面试官把一道手写快排的题拍在我面前的时候,我才发现平时背得滚瓜烂熟的“八大排序”,真到白纸上写的时候,边界条件还是会漏。后来系统梳理了一遍排序算法,才发现这东西不只是面试敲门砖,更像一把理解算法与数据结构之间关系的钥匙:同样是排序,为什么有的O(n²),有的O(n log n),有的甚至能到O(n)?同样是O(n log n),为什么快排实际表现比堆排好?这些问题的答案,藏着递归、分治、堆、哈希这些基础数据结构最核心的设计思想。

这篇内容不打算从教科书的角度把排序算法逐条念一遍,而是按我自己的理解,把排序算法的体系、原理、实现和避坑点串起来。无论你是正在准备算法工程师面试、复习408数据结构,还是被期末实验报告卡住了,都可以直接拿走参考。

1. 排序算法的体系化认知与选型思路

1.1 为什么排序算法是算法与数据结构的基石

很多人觉得排序算法无非就是那几段代码,背熟就完事了。但实际不是这样。排序是算法里少有的、能同时覆盖数组遍历、交换、递归、分治、堆、哈希映射、桶思想等多种基础能力的综合性问题。学透排序,基本等于把算法与数据结构里最核心的那几个抽象模型都过了一遍。

我举几个例子。归并排序的核心是分治思想,把数组对半拆,拆到不能再拆,再两两合并。合并两个有序数组这个操作本身就是双指针技巧的经典应用。快排的核心是partition分区操作,而partition操作又可以直接用来解决“无序数组找第K大”这类TopK问题,根本不需要完整排序。堆排序直接牵出完全二叉树、堆结构和优先队列,这个数据结构在贪心算法、Dijkstra最短路径里都是主角。计数排序和基数排序则和桶思想、哈希映射的概念是同源的。

从实用角度说,排序算法在真实项目里的出场率也极高。数据库里的ORDER BY、搜索引擎的结果排序、推荐系统的候选重排、数据分析里的分组聚合,底层都在用排序。很多新人觉得“排序算法用库函数不就行了吗”,确实,工程上直接调sort()没问题,但当你需要定制排序规则、处理超大文件的外部排序、或者优化某个关键链路的性能时,不理解排序原理,连问题在哪都找不到。

1.2 排序算法的分类方法与选型判断

要把排序算法记成体系,先学会分类。我习惯按四个维度来切分。

按时间复杂度分,大致三层:O(n²)级别的冒泡、选择、插入;O(n log n)级别的希尔、归并、快排、堆排;O(n)级别的计数、桶排、基数。这里有个容易误解的点:O(n)的排序不是无敌的,它们都有严格的适用条件,需要借助数据本身的特征,后面细说。

按“是不是基于比较”分,冒泡、选择、插入、希尔、归并、快排、堆排都是比较排序,它们的理论下界就是O(n log n)。计数、桶、基数属于非比较排序,通过数值本身的映射关系直接确定位置,才能突破O(n log n)的下界。

按稳定性分,稳定排序能保持相等元素的原始相对顺序,冒泡、插入、归并、计数、桶、基数稳定,选择、希尔、快排、堆排不稳定。这个属性在实际应用里常常被忽视,比如多关键字排序时,稳定性直接决定了一趟排序能否叠加在另一趟排序之上。

按空间占用分,原地排序(O(1)额外空间)的有冒泡、选择、插入、希尔、快排(递归栈不算)、堆排;归并排序需要O(n)辅助空间;计数排序需要O(k)的计数数组空间。

选型的时候,核心看四件事:数据规模多大、数据是否近乎有序、对稳定性有没有要求、内存够不够。我的经验是这样的:数据量小或者基本有序,插入排序是最优解,甚至比快排还快;数据量大且不确定分布,工程场景无脑用快排或优化过的混合排序(比如C++里的introsort);内存不够且要排序大文件,归并排序是外部排序的基础;只要TopK不要全排序,直接用堆;数值范围小且是整数,计数排序效率高得惊人。

2. 八大排序算法的核心原理与细节拆解

2.1 O(n²)级别的排序:冒泡、选择、插入——直观但慢

先说冒泡排序。它的思路最直白:从头到尾遍历,相邻两个元素两两比较,大的往后挪,一趟下来最大的数就冒到了最后。第二趟再从头开始,把第二大的数送到倒数第二个位置。反复n-1趟,整个数组有序。我写这段代码的时候有个小习惯,内层循环的边界是n - i - 1,因为每一趟结束后,末尾i个元素已经就位,没必要再比较。还有个经典的优化办法:加一个swapped标志位,如果某趟全程没有发生交换,说明数组已经有序,直接提前终止。这个优化在数据近乎有序的时候能大幅减少无效遍历。

选择排序的思路上了个层次:把数组看作两部分,左边是已排序区,右边是未排序区。每趟在未排序区里找最小值,记录它的下标,一趟结束后再交换到已排序区的末尾。问题就在这:它每趟只做一次交换,但比较次数完全没减少,复杂度依然是O(n²)。不过它的交换次数是所有O(n²)排序里最少的,只有n-1次,适合交换成本极高但比较成本低的场景,这在实际中很少遇到,但面试时提一句能显得你想得更全面。

插入排序是我实际用最多的O(n²)排序。它的思路和抓扑克牌一模一样:手里原有牌已经有序,新抓一张牌,从右往左逐个比,找到合适位置插进去。代码实现就是一个外层循环控制待插入元素,一个内层循环做元素后移,最后空出来的位置就是要插入的位置。这个算法对基本有序的数据极度友好,数据越接近有序,内层循环几乎一两次就停了,实际量级可以逼近O(n)。正因为这个特性,很多高质量快排实现会在递归到小规模子数组时,改用插入排序来收尾,而不是一路递归到底。

我建议这三个一起对比学习,它们的共同点是都适合小规模数据,区别在于排序策略:冒泡重交换,选择重比较,插入重移位。面试时如果让你在纸上写一个最简单的排序,我一般推荐插入排序,因为代码短、边界条件少、不容易出错。

2.2 高级比较排序:希尔、归并、快排、堆——效率的进阶

希尔排序是插入排序的升级版。插入排序慢就慢在“每次只能后移一位”,如果最小的元素在最后,要一步一步挪到最前面。希尔排序的思路是:先按大步长分组做插入排序,让元素快速逼近它最终的位置,再逐步缩小步长,最后步长为1,变成一次标准的插入排序。步长序列的选择很关键,常见的有n/2逐次折半,效果更好的有Hibbard序列、Sedgewick序列等。希尔排序的时间复杂度分析比较麻烦,目前已知的最佳步长序列可以把复杂度降到O(n^(4/3))级别,但实际工程用得不算多,主要因为它不稳定,且步长选择对性能影响很大,不如快排稳定。

归并排序是分治思想的典型代表。它的逻辑很清晰:一个数组拆成左右两半,分别排序,最后用双指针合并两个有序数组。拆分可以用递归实现,终止条件是子数组只剩一个元素,天然有序。合并过程需要申请一个临时数组,用两个指针分别指向左右子数组的头部,每次取较小的放入临时数组,最后把临时数组拷回原数组。归并排序有几个关键特点值得记住:它是稳定的,时间复杂度稳定在O(n log n),无论什么数据都是这个量级;它的额外空间是O(n);它特别适合链表排序和外部排序。面试常考的一个点是“用归并排序求逆序对”,在合并阶段,如果左边指针指向的元素比右边大,说明左边剩余的所有元素都能和这个右元素构成逆序对,直接累加计数。

快排是实际工程中最常用的排序算法。它的核心是partition分区操作:选一个基准元素(pivot),把数组分成左右两部分,左边都小于等于基准,右边都大于等于基准,基准落位,然后递归处理左右两部分。快排的时间复杂度理论上平均O(n log n),最坏O(n²),最坏情况发生在每次分区都极端不平衡的场景,比如已经有序的数组搭配固定选最后一个元素做基准。解决方法是三数取中、随机选定基准,或者像introsort那样,递归深度过深时退化成堆排序。我想强调的一点是:partition操作本身比快排排序更常用。比如找无序数组第K小的元素,只要做一次partition,如果基准下标恰好就是K,直接就找到了;如果不是,只递归处理包含K的那一侧,平均复杂度O(n),这在工程里是个高频技巧。

堆排序的关键是理解二叉堆。先说“堆”是什么:一个用数组表示的完全二叉树,大顶堆满足父节点大于等于孩子节点,小顶堆则相反。堆排序分两步:建堆和排序。建堆的时间复杂度是O(n),这很容易被写错成O(n log n),正确理解是:从最后一个非叶子节点开始逐个向下调整,越底层的节点数量越多但对数高度越小,整体累加是O(n)。排序阶段,把堆顶(最大值)和堆尾交换,堆的大小减一,然后重新调整堆顶元素向下沉,反复n-1次。堆排序的优点是原地排序、O(1)额外空间、最坏也是O(n log n),缺点是实际速度通常不如快排,因为堆的下沉调整中数据访问模式对CPU缓存不友好。

2.3 线性时间排序:计数、桶、基数——跳出比较的局限

回到一个根本问题:为什么比较排序的下界是O(n log n)?因为每次比较最多只能获得“大于/小于”两种信息,n个元素的排列有n!种可能,每次比较二分这个空间,比较次数至少是log2(n!),用斯特林公式展开就是O(n log n)。那怎么突破这个下界?答案是放弃比较,利用数据本身的数值特征直接定位。

计数排序的适用条件是:数据是整数,且值域范围不大。它的做法是建一个长度为范围大小的计数数组,遍历原始数据,统计每个值出现多少次,然后按顺序累加回写。比如要排序一堆年龄,范围就是0到100多,一趟O(n)就完了,比快排还快。要注意的是,计数排序是稳定排序,这依赖于“累加计数”这一步:计数数组每个元素先变成前缀和,这样回写数据时,每个值应该放到什么位置就计算出来了,从后往前回写可以保证稳定性。

桶排序的思路是把数据均匀分到若干个桶里,比如0到100的数据均匀分成10个桶,每桶内部范围是10,然后对每个桶分别排序,最后按桶顺序串起来。这里的关键是设计“均匀的映射函数”,数据必须均匀分布,桶排序才能发挥O(n)的威力。如果数据都挤到同一个桶里,桶排序退化成O(n log n),甚至更差。桶排序经常被用在海量数据的近似排序上,比如对百万级浮点数做分桶后,每个桶内部再排序。

基数排序是按位排序的思路:从最低位开始,依次对每一位做一次稳定排序,比如数字先按个位排序,再按十位排序,最后按百位排序,全部结束后所有数字自然有序。这个“按位排序”的每趟排序通常借助计数排序来实现,所以总复杂度是O(d * (n + k)),d是最大数字的位数,k是每位的基数范围。这里稳定的重要性体现得淋漓尽致:如果某趟排序不稳定,之前按低位排好的相对顺序就被破坏掉了。

2.4 排序算法的稳定性与适用场景对比

把主流的几个排序算法放在一张表里横向对比,比单看每个算法要清楚得多。我先说这个表的读法:时间复杂度“平均/最坏”两列要分开看,空间复杂度指的是除原数组外的额外空间,稳定性是面试和考研都爱抠的细节。

排序算法平均时间复杂度最坏时间复杂度空间复杂度稳定性
冒泡排序O(n²)O(n²)O(1)稳定
选择排序O(n²)O(n²)O(1)不稳定
插入排序O(n²)O(n²)O(1)稳定
希尔排序O(n^(3/2))左右取决于步长序列O(1)不稳定
归并排序O(n log n)O(n log n)O(n)稳定
快速排序O(n log n)O(n²)O(log n)(递归栈)不稳定
堆排序O(n log n)O(n log n)O(1)不稳定
计数排序O(n + k)O(n + k)O(k)稳定
桶排序O(n + k)O(n log n)O(n + k)稳定
基数排序O(d (n + k))O(d (n + k))O(n + k)稳定

稳定性这张表整理完,我记得最牢的一句话是:在值相等的情况下,稳定排序保留的是“先来后到”的顺序,不稳定排序会打乱这个顺序。什么场景看重这个属性?最典型的就是多字段排序。比如一个表格先按姓名排序,再按分数排序,如果第二次排序不稳定,同一个分数的人里,姓名的顺序就被打乱了。实际做数据库查询或者Excel多级排序时,要求每级排序都用稳定排序才能保证最终结果符合预期。

选型判断我再说几句。如果面试被问到“给你一个具体的排序场景,该怎么选”,我的回答框架分四步:先看数据规模,小于几千的直接考虑插入排序;再看数据特征,整数且值域小就上计数排序,数据均匀分布就上桶排序;然后看空间限制,内存紧张就不能用归并和计数;最后看稳定性要求,要求稳定就锁死归并或插入。这套框架看起来简单,但能帮你在实际场景里快速做出合理决策,而不是手里拿着快排一把锤子看哪都是钉子。

3. 实操:手写实现、复杂度分析与可视化验证

3.1 复杂度的大O分析:什么时候用O,什么时候用Θ

热词里有一条“计算算法复杂度时什么时候用o什么时候用θ”,这个问题在期末复习和考研里经常出现。简单讲:O是渐进上界,表示算法时间最多不超过某个量级;Ω是渐进下界,表示至少不低于某个量级;Θ是紧确界,上下界是同一个量级。

实际分析复杂度的时候,能用Θ就用Θ,因为它给出的是一个精确的量级描述。比如归并排序的时间复杂度,无论什么输入都是O(n log n),同时也是Ω(n log n),所以准确的写法是Θ(n log n)。快排就很微妙:平均情况是Θ(n log n),但最坏情况是Θ(n²),所以如果你说的是“快排的最坏复杂度”,用Θ(n²)更准确;如果你说的是“快排的复杂度”而不限定输入分布,严格来说只能说O(n²)上界,因为最坏情况确实会达到这个值。

怎么算复杂度?核心是找循环结构和递归结构。循环体执行次数就是复杂度主体,比如两层嵌套循环各跑n次,就是Θ(n²)。递归结构需要用递推式来解,归并排序的递推式是T(n) = 2T(n/2) + O(n),用主定理直接得到T(n) = Θ(n log n)。快排的递推式代入随机分布时,平均结果是Θ(n log n);最坏情况下,每次分区严重不平衡,T(n) = T(n-1) + O(n),累加得到Θ(n²)。

这里我想给你一个从实验角度验证复杂度的小方法:写一个函数,用不同规模的随机数组跑排序,记录耗时,然后在对数坐标轴上画图。如果一条线是直线,斜率接近1,说明复杂度接近线性O(n);斜率接近2,说明是O(n²)。这个方法在写实验报告的时候特别有用,能直观验证理论分析。

3.2 手写排序代码:面试与实验报告的最佳实践

手写排序是算法工程师面试几乎绕不过去的一关。我的建议是:把插入排序、归并排序、快排、堆排序这四类背到条件反射。不是死记,而是理解每行的意图,这样才能应对变种题。

先来一个插入排序的C语言版本,这是最不容易出错的:

void insertion_sort(int arr[], int n) { for (int i = 1; i < n; i++) { int key = arr[i]; int j = i - 1; while (j >= 0 && arr[j] > key) { arr[j + 1] = arr[j]; j--; } arr[j + 1] = key; } }

注意几个细节:key必须先保存,因为后面元素后移会覆盖掉arr[i];j要滑到j < 0才能停;比较条件是arr[j] > key,改成>=会破坏稳定性,相等元素会互换位置。

再看快排最常见的分区写法(Lomuto分区法):

int partition(int arr[], int low, int high) { int pivot = arr[high]; int i = low - 1; for (int j = low; j < high; j++) { if (arr[j] < pivot) { i++; swap(&arr[i], &arr[j]); } } swap(&arr[i + 1], &arr[high]); return i + 1; } void quick_sort(int arr[], int low, int high) { while (low < high) { int pi = partition(arr, low, high); quick_sort(arr, low, pi - 1); low = pi + 1; // 尾递归优化:只递归左半边,右半边用循环 } }

这里有个容易写错的点:递归调用区间。partition返回的pi是基准的最终位置,它已经就位了,递归的两个区间应该是[low, pi-1]和[pi+1, high],绝不能把pi再包含进去,否则会无限递归。上面代码我做了个尾递归优化,把右半边的递归改成了循环,可以减少递归深度,防止数据量大时栈溢出。

堆排序的代码比前两个麻烦些。核心是两个函数:sift_down负责把某个节点往下调整到合适位置,heap_build负责从最后一个非叶子节点向前逐个调用sift_down。我写堆排序的时候最常犯的错误是sift_down里左右孩子下标的边界判断,稍微不仔细就越界。建议每次都先用小的测试数组跑一遍再上大数组。

实验报告怎么写?我一般建议包含四块:算法原理描述、源码、复杂度分析、对比数据。对比数据要有说服力,我推荐跑三组数据:随机数据、有序数据、大量重复值数据,每一组都记录不同规模下的耗时。为什么强调这三组?因为只测随机数据,你会误以为选择排序比插入排序差不多,实际上插入排序在有序数据上的表现要远好于选择排序;只测有序数据,快排不优化的版本会退化到O(n²),你可能误以为快排“不咋样”。真实的排序性能必须用多组数据才能反映。

3.3 排序算法可视化:用数据感受每种排序的行为差异

代码写再多,不动手跑一遍,对排序的感受都是抽象的。我强烈建议你去找排序可视化网站(比如visualgo)或者自己用Python的matplotlib做动画,看每一种排序的过程。你会发现一些很微妙的行为差异。

冒泡排序像水里的气泡,大元素慢慢往上冒,每一趟都在进行大量的相邻交换,视觉效果是“整体在缓慢地有序化”。选择排序则很“抠门”,它一直在扫描整个未排序区,但很少动手,每趟只在末尾做一次交换,视觉上就像“从左到右一波一波地挑最小值填过去”。插入排序的视觉效果最像人类,右边不断抓牌,左边不断后移插入,“生长感”很明显。归并排序的视觉特征是“分块合并”,先处理小块,小块变大块,整个过程非常有规律。快排的视觉特征最剧烈,不断选基准、分区、交换,动作幅度很大,但同时速度也快。

我建议做一个自己的实验:把10000个随机整数分别用快排、归并、插入排序跑一次,从小到大打印耗时。跟你猜的结果对比一下,通常会发生三件颠覆认知的事。第一,归并排序不一定快,因为申请临时数组的拷贝开销在中等规模数据下可能拖慢速度;第二,插入排序在小规模数据上(比如几十个元素)甚至比快排还快,因为快排的递归和分区有固定开销;第三,你之前可能从未注意到,在数据含大量重复值的情况下,快排在反复交换相等元素,性能会明显下降,这时候三路快排就派上用场。这些经验在面试时随手讲出来,会让人觉得你是真动手学过,而不是背概念。

4. 排序算法高频面试题与避坑经验

4.1 面试官爱问的排序算法问题

我把这些年面试和被面试遇到的高频排序相关题目整理一下,供你参考。

“为什么快排通常比堆排快?”这是我被问到次数最多的问题之一。两者的平均复杂度都是O(n log n),但实际性能差异很大。原因有几点:快排在partition阶段的数据访问是顺序的,CPU缓存命中率高,堆排序的访问模式是跳跃式的,缓存友好性差;堆排序中堆顶和堆尾交换后,要重新调整整个堆,这个反复的下沉过程数据访问很不规整;另外,快排的分治结构天然适合小规模时切换到插入排序,进一步降低了常数因子。面试时能说出这些点,比只说“常数因子不同”有说服力得多。

“归并排序的逆序对问题怎么做?”这个问题考察的是对归并过程的理解。一个乱序数组里的逆序对数量,就是所有满足i < j且arr[i] > arr[j]的数对数量。用归并排序求解的思路:在合并左右两个有序子数组时,如果发现左边的某个元素比右边某个元素大,那么左边从这个位置到末尾的所有元素都大于这个右元素,每发现一次就累加这个数量。这样做的时间复杂度是O(n log n),而朴素的O(n²)双重循环在数据量大时根本跑不动。这个问题也是很多互联网公司笔试的常客。

“TopK问题:找最大(或最小)的K个数。”解法具备最佳的性价比。用一个大顶堆维护前K个最小元素(堆顶是K个里最大的那个),遍历数据,遇到比堆顶小的就替换堆顶并调整堆,最终堆里的K个元素就是整个数组最小的K个,时间复杂度O(n log K)。如果K很小,这个方案又快又省内存。如果用快排分区思路,则可以用类似找第K小的方式,平均O(n)。这两种方案在面试里对应不同的场景约束:堆排序适合数据流(无法一次性全部加载)的情况;快排分区适合数组整体在内存里的情况。两者都要掌握。

“什么场景用计数排序而不是快排?”这个问题的考点在于你是否清楚每种算法的适用边界。我给的答案是:当数据是整数且值域范围远小于数据规模时,计数排序是碾压性的优势。举个例子,要排序100万个范围在0到100之间的整数,计数排序只需要遍历一遍就完成,O(n + k),k就是101,远小于100万。快排无论如何都要做约n log n次比较,这场对决没有任何悬念。

“稳定排序到底有什么用?”这个问题看似简单,但很多候选人答不全。除了多关键字排序之外,稳定排序在基数排序里是根基,前面已经讲过。还有一个场景是“保持相同键值的原始顺序”在业务上的意义:比如一个排行榜里两个人分数相同,希望后提交的人排在前面,或者先提交的人保留顺序优势,这时候稳定性就很重要。数据库索引做多列排序时也会用到稳定排序的特性。

4.2 实操中踩过的坑和调试技巧

讲几个我在实际写排序代码和教学生时遇到的典型问题和排查技巧。

第一个坑是快排的递归栈溢出。现在的开发环境数据量动辄几十万上百万,如果快排的递归深度退化成O(n),栈很容易爆。除了随机选基准、三数取中之外,最有效的办法是尾递归优化(前面代码里展示的方式),把quick_sort(arr, low, pi - 1)递归,右半边改用循环处理。这样可以保证递归深度是O(log n),因为每次递归只处理较小的一半,另一半用循环解决。

第二个坑是堆排序建堆的复杂度算错。有人会认为建堆就是对n个元素逐个做下沉调整,每个调整是O(log n),所以总复杂度是O(n log n)。其实不然,如果从最后一个非叶子节点开始往前做下沉,累加起来的复杂度是O(n),这个结论可以用级数求和或树的层级分析来证明。我刚开始学的时候也写过O(n log n)的建堆实现,虽然也能排对,但面试老师问了“能不能在O(n)内建堆”,才真正理解什么是从下往上调整。

第三个坑是测试数据太单一。真实项目中,数据很少是完全随机的。我见过有人用有序数组测快排,结果耗时飙到几秒,差点怀疑算法被写错了。排查方法其实是准备三个典型的测试样本集:随机数据、有序或逆序数据、含有大量重复值的数据。这样一套跑下来,你立刻能看出算法在不同输入分布下的行为差异,也能顺势发现很多实际性能问题的根源。

第四个坑是稳定性被破坏而不自知。很多人在写插入排序时,把比较条件arr[j] > key写成arr[j] >= key,在元素相等的场景下会交换位置,虽然排序结果正确,但稳定性没了。这个细节在面试手写代码时特别容易被追问,你在代码里直接保证稳定性的写法,会给面试官留下好印象。

第五个坑是忽略空间复杂度。归并排序的O(n)额外空间在数据量翻倍时是实打实的开销,加上拷贝开销,实际耗时可能比预期高不少。做实验报告时,我建议记录“耗时”的同时记录“内存峰值”,这样能更完整地还原算法的真实表现。

我个人在实际操作里,最常做的一件事就是每学一个新的排序算法,就写一个小的基准性能测试,拿它和其他算法在同一组数据上对比。这个习惯帮我把“会写代码”和“理解算法”之间的差距补上了,也让我在面试中讲起排序算法时,脑子里不是背下来的结论,而是一张张真实的性能曲线和调试场景。这套学习路径虽然开头会费点时间,但后期收益极大,你不妨也试试。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 10:54:29

基于Go+Vue的开源工单系统:IT服务台高效运维实践

先说个真实经历&#xff1a;我负责的公司IT服务台&#xff0c;之前所有报修都走微信群。听起来挺互联网化&#xff0c;实际上一到周一人就麻了——五六十条“急&#xff0c;电脑开不了机”“打印机又卡纸了”“财务系统登录不上去”&#xff0c;哪条先报的、谁来处理、处理完没…

作者头像 李华
网站建设 2026/10/1 10:53:49

Linux下用Wireshark抓包分析TCP通信:从三次握手到四次挥手实战

我以前调试Linux下的网络程序&#xff0c;最头疼的事就是代码明明是按标准写法写的&#xff0c;可连接就是不正常。要么客户端连不上&#xff0c;要么服务器收不到完整的消息&#xff0c;要么长连接跑着跑着自己断了。这种时候光看日志很难定位&#xff0c;日志已经打印了成功&…

作者头像 李华
网站建设 2026/10/1 10:53:45

Godot 4 Compute Shader工具链实战:从零构建GPU粒子系统

1. 为什么要在Godot里重造一套Compute Shader工具链第一次在Godot里写Compute Shader的人&#xff0c;大概率会经历这样一个心理过程&#xff1a;先是被Godot轻量到极致的节点系统吸引&#xff0c;觉得这引擎真干净&#xff1b;然后想做一个GPU粒子系统或者大规模草地渲染&…

作者头像 李华
网站建设 2026/10/1 10:53:39

JavaScript字符串截取:substr、substring与slice的差异及最佳实践

先说个真实场景&#xff1a;你从接口返回里拿到一个相对路径/upload/2024/report-v2.pdf&#xff0c;现在需要把最后的文件名report-v2.pdf截出来。此刻十有八九会在substr()、substring()、slice()三个方法之间犹豫两秒&#xff0c;随手选一个&#xff0c;本地测试能通就提交了…

作者头像 李华
网站建设 2026/10/1 10:53:18

论文AIGC率从82.5%降至5.1%:10个降率工具与实操流程

论文查重刚出结果那天&#xff0c;我盯着屏幕上82.5%的AIGC疑似率&#xff0c;整个人是懵的。所谓AIGC率&#xff0c;就是系统判定论文由AI生成内容的比例&#xff0c;这个数字意味着我的论文在导师眼里基本等于“机器写的”&#xff0c;别说答辩&#xff0c;初稿这关都过不去。…

作者头像 李华