当前位置: 首页 > news >正文

归并排序的趟数和时间复杂度

一、归并排序的趟数

归并排序的核心是分治思想:先把数组递归地分成两半(分),直到每个子数组只有 1 个元素;再把相邻的子数组合并成有序数组(治)。这里的 “趟数”,本质是合并阶段的轮次(也等于拆分阶段的递归深度)。

1. 趟数的计算逻辑

假设待排序数组的元素个数为n

  • 拆分阶段:每次将数组分成 2 份,直到子数组长度为 1。这个过程的次数等于以 2 为底的 n 的对数(向上取整或向下取整,最终结果一致),数学表示为⌊log₂n⌋⌈log₂n⌉(也可简化为log₂n,因为时间复杂度中对数的底数不影响阶数)。
  • 合并阶段:每一轮合并都是将当前的子数组两两合并,轮次和拆分的次数完全相同,这就是归并排序的趟数
2. 举例说明
  • 例 1:n=8(2³)拆分过程:8 → 4+4 → 2+2+2+2 → 1+1+…+1(共 8 个 1)。拆分次数(趟数):log₂8=3趟。
  • 例 2:n=7(非 2 的幂)拆分过程:7 → 3+4 → 1+2+2+2 → 1+1+1+1+1+1+1。拆分次数(趟数):log₂7≈2.8,向上取整为3 趟(或向下取整⌊log₂7⌋=2,但实际合并时仍需 3 趟,因此通常统一表述为log₂n趟,忽略小数部分的影响)。

结论:归并排序的趟数为 **log2​n**(n为元素个数,对数的底数为 2)。

二、归并排序的时间复杂度

归并排序的时间复杂度需要从拆分阶段合并阶段分别分析,最终结合得出整体复杂度。

1. 拆分阶段的时间复杂度

拆分操作只是将数组分成两半,没有元素的比较和交换,仅涉及索引的计算,时间复杂度为O(1)(每一次拆分),整个拆分阶段的总时间复杂度为 O (log n)(因为拆分次数是 log n 趟),相对于合并阶段可以忽略。

2. 合并阶段的时间复杂度

合并阶段是归并排序的核心,每一趟合并都需要遍历所有 n 个元素(将两两子数组合并时,每个元素都会被处理一次)。

  • 每一趟合并的时间复杂度:O (n)。
  • 总趟数:log n。
  • 因此,合并阶段的总时间复杂度:O (n × log n)。
3. 不同情况下的时间复杂度

归并排序是稳定的排序算法(相同值的元素相对位置不变),且其时间复杂度不受原始数组有序性的影响:

  • 最好情况:O (n log n)(原始数组已有序)。
  • 最坏情况:O (n log n)(原始数组逆序)。
  • 平均情况:O (n log n)。
补充:空间复杂度

归并排序需要额外的辅助数组来存储合并后的元素,因此空间复杂度为O(n)(这是归并排序的主要缺点,相比快速排序的 O (log n) 空间复杂度更高)。

三、代码示例(验证趟数和复杂度)

下面是 Python 实现的归并排序,添加了趟数统计的功能,帮助你直观看到趟数:

python

运行

def merge_sort(arr): # 统计趟数(使用非局部变量,避免全局变量) global merge_pass # 递归终止条件:子数组长度为1 if len(arr) <= 1: return arr # 拆分:将数组分成左右两半 mid = len(arr) // 2 left = merge_sort(arr[:mid]) right = merge_sort(arr[mid:]) # 合并:将两个有序子数组合并 merge_pass += 1 # 每合并一次,趟数+1(这里统计的是合并操作的次数,总趟数为log2n) return merge(left, right) def merge(left, right): result = [] i = j = 0 # 双指针遍历两个子数组,按序添加到结果 while i < len(left) and j < len(right): if left[i] <= right[j]: result.append(left[i]) i += 1 else: result.append(right[j]) j += 1 # 添加剩余元素 result.extend(left[i:]) result.extend(right[j:]) return result # 测试 if __name__ == "__main__": # 初始化趟数 merge_pass = 0 arr = [3, 1, 4, 1, 5, 9, 2, 6] sorted_arr = merge_sort(arr) print("排序后的数组:", sorted_arr) print("合并操作的次数(趟数相关):", merge_pass) # 对于n=8,输出7(合并次数),趟数为3(log28=3)

说明:代码中merge_pass统计的是合并操作的次数(比如 n=8 时,合并次数是 7),而我们说的 “趟数” 是合并的轮次(n=8 时是 3 轮),两者的关系是:第 k 趟的合并次数为n/2k,总合并次数为n−1(比如 8-1=7)。

总结

  1. 趟数:归并排序的趟数为 **log2​n**(n 为元素个数,即拆分 / 合并的轮次),若 n 不是 2 的幂,趟数为向上取整后的log2​n。
  2. 时间复杂度:最好、最坏、平均情况均为O(n log n),这是归并排序的核心优势(不受原始数组有序性影响)。
  3. 补充:归并排序的空间复杂度为O(n),因为需要辅助数组存储合并后的元素。

合并阶段是归并排序的核心,每一趟合并都需要遍历所有 n 个元素 讲讲

要理解 “归并排序的每一趟合并都需要遍历所有 n 个元素”,我们需要先明确 “” 的定义(合并阶段的 “轮次”),再结合具体例子拆解每一趟的合并过程 —— 核心逻辑是:每一趟合并的目标是将当前所有 “有序子数组” 两两合并,而这些子数组的总长度恰好是 n,因此遍历的总元素数必然是 n

第一步:先明确 “合并阶段的趟” 是什么

归并排序的 “合并阶段”,是从 “最小有序子数组”(长度为 1 的子数组,天然有序)开始,每一轮(一趟)都将所有相邻的 “同长度有序子数组” 两两合并成 “长度翻倍的有序子数组”,直到最终合并成一个长度为 n 的有序数组。

这里的 “趟”=“合并轮次”,每一趟的核心特征是:

  • 趟内所有待合并的子数组长度相同(记为len);
  • 合并后生成的子数组长度为2*len
  • 每一趟处理的子数组总数是 “当前子数组个数”,但它们的总长度始终是 n。

第二步:用具体例子拆解 “每趟遍历 n 个元素”

n=8(元素:[3,1,4,1,5,9,2,6])为例,合并阶段共 3 趟(因为log₂8=3),我们逐趟分析:

第 1 趟合并:子数组长度 = 1 → 合并后长度 = 2
  • 初始状态:拆分阶段结束后,得到 8 个长度为 1 的有序子数组(每个子数组只有 1 个元素,天然有序):[3], [1], [4], [1], [5], [9], [2], [6]
  • 合并逻辑:将相邻的两个子数组两两合并,共需要合并 4 次(8 个→4 个):
    1. 合并[3][1][1,3](遍历 2 个元素);
    2. 合并[4][1][1,4](遍历 2 个元素);
    3. 合并[5][9][5,9](遍历 2 个元素);
    4. 合并[2][6][2,6](遍历 2 个元素);
  • 第 1 趟总遍历元素数:2+2+2+2 = 8 = n;
  • 第 1 趟结束后:得到 4 个长度为 2 的有序子数组:[1,3], [1,4], [5,9], [2,6]
第 2 趟合并:子数组长度 = 2 → 合并后长度 = 4
  • 当前状态:4 个长度为 2 的有序子数组,总长度仍为 8;
  • 合并逻辑:继续相邻两两合并,共需要合并 2 次(4 个→2 个):
    1. 合并[1,3][1,4]
      • 双指针遍历两个子数组,依次取较小元素:1(来自第二个子数组)→1(第一个)→3(第一个)→4(第二个),最终得到[1,1,3,4](遍历 4 个元素);
    2. 合并[5,9][2,6]
      • 双指针遍历:2(第二个)→5(第一个)→6(第二个)→9(第一个),最终得到[2,5,6,9](遍历 4 个元素);
  • 第 2 趟总遍历元素数:4+4 = 8 = n;
  • 第 2 趟结束后:得到 2 个长度为 4 的有序子数组:[1,1,3,4], [2,5,6,9]
第 3 趟合并:子数组长度 = 4 → 合并后长度 = 8(最终数组)
  • 当前状态:2 个长度为 4 的有序子数组,总长度还是 8;
  • 合并逻辑:最后一次两两合并(2 个→1 个):合并[1,1,3,4][2,5,6,9]
    • 双指针遍历:1(第一个)→1(第一个)→2(第二个)→3(第一个)→4(第一个)→5(第二个)→6(第二个)→9(第二个),最终得到[1,1,2,3,4,5,6,9](遍历 8 个元素);
  • 第 3 趟总遍历元素数:8 = n;
  • 第 3 趟结束后:得到 1 个长度为 n 的有序数组,排序完成。

第三步:为什么 “每趟都必须遍历 n 个元素”?

核心原因有两个,结合上面的例子很容易理解:

  1. 子数组总长度不变:无论每趟的子数组长度是 1、2 还是 4,所有子数组的总长度始终是 n(因为合并只是 “组合子数组”,没有增加或减少元素);
  2. 合并需完整遍历子数组:要将两个有序子数组合并成一个有序数组,必须完整遍历这两个子数组的所有元素(否则无法保证合并后的数组有序)。因此,每趟合并时,所有子数组的元素都会被遍历一次,总遍历数就是 n。

第四步:延伸思考 ——n 不是 2 的幂时,是否仍满足 “每趟遍历 n 个元素”?

即使 n 不是 2 的幂(比如 n=7),结论依然成立。以 n=7 为例,合并阶段共 3 趟(log₂7≈2.8,向上取整为 3 趟):

  • 第 1 趟:7 个长度为 1 的子数组 → 合并 3 次(得到 3 个长度为 2 的子数组 + 1 个长度为 1 的子数组),总遍历元素数 = 2+2+2+1=7=n;
  • 第 2 趟:3 个长度为 2 的子数组 + 1 个长度为 1 的子数组 → 合并 2 次(得到 1 个长度为 4 的子数组 + 1 个长度为 2 的子数组 + 1 个长度为 1 的子数组?不,实际是 “优先合并同长度”,最终总遍历数仍为 7);
  • 第 3 趟:合并剩余子数组,总遍历数还是 7=n。

本质是:即使子数组长度不完全相同,每趟所有待合并子数组的总长度依然是 n,因此遍历的总元素数始终是 n

总结

“归并排序每一趟合并都遍历所有 n 个元素” 的核心逻辑可概括为:

  • 合并的本质是 “将当前所有有序子数组合并成更大的有序子数组”;
  • 所有子数组的总长度永远等于 n(元素没有增减);
  • 合并任意两个子数组都需要完整遍历它们的元素,因此每趟遍历的总元素数必然是 n。

这也是归并排序时间复杂度为 O (n log n) 的关键:共 log n 趟,每趟 O (n),总复杂度 O (n log n)。

http://www.cnnetsun.cn/news/56747.html

相关文章:

  • Python入门篇【函数巩固题】
  • 23、互联网文件获取与网络冲浪全攻略
  • 28、Unix 系统超级用户操作指南
  • 31、Unix 文件、目录与命令使用指南
  • 腾讯开源Hunyuan大模型系列:从边缘到云端的全场景AI解决方案
  • 15、Awk编程:表达式、系统变量及应用示例
  • 32、拼写检查与索引生成工具详解
  • 10、实用脚本工具:温度转换、贷款计算与日程管理
  • 20、网站管理黑客技巧:CGI脚本的应用与安全
  • Holo1.5开源发布:重塑计算机交互智能,引领多模态代理技术新纪元
  • 30、图像魔法棒:ImageMagick实用脚本指南
  • 百度网盘极速下载:3步告别龟速等待的实用指南
  • 28、网络数据分类与回归分析技术详解
  • Unity反向遮罩技术深度解析与应用实践
  • 多模态大模型新突破:Janus-Pro-7B重构跨模态理解与生成范式
  • 13、系统管理:用户管理脚本实用指南
  • PyQt-Fluent-Widgets 现代桌面应用开发终极指南
  • Duplicity:高效《缺氧》存档编辑器助力玩家打造个性化殖民地
  • AutoGPT文化展览策展助手
  • RSSHub-Radar终极指南:智能信息管理的完整解决方案
  • 蚂蚁开源Ring-1T引爆AI推理革命:万亿参数模型重构开源技术边界
  • 一、基于freertos系统上关于ATGM336H定位模块的定位测试验证
  • Flutter包体积优化终极指南:让你的直播App轻装上阵
  • Qwen3-0.6B震撼发布:轻量级大模型迎来推理与多语言能力的双重突破
  • Pig企业级权限管理系统:从零搭建微服务架构的实战指南
  • Obsidian Git高效配置:构建智能笔记备份系统
  • 心电图AI分类终极指南:3个简单步骤让新手快速上手
  • Unity反向遮罩技术深度解析:从原理到实战应用
  • 多模态生成革命:Lumina-DiMOO全能模型重塑跨模态交互新范式
  • MarkText主题定制完全攻略:打造专属写作空间的5个关键步骤