个人简介
- 大家好,我是翰慧腾。一名正在努力学JAVA的大一小白,本文章为初学的笔记,希望各位多多指教。💙
- 欢迎点赞+收藏+留言💜
- 一个人想要创业,先要学会打工🧡
算法的时间复杂度分析:
1.事后分析估算方法:就是用我们之前学的计时方法来计算程序执行所花费的时间。
程序输出100个数所花费是时间:
public class Demo_ {
public static void main(String[] args) {
long start=System.currentTimeMillis();
for(int i=0;i<100;i++) {
System.out.println(i);
}
long end=System.currentTimeMillis();
System.out.println(end-start);//得出程序输出100个数所花费的时间
}
}
2.事前分析估计方法:算法采用的策略和方案、编译产生的代码质量、问题的输入规模、机器执行指令的速度都会影响程序运行的时间。其中我们可以干预的是算法的策略方案和问题的输入规模,其余两个是我们程序员无法控制的。此时的时间复杂度就是一个执行次数与输入规模的函数,而函数类型就与实际情况有关了。
结论:随着输入规模的增加,算法的常数操作可以忽略不计
结论:随着输入规模的增大,与最高次项相乘的常数可以忽略
结论:最高次项的指数大的,随着n的增长,结构变化非常快。(类似高中学的指数型函数,爆炸性增长函数)。
结论:算法函数中n最高次幂越小,算法效率越高。
在算法函数中,跟在后面的常数通常是可以忽略的,最高次幂的常数因子可以忽略;算法函数中最高次幂越小,算法效率越高。
算法时间复杂度的表示方法:大O记法
定义:在执行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随着n的变化情况并确定T(n)的量级,算法的时间复杂度就是算法的时间量度,记作:T(n)=O(f(n))。它表示随着问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称时间杂度,其中f(n)是问题规模n的某个函数。执行次数=执行时间
大O记法的规则:1.用常数1取代运行时间中的所有加法常数
2.在修改后的运行次数中,只保留高阶项
3.如果最高阶项存在,且常数因子不为1,则去除与这个项相乘的常数
常见的大O阶:
- 线性阶:
public class Demo_ {
public static void main(String[] args) {
int n=100;
for(int i=0;i<=n;i++) {
System.out.println(i);
}
}
}
上述循环体中的代码需要执行n次,循环的时间复杂度为O(n)
- 平方阶:
public class Demo_ {
public static void main(String[] args) {
int n=100;
int sum=0;
for(int i=0;i<=n;i++) {
for(int j=0;j<=n;j++) {
sum+=i;
}
}
System.out.println(sum);
}
}
在程序中,外层循环执行一次,内层循环就要执行n次。内部循环体就要执行n*n次。那么这段代码的时间复杂度是O(n^2).立方阶同上
- 对数阶:
public class Demo_ {
public static void main(String[] args) {
int n=100;
int i=1;
while(i<=100) {
i*=2;
}
i每次进到循环体内都会乘以2,假设循环体执行了x次,则就有x个2相乘。由于是2^x=n。x=log(2)n。这个循环的时间复杂度为O(log(n))。
常数阶:常数阶通常是不涉及到循环的,操作次数与n无关。通常时间复杂度记为O(1)。
上述的复杂程度从低到高为:O(1)<O(log n)<O(n)<O(n log n)<O(n^2)<O(n^3).越复杂耗费的时间就越高。
函数调用的时间复杂度分析
public class Demo_ {
public static void main(String[] args) {
int n=100;
for(int i=0;i<n;i++) {
show(i);
}
}
static void show(int i) {
// TODO 自动生成的方法存根
System.out.println(i);
}
}
在main方法内,有一个for循环,循环体内调用了show方法,由于show方法只有一行代码,所以show方法的时间复杂度为O(1),那么main方法的时间复杂度就是O(n)。
public class Demo_ {
public static void main(String[] args) {
int n=100;
for(int i=0;i<n;i++) {
show(i);
}
}
static void show(int i) {
// TODO 自动生成的方法存根
for(int j=0;j<i;j++) {
System.out.println(i);
}
}
}
在main方法内,有一个for循环,循环体内调用了show方法,而show方法是一个for循环,所以show方法的时间复杂度为O(n),那么main方法的时间复杂度就是O(n^2)。
public class Demo_ {
public static void main(String[] args) {
int n=100;
show(n);
for(int i=0;i<n;i++) {
show(i);
}
for(int i=0;i<n;i++) {
for(int j=0;j<n;j++) {
System.out.println(j);
}
}
}
static void show(int i) {
// TODO 自动生成的方法存根
for(int j=0;j<i;j++) {
System.out.println(i);
}
}
}
在show方法中,有一个for循环,所以show方法的时间复杂度为O(n)。在main方法中,show(n)这行代码执行的次数为n;for循环体内调用了show方法,所以其执行次数为n^2,同样第二个for循环也执行次数为n^2,那么main方法总共执行次数为2n^2+n。根据该记法的规则,最终main方法的时间复杂度为O(n^2)。
空间复杂度分析
Java中常用内存占用
- 计算机访问内存的方式都是一次一个字节
- 一个引用需要8个字节表示,eg:Date date=new Date();则date这个变量需要占用8个字节来表示
- 创建一个对象,比如说new Date(),除了Date对象内部存储的数据,该对象本身也有内存开销,每个对象的自身开销是16字节,用来保存头信息
- 一般内存的使用,如果不够8个字节,都会自动填充为8个字节。意思就是如果你创建了一个对象需要20字节,但由于不是以8位为单位,会自动填充为24字节。
- Java中数组被限定为对象,他们一般会因为记录长度而需要额外的内存,一个原始数据类型和数组一般需要24字节的头信息(16个是自己对象的开销,4字节用于保存长度以及4个填充字节)再加上需要保存信息所需要内存
案例:对指定的数组元素进行反转
解法一;
public static int[] reverse(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;
}
该解法与数组长度无关,都是占用相同的内存。空间复杂度记为:O(1)
解法二:
public static int[] reverse(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-i-1]=arr[i];
}
return temp;
}
空间复杂度:O(4n+28),经过规则后可记为O(n)。
声明:以上图片来源于黑马程序员,内容为黑马程序员网课笔记