文章目录

  • 排序算法的时间复杂度
  • 二叉树与nlog2nnlog_2^nnlog2n​
  • 快速排序的大致复杂度分析
  • 进一步的复杂度分析
    • 最坏情况worst case
    • 最佳情况best case
  • 用表达式计算更加精确的复杂度
    • 分析
    • 测试
    • 图像

排序算法的时间复杂度

时间复杂度的本质就是一个函数f(x)=y,其中y是时间,x是被操作的元素数量,分析的是随着x元素的增加,计算机所需计算时间y的变化。时间复杂度的写法是O(x)O(x)O(x),x是元素数量,O返回的是时间,时间复杂度不会追求计算机完成该算法的精确时间,也没办法计算,例如计算机对于不同的数据类型例如float或int所需的计算时间肯定是不同的,所以O()只是对一种xy线性关系粗糙的描述。

绝大部分的排序算法的平均时间复杂度就是两种,慢的O(n2)O(n^2)O(n2)或是快的O(n∗log2n)O(n*log_2^n)O(n∗log2n​)。O(n2)O(n^2)O(n2)数学的名字叫quadratic time平方时间 ,从代码的角度看就是两个for循环,比如说n=52=25n=5^2=25n=52=25,那么两个五次for循环正好是25次循环,下面要研究的是O(n∗log2n)O(n*log_2^n)O(n∗log2n​),linearithmic time线性对数时间。

二叉树与nlog2nnlog_2^nnlog2n​

假设n为一个2的倍数,它可以以二叉树的形式不断向下二分,设这个树的深度为k,可以看出每一层的节点内的数的和都是n,那么整个树所有节点的合为n*k

[图1]
从上图中可以看出每个节点内的数量还可以用12a∗n\frac{1}{2^a}*n2a1​∗n来表示,观察最底层可得出:
1=12k−1∗n1=\frac{1}{2^{k-1}}*n1=2k−11​∗n
既是
2k−1=n2^{k-1}=n2k−1=n
根据对数公式:
k−1=log2nk-1=log_{2}^{n}k−1=log2n​
k=log2n+1k=log_{2}^{n}+1k=log2n​+1
已知sum=n∗ksum=n*ksum=n∗k所以:
sum=n∗(log2n+1)=nlog2n+nsum=n*(log_{2}^{n}+1)=nlog_{2}^{n}+nsum=n∗(log2n​+1)=nlog2n​+n
分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,当用二分法进行问题分解时,这里nlog2nnlog_{2}^{n}nlog2n​可以代表在最完美的二分情况下的各个子问题的和。代入进O(nlog2n+n)O(nlog_{2}^{n}+n)O(nlog2n​+n),最后的n按O()约定可以当常数约掉(另外很多算法里当问题的规模只有1时计算就已经结束了,O(nlog2n)O(nlog_{2}^{n})O(nlog2n​)反而更加精准)。下面分析一个具体的nlogn算法。

快速排序的大致复杂度分析

本文分析的案例是快速排序,快排根据如何对数组进行划分再次递归有一些不同的版本,在一些case上的复杂度会有些区别,以下是称为Hoare分区法的C#版本
C#代码:

    void Quicksort(int[] arr, int low, int high){if (low < high){int pivot = arr[(low + high) / 2];int left = low - 1;int right = high + 1;while (true){do{left++;} while (arr[left] < pivot);do{right--;} while (arr[right] > pivot);if (left >= right){pivot = right;break;}//交换int leftCopy = arr[left];arr[left] = arr[right];arr[right] = leftCopy;}Quicksort(arr, low, pivot);Quicksort(arr, pivot + 1, high);}}

参数arr是被处理的数组,low是0,high是arr.length-1,从代码可以看出只要输入元素大于两个,那么Quicksort就会进行递归,Sort函数返回的pi可以看做是一个锚点,它将arr划分为两个部分,交给Quicksort分别处理,如果将Sort的递归调用画为一个二叉树,pi就决定了二叉树的分叉情况

(图1:将Sort递归调用次数画为二叉树)

上图假设arr的长度为10,每个节点代表一次Quicksort调用,节点内数字代表当前函数调用需要处理的数组元素数量,当元素等于1时不会调用。上文已经分析越是完美二分的二叉树,它的深度越接近logn+1,由于这里把1的叶子节点删除了,所以这里的深度k变为了logn。左面的二叉树高度非常接近log210=3.32log_2^{10}=3.32log210​=3.32。从上图可以看出越是一个平衡的二叉树它需要处理的元素越少,右边那种极端非平衡的树需要处理的元素总数是最多的。但是该算法的复杂度不只和Quicksort函数的调用次数有关,更主要的是函数内部的操作数量,实际的复杂度还要具体分析。但是上图还是可以反映出一个一般趋势,既是良好平衡的划分有助于降低复杂度。

进一步的复杂度分析

最坏情况worst case

Quicksort函数最主要的操作是数组遍历与数组交换,left与right从arr数组两边向中央移动遍历直到相交,在移动过程中如果发现有需要交换的数就进行交换,当left与right相交后则返回right作为pi锚点将数组一分为二进行递归。

每次数组元素交换行为都会导致right向左移动一位,如果说图1右方的二叉树是最坏情况,那么对于1个长度为10的arr,它的具体行为就是:
left左移10次
right右移1次
交换1次
交换有三行代码,总操作次数为10+1+3=14。
但是考虑一个10个相同数的数组:
left左移5
right右移6次
交换5次
5+6+5*3=26次操作。由于等值数组的二分是完全平衡的,n的大小与交换次数成等比例关系,在n>100时,全等数组就是该算法的worst case。(实测当n不是很大的时候,有些特殊形式的数组操作数量会超过全等值数组例如aaaabaaaab,a<b)。

(图2:Quciksort处理等值数组时的具体行为)

(图3:Quciksort处理等值数组时的具体行为)

(图4:Quciksort处理等值数组时的具体行为)

最佳情况best case

从上文已知交换次数会导致复杂度上升,平衡二分会导致复杂度下降。那么一个不需要交换的平衡二叉树既是best case。它就是一个已经排好序的数组,例如1,2,3,4,5,6,7,8,9,10。不需要任何交换并且每次都是在中央二分。

用表达式计算更加精确的复杂度

分析

首先要更加仔细的剖析代码,给各个操作划分case计数。
Unity测试脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class QuickSortBlog : MonoBehaviour {//计数器struct counter{public int operations;   //操作计数public int qsCalls;      //Quicksort方法调用次数public int qsValidCalls; //有效的Quicksort方法调用次数(操作数大于2)public int swapCount;    //交换次数public int moveCount;    //移动次数};counter c;counter randomMaxC;int[] randomMaxArr;// Use this for initializationvoid Start () {c=new counter();c.operations = 0;c.qsCalls = 0;c.qsValidCalls = 0;c.swapCount = 0;c.moveCount = 0;int[] arr = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};Quicksort(arr, 0, 9);Debug.Log("successive numbers,operations:" + c.operations + ",qsCalls:" + c.qsCalls + ",qsValidCalls:" + c.qsValidCalls + ",swapCount:" + c.swapCount+",moveCount:"+c.moveCount);c.operations = 0;c.qsCalls = 0;c.qsValidCalls = 0;c.swapCount = 0;c.moveCount = 0;arr = new int[10] { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8};Quicksort(arr, 0, 9);Debug.Log("same numbers,operations:" + c.operations + ",qsCalls:" + c.qsCalls + ",qsValidCalls:" + c.qsValidCalls + ",swapCount:" + c.swapCount + ",moveCount:" + c.moveCount);/*Quicksort处理aaaabaaaab形式数组有更差的性能表现c.total = 0;c.qsCall = 0;c.validQSCall = 0;c.swapCall = 0;c.move = 0;temp = new int[10] { 6,6,6,6,8,6,6,6,6,8 };Quicksort(temp, 0, 9);Debug.Log("aaaabaaaab,numbers:" + c.operations + ",qsCalls:" + c.qsCalls + ",qsValidCalls:" + c.qsValidCalls + ",swapCount:" + c.swapCount + ",moveCount:" + c.moveCount);*///10000次随机测试randomMaxC = new counter();randomMaxC.operations = 0;randomMaxC.qsCalls = 0;randomMaxC.qsValidCalls = 0;randomMaxC.swapCount = 0;randomMaxC.moveCount = 0;for (int i = 0; i < 100000;i++){RandomDebug();}Debug.Log("random numbers,operations:" + randomMaxC.operations + ",qsCalls:" + randomMaxC.qsCalls + ",qsValidCalls:" + randomMaxC.qsValidCalls + ",swapCount:" + randomMaxC.swapCount+ ",moveCount:" + randomMaxC.moveCount);for (int i = 0; i < 10; i++){Debug.Log(randomMaxArr[i].ToString());}}//随机数组生成与测试void RandomDebug(){c.operations = 0;c.qsCalls = 0;c.qsValidCalls = 0;c.swapCount = 0;c.moveCount = 0;int[] debugArray = new int[10];int[] copyArray = new int[10];for (int i = 0; i < 10;i++){debugArray[i] = Random.Range(1, 11);copyArray[i] = debugArray[i];}Quicksort(debugArray, 0, 9);SelectRandomMax(copyArray);}//worst case筛选void SelectRandomMax(int[] arr){if(c.operations>randomMaxC.operations){randomMaxArr = arr;randomMaxC.operations = c.operations;randomMaxC.qsCalls = c.qsCalls;randomMaxC.qsValidCalls = c.qsValidCalls;randomMaxC.swapCount = c.swapCount;randomMaxC.moveCount = c.moveCount;}}void Quicksort(int[] arr, int low, int high){c.qsCalls++;c.operations++;if (low < high){c.qsValidCalls++;int pivot = arr[(low + high) / 2];int left = low - 1;int right = high + 1;c.operations+=3;while (true){do{left++;c.operations++;c.moveCount++;} while (arr[left] < pivot);do{right--;c.operations++;c.moveCount++;} while (arr[right] > pivot);if (left >= right){pivot = right;c.operations += 2;break;}c.swapCount++;c.operations++;//交换int leftCopy = arr[left];arr[left] = arr[right];arr[right] = leftCopy;c.operations+=3;}Quicksort(arr, low, pivot);Quicksort(arr, pivot + 1, high);c.operations+=2;}}
}

结合上面代码与图1可以归纳出以下几点,当arr长度为n时:
worst case最差情况:
1,有效调用(arr长度大于2)Quicksort方法n-1次。每次调用与之相关的操作有8次。
2,无效调用Quicksort方法n次。每次调用与之相关的操作有1次。
3,问题分治的二叉树高度基本等于logn。
4,在二叉树每一层,数组元素交换相关操作次数根据奇偶数case平均后等于n+1.5。
5,在二叉树每一层,数组遍历相关操作次数根据奇偶数case平均后等于(n/2+0.25)*4。

best case最佳情况:
1,2,3,5同上。第4条数组元素交换次数为0。

结合以上情况可得出表达式:
worst case:
f(n)=(n−1)∗8+n+log2n∗((n2+0.25)∗4+(n+1.5))f(n)=(n-1)*8+n+log_2^n*((\frac{n}{2}+0.25)*4+(n+1.5))f(n)=(n−1)∗8+n+log2n​∗((2n​+0.25)∗4+(n+1.5))
best case:
f(n)=(n−1)∗8+n+log2n∗(n+1.5)f(n)=(n-1)*8+n+log_2^n*(n+1.5)f(n)=(n−1)∗8+n+log2n​∗(n+1.5)

测试

1,n=10
最佳情况,连续数字
Unity脚本:operations=125
表达式计算结果:f(n)=120.2

最差情况,相同数字
Unity脚本:operations=190
表达式计算结果:f(n)=189.96

2,n=100
最佳情况,连续数字
Unity脚本:operations=1663
表达式计算结果:f(n)=1566.35

最差情况,相同数字
Unity脚本:operations=2986
表达式计算结果:f(n)=2901.7

3,n=1000
最佳情况,连续数字
Unity脚本:operations=19967
表达式计算结果:f(n)=18972.3

最差情况,相同数字
Unity脚本:operations=40582
表达式计算结果:f(n)=38914.27

表达式计算出的值与真实数字还是有一些误差,主要是二叉树深度那块的处理方案还是有问题。

图像

有了表达式就可以画图了。红线是O(n2)O(n^2)O(n2),蓝线是O(nlogn)O(nlogn)O(nlogn),灰色区域是best case和worst case表达式的积分面积,该算法所有case的复杂度都在这范围内

(图5:n<100)

(图5:n<1000)

(图5:n<50000)


参考:
Quicksort–hoare partition scheme:https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme
时间复杂度 O(log n) 意味着什么?:https://juejin.im/entry/593f56528d6d810058a355f4
A Gentle Introduction to Algorithm Complexity Analysis: https://discrete.gr/complexity/


维护日志:
2020-2-5:修改整理

时间复杂度-线性对数时间nlogn的一些研究相关推荐

  1. pde与波长 sipm 关系_基于SiPM和TCMPC的时间分辨拉曼散射测量技术研究

    引用本文 苗泉龙, 代雷, 李佰成, 赵天琦, 梁琨, 杨茹, 韩德俊. 基于SiPM和TCMPC的时间分辨拉曼散射测量技术研究[J]. 光谱学与光谱分析, 2018,38(5): 1444-1450 ...

  2. 用MATLAB app designer设计人机交互界面——二阶线性动态电路可视化分析的研究

    用MATLAB app designer设计人机交互界面--二阶线性动态电路可视化分析的研究 这是我第一次尝试写博客,我试着给出电路课上要求的电路实验编程.但是电路的类型有点儿多,所以我只以二阶动态电 ...

  3. matlab死亡时间推测实验,死后间隔时间推断的新研究.pdf

    死后间隔时间推断的新研究.pdf 还剩 100页未读, 继续阅读 下载文档到电脑,马上远离加班熬夜! 亲,很抱歉,此页已超出免费预览范围啦! 如果喜欢就下载吧,价低环保! 内容要点: 简称PMIETS ...

  4. 线性连续时间状态空间模型的离散化及实例

    线性连续时间状态空间模型的离散化(Discretization of Linear Continuous-Time State-Space Models) 1 .状态空间模型 非线性连续时间状态空间模 ...

  5. 员工时间管理系统市场现状研究分析报告-

    辰宇信息咨询市场调研公司最近发布-<022-2028中国员工时间管理系统市场现状研究分析与发展前景预测报告> 内容摘要 本文研究中国市场员工时间管理系统现状及未来发展趋势,侧重分析在中国市 ...

  6. 三篇ICLR2022与时间图序列相关的研究工作

    时序图学习研究多了||三篇ICLR与时间图序列相关的研究工作 1. 时空图神经网络 title: Space-Time Graph Neural Networks 作者:Samar Hadou, Ch ...

  7. 经过一段时间的努力和研究,开心农场外挂助手终于小有成就(欢迎大家与我交流)

    经过一段时间的努力和研究,开心农场外挂助手终于小有成就 注:最近校内更新比较频繁,好像要反外挂......... 一.使用说明 1.打开config.ini改成自己的配置 2.出现sessionKey ...

  8. O(logn)对数时间求中位数

    目录 中位数 引入问题 代码实现 中位数 偶数个数升序序列的中位数 = (n/2 + n/2 +1)/2 奇数个数升序序列的中位数 = n/2 引入问题 有两个升序的数组,长度分别是m和n,求两个数组 ...

  9. 算法复杂度(时间频度,时间复杂度介绍计算,空间复杂度)

    算法的时间复杂度 度量一个程序(算法)执行时间的两种方法 事后统计的方法(直接运行看花了多长时间) 这种方法可行, 但是有两个问题:一是要想对设计的算法的运行性能进行评测,需要实际运行该程序:二是所得 ...

最新文章

  1. 微软在.NET官网上线.NET 架构指南频道
  2. FZU 1649 Prime number or not (Miller-Rabin素数测试)
  3. Mapped Statements collection does not contain value for 之运行异常原因
  4. Cacti auth.php,Cacti微信企业号图文报警
  5. JSF请求处理过程(一) FacesServlet初始化
  6. 巧妙解决Windows 7系统中软件乱码问题
  7. oracle byte 转string,C# 中 byte 转化成string
  8. 浏览器-09 javascript引擎和Chromium网络栈
  9. 《App后台开发运维和架构实践》资源汇总
  10. 关于wintc编译成功,输出黑框中无结果显示
  11. 开发软件费用为什么这么贵?
  12. 有一群海盗(不多于20人),在船上比拼酒量。过程如下:打开一瓶酒,所有在场的人平分喝下, 有几个人倒下了。再打开一瓶酒平分,又有倒下的,再次重复...... 直到开了第4瓶酒,坐着的已经所剩无
  13. VS2017 出现Miscellaneous Files
  14. 最好的五款骨传导耳机推荐,双十一必入骨传导蓝牙耳机
  15. 第一章 Java特性
  16. ChatGPT软件技术栈解密
  17. PreCreateWindow作用
  18. 微信小程序踩坑记录 ------- canvas 生成带小程序码的微信朋友圈分享图
  19. Openwrt 官方镜像下载安装
  20. ACM模式下输入输出写法 Java版本

热门文章

  1. Java中使用JNA实现全局监听Linux键盘事件
  2. 队列 开源 php,消息队列 - 基于think-queue消息队列 – 基于ThinkPHP和Bootstrap的极速后台开发框架...
  3. python画图显示中文乱码_解决Python pandas plot输出图形中显示中文乱码问题
  4. php上传图片并显示代码,php图片上传代码(完整版已测试)
  5. 生成式模型与判别式模型—大厂笔试汇总
  6. 蒸汽管道图纸符号_库尔勒蒸汽连续动疏水装置(架空)(长输低能耗)
  7. sap甲方_带你走进SAP项目实施过程——前言
  8. linux transmission,Linux下使用Transmission新版
  9. android 销毁按钮,Android实现所有Activity全部销毁
  10. opencv android模版匹配,Opencv for android 模板匹配