算法是一段执行的程序, 能够理解成几行代码,或者一个方法; 算法的时间复杂度是指这段代码须要消耗的时间资源;算法的空间复杂度是指这段代码须要消耗的空间资源(空间资源一般是指占用的内存)。java
一般咱们在讨论一个算法时会说,这个算法时间复杂度是O(), 那个O(
)。而这个O(
)、O(
)就是大O复杂度表示法。这个
和
具体是怎么来的呢,下面简单举个例子:算法
int cal(int n) {
int sum = 0; (1)
for(int i = 1;i <= n; ++i){ (2)
sum = sum + i; (3)
} (4)
return sum;
}
复制代码
上面的代码是计算 1, 2, 3… n 的和一个方法,咱们假设每一行代码cpu的时间都是相同的,每一行代码执行时间为run_time, 那这段代码的总执行时间 T(n) 是多少 ? 第1行为run_time, 第2行和第3行代码循环n次,时间为2 * n * run_time。全部总时间:数组
T(n) = (2 * n + 1)* run_time
复制代码
根据规律,能够总结成一个公式:bash
T(n) = O (f(n))
复制代码
因此, 上面的O()、O(
)例子中,
以及
。
中run_time是常数, 当n足够大, 公式中的低阶、常量、系数均可以忽略不计,全部这段计算 1, 2, 3… n总和的代码时间复杂度是O(
)。性能
常见时间复杂度有:测试
下面在举个空间复杂度的例子:spa
void print(int n){
int i = 0; (1)
int[] arr = new int[n]; (2)
int j = 0; (3)
}
复制代码
第1行和第3行代码中都是分别申请了一个空间存储变量, 都有数据规模n无关,全部能够忽略,第2行中申请了数量为n的数组空间。全部整段代码的空间复杂度是。.net
常见空间复杂度有: 3d
首先,对比几个算法的好坏须要结合使用场景,好比数据量,内存,数据特征等。一样一段代码,在不一样机器上运行速度也是不一样的。一般比较几个算法的性能,就须要在同一个机器,同一批数据,同一个环境下去运行对比测试它们所须要的运行时间。code
可是,但开发者是使用或者选择一个算法时,一般是没办法精准的对比算法的优劣,而且时间成本也不容许。全部咱们须要一个不用具体数据来测试, 就能够粗略估算出算法的执行效率, 这就是算法的时间复杂度与空间复杂度做用啦。
什么状况下,一段代码会出现有最好、最坏的状况呢; 举个例子, 好比在一个长度为n的整数数组中去找一个数,找到就立马返回数组的下表。在循环查找的过程当中,最好的状况就是数组第一个就要找的,全部最好状况时间复杂度为; 若是数组最后一个是要找的, 这时就是最坏的状况,最坏状况时间复杂度为
。
像上面一个例子,在一个长度为n的整数数组中去找一个数, 有最好和最坏状况,最好、最坏状况时间复杂度没法衡量一段代码的执行效率,这时就须要用到平均状况时间复杂度。平均=全部状况总和/总次数。
那么平均状况时间复杂度怎么算出来呢,要找的数要么在数组里,要么不在,假设在与不在的几率是, 查找的数出如今0 ~ n-1 的位置几率是同样的,为
, 全部查找的数在数组里几率为
。总过程为:
去掉常量,复杂度为O(n)。
均摊时间复杂度就是一种特殊的平均时间复杂度,只能在特殊的场景才能是使用。很少说,举个例子,会java的对容器arraylist不陌生吧,arraylist有个默认长度的数组,add方法里,若是数组长度不够,是须要扩容的。下面以一段add方法伪代码为例:(为了代码简单易懂,以添加int为例)
int arr[] = new int[10]; //初始默认长度为10
int len = 10;
int index = 0;
void add(int element){
if(index >= len){ // 超出长度,数组须要扩容,并复制值到一个新数组里
final int newLen = len * 2;
int new_arr[] = new int[newLen]; //申请2倍的数组
for (int i = 0; i < len; ++i){
new_arr[i] = arr[i];
}
arr = new_arr;
len = newLen;
}
arr[index] = element;
++index;
}
复制代码
当不扩容时, 这个add方法时间复杂度就是, 当扩容时, if里面复制到新数组须要遍历一次,
int arr[] = new int[10]; //初始默认长度为10
int len = 10;
int index = 0;
void add(int element){
if(index >= len){ // 超出长度,数组须要扩容,并复制值到一个新数组里
final int newLen = len * 2;
int new_arr[] = new int[newLen]; //申请2倍的数组
for (int i = 0; i < len; ++i){
new_arr[i] = arr[i];
}
arr = new_arr;
len = newLen;
}
arr[index] = element;
++index;
}
复制代码
当不扩容时, 这个add方法时间复杂度就是, 当扩容时, if里面复制到新数组须要遍历一次,
int arr[] = new int[10]; //初始默认长度为10
int len = 10;
int index = 0;
void add(int element){
if(index >= len){ // 超出长度,数组须要扩容,并复制值到一个新数组里
final int newLen = len * 2;
int new_arr[] = new int[newLen]; //申请2倍的数组
for (int i = 0; i < len; ++i){
new_arr[i] = arr[i];
}
arr = new_arr;
len = newLen;
}
arr[index] = element;
++index;
}
复制代码
当不扩容时, 这个add方法时间复杂度就是O(1), 当扩容时, if里面复制到新数组须要遍历一次,数组默认长度假设是n, 扩容时的时间复杂度是O(n)。平均状况时间复杂度是多少呢,若是像上一个例子用几率论去计算也是能够,可是麻烦。咱们假设须要添加11个数,添加第11个数时须要扩容,循环10次把前10个数复制到新的数组中而后把第11个数添加进去。添加前10个数时间复杂度都是O(1), 若是把扩容时循环10次分摊到添加前10个数操做中,那么添加前10个数操做都只是都运行了一行代码而已,仍是常量级别的,全部这个add方法的平均时间复杂度就是O(1)。 经过分摊分析时间复杂度就叫作均摊时间复杂度。