为什么合并排序和快速排序的平均时间复杂度都是O(nlogn)

合并排序和快速排序的平均时间复杂度都是O(nlogn)的原因:它们都采用了分治策略,将问题分解成子问题并递归地求解。合并排序算法将待排序序列不断划分成两个子序列,直到每个子序列只有一个元素;快速排序算法每次分割操作将数组分成两个长度较为相等的部分。

一、合并排序和快速排序的平均时间复杂度都是O(nlogn)的原因

合并排序和快速排序的平均时间复杂度都是 O(nlogn),是因为它们都采用了分治策略,将问题分解成子问题并递归地求解。

合并排序

当我们计算合并排序的时间复杂度时,需要考虑拆分和合并这两部分。在每一次拆分中,我们将输入数组划分成两个子数组,其中每个子数组的长度为n/2,因此我们需要做log(n)轮拆分。每一轮拆分的时间复杂度为O(n),因此平均时间复杂度为O(nlogn)。

快速排序

快速排序算法中每次分割操作将数组分成了两个长度相等的部分,因此需要做log(n)次分割。在每个分割操作中,需要扫描数组一次找到最终位置的关键值,并将其与数组中的其他值分成两个部分。因此,每个分割操作的时间复杂度为O(n),平均时间复杂度为O(nlogn)。

二、合并排序简介

合并排序是采用分治策略实现对N个元素进行排序的算法,是分治法的一个典型应用和完美体现,它是一种平衡,简单的二分分治策略,计算过程分为三步:

  1. 分解:将待排序元素分成大小大致相同的两个子序列。
  2. 求解子问题:用合并排序法分别对两个子序列递归地进行排序。
  3. 合并:将排好序的有序子序列进行合并,得到符合要求的有序序列。

算法步骤

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
  4. 重复步骤 3 直到某一指针达到序列尾;
  5. 将另一序列剩下的所有元素直接复制到合并序列尾。

代码

int min(int x, int y) {
    return x < y ? x : y;
}
void merge_sort(int arr[], int len) {
    int* a = arr;
    int* b = (int*) malloc(len * sizeof(int));
    int seg, start;
    for (seg = 1; seg < len; seg += seg) {
        for (start = 0; start < len; start += seg + seg) {
            int low = start, mid = min(start + seg, len), high = min(start + seg + seg, len);
            int k = low;
            int start1 = low, end1 = mid;
            int start2 = mid, end2 = high;
            while (start1 < end1 && start2 < end2)
                b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
            while (start1 < end1)
                b[k++] = a[start1++];
            while (start2 < end2)
                b[k++] = a[start2++];
        }
        int* temp = a;
        a = b;
        b = temp;
    }
    if (a != arr) {
        int i;
        for (i = 0; i < len; i++)
            b[i] = a[i];
        b = a;
    }
    free(b);
}

三、快速排序简介

快速排序的基本思想是随机找出一个数,可以随机取,也可以取固定位置,一般是取第一个或最后一个称为基准,然后就是比基准小的放在左边,比基准大的放到右边。如何放呢?就是和基准进行交换,这样交换完左边都是比基准小的,右边都是比较基准大的,这样就将一个数组分成了两个子数组,然后再按照同样的方法把子数组再分成更小的子数组,直到不能分解为止。快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。

算法步骤

  1. 从数列中挑出一个元素,称为 “基准”(pivot)。
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

代码

typedef struct _Range {
    int start, end;
} Range;
Range new_Range(int s, int e) {
    Range r;
    r.start = s;
    r.end = e;
    return r;
}
void swap(int *x, int *y) {
    int t = *x;
    *x = *y;
    *y = t;
}
void quick_sort(int arr[], const int len) {
    if (len <= 0)
        return; //避免len等于负值时错误
    //r[]模拟堆疊,p为数量,r[p++]为push,r[--p]为pop且取得元素
    Range r[len];
    int p = 0;
    r[p++] = new_Range(0, len - 1);
    while (p) {
        Range range = r[--p];
        if (range.start >= range.end)
            continue;
        int mid = arr[range.end];
        int left = range.start, right = range.end - 1;
        while (left < right) {
            while (arr[left] < mid && left < right)
                left++;
            while (arr[right] >= mid && left < right)
                right--;
            swap(&arr[left], &arr[right]);
        }
        if (arr[left] >= arr[range.end])
            swap(&arr[left], &arr[range.end]);
        else
            left++;
        r[p++] = new_Range(range.start, left - 1);
        r[p++] = new_Range(left + 1, range.end);
    }
}

延伸阅读

选择排序算法简介

选择排序(Select Sort)是直观的排序,通过确定一个 Key 最大或最小值,再从带排序的的数中找出最大或最小的交换到对应位置。再选择次之。双重循环时间复杂度为 O(n^2)。算法描述为:

  • 在一个长度为 N 的无序数组中,第一次遍历 n-1 个数找到最小的和第一个数交换。
  • 第二次从下一个数开始遍历 n-2 个数,找到最小的数和第二个数交换。
  • 重复以上操作直到第 n-1 次遍历最小的数和第 n-1 个数交换,排序完成。

文章标题:为什么合并排序和快速排序的平均时间复杂度都是O(nlogn),发布者:Z, ZLW,转载请注明出处:https://worktile.com/kb/p/49352

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
Z, ZLWZ, ZLW认证作者
上一篇 2023年4月15日
下一篇 2023年4月15日

相关推荐

  • 计算机编程学什么内容的

    在探讨计算机编程领域,我们可将其核心内容归纳为1、编程语言基础、2、数据结构与算法、3、软件开发原则、4、计算机系统结构、5、网络基础。特别地,数据结构与算法是编程学习中最重要的组成部分,它不仅关系到程序的效率和性能,更是衡量一个程序员理论和实际编程能力的重要标准。数据结构是为了高效地存储和管理数据…

    2024年4月27日
    3700
  • Java中的持续集成(CI)和持续交付(CD)是什么

    持续集成(CI)是一种软件开发实践,开发人员定期将代码更改合并到中央仓库中,自动执行测试以验证更改。持续交付(CD)紧随CI之后,确保软件可以快速、安全地部署到生产环境。CI和CD是现代软件开发的关键部分、降低错误和改进质量,同时缩短上市时间。特别地,CI涉及到开发者频繁地合并代码变更到共享的版本控…

    2024年1月8日
    34600
  • 实物编程和编程有什么区别

    实物编程是一个以实体对象为基础,在现实世界中进行的编程活动,强调与物理环境的交互;而编程通常是指在计算机上用特定编程语言写代码的过程,侧重于抽象逻辑和算法的实现。两者的根本区别体现在交互方式、环境和目的上。实物编程往往通过操作实体来实现代码逻辑,通常用于教育和创新设计,而编程则以虚拟代码为主,关注软…

    2024年5月1日
    2800
  • 编程什么是环境变量和数据

    环境变量是操作系统中用来指定运行环境的一组动态赋值的值,数据则是程序处理和操作的原材料。环境变量主要用于存储影响程序行为的信息,如路径设置、系统使用的语言等,而数据是程序在执行过程中使用和修改的信息,可以是用户输入、文件内容或程序生成的结果。 一、环境变量的重要性 环境变量对于程序的运行来说至关重要…

    2024年4月27日
    4600
  • cvm是什么编程

    CVM是虚拟机(Cloud Virtual Machine),它提供了在云端运行程序的环境。 Cloud Virtual Machine (CVM) 是构建在云计算平台之上的抽象计算资源。与物理服务器不同,CVM 可以快速部署、配置,并随用随开,具有极高的灵活性和可扩展性。在 CVM 上,用户可以运…

    2024年5月2日
    2700
  • 施工单位甲方如何管理项目

    施工单位甲方管理项目的关键涉及规划与资源配置、合同管理、现场管理、风险控制与质量保证、定期评估与沟通机制的建立。首先,甲方需要确立明确的项目目标与计划,它是项目成功的基石。这要求甲方从项目伊始就设立具体、可行的目标,同时制定详细的施工计划和时间表,确保项目按期完成。此外,甲方应进行资源的周密规划,合…

    2024年4月10日
    10900
  • 测试用例写在程序哪里

    测试用例应当写在特定的测试框架中,而不是直接写在程序中。它的写法是:1、使用标题;2、具体描述;3、撰写假设和前提条件;4、保持测试步骤清晰简洁;5、撰写预期结果等。使用标题是指按照与要测试的模块相同的行命名测试用例。 一、测试用例的写法 1、使用标题 一个好的测试用例始于一个强大的标题。作为优异实…

    2023年4月6日
    61900
  • 自学编程为什么那么难呢

    自学编程之所以显得较为困难主要因为1、缺乏系统性学习路径,2、自我学习时面对的困惑缺乏即时解答,3、缺乏实际应用项目经验。特别是自学过程中缺乏系统性学习路径,很容易导致学习者在无尽的信息海洋中迷失方向,进而陷入方向性不明确与学习效率极低的状态。有效的学习路径能够为学习者规划出从入门到精通的清晰路线图…

    2024年4月27日
    3700
  • 编程孩子学什么比较好

    Python、Scratch、JavaScript和Java 是对于编程初学者尤其是孩子学习比较好的选择。以 Python 为例,它作为一种高级编程语言,在全球范围内广泛应用于开发、数据科学、人工智能等领域。Python 的语法简洁明了,易于理解和学习,特别适合编程新手,尤其是儿童。Python 社…

    2024年4月27日
    3700
  • 编程中右转的代码叫什么

    在编程中,实现"右转"的代码通常依赖于具体的编程语言和应用场景。1、在图形编程中,右转可能通过调整对象的旋转角度实现;2、在文本处理中,"右转"可能意味着字符串的旋转或调整方向;3、在机器人编程中,"右转"指令通常是通过发送特定的命令到机器…

    2024年4月27日
    4700

发表回复

登录后才能评论
注册PingCode 在线客服
站长微信
站长微信
电话联系

400-800-1024

工作日9:30-21:00在线

分享本页
返回顶部