iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >通俗易懂的C语言快速排序和归并排序的时间复杂度分析
  • 147
分享到

通俗易懂的C语言快速排序和归并排序的时间复杂度分析

C语言排序时间复杂度C语言快速排序归并排序 2023-01-28 06:01:52 147人浏览 泡泡鱼
摘要

目录快速排序和归并排序的时间复杂度分析——通俗易懂归并排序的时间复杂度分析快速排序的时间复杂度快速排序的最坏情况O(n^2)总结快速排序和归并排序的时间复杂度

快速排序和归并排序的时间复杂度分析——通俗易懂

今天面试的时候,被问到归并排序的时间复杂度,这个大家都知道是O(nlogn),但是面试官又继续问,怎么推导出来的。这我就有点懵了,因为之前确实没有去真正理解这个时间复杂度是如何得出的,于是就随便答了一波(理解了之后,发现面试的时候答错了......)。

归并排序和快速排序,是算法中,非常重要的两个知识点,同时也是在面试中被问的非常频繁的内容,我明知如此,却没有彻底理解,真是太不应该了。所以,今天这篇博客就来分析一下这两种排序算法的时间复杂度是如何得出的。我查了许多篇博客,很多都是通过公式进行分析,十分难理解,下面我就结合自己的理解,使用通俗易懂的方式进行描述(为了好理解,可能会有些啰嗦)。

归并排序的时间复杂度分析

了解归并排序的应该都知道,归并排序的时间复杂度是O(nlogn),且这个时间复杂度是稳定的,不随需要排序的序列不同而产生波动。那这个时间复杂度是如何得来的呢?我们可以这样分析,假设我们需要对一个包含n个数的序列使用归并排序,并且使用的是递归的实现方式,那么过程如下:

  • 递归的第一层,将n个数划分为2个子区间,每个子区间的数字个数为n/2
  • 递归的第二层,将n个数划分为4个子区间,每个子区间的数字个数为n/4
  • 递归的第三层,将n个数划分为8个子区间,每个子区间的数字个数为n/8;

......

  • 递归的第logn层,将n个数划分为n个子区间,每个子区间的数字个数为1

我们知道,归并排序的过程中,需要对当前区间进行对半划分,直到区间的长度为1。也就是说,每一层的子区间,长度都是上一层的1/2。这也就意味着,当划分到第logn层的时候,子区间的长度就是1了。而归并排序的merge操作,则是从最底层开始(子区间为1的层),对相邻的两个子区间进行合并,过程如下:

  • 在第logn层(最底层),每个子区间的长度为1,共n个子区间,每相邻两个子区间进行合并,总共合并n/2次。n个数字都会被遍历一次,所有这一层的总时间复杂度为O(n)

......

  • 在第二层,每个子区间长度为n/4,总共有4个子区间,每相邻两个子区间进行合并,总共合并2次。n个数字都会被遍历一次,所以这一层的总时间复杂度为O(n)
  • 在第一层,每个子区间长度为n/2,总共有2个子区间,只需要合并一次。n个数字都会被遍历一次,所以这一层的总时间复杂度为O(n)

通过上面的过程我们可以发现,对于每一层来说,在合并所有子区间的过程中,n个元素都会被 操作一次,所以每一层的时间复杂度都是O(n)。而之前我们说过,归并排序划分子区间,将子区间划分为只剩1个元素,需要划分logn次。每一层的时间复杂度为O(n),共有logn层,所以归并排序的时间复杂度就是O(nlogn) 。

上面的描述算是非常详细了,应该不会太难理解。如果上面的过程还是不太理解,那么我们通过另外一种更直观的方式进行分析。上面描述的是递归的过程,下面我们通过非递归(迭代)方式实现的归并排序,再来分析一波,这种方式更加直观(为什么不直接通过非递归的方式描述,而是先通过递归的方式分析,是因为上面的过程也可以用来分析快速排序)。下面是通过非递归方式实现的归并排序代码,其中有两处分析时间复杂度的关键点,我标注出来了(重点关注注释):

**


public static void merge(int[] arr) {
    // 关键点1:划分子区间,每一次的子区间长度是上一次的两倍,所以这个循环需要执行logn次
    for(int i = 1;i<arr.length;i *= 2){
        // 关键点2:此方法每次执行的时间复杂度为O(n),具体看下方
        mergeSort(arr,i);
    }
}

public static void mergeSort(int[] arr, int gap) {
    int[] tmp = new int[arr.length];
    int index = 0;
    int start1 = 0;
    int end1 = start1 + gap - 1;
    int start2 = end1 + 1;
    int end2 = (start2 + gap - 1)>=arr.length?arr.length-1:start2+gap-1;
    while(start2<arr.length){
        while(start1<=end1&&start2<=end2){
            if(arr[start1]<arr[start2]){
                tmp[index++] = arr[start1++];
            }else{
                tmp[index++] = arr[start2++];
            }
        }
        while(start1<=end1){
            tmp[index++] = arr[start1++];
        }
        while(start2<=end2){
            tmp[index++] = arr[start2++];
        }
        start1 = end2+1;
        end1 = start1 + gap - 1;
        start2 = end1 + 1;
        end2 = (start2 + gap - 1)>=arr.length?arr.length-1:start2+gap-1;
    }
    while(start1<arr.length){
        tmp[index++] = arr[start1++];
    }
    for(int j = 0;j<tmp.length;j++){
        arr[j] = tmp[j];
    }
}

上面的代码,merge方法中的循环需要循环logn次,每次循环都调用一次mergeSort方法,mergeSort方法的时间复杂度为O(n),所以很容易得出归并排序的时间复杂度为O(nlogn)

快速排序的时间复杂度

了解快速排序的应该知道,快速排序的时间复杂度在O(nlogn)~ O(n^2)之间,下面我就来分别分析这两种情况:

(一)快速排序的最好情况O(nlogn)

这种情况下,其实和上面通过递归分析的归并排序很类似,理解了归并排序的时间复杂度分析,那这里应该也很好理解。快速排序的实现方式,就是在当前区间中选择一个轴,区间中所有比轴小的数都需要放到轴的左边,而比轴大的数则放到轴的右边。在理想的情况下,我们选取的轴刚好就是这个区间的中位数。也就是说,在操作之后,正好将区间分成了数字个数相等的左右两个子区间。此时就和归并排序基本一致了:

  • 递归的第一层,n个数被划分为2个子区间,每个子区间的数字个数为n/2
  • 递归的第二层,n个数被划分为4个子区间,每个子区间的数字个数为n/4
  • 递归的第三层,n个数被划分为8个子区间,每个子区间的数字个数为n/8;

......

  • 递归的第logn层,n个数被划分为n个子区间,每个子区间的数字个数为1

以上过程与归并排序基本一致,而区别就是,归并排序是从最后一层开始进行merge操作,自底向上;而快速排序则是从第一层开始,交换区间中数字的位置,也就是自顶向下。但是,merge操作和快速排序的调换位置操作,时间复杂度是一样的,对于每一个区间,处理的时候,都需要遍历一次区间中的每一个元素。这也就意味着,快速排序和归并排序一样,每一层的总时间复杂度都是O(n),因为需要对每一个元素遍历一次。而且在最好的情况下,同样也是有logn层,所以快速排序最好的时间复杂度为O(nlogn)

快速排序的最坏情况O(n^2)

下面我们再来说一说快速排序的最坏情况,这种情况就比较好理解了。什么是快速排序的最坏情况,那就是,对于每一个区间,我们在处理的时候,选取的轴刚好就是这个区间的最大值或者最小值。比如我们需要对n个数排序,而每一次进行处理的时候,选取的轴刚好都是区间的最小值。于是第一次操作,在经过调换元素顺序的操作后,最小值被放在了第一个位置,剩余n-1个数占据了2到n个位置;第二次操作,处理剩下的n-1个元素,又将这个子区间的最小值放在了当前区间的第1个位置,以此类推......每次操作,都只能将最小值放到第一个位置,而剩下的元素,则没有任何变化。所以对于n个数来说,需要操作n次,才能为n个数排好序。而每一次操作都需要遍历一次剩下的所有元素,这个操作的时间复杂度是O(n),所以总时间复杂度为O(n^2)

其实上面的过程,我们可以换一个角度理解:每次操作,找出最小值放到剩余区间的第一个位置,这不就是选择排序的实现方式吗?而选择排序的时间复杂度就是O(n^2),所以上面的过程也就O(n^2)

总结

以上内容,就是我基于自己的理解,对快速排序和归并排序时间复杂度的分析。为了更好理解,我的描述都尽可能的详细,所以可能会有点啰嗦,但是我认为还是很通俗易懂的。希望这篇博客能够为之前对这两种排序算法理解不是特别清晰的人提供帮助,同时,若上面的内容存在错误或不足,欢迎指正或补充。

以上就是通俗易懂的C语言快速排序和归并排序的时间复杂度分析的详细内容,更多关于C语言排序时间复杂度的资料请关注编程网其它相关文章!

--结束END--

本文标题: 通俗易懂的C语言快速排序和归并排序的时间复杂度分析

本文链接: https://www.lsjlt.com/news/178387.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 通俗易懂的C语言快速排序和归并排序的时间复杂度分析
    目录快速排序和归并排序的时间复杂度分析——通俗易懂归并排序的时间复杂度分析快速排序的时间复杂度快速排序的最坏情况O(n^2)总结快速排序和归并排序的时间复杂度...
    99+
    2023-01-28
    C语言排序时间复杂度 C语言快速排序归并排序
  • 编程技术中冒泡排序、快速排序和堆排序的时间复杂度是多少
    这篇文章主要介绍编程技术中冒泡排序、快速排序和堆排序的时间复杂度是多少,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!冒泡排序的时间复杂度:最好情况是“O(n)”,最坏情况是“O(n2)”。快速排序的的时间复杂度:最好...
    99+
    2023-06-14
  • C语言非递归算法解决快速排序与归并排序产生的栈溢出
    目录1、栈溢出原因和递归的基本认识2、快速排序(非递归实现)3、归并排序(非递归实现)建议还不理解快速排序和归并排序的小伙伴们可以先去看我上一篇博客​​​​​​哦!C语言超详细讲解排...
    99+
    2024-04-02
  • java实现堆排序以及时间复杂度的分析
    完全二叉树:从上到下,从左到右,每层的节点都是满的,最下边一层所有的节点都是连续集中在最左边。 二叉树的特点就是左子节点是父节点索引值的2倍加一,右子节点是父节点索引值的2倍加二 堆...
    99+
    2024-04-02
  • php中冒泡排序的时间复杂度和空间复杂度是什么
    小编给大家分享一下php中冒泡排序的时间复杂度和空间复杂度是什么,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!冒泡排序的时间复杂度和空间复杂度1、代码实现   ...
    99+
    2024-04-02
  • 分析 Go 语言中的时间复杂度和空间复杂度
    Go 语言是一种越来越流行的编程语言,它被设计成易于编写、易于阅读和易于维护的语言,同时也支持高级编程概念。时间复杂度和空间复杂度是算法和数据结构分析中重要的概念,它们衡量着一个程序的...
    99+
    2024-04-02
  • C语言数据结构的时间复杂度和空间复杂度实例分析
    这篇文章主要讲解了“C语言数据结构的时间复杂度和空间复杂度实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C语言数据结构的时间复杂度和空间复杂度实例分析”吧!一、数据结构前言 ...
    99+
    2023-07-06
  • C语言中关于时间复杂度的示例分析
    本篇文章为大家展示了C语言中关于时间复杂度的示例分析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一、时间复杂度1.什么是时间复杂度?空间效率,时间效率(较为关注)时间复杂度:算法中的操作执行次数,...
    99+
    2023-06-26
  • C语言数据结构之算法的时间复杂度实例分析
    这篇文章主要讲解了“C语言数据结构之算法的时间复杂度实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C语言数据结构之算法的时间复杂度实例分析”吧!1、算法的复杂度算法在编写成可执行程序...
    99+
    2023-06-30
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作