为什么合并排序和快速排序的平均时间复杂度都是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年1月9日
    40400
  • oa办公软件有哪些品牌

    开门见山地回应,OA办公软件主要包括这几个品牌:1、Microsoft Office 365、2、Google Workspace(原G Suite)、3、Zoho Office Suite、4、LibreOffice、5、Apache OpenOffice。这些品牌的软件因其功能的全面性、云服务的…

    2024年1月11日
    26900
  • 如何处理DevOps中的容量规划

    DevOps中的容量规划至关重要,理应通过以下主要方法进行优化:1、评估现状与目标资源要求、2、实施连续的性能监控、3、预测性分析与模拟、4、采用自动化和弹性资源管理、5、持续的反馈与调整。在这些方法中,实施连续的性能监控是核心,因为它能实时提供系统性能数据,有助于根据实际使用情况灵活调整资源,保障…

    2024年1月4日
    16900
  • oa系统贵吗

    标题:OA系统的成本价值分析 开门见山直接解答问题:OA系统的价格受多重因素影响。平均成本在数千至数万元不等,取决于1、系统定制化程度、2、用户数量和规模、3、功能复杂性、4、服务支持以及5、供应商声誉。具体价格从基础版本的免费或低成本方案,到高级、全面定制解决方案的昂贵费用都有。系统定制化程度影响…

    2024年1月11日
    16100
  • Java中的内存泄漏如何检测

    在Java中检测内存泄漏,1)使用分析工具,如VisualVM、Eclipse Memory Analyzer (MAT)、YourKit 等,以便对Java虚拟机中的对象分配进行监控和分析;2)利用代码剖析,如查找过早对象引用或不必要的静态引用;3)关注JVM警报,特别是与堆内存使用模式有关的异常…

    2024年1月8日
    31700
  • 国内oa软件排名

    国内OA(Office Automation,办公自动化)软件市场繁荣,众多产品汇聚。归纳起来,几款主导性的OA软件便于企业提升工作效率、实现资源共享。1、钉钉;2、腾讯会议;3、微软Teams;4、华为WeLink;5、用友NC。其中,钉钉由阿里巴巴推出,强化了移动办公和企业服务的功能,通过整合即…

    2024年1月17日
    31100
  • 甘特图在产品开发项目中如何发挥作用

    甘特图在产品开发项目中的作用包括提供视觉化进度追踪、促进资源分配、优化进程管控、加强协作交流、助力风险评估。它通过直观的横向条形图展现任务进度与时限,助力项目管理者监控每个阶段和活动的执行情况。诸如此类工具不仅描绘项目大纲,而且突出各任务间的依赖关系。甘特图还有助于识别关键路径,即项目成功完成的关键…

    2023年12月21日
    23700
  • Java中的异常处理方式有哪些

    Java异常处理方式主要包括以下5种:1、try-catch块、2、throws子句、3、try-with-resources语句、4、自定义异常、5、使用异常类库。在try-catch块中,特指程序代码在try块执行时如遇异常,catch块会捕获并进行处理。这种机制允许开发者控制异常处理的逻辑与流…

    2024年1月8日
    21000
  • 编程算法和数据挖掘涉及的算法有什么区别和联系

    区别是:编程算法不依赖于数据,能精确的解决问题,是对存储的数据进行处理,最终得到问题的答案。数据挖掘算法是一类从数据中运用数学工具自动分析获得规律,并利用规律对未知数据进行预测的算法,注重数据来源以及数据规律。 编程算法不依赖于数据,能精确的解决问题,是对存储的数据进行处理,最终得到问题的答案。数据…

    2023年2月21日
    46800
  • 数据库中的表是怎么在硬盘中存储的

    数据库中的表是信息存储的基础单元,他们在硬盘中存储的原因本文为您进解读:1.数据库文件的基本结构;2.表的物理存储方式;3.数据页与索引的工作机制;4.数据的读写过程;5.对数据存储的优化建议。表中的数据记录通常是按主键或插入顺序进行存储的,但是实际的存储顺序可能会因为数据的插入、删除和更新而发生变…

    2023年7月12日
    40600

发表回复

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

400-800-1024

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

分享本页
返回顶部