什么是算法
官方解释:算法是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法解决问题的策略机制,也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出
大白话
根据一定的条件,对一些数据进行计算,得到需要的结果
举例
1.从东北到北京,可以做火车、汽车、飞机等等交通工具,但是不同交通工具所带来的时间成功和金钱成本是不一样的
2.买一套房子,一次性付清还是贷款,所带来的后续生活所带来的压力和首次支付的金额的压力是不一样的。
一个优秀的算法追求两个目标
1.花最少的时间
2.占用最少的内存
计算1到100的和的两种算法
/**
*
* 1-100的和
*/
public class Sum1 {
/**
*1.定义两个整型变量
* 2.执行100次加法运算
* 3.打印结果到控制台
* @param args
*/
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);
}
}
/**
* 1-100的和
*/
public class Sum2 {
/**
* 1.定义两个整型变量
* 2.执行1次加法,1次乘法,1次除法,总共3次运算
* 3.打印结果到控制台
* @param args
*/
public static void main(String[] args) {
int sum=0;
int n=100;
sum =n*(n+1) /2;
System.out.println(sum);
}
}
很明显sum2比sum1花费的时间更少一些
计算1到10的阶乘的两种算法
/**
* 1-10的阶乘
*
*/
public class Factorial1 {
/**
* 该方法使用递归完成1到10的阶乘,fun1方法会执行10次,
* 并且第一次未执行完毕,调用二次执行,第二次未执行完毕,调用第三次执行。。。。。。,
* 最多的时候需要在占内存中开辟10块内存分别执行10个fun1方法
* @param args
*/
public static void main(String[] args) {
int result = fun1(10);
System.out.println(result);
}
public static int fun1(int n){
if(n==1){
return n;
}
return n*(fun1(n-1));
}
}
/**
* 1-10的阶乘
*该方法使用for循环的方式完成1到10的阶乘,fun2方法只会执行1次,最终只需要在占内存中开辟1块内存即可
*/
public class Factorial2 {
public static void main(String[] args) {
int result = fun2(10);
System.out.println(result);
}
private static int fun2(int n) {
int result = 1;
for (int i = 1; i <= n; i++) {
result*=i;
}
return result;
}
}
很明显Factorial2比Factorial1花费的时间更少一些
算法分析
如何衡量一个算法的好坏?花最少的时间,占最少的内存完成需求是一个优秀的算法追求的目的。如何将一个算法所占用的时间和空间量化,叫做算法分析。关于时间的消耗分析叫时间复杂度,关于空间的消耗分析叫空间复杂度
算法的时间时间复杂度分析
如何度量一个算法所耗费的时间?有2种方法:事后分析估算法、事前分析估算法
事后分析估算法
将一个算法编写完毕之后,在算法执行开始前记录下当前时间,在算法执行完毕后记录下当前时间,最后两个时间求和,就是当前算法所耗费的时间,如下代码所示。缺点:必须在算法编写完成才能实现,如果最后发现是非常糟糕的算法,那么之前的所有事情就白做了,不同硬件环境执行的结果差异也很大
事前分析估算方法
在程序编写前,依据统计方法对算法进行估算,经过总结,我们发现一个高级语言编写的程序在计算机上运行所消耗的时间取决于哪些因素
1.算法采用的方案
2.编译产生的代码质量,一般不需要干预
3.输入规模
4.机器执行指令的速度,一般不需要干预
求和分析
当输入规模为n时,第一种算法执行了1+1+n+1+n=2n+3次
第二种算法执行了1+1+1=3次,如果我们将算法的循环体看做一个整体,忽略条件判断,那么其实两个算法运行时间的差距就会n和1的差距
为什么第一种算法的循环判断n+1,看起来是个不小的数量,缺可以省略呢?
1、简化分析
2、侧重研究随着算法的输入规模的增加,所耗费时间的变化(执行次数的变化)
函数渐进增长
给定两个函数T(n)和G(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比G(n)大,那么我们说T(n)的增长渐近快于G(n)
n^2的渐近增长快于n的渐近增长,n的渐近增长快于1的渐近增长
测试一:
随着输入规模的增大,算法A1的执行次数约等于算法A2执行次数,算法B1的执行次数约等于算法B2执行次数,所以我们可以得到结论: 随着输入规模的增大,算法的常数操作可以忽略不计
测试二:
随着输入规模的增大,算法C1和算法C2几乎重叠
随着输入规模的增大,即使去掉n^2前面的常数因子,d系列执行次数也要远远高于c系列
随着输入规模的增大,与最高次项相乘的常数可以忽略
测试三
随着输入规模的增大,算法f系列比算法e系列快很多
随着输入规模的增大,最高次项指数越大,执行次数也越大
大O记法
在进行算法分析时,语句的总执行次数T(n)是关于问题规模n的函数,记T(n)=O(n)。表示随着问题规模n的增大,算法执行时间的增长率O(n)和执行次数T(n)的增长率相同,称为算法的渐近时间复杂度,简称时间复杂度,执行次数=执行时间,用大写的O来体现算法时间复杂度的记法,称为大O记法,一般情况下,随着输入规模n的增大,时间增长率O(n)越慢和执行次数T(n)越慢,算法越优秀。
1.sum1的大O记法为O(1)
2.sum2的大O记法为O(n)
常见的大O阶
1.线性阶
一般1层循环的函数,属于线性阶,例如sum1,记作O(n)
2.平方阶
一般2层循环的函数,属于平方阶,记作O(n^2)
/**
* 求1-100的和在乘以2
*/
public static void main(String[] args) {
int sum=0;
int n=100;
for (int i = 1; i <=n; i++) {
for (int j = 1; j <=n; j++) {
sum+= j;
}
}
}
3.立方阶
一般3层循环的函数,属于立方阶,记作O(n^3)
/**
* 求1-100的和在乘以2
*/
public static void main(String[] args) {
int sum=0;
int n=100;
for (int i = 1; i <=n; i++) {
for (int j = 1; j <=n; j++) {
for (int k = 0; k <n ; k++) {
sum+= k;
}
}
}
}
3.对数阶
记作O(logn)
public static void main(String[] args) {
int i = 1;
int n = 100;
while (i < n) {
i *= 2;
}
}
已知循环体执行次数为x,每次执行x都有自己的2倍增长,当n等于100的时候束循环,n的大小是多少
2^x=100,求x=log(2)100
把100看做一个变化的数字n
2^x=n,求x=log(2)n
研究算法的时间复杂度的时候,可以将底数省略,记作logn
4.常数阶
一般不涉及循环操作的函数,属于常数阶,例如sum2,记作O(1)
常见的大O的时间复杂度的排序
O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)
时间复杂越大,耗费的时间越长
函数调用过程中时间复杂度分析
1.案例1
public static void main(String[] args) {
int n =100;
for (int i = 0; i < n; i++) {
show(i);
}
}
public static void show(int i){
System.out.println(i);
}
}
show()方法的时间复杂度O(1)可以省略;int n =1,O(1)可以省略;for循环的时间复杂度为O(n),和在一起main()方法的时间复杂度O(n)
2.案例2
public static void main(String[] args) {
int n =3;
for (int i = 1; i <= n; i++) {
show(n);
}
}
public static void show(int n){
for (int j = 1; j <= n; j++) {
System.out.println(j);
}
}
show()方法的时间复杂度O(n),main()方法中的for循环的时间复杂度为O(n),和在一起main()方法的时间复杂度O(n^2)
案例三
public static void main(String[] args) {
int n =3;
show(n);
for (int i = 1; i <= n; i++) {
show(n);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <=n ; j++) {
System.out.println(n);
}
}
}
public static void show(int n){
for (int j = 1; j <= n; j++) {
System.out.println(j);
}
}
public static void main(String[] args) {
int arr[] = {11,12,12,14,0,6};
for (int i = 0; i < arr.length; i++) {
if(arr[i]== 0){
System.out.println(i);
}
}
}
从一个数组中查询某个值,根据查询的值不同,查询的次数也不同。根据上述列子,如果要查询的值是11那么就是最好情况,如果要查询的值是6那么就是最坏情况,如果任何数字查找的平均成本都是O(n/2)叫做平均情况,最坏情况是一种保证,在应用中,这是一种最基本的保障,即使在最坏情况下,也能够正常提供服务,所以除特指情况,我们提到的运行时间都是指最坏情况的运行时间