数据结构与算法
一 时间复杂度与空间复杂度
有关算法时间耗费分析,我们称之为算法的时 间复杂度分析,有关算法的空间耗费分析,我们称之为算法的空间复杂度分析。
1.1 时间复杂度(重点)
取决因素
- 代码质量
- 执行机器的优劣,执行指令的速度
- 算法采用的策略和
- 问题的输入规模
- 。。。。。。
案例:求和1-100的和?
package 算法入门.求和;
/**
* @author shu
* @date 2021/6/7
* @description 普通求和
*/
public class SumText01 {
public static void main(String[] args) {
int sum = 0;
int n=100;
for (int i = 1; i <= n; i++) {
sum += i;
}
System.out.println("sum=" + sum);
}
}
package 算法入门.求和;
/**
* @author shu
* @date 2021/6/7
* @description 高斯算法 an=(an+a1)n/2
*/
public class SumText02 {
public static void main(String[] args) {
int sum = 0;
int n=100;
sum = (n+1)*n/2;
System.out.println("sum="+sum);
}
}
你认为上面两种解法?谁的速度更快?
1.2 分析
如果输入量为n为1,则需要计算1次;
如果输入量n为1亿,则需要计算1亿次;
public static void main(String[] args) {
int sum = 0;//执行1次
int n=100;//执行1次
for (int i = 1; i <= n; i++) {//执行了n+1次
sum += i;//执行了n次
}
System.out.println("sum=" + sum);
}
//时间复杂度O(1)
如果输入量为n为1,则需要计算1次;
如果输入量n为1亿,则需要计算1次;
public static void main(String[] args) {
int sum = 0;//执行1次
int n=100;//执行1次
sum = (n+1)*n/2;//执行1次
System.out.println("sum="+sum);
}
//时间复杂度O(n)
- 我们研究算法复杂度,侧重的是当输入规模不断增大时,算法的增长量的一个抽象(规律),而不是精确地定位需要 执行多少次,因为如果是这样的话,我们又得考虑回编译期优化等问题,容易主次跌倒,我们分析一个算法的运行时间,最重要的 就是把核心操作的次数和输入规模关联起来。
- 别急,我们慢慢来看一下,怎样计算出时间复杂度的?
1.3 测试
- 常数影响
上图可以看出常数对其影响还行很小的
- 输入规模影响
上图可以看出输入规模对其影响还行很小的
- 指数的影响
最高次项的指数大的,随着n的增长,结果也会变得增长特别快
总结
- 算法函数中的常数可以忽略
- 算法函数中最高次幂的常数因子可以忽略
- 算法函数中最高次幂越小,算法效率越高
1.4 大O记法(也适用于函数调用)
- 在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随着n的变化情况并确定T(n)的 量级。算法的时间复杂度,就是算法的时间量度,记作:T(n)=O(f(n))。
- 它表示随着问题规模n的增大,算法执行时间 的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称时间复杂度,其中f(n)是问题规模n的某个函数。
- 在这里,我们需要明确一个事情:执行次数=执行时间 用大写O()来体现算法时间复杂度的记法,我们称之为大O记法。
规则
- 用常数1取代运行时间中的所有加法常数
- 在修改后的运行次数中,只保留高阶项
- 如果最高阶项存在,且常数因子不为1,则去除与这个项相乘的常数
案例
public static void main(String[] args) {
int sum = 0;//执行1次
int n=100;//执行1次
sum = (n+1)*n/2;//执行1次
System.out.println("sum="+sum);
}
//时间复杂度:3次 O(1)
public static void main(String[] args) {
int sum = 0;//执行1次
int n=100;//执行1次
for (int i = 1; i <= n; i++) {
sum += i;//执行了n次
}
System.out.println("sum=" + sum);
}
//时间复杂度 n+3次 O(n)
public static void main(String[] args) {
int sum=0;//执行1次
int n=100;//执行1次
for (int i = 1; i <=n ; i++) {
for (int j = 1; j <=n ; j++) {
sum+=i;//执行n^2次
}
}
System.out.println("sum="+sum);
}
//时间复杂度 n^2+2次 O(n^2)
1.5 注意
- 我们会发现,从平方阶开始,随着输入规模的增大,时间成本会急剧增大,所以,我们的 算法,尽可能的追求的是
O(1),O(logn),O(n),O(nlogn)
这几种时间复杂度,而如果发现算法的时间复杂度为平方阶、 立方阶或者更复杂的,那我们可以分为这种算法是不可取的,需要优化。
1.6 空间复杂度(了解)
计算机的软硬件都经历了一个比较漫长的演变史,作为为运算提供环境的内存,更是如此,从早些时候的512k,经 历了1M,2M,4M...
等,发展到现在的8G
,甚至16G
和32G
,所以早期,算法在运行过程中对内存的占用情况也是 一个经常需要考虑的问题。我么可以用算法的空间复杂度来描述算法对内存的占用。
算法的空间复杂度计算公式记作:S(n)=O(f(n)),其中n为输入规模,f(n)为语句关于n所占存储空间的函数。
案例:反转数组
public static int[] reverse1(int[] arr){
int n=arr.length;//申请4个字节
int temp;//申请4个字节
for(int start=0,end=n-1;start<=end;start++,end--){
temp=arr[start];
arr[start]=arr[end];
arr[end]=temp;
}
return arr;
}
public static int[] reverse2(int[] arr){
int n=arr.length;//申请4个字节
int[] temp=new int[n];//申请n*4个字节+数组自身头信息开销24个字节
for (int i = n-1; i >=0; i--) {
temp[n-1-i]=arr[i];
}
return temp;
}
- 根据大O推导法则,算法一的空间复杂度为O(1),算法二的空间复杂度为O(n),所以从空间占用的角度讲,算法一要 优于算法二。
- 由于
java
中有内存垃圾回收机制,并且jvm
对程序的内存占用也有优化(例如即时编译),我们无法精确的评估一 个java
程序的内存占用情况,但是了解了java
的基本内存占用,使我们可以对java
程序的内存占用情况进行估算。 - 我们可以不用太注意空间复杂度