同一个问题可使用不一样的算法解决,那么不一样的算法孰优孰劣如何区分呢?所以咱们须要一个表示方法来表明每一个程序的效率。算法
衡量一个程序好坏的标准,通常是运行时间与占用内存两个指标。数组
不过咱们在写代码的时候确定没法去估量程序的执行时间,由于真实的执行时间受到多方面因素的影响,好比一样一段程序,放在高配服务器上跑和放在低配服务器上跑彻底是两个表现效果,好比遍历一个数组的函数,执行时间彻底取决于调用函数传入的数组大小。服务器
如何在不运行程序的状况下,判断出代码的执行时间呢?显然是不可能的。函数
不过咱们虽然没法预估代码的绝对执行时间,可是咱们能够预估代码基本的执行次数。优化
一段代码的执行时间若是有变化,则必定是受到外部输入的数据所影响,咱们将代码中全部不变的因素,表示为大O,将变化的因素做为基数n,表示为:O(n),大O的意思是忽略重要项之外的内容,咱们常以这种大O表示法来判断比较各类算法的执行效率。spa
接下来我会介绍几种经常使用的复杂度表示方法。code
PS:专业的解释必然全篇都是数学证实,未免太过于复杂,让学者更加迷茫,我这里写的并非教材,而是最直白的理解。blog
本节中例举的各类时间复杂度以好到差依次排序。排序
先看下这个函数:内存
private static void test(int n) { int a = 2; int b = 3; int c = a+b; System.out.println(c); }
一共4行代码,CPU要将a的值写入内存,b的值写入内存,a和b进行计算,将计算结果写入c,最后将c输出到控制台。
尽管计算机内部要作这么多事情,这段代码的时间复杂度依然是O(1),缘由是这几行代码所作的操做是固定的,是不变的因素。
再看下这个函数:
private static void test(int n) { for (int i=0;i<100000;i++){ System.out.println(i); } }
循环10W次,可能你以为功耗可能有点大,不过它的时间复杂度仍然是O(1)。
咱们能够这么固定的认为:不管接收的参数怎么变化,只要代码执行次数是无变化的,则用1来表示。 凡是O(1)复杂度的代码,一般表明着它是效率最优的方案。
广泛性的说法是复杂度减半,就像纸张对折。
示例代码:
private static void test(int n) { for (int j=1;j<=n;j=j*2){ System.out.println(j); } }
这段代码的执行效果并不是是一次折半,它是次次折半,以2为底,不断的进行幂运算,实际上只要有幂指数关系的,无论你的底数是几,只要可以对原复杂度进行求幂逆运算咱们均可以称之为O(log n)
好比:
private static void test(int n) { for (int j=1;j<=n;j=j*3){ System.out.println(j); } }
在忽略系数、常数、底数以后,最后均可以表示为O(log n),只不过咱们遇到的算法几乎不会出现一些极端例外状况,对数时间的所在地常见以二分查找为表明。
咱们将test方法稍稍修改一下:
private static void test(int n) { for (int i=0;i<n;i++){ System.out.println(i); } }
修改以后此次不是执行10W次,而是执行n次,n是由参数传入的一个未知值,在没有真实运行的时候咱们没法判断这个n究竟是多少?由于它能够是任意int型数字,你能够这么认为:在理想的状况下,它的复杂度是O(1),在恶劣的状况下,它的复杂度是无限大。彻底取决于方法调用方。
直白的说,for循环就是循环n次,所以这段代码的时间复杂度为O(n),这种复杂度经常表现为线性查找。
线性对数时间也就是线性时间嵌套对数时间:
private static void t(int n){ for (int i=0;i<n;i++){ test(n); } } private static void test(int n) { for (int j=1;j<=n;j=j*2){ System.out.println(j); } }
t这个方法的时间复杂度就是O(n log n)。
平方时间就是执行程序须要的步骤数是输入参数的平方,最多见的是嵌套循环:
private static void test(int n) { for (int i=0;i<n;i++){ for (int j=n;j>0;j--){ System.out.println(j); } } }
比O(n^2)还要慢的天然有立方级O(n^3)
比O(n^3)更慢慢的还有指数级O(2^n)
慢到运行一次程序要绕地球三百圈的有O(n!)
正常状况下咱们不会接触到这些类型的算法。
所谓空间,就是程序运行占用的内存空间,空间复杂度指的就是执行算法的空间成本。
这里咱们抛一道题来作例子:在一个数组中找出有重复的值,如数组[3,8,13,7,15,8,6,6] 找出8和6。
解法:
private static void test(int[] arr) { for (int i=0;i<arr.length;i++){ for(int j=0;j<i;j++){ if(arr[j] == arr[i]){ System.out.println("找到了:"+arr[i]); } } } }
很显然:时间复杂度为O(n^2)。
那咱们还可使用一种更优的解法:
private static void test(int[] arr) { HashSet hashSet = new HashSet(); for (int i=0;i<arr.length;i++){ if(hashSet.contains(arr[i])){ System.out.println("找到了:"+arr[i]); } hashSet.add(arr[i]); } }
也许你会惊讶的发现,时间复杂度被优化成了O(n)。
虽然时间复杂度下降成了O(n),可是付出的代价是空间复杂度变成了O(n),由于新的解法使用了一个HashSet来存储数据,存储数据天然要占用内存空间,而占用的空间大小彻底取决于传入数组大小。
咱们之因此说第二种解法更优,实际上是一种常规思想,由于现实中绝大部分状况,时间复杂度显然比空间复杂度更为重要,咱们宁愿多分配一些存储空间做为代价,来提高程序的执行速度。
总而言之,比较两个算法优劣的指标有两个,时间复杂度与空间复杂度,优先比较时间复杂度,时间复杂度相同的状况下比较空间复杂度。
最后:感谢阅读。