算法(3) 算法分析

人们在使用计算机解决困难问题或是处理大量数据时不可避免的将会产生这样的疑问:
“我的程序会运行多长时间?”
“为什么我的程序耗尽了所有内存?”

尽管有这些困难,你在本节中将会看到,为这些基础问题给出实质性的答案有时其实非常简单。这个过程的基础是科学方法,它是科学家们为获取自然界知识所使用的一系列为大家所认同的方法。我们将会使用数学分析为算法成本建立简洁的模型并使用实验数据验证这些模型。

1、科学方法      

科学家用来理解自然世界的方法对于研究计算机程序的运行时间同样有效:

.细致的观察真实世界的特点,通常还要有精确的测量;
.根据观察结果提出假设模型;
.根据模型预测未来的事件;
.继续观察并核实预测的准确性;
.如此反复直到确认预测和观察一致。

举例:

ThreeSum程序会统计一个文件中所有和为0的三整数元组的数量(假设整数不会溢出)。这种计算可能看起来不太自然,但其实它和很多基础计算性任务都有着深刻的联系。

package algorithm;

import stdLib.In;
import stdLib.StdOut;

/**
 * 〈〉<br>
 */
public class ThreeSum {
    public static int count(int[] a){
        //统计和为0的元组的数量
        int N = a.length;
        int cnt = 0;
        for(int i=0; i<N; i++){
            for(int j=i+1; j<N; j++){
                for(int k=j+1; k<N; k++){
                    if(a[i] + a[j] + a[k] == 0){
                        cnt++;
                    }
                }
            }
        }
        return cnt;
    }

    public static void main(String[] args) {
        int[] a = In.readInts(args[0]);
        StdOut.println(count(a));
    }
}
View Code

事实上,对于算法的分析,我们有许多数学模型强烈支持这种函数和其他类似的假设,我们现在就来学习他们。

 

2、数学模型      

常见的增长数量级函数

增长的数量级
描述                        函数
常数级别                    1
对数级别                    logN
线性级别                    N
线性对数级别                 NlogN
平方级别                    N²
立方级别                    N³
指数级别                    2的N次方

我们观察到的一个关键现象是执行最频繁的指令决定了程序执行的总时间——我们将这些指令成为程序的内循环。对于ThreeSum来说,它的内循环是将k+1、判断它是否小于N以及判断给定的三个整数之和是否为0的语句(也许还包括记数的语句,不过这取决于输入)。这种情况是很典型的:许多程序的运行时间都只取决于其中的一小部分指令。

总结:
对于大多数程序,得到其运行时间的数学模型所需的步骤如下:
.确定输入模型,定义问题的规模;
.识别内循环;
.根据内循环中的操作确定成本模型;
.对于给定的输入,判断这些操作的执行频率。这可能需要进行数学分析。

 

设计更快的算法

解决3-sum问题的更快的算法。在做解决3-sum问题更快的算法之前,先看TwoSum问题的更快算法。

package algorithm;

import java.util.Arrays;

/**
 * 〈2-sum问题的线性对数级别的解法〉<br>
 */
public class TwoSumFast {

    public static int count(int[] a){
        //计算和为0的整数对的数目
        Arrays.sort(a);
        int N = a.length;
        int cnt = 0;
        for(int i=0; i<N; i++){
            if(BinarySearch.rank(-a[i], a) > i){
                cnt++;
            }
        }
        return cnt;
    }

}
View Code
package algorithm;

import java.util.Arrays;

/**
 * 〈3-sum问题的N²lgN解法〉<br>
 */
public class ThreeSumFast {

    public static int count(int[] a){
        //计算和为0的三元组的数目
        Arrays.sort(a);
        int N = a.length;
        int cnt = 0;
        for(int i=0; i<N; i++){
            for(int j=i+1; j<N; j++){
                if(BinarySearch.rank(-(a[i]+a[j]), a) > j){
                    cnt++;
                }
            }
        }
        return cnt;
    }
}
View Code

 ...


算法分析者的任务就是尽可能的揭示关于某个算法的更多信息,而程序员的任务则是利用这些信息开发有效解决现实问题的程序。在理想状态下,我们希望根据算法能够得到清晰简洁的代码并能够为我们感兴趣的输入提供良好的保证和性能。我们在本章讨论的许多经典算法之所以对众多应用都十分重要就是因为它们具备这些性质。以它们作为样板,在编程中遇到典型问题时你也能独立给出很好的解决方法。

 

posted on 2018-11-28 20:15  有点懒惰的大青年  阅读(273)  评论(0)    收藏  举报