1、算法的概念:
算法 (Algorithm),是对特定问题求解步骤的一种描述。
解决一个问题往往有不止一种方法,算法也是如此。那么解决特定问题的多个算法之间如何衡量它们的优劣呢?有如下的指标:
2、衡量算法的指标:
(1)时间复杂度:执行这个算法需要消耗多少时间。
(2)空间复杂度:这个算法需要占用多少内存空间。
同一个问题可以用不同的算法解决,而一个算法的优劣将影响到算法乃至程序的效率。算法分析的目的在于为特定的问题选择合适算法。一个算法的评价主要从时间复杂度和空间复杂度来考虑。
算法在时间的高效性和空间的高效性之间通常是矛盾的。所以一般只会取一个平衡点。通常我们假设程序运行在足够大的内存空间中,所以研究更多的是算法的时间复杂度。
3、算法的时间复杂度
(1)语句频度T(n): 一个算法执行所花费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。而且一个算法花费的时间与算法中的基本操作语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度,记为T(n)。
(2)时间复杂度: 在刚才提到的语句频度中,n称为问题的规模,当n不断变化时,语句频度T(n)也会不断变化。但有时我们想知道它的变化呈现什么规律。为此,我们引入时间复杂度概念。 一般情况下,算法中的基本操作语句的重复执行次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n) / f(n) 的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作 T(n)=O( f(n) ),称O( f(n) ) 为算法的渐进时间复杂度,简称时间复杂度。
T(n) 不同,但时间复杂度可能相同。 如:T(n)=n²+5n+6 与 T(n)=3n²+3n+2 它们的T(n) 不同,但时间复杂度相同,都为O(n²)。
(3)常见的时间复杂度有:常数阶O(1),对数阶O(log2n),线性阶O(n),线性对数阶O(nlog2n),平方阶O(n2),立方阶O(n3), k次方阶O(nk),指数阶O(2n)。随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。
常见的时间复杂度排序如下:
O(1)< O(logn)<O(n)<O(nlog2n)<O(n2)<O(n3)<…<O(2n)<O(n!)
(4)平均时间复杂度和最坏时间复杂度:
平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,该算法的运行时间。
最坏情况下的时间复杂度称最坏时间复杂度。一般讨论的时间复杂度均是最坏情况下的时间复杂度。 这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长。
(5)如何求时间复杂度:
【1】如果算法的执行时间不随着问题规模n的增加而增长,即使算法中有上千条语句,其执行时间也不过是一个较大的常数。此类算法的时间复杂度是O(1)。
public static void main(String[] args) {
int x = 91;
int y = 100;
while (y > 0) {
if (x > 100) {
x = x - 10;
y--;
} else {
x++;
}
}
}
该算法的时间复杂度为:O(1)
这个程序看起来有点吓人,总共循环运行了1100次,但是我们看到n没有?
没。这段程序的运行是和n无关的,
就算它再循环一万年,我们也不管他,只是一个常数阶的函数
【2】当有若干个循环语句时,算法的时间复杂度是由嵌套层数最多的循环语句中最内层语句的频度f(n)决定的。
1 int x = 1;
2 for (int i = 1; i <= n; i++) {
3 for (int j = 1; j <= i; j++) {
4 for (int k = 1; k <= j; k++) {
5 x++;
6 }
7 }
8 }
该算法的时间复杂度为:O(n3)
该程序段中频度最大的语句是第5行的语句,内循环的执行次数虽然与问题规模n没有直接关系,但是却与外层循环的变量取值有关,而最外层循环的次数直接与n有关,因此该程序段的时间复杂度为 O(n3)
【3】算法的时间复杂度不仅仅依赖于问题的规模,还与输入实例的初始状态有关。
在数值 A[n-1,n-2 ...0] 中查找给定值k的算法大致如下:
1 int i = n - 1;
2 while (i >= 0 && (A[i] != k)) {
3 i--;
4 }
5 return i;
该算法的时间复杂度为:O(n)
此算法中第3行语句的频度不仅与问题规模n有关,还与输入实例A中的各元素取值和k的取值有关:如果A中没有与k相等的元素,那么第3行语句的频度为 f(n)=n ,该程序段的时间复杂度为 O(n)
(6)用时间复杂度来评价算法的性能
用两个算法A1和A2求解同一问题,时间复杂度分别是O(100n2),O(5n3)
(1) 5n3/100n2=n/20 ,当输入量n<20时,100n2 > 5n3 ,这时A2花费的时间较少。
(2)随着问题规模n的增大,两个算法的时间开销之比 5n3/100n2=n/20 也随着增大。即当问题规模较大时,算法A1比算法A2要高效的多。它们的渐近时间复杂度O(n2)和O(n3) 评价了这两个算法在时间方面的性能。在算法分析时,往往对算法的时间复杂度和渐近时间复杂度不予区分,而经常是将渐近时间复杂度 O(f(n)) 简称为时间复杂度,其中的f(n)一般是算法中频度最大的语句频度。
4、算法的空间复杂度
空间复杂度(Space Complexity) 是对一个算法在运行过程中临时占用存储空间大小的量度,记做 S(n)=O(f(n)) ,其中n为问题的规模。利用算法的空间复杂度,可以对算法的运行所需要的内存空间有个预先估计。
一个算法执行时除了需要存储本身所使用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的工作单元和存储一些计算所需的辅助空间。算法执行时所需的存储空间包括以下两部分。
(1)固定部分。这部分空间的大小与输入/输出的数据的个数、数值无关。主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间。这部分属于静态空间。
(2)可变空间,这部分空间的主要包括动态分配的空间,以及递归栈所需的空间等。这部分的空间大小与算法有关。
举例分析算法的空间复杂度:
public void reserse(int[] a, int[] b) {
int n = a.length;
for (int i = 0; i < n; i++) {
b[i] = a[n - 1 - i];
}
}
上方的代码中,当程序调用 reserse() 方法时,要分配的内存空间包括:引用a、引用b、局部变量n、局部变量i
因此 f(n)=4 ,4为常量。所以该算法的空间复杂度 S(n)=O(1)
5、排序的分类
比较排序(7种):
交换排序(冒泡排序、快速排序)
选择排序(直接选择排序、堆排序)
插入排序(直接插入排序,希尔排序)
归并排序
非比较排序(3种):
计数排序
基数排序
桶排序
排序方法,在排序的最终结果中,各个元素的次序依赖于他们之间的比较,我们把这一类的排序算法称为比较排序。在最坏情况下,任何比较排序算法都要经过 Omega(nlgn)次比较。因此堆排序和归并排序都是渐近最优的比较排序算法。
计数排序、基数排序和桶排序是非比较排序。因为不采用比较排序方法,可以打破其下界。
6、稳定性
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
排序算法是否为稳定的是由算法具体实现决定的。
总体上,堆排序、快速排序、希尔排序、直接选择排序不是稳定的排序算法,
而冒泡排序、直接插入排序、归并排序、基数排序、计数排序、桶排序是稳定的排序算法。
7、总结
算法的时间复杂度和两个因素有关:算法中的最大嵌套循环层数;最内层循环结构中循环的次数。
一般来说,具有多项式时间复杂度的算法是可以接受的;具有指数(不是对数)时间复杂度的算法,只有当n足够小时才可以使用。一般效率较好的算法要控制在O(log2n) 或者 O(n)
---------------------------------------------------------------------------------------------------------------------------------------
算法复杂度分为时间复杂度和空间复杂度。
其作用:
时间复杂度是指执行算法所需要的计算工作量;
而空间复杂度是指执行这个算法所需要的内存空间。
(算法的复杂性体现在运行该算法时的计算机所需资源的多少上,计算机资源最重要的是时间和空间(即寄存器)资源,因此复杂度分为时间和空间复杂度)。
简单来说,时间复杂度指的是语句执行次数,空间复杂度指的是算法所占的存储空间
时间复杂度
计算时间复杂度的方法:
- 用常数1代替运行时间中的所有加法常数
- 修改后的运行次数函数中,只保留最高阶项
- 去除最高阶项的系数
按数量级递增排列,常见的时间复杂度有:
常数阶O(1)
,对数阶O(log2n)
,线性阶O(n)
,
线性对数阶O(nlog2n)
,平方阶O(n^2)
,立方阶O(n^3)
,…,
k次方阶O(n^k)
,指数阶O(2^n)
。
随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。
举个栗子:
sum = n*(n+1)/2; //时间复杂度O(1)
for(int i = 0; i < n; i++){
printf("%d ",i);
}
//时间复杂度O(n)
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
printf("%d ",i);
}
}
//时间复杂度O(n^2)
for(int i = 0; i < n; i++){
for(int j = i; j < n; j++){
printf("%d ",i);
}
}
//运行次数为(1+n)*n/2
//时间复杂度O(n^2)
int i = 1, n = 100;
while(i < n){
i = i * 2;
}
//设执行次数为x. 2^x = n 即x = log2n
//时间复杂度O(log2n)
最坏时间复杂度和平均时间复杂度
最坏情况下的时间复杂度称最坏时间复杂度。一般不特别说明,讨论的时间复杂度均是最坏情况下的时间复杂度。
这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的上界,这就保证了算法的运行时间不会比任何更长。
平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,算法的期望运行时间。设每种情况的出现的概率为pi,平均时间复杂度则为sum(pi*f(n))
常用排序算法的时间复杂度
相关概念:
1、时间复杂度
时间复杂度可以认为是对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
常见的时间复杂度有:常数阶O(1),对数阶O(log2n),线性阶O(n), 线性对数阶O(nlog2n),平方阶O(n2)
时间复杂度O(1):算法中语句执行次数为一个常数,则时间复杂度为O(1),
2、空间复杂度
空间复杂度是指算法在计算机内执行时所需存储空间的度量,它也是问题规模n的函数
空间复杂度O(1):当一个算法的空间复杂度为一个常量,即不随被处理数据量n的大小而改变时,可表示为O(1)
空间复杂度O(log2N):当一个算法的空间复杂度与以2为底的n的对数成正比时,可表示为O(log2n)
ax=N,则x=logaN,
空间复杂度O(n):当一个算法的空间复杂度与n成线性比例关系时,可表示为0(n).
空间复杂度
空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度,记做S(n)=O(f(n))。
对于一个算法来说,空间复杂度和时间复杂度往往是相互影响的。当追求一个较好的时间复杂度时,可能会使空间复杂度的性能变差,即可能导致占用较多的存储空间;反之,当追求一个较好的空间复杂度时,可能会使时间复杂度的性能变差,即可能导致占用较长的运行时间。
有时我们可以用空间来换取时间以达到目的。