三、算法分析(three-sum算法)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/guanhang89/article/details/50775508

算法分析

计时类设计

方便计时

public class Stopwatch { 

    private final long start;
    public Stopwatch() {
        start = System.currentTimeMillis();
    } 
    public double elapsedTime() {
        long now = System.currentTimeMillis();
        return (now - start) / 1000.0;
    }

分析

困难程度可以用问题的规模来衡量
下面的讲解基于three_sum(一组数中找三个和为零的三元组):
首先是暴力版代码,后面会有优化:

    public static int count(int[] a) {
        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;
    } 

幂次法则:假设运行时间模型为 T(N)=aNb


数学模型

一个程序运行时间主要和两点有关:

执行每条语句的耗时;
执行每条语句的频率。

分析中的近似:忽略较小的项,用~表示
定义:

 f(N)表示所有随着N的增大除以f(N)的结果趋近于1的函数。我们用g(N)~f(N)表示g(N)f(N)随着N的增大趋近于1

g(N)af(N)
f(N)=Nb(logN)c
其中abc均为常数,f(N)称为g(N)的增长的数量级

这里写图片描述

内循环:观察到的一个关键现象是执行最频繁的指令决定了程序执行的总时间

使用性质表示需要用实验验证的猜想

算法(有时还算上输入模型)决定了增长的数量级

成本模型:定义了算法中的基本操作(threesum问题的成本模型是访问的次数)
成本模型应该和内循环中的操作相关
这里写图片描述
分析举例:二分查找的输入模型是大小为N 的数组a[],内循环是一个while循环中的所有语句,成本模型是比较操作(比较两个元素的值)

常见函数和近似函数
这里写图片描述

增长数量级分类
这里写图片描述


算法优化

2-sum:先从2-sum推导
以线性对数级别(NlogN,对于这个问题没有更快的)解决2-sum问题(假设数据不重复):
这里写图片描述

public static int count(int[] a){
    Arrays.sort(a);
    int N = a.length;
    int cnt = 0;
    for (int i = 0; i < N; i++) {
        if(BinarySearch.indexOf(a,-a[i])>1)
            cnt++;
    }
    return cnt;
}

3-sum快速算法
运行总时间和N2logN成正比

package threesum;
import java.util.Arrays;
import edu.princeton.cs.introcs.In;
import edu.princeton.cs.introcs.StdOut;
public class ThreeSumFast {
    // Do not instantiate.
    private ThreeSumFast() { }
    // returns true if the sorted array a[] contains any duplicated integers
    private static boolean containsDuplicates(int[] a) {
        for (int i = 1; i < a.length; i++)
            if (a[i] == a[i-1]) return true;
        return false;
    }
    public static void printAll(int[] a) {
        int N = a.length;
        Arrays.sort(a);
        if (containsDuplicates(a)) throw new IllegalArgumentException("array contains duplicate integers");
        for (int i = 0; i < N; i++) {
            for (int j = i+1; j < N; j++) {
                int k = Arrays.binarySearch(a, -(a[i] + a[j]));
                if (k > j) StdOut.println(a[i] + " " + a[j] + " " + a[k]);
            }
        }
    } 
    public static int count(int[] a) {
        int N = a.length;
        Arrays.sort(a);
        if (containsDuplicates(a)) throw new IllegalArgumentException("array contains duplicate integers");
        int cnt = 0;
        for (int i = 0; i < N; i++) {
            for (int j = i+1; j < N; j++) {
                int k = Arrays.binarySearch(a, -(a[i] + a[j]));
                if (k > j) cnt++;
            }
        }
        return cnt;
    } 
}

倍率实验(估计运行时间的方法)

这里写图片描述
three-sum里面的比例为23,乘以8就行了
这里写图片描述

这里写图片描述
(除了指数级别之外都会出现收敛)
估计运行时间的注意事项:
1.非首项中的大常熟对结果影响很大
2.非决定性的内循环(即成本模型不是你想象的那样)
3.指令时间(假设时间不对)
4.系统因素
5.对输入的强烈依赖
6.多个问题参量


处理对输入的依赖

困难点:输入模型不切实际;模型分析困难(要大量数学技巧)

对最坏情况下的性能的保证
这里写图片描述

随机化算法
为性能提供保证的一个重要方法就是引入随机性(不随机的可能会导致最坏情况)

操作序列
有些算法的输入不只是数据,还包括一系列的操作,这个要考虑进去

均摊分析
相应地,提供性能保证的另一个重要的方法是通过记录所有操作的总成本除以操作总数来将成本均摊,(有些操作例如stack的resize不是每次都执行的,所以允许这种昂贵成本存在)
这里写图片描述


内存

要知道一个对象使用的内存量,需要将所有实例变量使用的内存与对象本身的开销(16个字节)相加。这些开销包括一个指向对象的类的引用、垃圾收集信息以及同步信息


补充:
这里写图片描述

展开阅读全文

没有更多推荐了,返回首页