你们好,我是练习java两年半时间的南橘,从一名连java有几种数据结构都不懂超级小白,到如今懂了一点点的进阶小白,学到了很多的东西。知识越分享越值钱,我这段时间总结(包括从别的大佬那边学习,引用)了一些日常学习和工做中的重点(自我认为),但愿给你们带来一些帮助java
由于最近在学习软件设计师、正巧赶上了概念性的算法题。由于以前学习并不系统的缘由,虽然能作题,可是却不是很是了解算法中时间复杂度。本着研究学习的心理,这几天就开始研究算法中的时间复杂度,还真学到了一些东西。程序员
在计算机科学中,时间复杂性,又称时间复杂度,算法的时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个表明算法输入值的字符串的长度的函数。时间复杂度经常使用大O符号表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,亦即考察输入值大小趋近无穷时的状况。算法
Landau符号(大O符号)实际上是由德国数论学家保罗·巴赫曼(Paul Bachmann)在其1892年的著做《解析数论》首先引入,由另外一位德国数论学家艾德蒙·朗道(Edmund Landau)推广。Landau符号的做用在于用简单的函数来描述复杂函数行为,给出一个上或下(确)界。在计算算法复杂度时通常只用到大O符号,Landau符号体系中的小o符号、Θ符号等等比较不经常使用。这里的O,最初是用大写希腊字母,但如今都用大写英语字母O;小o符号也是用小写英语字母o,Θ符号则维持大写希腊字母Θ。数组
因此,在其余条件不变的状况下,选择时间复杂度低的算法更有利于提升程序的效率。你们不要以为算法这东西没有用,以为是顶尖的程序员才用获得。其实咱们在工做中,要常常根据状况新增许多工具类,这些解决具体问题的工具类每每又要涉及到递归、循环、排序、动态规划等问题。对于咱们来讲,可以实现功能就够了,可是若是能进一步地下降这些工具类的时间复杂度,或许能让咱们感受到本身的价值吧。数据结构
常见的时间复杂度有:常数阶O(1),对数阶O(log2n),线性阶O(n), 线性对数阶O(nlog2n),平方阶O(n²), k次方阶O(n^k),指数阶O(2^n)。随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。
函数
int a = 1; int b = 2; int c = 3;
咱们假定每执行一行代码所须要消耗的时间为1个时间单位,那么以上3行代码就消耗了3个时间单位。O(1)的1表明的是常数,常数阶的算法的复杂度是不会随着问题规模的增大而增大,这样的代码无论有多少行,均可以用O(1)来表示它的时间复杂度。工具
int i = 1; while(i < n) { i = i * 2; }
从数学上咱们能够很简单的看出它的函数:性能
2^f(n)<=n,因此f(n)<=log2n学习
每次循环的时候 i都会乘2,那么总共循环的次数就是log2n,所以这个代码的时间复杂度为O(log2n)。可是,底数如何对于程序运行的效率来讲并不重要,就和以前的常数阶同样,常数部分则忽略,一样的,若是不一样时间复杂度的倍数关系为常数,那也能够近似认为二者为同一量级的时间复杂度。测试
若是这样很差理解,咱们能够用二叉树来表示。若是二叉树的是以红黑树等平衡二叉树实现的,则n个节点的二叉排序树的高度为 log2n+1 ,其查找效率为O(Log2n),近似于折半查找。
for(i = 0; i <n; i++) { int a =1; int b=1; int c =a+b; }
这段代码会执行多少次呢?若是从代码上来看,每一行都会执行n次(或者n-1次),因此最后会执行 T(n)=n+3(n-1)=4n-3次。
咱们知道:大O符号表示法并非用于来真实表明算法的执行时间的,它是用来表示代码执行时间的增加变化趋势的。因此线性阶O(n)的时间复杂度实际上是O(n);
for(m = 1; m < n; m++) { i = 1; while(i < n) { i = i * 2; } }
线性对数阶O(nlogN) 就很是很是容易理解了,将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n*O(logN)。
for(i = 1; i <= n; i++){ for(j = 1; j <= n; j++) { j = i; j++; } }
把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²) 了。
参考上面的O(n²) 去理解就行了,O(n³)至关于三层n循环,O(n^k)就是k层循环。
一个程序的空间复杂度是指运行完一个程序所需内存的大小。与时间复杂度相相似的,利用程序的空间复杂度,能够对程序的运行所须要的内存多少有个预先估计。一个程序执行时除了须要存储空间和存储自己所使用的指令、常数、变量和输入数据外,还须要一些对数据进行操做的工做单元和存储一些为现实计算所需信息的辅助空间。程序执行时所需存储空间包括如下两部分。
-1 、固定部分:这部分空间的大小与输入/输出的数据的个数多少、数值无关,主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间,这部分属于静态空间。
-2 、可变空间:这部分空间的主要包括动态分配的空间,以及递归栈所需的空间等,这部分的空间大小与算法有关。一个算法所需的存储空间用f(n)表示。S(n)=O(f(n)),其中n为问题的规模,S(n)表示空间复杂度。
int i = 1; int j = 1; int k = i + j;
若是算法执行所须要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1)。
i、j、k所分配的空间都不随着处理数据量变化,所以它的空间复杂度 S(n) = O(1)。
int[] m = new int[n] for(i = 0; i <n; i++) { int a =1; int b=1; int c =a+b; }
这段代码的第一行new了一个数组出来,这个数据占用的大小为n,后面虽然有循环,但没有再分配新的空间,所以,这段代码的空间复杂度主要看第一行便可,即 S(n) = O(n),同时时间复杂度也是O(n)。
间复杂度取决于额外建立的数组m,若是使用二维数组 new int[n][m] ,则空间复杂度是 O(n*m)
对于相同的输入规模,数据分布不相同也影响了算法执行路径的不一样,所以所须要的执行时间也不一样。根据不一样的输入,将算法的时间复杂度分析分为3种状况。
一、最佳状况。使算法执行时间最少的输入。通常状况下,不进行算法在最佳状况下的时间复杂度分析。如已经证实基于比较的排序算法的时间复杂度下限为O(nlog2n),那么就不须要白费力气去千方百计将该类算法改进为线性时间复杂度的算法。
二、最坏状况。使算法执行时间最多的输入。通常会进行算法在最坏时间复杂度的分析,由于最坏状况是在任何输入下运行时间的一个上限,它给咱们提供一个保障,实际状况不会比这更糟糕。另外,对于某些算法来讲,最坏状况仍是至关频繁的。并且对于许多算法来讲,平均状况一般与最坏状况下的时间复杂度同样。
三、平均状况。算法的平均运行时间,通常来讲,这种状况很难分析。举个简单的例子,现要排序10个不一样的整数,输入就有10!种不一样的状况,平均状况的时间复杂度要考虑每一种输入及其该输入的几率。平均状况分析能够按如下3个步骤进行:
算法很重要的一点就是时间换空间或者空间换时间。
当追求一个较好的时间复杂度时,可能会使空间复杂度的性能变差,便可能致使占用较多的存储空间。
反之,求一个较好的空间复杂度时,可能会使时间复杂度的性能变差,便可能致使占用较长的运行时间。
另外,算法的全部性能之间都存在着或多或少的相互影响。所以,当设计一个算法(特别是大型算法)时,要综合考虑算法的各项性能,算法的使用频率,算法处理的数据量的大小,算法描述语言的特性,算法运行的机器系统环境等各方面因素,才可以设计出比较好的算法。