2.大O表示法
二分查找和线性查找在数据个数较少时看不出太大的区别,但是随着数据项个数的增多,差异也就回越来越明显,如下表:
范围 | 所需比较次数 |
---|---|
10 | 4 |
100 | 7 |
1000 | 10 |
10000 | 14 |
100000 | 17 |
10000000 | 20 |
100000000 | 24 |
1000000000 | 27 |
10000000000 | 30 |
上面的表让我们感觉到最大范围和比较次数之间存在着一定的联系,如下表,将范围由1开始,按每次乘2逐一扩大,观察比较次数:
s所需比较次数log2(r) | 范围r | 由2的幂表示范围r |
---|---|---|
0 | 1 | 2^0 |
1 | 2 | 2^1 |
2 | 4 | 2^2 |
3 | 8 | 2^3 |
4 | 16 | 2^4 |
5 | 32 | 2^5 |
6 | 64 | 2^6 |
7 | 128 | 2^7 |
8 | 256 | 2^8 |
9 | 512 | 2^9 |
10 | 1024 | 2^10 |
由上表可知6次最大只能猜测的范围为64,所以100的范围需要7次,这与我们之间规定的范围1~100,总共用7步猜出答案是符合的。
每次对范围的改变是2的幂的数列,设比较次数为变量s,范围为r,由上表我们可以得出如下式:
r=2^s
即当我们知道比较次数s就可以求出他能猜测的最大范围r。
但是通常我们的问题是相反的,我们需要的是知道范围,然后求出他需要比较的次数。如下式:
s=log2(r)
即需要比较的次数s是以2为底的范围r的对数。
在多种计算机算法中我们需要一种方式来比较不同算法之间的效率,在计算机科学中我们可以使用粗略的度量方法”大O表示法“来进行。
在现实世界我们一般都用”A比B快两倍“的说法来衡量两个算法,但是根据上面的一些讨论,我们发现算法是随着数据项个数的变化而变化的。也许现在AB之间是两倍的关系,但是当数据项个数发生变化时,他们之间的比例也会发生变化。下面就是我们所见过的算法的”大O表示“。
无序数组的插入:常数
无序数组的插入,只需将新的数据项放入下一个为空的位置即可,它与数组的数据项个数是无关的,因此无论数据项的个数为多少,它的插入所需时间T都是相同的,都是一个常数K:即T=K。在现实情况中,K值得大小受到了微处理器、编译程序生成代码的效率等等因素的影响。
线性查找:与N成正比
在数组的线性查找中,N个数据项平均需要比较的次数为N/2,在对于比较一次需耗时K的情况,总共消耗的时间为:T=K*N/2。因为K和2都属于常数,将两常数合并,即得:T=K*N,线性查找时间与数据项个数N成正比。
二分查找:与log(N)成正比
在之前已经讨论过,当范围即数据项个数为N时,所需比较的次数为log2(N),若比较一次所需的时间为K,那么可得所需消耗的时间T=K*log2(N);实际上所有对数都是和其他对数成比例的,所以将底数2也合并到常数项当中,就可得:T=K*log(N),就是说二分查找所需消耗的比较时间与log(N)成正比。
省去常数
“大O表示法”的O可以认为的含义是“order of”(大约是),它省去了常数K,在比较算法时,并不在乎微处理器以及编译器等对时间的影响,真正需要比较的只是时间T与数据项个数N之间的关系,因此不需要常数。下表总结了我们目前所见的算法运行时间的“大O表示法”:
算法 | 大O表示法 |
---|---|
线性查找 | O(N) |
二分查找 | O(log(N)) |
线性数组的插入 | O(1) |
有序数组的插入 | O(N) |
线性数组的删除 | O(N) |
有序数组的删除 | O(N) |
通过对上述的“大O表示法”的表示,我们也可以得出结论:O(1)表示优秀,O(log(N))表示良好,O(N)表示还可以,O(N^2)表示差一些。“大O表示法”的实质并不是算法运行的实际值,而只是表达了运行时间是怎么受数据项个数的影响的。
3.为什么不用数组表示一切?
数组的使用似乎能做所有的工作,但是为什么不直接使用数组,还要研究其他的数据结构呢?我们发现在数组的使用存在着许多的缺点,无序数组
虽然插入很快,但查找很慢需要花费O(N)的时间。有序数组利用二分查找能很快的查到数据,但插入却很慢。而且无论是有序还是无序数组都需要在删除时移动数据项,所以要耗费O(N)的时间。
如果有一种数据结构能够插入、删除、查找都很快就好了。但是我们在之后的学习中会发现时间的减少程度是要利用算法的复杂程度作为代价的。
数组的另一个缺点是,在数组创建之后,它的大小是不能改变的,但在现实情况中,我们在最初往往不能确定数据项的个数大小,如果猜的大小太大,就会浪费很多空间,如果猜的过小,就会出现数组溢出的情况,从而程序报出警告,甚至程序崩溃。