当你写的一个程序在跑一组很大的数据的时候结果迟迟没有出来,这个时候你就会想这个程序要跑多久?
那么不要等了,尝试估计一下让心里有个底,不再迷茫等待!
当你想到一个算法处理一定数据量所需要的时间会是多少,你先会去计算这个算法的时间复杂度。时间复杂度是估计一个程序运行时间概念上的重要参考。
一般估计
如果算法的时间复杂度是线性的,数学模型是 T = aN + b;
- T是运行时间
- N是数据规模
下面出现的T,N含义都一样.
尝试绘制一个N关于T的函数图像。
倍率实验
倍率实验的数据表现形式
实验规模 | 实验耗时 | 倍率(本次实验与上一次实验运行时间之比) |
---|---|---|
N | T | X |
实验规模按N的倍数增长
实验的数据来源于随机生成
理论依据
如果一个算法的时间复杂度是T(N)且T(N)~a(N^b)lgN
那么
T
(
2
N
)
/
T
(
N
)
→
2
b
T(2N)/T(N) \rightarrow\ 2^b
T(2N)/T(N)→ 2b
实验的准备
一个模拟数据生成器
这个生成器得就是一个方法,该方法
-
接收一个模拟规模数
-
返回 算法处理这个规模的数据所需要的时间
我们来看下面的这个实现
/**
*
* @param n 模拟数据的规模
* @return 返回算法处理这个规模数据所耗费的时间
*/
public static double libIn(Integer n) {
Random ran = new Random();
int[][] Data = new int[n][2];//模拟数据容器
for(int i = 0; i<n; i++)
{
Data[i][0] = ran.nextInt(n);
Data[i][1] = ran.nextInt(n);
}//随机生成数据
Stopwatch stopwatch = new Stopwatch();//开始计时 beginTime
//算法开始
QuickUnion uf = new QuickUnion(n);
for(int[] a : Data)
{
int p = a[0];
int q = a[1];
if(uf.connected(p, q))
continue;
uf.union(p, q);
}
//算法结束
return stopwatch.elapsedTime();//结算耗时 currentTime - beginTime
}
预测方式
假设 算法处理N组数据所需要的时间为time
-
选定一个初始规模N。记录其运行时间 T。
-
根据倍率实验的数据得出 倍率 X
-
根据公式
N 0 ∗ 2 b = N N_{0} * 2^b = N N0∗2b=N
算出
b
=
l
o
g
2
N
/
N
0
b = log_2{N/N_0}
b=log2N/N0
最后得到运行时间
t
i
m
e
=
T
0
∗
X
b
time = T_{0}*X^b
time=T0∗Xb
这是根据极限比例来计算的。即 N–>无穷
你如果没有看过书,我这个专题的名字就是那本书的名字,你会看不懂,不过没关系,你可以根据下面的实例演示结合体会;
除了根据极限比例,我们还可以通过绘图!
幂次法则
描绘 对数图像
一个二维图像
- X轴:lg(N )
- Y轴:lg(T(N)) (时间的对数)
- T(N) 是数据量和时间的关系函数。
用这个法则怎么来算程序的运行时间呢
设这个算法满足数学模型
l
g
(
T
(
N
)
)
=
k
l
g
(
N
)
+
l
g
(
a
)
①
lg(T(N)) = klg(N) + lg(a)①
lg(T(N))=klg(N)+lg(a)①
T
=
T
(
N
)
T = T(N)
T=T(N)
k是图像的斜率,而a的计算只需要从实验数据中取一对(N,T)带入公式就能得出.
我们再将上面的公式①推一推就可以得到
T
(
N
)
=
a
∗
N
k
T(N) = a*N^k
T(N)=a∗Nk
给你们看一个图。
代码贡献
模拟数据生成器,因应对的算法的不同而不同。
而倍率实验因为对任何的算法的数据输出格式都是一致的。
所以我现实了一个倍率实验通用类。
如果你想使用下面的代码,那么你只需要实现一个方法
将其标志为public double libIn(Integer n)
,方法实现请参考上面的代码,然后将包含这个方法的类的对象作为参数传给MPA的构造方法。
当你得到倍率实验数据的时候,我们就可以从实验数据中提取相关的参数,来预测该算法在处理一定规模数据时大概将耗费的时间。
为了方便,我同时也实现了这样的一个类,你需要提供
- 预测数据的规模 targetN
- 实验数据中的一个数据规模 startN
- 该规模对应的运行时间 baseTime
- 倍率 X
- startN怎么选? 这个的选择没有一定。一般来讲,在倍率接近于X的数据项中选中间的一个。
- 倍率X怎么确定? 最下面的一项的倍率就是我们的X因为越靠近下面即数据规模越大越趋近于理论倍率。
//倍率实验
/**
* 用于 预测一个算法在处理一定量数据(很大的数据量)时所需要的时间
* 信息输出格式 数据量 + 运行时间 + 与上一次实验运行时间之比
* 当比值趋近于 2^b次方时 倍率被确定
* 计算一个实验需要的时间 用上一次实验的时间乘于2^b 并将N加倍 如此反复
*/
public class MPA{
Class<?> cla;
Object obj;
/**
* @param obj 需要传递一个你实现的模拟数据生成器的实例对象
*/
public MPA(Object obj)
{
this.cla = obj.getClass();
this.obj = obj;
}
/**
* 输出的数据格式是 数据规模---实验耗时---倍率
* @param startDataGroup 初始的实验数据规模 以后的数据规模按这个规模成倍增长
* @throws Exception
*/
public void mpaExp(int startDataGroup) throws Exception
{
Method libIn = cla.getDeclaredMethod("libIn", Integer.class);
Double pre = (Double) libIn.invoke(obj, startDataGroup);
StdOut.println("N---time---X");
for(int N = (startDataGroup*2);true;N*=2)
{
Double time = (Double) libIn.invoke(obj, N);
StdOut.printf("%7d %5.1f %4.1f\n", N, time, time/pre);
pre = time;
}
}
/**
*这是一个对特定算法特定数据量的运行时间的预测
* startN * 2^b = targetN. resultTime = baseTime*T^b;
* 因为 targetN 可能不会刚好是startN乘2的幂的倍数。所以最后在确定 b 上,会挑选一个更接近于 targetN 的值,作为计算预测时间的指数。
* @param targetN 目标数据量
* @param startN 源数据量
* @param baseTime 该算法运行源数据量所需要的时间
* @param X 倍率
* @return Double 返回的是一个时间 ,即预测时间
* @author 肖沫
*/
public static Double pridictTime(int targetN, int startN, Double baseTime, Double X)
{
int b = -1;
double truthX = targetN/startN;//实际倍率
while(truthX>=1)
{
truthX = truthX / 2;
b += 1;
}
b = Math.abs(Math.pow(2, b+1) - targetN) > Math.abs(Math.pow(2, b) - targetN) ? b : b+1;
//因为我们预测的数据规模targetN可能不是,startN与2^b的乘积的倍数,那么当循环出来以后,startN*2^b < targetN < startN*2^(b+1),那b要不要加一,就看b与(b+1)进行计算以后那种情况更趋近于targetN,我就选哪一个,作为时间估计的参数。
return baseTime * Math.pow(X, b);
}
}
如果你真的想试一试,但不知道如何下手,来吧,看我的演示!
运用实例