马上开始今天的学习,今天的内容是算法分析,算法的一个评估环节,当我们在设计、使用一个算法的时候,一个算法的好坏往往与它的运行时间(时间复杂度),运行所需空间(空间复杂度)挂钩,首先来学习时间复杂度问题。
书中一开始提到一个例子,讲的是计算从数组中抽取三个数,统计三个数加起来等于0的个例,然后进行返回。文章之中给出的ThreeSum,典型的暴力算法,逐个遍历,在P112,我遇到了我第一个问题,if语句会执行N(N-1)(N-2)/6次,这个6是哪里来的呢,下面我们就来分析一下,其实if语句的执行次数就是这三个嵌套循环的核心循环的执行次数,三个循环分别是为了找出第一个数,第二个数和第三个数,若果是N(N-1)(N-2),这就是表明了,每个数字的位置是没什么要求的,只要跟上一个不重就好了,就像从箱子里抽球,抽一个,总数减一个,抽一个,总数再减一,那我们可能会抽到1 2 5, 5 2 1,在这个问题里这两个 抽取有差吗?其实并不是这样的,我们只是要三个,先后次序并没用,运用数学排列组合公式。
我们能清晰地看出无序选择在这个问题中是有选择的1/6的规模,好吧1/6得出来了。
知识点一:当N足够大的时候,我们将我们往往只要考虑次方最高的的式子就好了,例如书上的,当N足够大的时候,次方小的完全可以忽略不计。
知识点二:数量级,①取最大次方 ②没有次方的话,有对数就是对数级别或者线性对数级别(单个变量跟对数相乘) ③对数都没有的话,有单个变量的叫做线性④变量都没有的话就是常数次了。
知识点三:常用算法的数量级,普通语句,最普通的语句,常数级别;二分查找,对数级别;归并排序线性对数级别;嵌套循环,多少层嵌套就多少次方级别;穷举,指数级别。
话不多说,我们来改造一下那个丢人的ThreeSum算法吧,书中将第三层循环去掉,套了一个二分查找,将N^3降为了N^2logN。
倍率实验:通过提高实验的基数,测试执行时间之间的倍率,书上给的例子,由于数量级为N^3,在实验基数*2的情况下,时间倍率慢慢稳定在8,2^3=8嘛,比如2^3/1^3=8,4^3/2^3=8.
书上讲到一个~,近似,类似于高数上极限的问题,当n足够大的时候,使得F(x)近似等于G(x),是由于分母太大啊,或者对数底数太大啊,而造成的,接近于0嘛
评估一个算法的注意事项:
①虽然跟次方数比起来,常数有点微不足道,但是其实有些时候,会有一些大常数的出现,比如它就执行1000次,虽然当N很大的时候吗,它微不足道,但是当N小的时候,N的次方或许还没有它大呢。
②非决定性的内循环,就是这种内循环并不是执行算法的关键操作,而在外部有更多指令需要消耗大量的时间,这样的话,我们在计算整体的时间数量级的时候,我们就不能只是考虑内循环。
③有的时候,算法在执行的过程中并没有达到设想的效率,可能是因为计算机的机制问题,比如java来说,你一下子创建太多的对象,又一下子释放,垃圾回收机制在没进行回收之前,会一定程度的影响到运行的速度。
均摊分析:有时候我们在分析一些算法的时候,他们会在某个时候,突然出现耗时很高的操作,这些操作可能是为了,之后的算法能够平均上减少算法的耗时。
内存:
我们在设计算法的时候,不应只考虑时间复杂度,还应该考虑到空间复杂度(尽管如今的计算机,运算空间都是充裕的),书上分别讲述了各种数据结构在java上所占内存大小,需要记忆一些重要地。
如:基本数据类型所占的内存大小:boolean 1个字节 byte 1个字节 char 2个字节 int 4个字节 float 4个字节 long 8个字节 double 8个字节,而组装成对象的话,就这么算吧,对象开销16字节(用来放引用,垃圾收集信息,同步信息),然后基本类型有多少个市设恩么就占多少位置,引用外部对象就要8个字节,最后取8的整数倍,多余的叫做填充字节。不同种类的数据类型有不同的组成部分。
练习部分:(以后的话我会选择,比较有难度的题去做,看一眼就有思路的或者测试工具包的,我可能会选择跳过)
1.4.1 前面分析过了
1.4.2 较大int溢出问题;在处理int之前:用int最大值-其中一个,在跟另一个作比较,如果另一个要更大,就肯定会产生溢出,就跳过。
1.4.3 打印标准图像和对数图像:这个嘛,标准的话最简单了,y=kx+b嘛,输入k和b之后,从x=0,y=b开始,按着你想要的密度画就好了,不如点与点之间x的间隔为0.1,循环时x+0.1,y+k(0.1x),循环直到结束,而对数的话,我们可以一选择用java自带的Math算出对数值,再画点。
1.4.4 最后结果的数量级 N^2,表格主要用来分析,循环过程中每一层循环的执行次数,相加之后去近似数,得到数量级
1.4.5 这种基础性问题我就略过了,以后就直接题号都不写了。
1.4.6 求增长数量级
先说a,第一层循环for(int n=N;n>0;n/=2),一看到/2,对数好吧,log2N,再看第二层内有乾坤,是每次n的大小,但是n每次减半,所以二层应该是n+n/2+n/4+....+n/N+0,所以第二层的数量级为2n->n,所以整体的数量级应为n
b呢,只是第一层遍历反过来而已,最后是一样的,数量级是n
c呢,一层同样log2N,第二层N,相乘得Nlog2N
1.4.8 这个问题我感觉有点问题,在使用了Arraysort之后,已经基本有序了,我只要遍历一次就好了呀,说下我的想法哈,int p=a[0],int num=0,sum=0开始遍历,若跟p相同,num+1,知道遇到跟p不一样的,若num>1,将num排列组合,sum+结果,
p=新的a[i],继续往后遍历,知道遍历完成,得到sum,这个计算公司的操作顶多算个数量级1,整体数量级N+1,即N
1.4.9 计算题,没什么好讲的
1.4.10 二分的改进要求输出序号最小的与键值相等的位置。
public class BinarySearchLeft {
int[] a={1,2,3,4,5,5,6,7,8,10};
public static void main(String[] args) {
BinarySearchLeft bsl=new BinarySearchLeft();
//搜索5,按照题意应该返回的是4,下面来讲一下,我所选择的方法,正常的话,当a[mid]==key;
//是直接返回mid的,我就修改一点点,当a[mid]==key的时候,继续跑二分,r=mid,
//if(a[mid]==key),而且r==l,输出mid,跳出方法
//但是我们还需要考虑一个问题,就是数组里面根本没有这个数字
//当没有的话l会大于r,跳出循环,输出底下那句话
//跟你们讲下过程,用我上面的数组,一开始mid为5,a[5]正好是key值,命中r=mid,考虑部分缩成
//1 2 3 4 5 5,mid=2,a[2]<5,l=mid+1=3,考虑部分缩成4 5 5,a[mid]命中,变成 4 5,a[mid]<5,
//l=mid+1;l=4,r=4,a[mid]=key,输出结果结束
bsl.bs(0,10,5);
}
public void bs(int left,int right,int key){
int l=left;
int r=right;
int mid=0;
while(!(l>r)){
mid=(l+r)/2;
if(a[mid]>key){
r=mid-1;
}else if(a[mid]<key){
l=mid+1;
}else {
if (l==r) {
System.out.println(mid);
return;
}
r=mid;
}
}
System.out.println("数字不存在");
}
}
1.4.12 两个有序数组分开放置在两个队列里,队头进行比较,小的出队,当其中一个队列全部出列,另一队列也全部出列,数量级也就N,因为完成4最快是N,最慢的话是2N-1
1.4.13 计算对象所占内存,
第一个Accumulator,首先创建对象要16个字节,然后对象内部有一个double占8个字节,好吧24个字节。
第二个Transaction,唉呀!找不到,好奇啊,反正也就那样组装,不写了。
提高题之后再补充,今天就这样吧,算法的分析,这里好像并没非常量化的衡量算法复杂度,只是一个粗略的分析,真正的算法分析是不会考虑到任何除算法以外的条件的,计算机运行速度啊,时钟周期,都不在考虑范围之内。