算法的时间复杂度分析

算法分析

​ 研究算法的目的为如何花更少的时间,占用更少的内存去完成相同的需求.我们并不能将时间和孔家量化,故有了算法的时间复杂度分析和算法的空间复杂度分析.

算法的时间复杂度分析
事后分析估算方法

在程序执行前定义一个开始的时间戳start,程序结束末尾定义一个结束的时间戳end.在程序结束是计算end-start即为程序运行所需的时间

//求解计算1到100之间整数的和
package Demo;

public class DateTimeDemo {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        int sum = 0;
        int n = 100;
        for (int i = 0; i <= n; i++) {
            sum += i;
        }
        System.out.println("sum = " + sum);
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}

事后分析估算方法必须依据算法实现编制好的测试程序,需要花费大量的时间和精力,如果测试完成后发现结果非常糟糕,那就较为耗时耗力且达不到较为好的效果.

事前分析估算法

在计算机程序编写前,依据统计方法对算法进行估算,我们会发现程序所小号的时间取决于以下因素:

算法才用的策略和方法

编译所产生的代码质量

问题的输入规模,即为解决问题所需要的问题输入规模

机器执行指令的速度

除了硬件,软件等有关因素,程序运行时间较依赖于算法的好坏和问题输入的规模.

当我们用两种不同的算法再次解决计算1到100之间整数和的问题

//方案一
package Demo;

public class Doubleway {
    public static void main(String[] args) {
        int sum = 0;//执行一次
        int n = 100;//执行一次
        for (int i = 0; i <= n; i++) {
            sum += n;//核心操作执行n次
        }
        System.out.println("sum = " + sum);
    }
}
//方案二
package Demo;

public class DoublewayDemo2 {
    public static void main(String[] args) {
        int sum = 0;//执行一次
        int n = 100;//执行一次
        sum = (n + 1) * n / 2;//执行一次
        System.out.println(sum);
    }
}

因此,当输入规模为n时,第一种算法执行了1+1+(n+1)+n=2n+3次;第二种算法执行了1+1+1=3次。如果我们把 第一种算法的循环体看做是一个整体,忽略结束条件的判断,那么其实这两个算法运行时间的差距就是n和1的差距

函数渐进增长

给定两个函数f(n)和g(n),如果存在一个整数N,是的对于所有的n>N,f(n)总是比g(n)大,那么我们说f(n)的增长渐进快于g(n).

我们做几个测试来了解这个概念

测试一

image-20221004163522501

观察数据得出当输入规模n>2时,算法A1的渐近增长小于算法B1 的渐近增长

可以得出结论:随着输入规模的增大,算法的常数操作可以忽略不计

测试二

image-20221004163730016

观察数据得出随着输入规模的增大,即使去除n^2前面的常数因子,D系列的次数要远远高于C系列。

可以得出结论:随着输入规模的增大,与最高次项相乘的常数可以忽略

测试三

image-20221004163920043

通过折线图我们会看到,算法F系列随着n的增长会变得特块,算法E系列随着n的增长相比较算法F来说,变得比较慢

可以得出结论:最高次项的指数大的,随着n的增长,结果也会变得增长特别快

测试四

image-20221004164059795

得出结论: 算法函数中n最高次幂越小,算法效率越高

总而言之:

在我们比较算法随着输入规模的增长时,可以有以下规则:

1.算法函数中的常数可以忽略;

2.算法函数中最高幂的常数因子可以忽略;

3.算法函数中最高幂越小,算法效率越高.

大O记法

​ 定义:在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,记作:T(n)=O(f(n)).

​ 用大写O()来体现算法时间复杂度的记法,我们称之为大O记法。一般情况下,随着输入规模n的增大,T(n)增长最 慢的算法为最优算法.

执行次数=执行时间

我们用大O表示法来表示一些求和算法的时间复杂度:

算法一:

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记法: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记法: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记法:O(n^2+2)

大O记法表示算法的时间复杂度通常有一下规则:

1.用常数1取代运行时间中的所有加法常数;

2.在修改后的运行次数中,只保留高阶项;

3.如果最高阶项存在,且常数因子不为1,则去除与这个项相乘的常数;

常见的大O阶

1.线性阶O(n)

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);
}

2.平方阶O(n^2)

一般循环嵌套属于这种时间复杂度.

public static void main(String[] args) {
	int sum=0,n=100;
	for (int i = 1; i <=n ; i++) {
		for (int j = 1; j <=n ; j++) {
			sum+=i;
		}
	}
	System.out.println(sum);
}

3.立方阶O(n^3)

一般三层循环嵌套属于这种时间复杂度

public static void main(String[] args) {
	int x=0,n=100;
	for (int i = 1; i <=n ; i++) {
		for (int j = i; j <=n ; j++) {
			for (int j = i; j <=n ; j++) {
				x++;
			}
		}
	}
	System.out.println(x);
}

4.对数阶O(logn)

对数阶随着输入规模n的增大,不管底数是多少,增长趋势都是一样的,所以对于对数阶我们一般都忽略底数.

int i=1,n=100;
while(i<n){
	i = i*2;
}

5.常数阶O(1)

不管n是多少,最终执行的次数都为常熟次,可以用1来代替.

public static void main(String[] args) {
	int n=100;
	int i=n+2;
	System.out.println(i);
}
描述增长的数量级说明举例
常数级别1普通语句将两个数相加
对数级别logN二分策略二分查找
线性级别N循环找出最大元素
线型对数级别NlogN分治思想归并排序
平方级别N^2双层循环检查所有元素对
立方级别N^3三层循环检查所有三元组
指数级别2^N穷举查找检查所有子集

O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)

在函数调用过程中对于时间复杂度的分析

案例一

public static void main(String[] args) {
	int n=100;
	for (int i = 0; i < n; i++) {
		show(i);
	}
}
private static void show(int i) {
	System.out.println(i);
}

main方法中有for循环,for循环又调用了show方法.由于show方法中只有一行代码,所以show方法的时间复杂度为O(1),main方法的时间复杂度为O(n).

案例二

public static void main(String[] args) {
	int n=100;
	for (int i = 0; i < n; i++) {
		show(i);
	}
}
private static void show(int i) {
	for (int j = 0; j < i; i++) {
		System.out.println(i);
	}
}

在main方法中,有一个for循环,循环体调用了show方法,由于show方法内部也有一个for循环,所以show方法 的时间复杂度为O(n),那main方法的时间复杂度为O(n^2).

案例三

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);
		}
	}
}
private static void show(int i) {
	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循环内只执行了一行代码, 所以其执行次数为n2,那么main方法总执行次数为n+n2+n2=2n2+n。根据大O推导规则,去掉n保留最高阶 项,并去掉最高阶项的常数因子2,所以最终main方法的时间复杂度为O(n^2)

最坏情况

有一个存储了n个随机数字的数组,请从中查找出指定的数字。

public int search(int num){
	int[] arr={11,10,8,9,7,22,23,0};
	for (int i = 0; i < arr.length; i++) {
		if (num==arr[i]){
			return i;
		}
	}
	return -1;
}

最好情况: 查找的第一个数字就是期望的数字,那么算法的时间复杂度为O(1)

最坏情况: 查找的最后一个数字,才是期望的数字,那么算法的时间复杂度为O(n)

平均情况: 任何数字查找的平均成本是O(n/2) 最坏情况是一种保证,在应用中,这是一种最基本的保障,即使在最坏情况下,也能够正常提供服务,所以,除非 特别指定,我们提到的运行时间都指的是最坏情况下的运行时间。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值