算法札记:归并排序

++先取中间值,对两边递归,使得两边(假设中间值在左边)顺序满足题目条件,然后用双指针算法合并++

《信奥赛一本通》中提到++归并排序是稳定++ 的,++稳定排序指的是在排序过程中,‌如果两个元素值相等,排序后它们的先后顺序保持不变‌++ 。比如原本在前面的相等元素,排完序后依然在前面,不会乱序 。‌‌而++快排是不稳定++的。

归并排序每一层都是O(n),一共O(log₂n)层(每次"二分"),乘在一起就是++O(nlog₂n)++

关于快速排序的时间复杂度,DeepSeek讲的很好

‌**快速排序平均时间复杂度是 O(nlogn),最坏情况是 O(n²)**‌。计算核心是看递归层数和每层处理的数据量 。‌‌百科

复杂度是怎么算出来的

  1. 每层工作量‌:每次划分都要把数组从头到尾遍历一遍,不管数据多少,这一步耗时固定是 O(n)。
  2. 递归层数‌:理想情况下,每次把数组切成两半,就像折纸一样,切 log₂n 次就只剩 1 个元素了,所以层数是 O(log n)。
  3. 总时间‌:把层数乘以每层工作量,就是 O(n) × O(log n) = O(nlogn),这是平均和最好情况 。
  4. 分治思想‌:简单来说就是把大问题拆成小问题,小问题排好了,大问题也就好了 。‌‌百科‌

什么时候会变成 O(n²)

  1. 划分不均匀‌:如果每次选的基准数都是最大或最小的,数组没法对半切,只能切掉一个元素 。
  2. 递归变深‌:这时候递归层数变成了 n 层,总时间就变成了 O(n) × O(n) = O(n²),跟冒泡排序一样慢了 。
  3. 常见场景‌:比如数组本来就是有序的,你还固定选第一个数当基准,就会触发最坏情况 。
  4. 优化建议‌:随机选基准数或者用"三数取中法",能大概率保持 O(nlogn) 的速度 。‌‌

++三数取中法++的意思是x=mid(a1,an+1\>\>1,an) (我自己发明的函数,别当真)

根据上述分析,归并排序按理来说性能大于快排,但++现实显然反的++,我又去问了DeepSeek

在通用内存排序场景中,‌快速排序的用处更广泛 ‌,它是大多数编程语言标准库(如 C++ std::sort)的基础或核心组件;但在特定领域(如外部排序、链表排序、稳定性要求高的场景),归并排序具有不可替代的地位。‌‌

核心结论:为何快速排序应用更广?

  • 空间效率优势 ‌:快速排序是‌原地排序‌,仅需 O(log n) 栈空间,适合内存受限环境;归并排序需 O(n) 额外空间,在大数组下内存开销显著。
  • 缓存友好性‌:快速排序访问内存连续,CPU 缓存命中率高,实际运行常数因子更小,通常比归并排序快 20%-50%。
  • 工程优化成熟‌:现代实现多采用"三数取中"、"小数组切插排"等优化,极大降低了最坏情况 O(n²) 的发生概率,使其在随机数据下表现极稳。‌‌

归并排序的关键不可替代场景

尽管整体使用频率略低,归并排序在以下领域是‌首选甚至唯一解‌:

  • 需要稳定排序‌:当相等元素的相对顺序必须保留时(如多关键字排序、金融交易记录),归并排序是稳定算法,而快速排序不稳定。
  • ‌**外部排序(大数据)**‌:当数据量远超内存容量(如磁盘文件排序),归并排序的"分块-合并"机制天然适配,是数据库和大数据框架(MapReduce)的核心算法。
  • 链表排序‌:归并排序在链表上可实现 O(1) 额外空间且效率稳定,而快速排序在链表中分区操作复杂且性能下降。‌‌

选型建议

  • 默认选快速排序‌:处理普通数组、对内存敏感、追求极致速度且无需稳定性时。
  • 选归并排序‌:要求排序稳定、数据存储在磁盘/网络(外部排序)、或数据结构为链表时。

注:现代语言标准库常采用混合策略(如 Python 的 Timsort、Java 的对象数组排序),融合了两者优点,但底层逻辑仍偏向归并思想的稳定性保障。‌‌

有关"缓存友好性",是因为计算机存数组的内存是连续的,归并排序访问时p1、p2所对应的内存不连续,即"东一个西一个",而快排是首先一顿i++,然后一顿j++,每一次"一顿"都是一小段连续的内存访问

有关外部排序,说实话我没看懂,级别不够QwQ

cpp 复制代码
void merge_sort(int l,int r){
    if (l>=r){
        return;
    }
    int mid=l+r>>1;
    merge_sort(l,mid),merge_sort(mid+1,r);
    int p1=l,p2=mid+1,p=0;
    while (p1<=mid || p2<=r){
        if (p1<=mid && (p2>r || a[p1]<=a[p2])){
            b[++p]=a[p1++];
        }else{
            b[++p]=a[p2++];
        }
    }
    _for(i,1,p){
        a[l+i-1]=b[i];
    }
}

就这样吧