算法 - 算法分析.md 7.4 KB
Newer Older
C
CyC2018 已提交
1
<!-- GFM-TOC -->
C
CyC2018 已提交
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
* [数学模型](#数学模型)
    * [1. 近似](#1-近似)
    * [2. 增长数量级](#2-增长数量级)
    * [3. 内循环](#3-内循环)
    * [4. 成本模型](#4-成本模型)
* [注意事项](#注意事项)
    * [1. 大常数](#1-大常数)
    * [2. 缓存](#2-缓存)
    * [3. 对最坏情况下的性能的保证](#3-对最坏情况下的性能的保证)
    * [4. 随机化算法](#4-随机化算法)
    * [5. 均摊分析](#5-均摊分析)
* [ThreeSum](#threesum)
    * [1. ThreeSumSlow](#1-threesumslow)
    * [2. ThreeSumBinarySearch](#2-threesumbinarysearch)
    * [3. ThreeSumTwoPointer](#3-threesumtwopointer)
* [倍率实验](#倍率实验)
C
CyC2018 已提交
18
<!-- GFM-TOC -->
C
CyC2018 已提交
19 20


C
CyC2018 已提交
21
# 数学模型
C
CyC2018 已提交
22

C
CyC2018 已提交
23
##  1. 近似
C
CyC2018 已提交
24

C
CyC2018 已提交
25
N<sup>3</sup>/6-N<sup>2</sup>/2+N/3 \~ N<sup>3</sup>/6。使用 \~f(N) 来表示所有随着 N 的增大除以 f(N) 的结果趋近于 1 的函数。
C
CyC2018 已提交
26

C
CyC2018 已提交
27 28
##  2. 增长数量级

C
CyC2018 已提交
29
N<sup>3</sup>/6-N<sup>2</sup>/2+N/3 的增长数量级为 O(N<sup>3</sup>)。增长数量级将算法与它的具体实现隔离开来,一个算法的增长数量级为 O(N<sup>3</sup>) 与它是否用 Java 实现,是否运行于特定计算机上无关。
C
CyC2018 已提交
30 31

##  3. 内循环
C
CyC2018 已提交
32 33 34

执行最频繁的指令决定了程序执行的总时间,把这些指令称为程序的内循环。

C
CyC2018 已提交
35
##  4. 成本模型
C
CyC2018 已提交
36 37 38

使用成本模型来评估算法,例如数组的访问次数就是一种成本模型。

C
CyC2018 已提交
39
# 注意事项
C
CyC2018 已提交
40

C
CyC2018 已提交
41
##  1. 大常数
C
CyC2018 已提交
42

C
CyC2018 已提交
43
在求近似时,如果低级项的常数系数很大,那么近似的结果是错误的。
C
CyC2018 已提交
44

C
CyC2018 已提交
45
##  2. 缓存
C
CyC2018 已提交
46 47 48

计算机系统会使用缓存技术来组织内存,访问数组相邻的元素会比访问不相邻的元素快很多。

C
CyC2018 已提交
49
##  3. 对最坏情况下的性能的保证
C
CyC2018 已提交
50 51 52

在核反应堆、心脏起搏器或者刹车控制器中的软件,最坏情况下的性能是十分重要的。

C
CyC2018 已提交
53
##  4. 随机化算法
C
CyC2018 已提交
54 55 56

通过打乱输入,去除算法对输入的依赖。

C
CyC2018 已提交
57
##  5. 均摊分析
C
CyC2018 已提交
58

C
CyC2018 已提交
59
将所有操作的总成本除于操作总数来将成本均摊。例如对一个空栈进行 N 次连续的 push() 调用需要访问数组的次数为 N+4+8+16+...+2N=5N-4(N 是向数组写入元素的次数,其余都是调整数组大小时进行复制需要的访问数组次数),均摊后访问数组的平均次数为常数。
C
CyC2018 已提交
60

C
CyC2018 已提交
61
# ThreeSum
C
CyC2018 已提交
62

C
CyC2018 已提交
63
ThreeSum 用于统计一个数组中和为 0 的三元组数量。
C
CyC2018 已提交
64 65

```java
C
CyC2018 已提交
66 67
public interface ThreeSum {
    int count(int[] nums);
C
CyC2018 已提交
68 69 70
}
```

C
CyC2018 已提交
71
##  1. ThreeSumSlow
C
CyC2018 已提交
72

C
CyC2018 已提交
73
该算法的内循环为 `if (nums[i] + nums[j] + nums[k] == 0)` 语句,总共执行的次数为 N(N-1)(N-2) = N<sup>3</sup>/6-N<sup>2</sup>/2+N/3,因此它的近似执行次数为 \~N<sup>3</sup>/6,增长数量级为 O(N<sup>3</sup>)。
C
CyC2018 已提交
74 75

```java
C
CyC2018 已提交
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
public class ThreeSumSlow implements ThreeSum {
    @Override
    public int count(int[] nums) {
        int N = nums.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 (nums[i] + nums[j] + nums[k] == 0) {
                        cnt++;
                    }
                }
            }
        }
        return cnt;
    }
C
CyC2018 已提交
92 93 94
}
```

C
CyC2018 已提交
95
##  2. ThreeSumBinarySearch
C
CyC2018 已提交
96

C
CyC2018 已提交
97
将数组进行排序,对两个元素求和,并用二分查找方法查找是否存在该和的相反数,如果存在,就说明存在和为 0 的三元组。
C
CyC2018 已提交
98 99 100

应该注意的是,只有数组不含有相同元素才能使用这种解法,否则二分查找的结果会出错。

C
CyC2018 已提交
101
该方法可以将 ThreeSum 算法增长数量级降低为 O(N<sup>2</sup>logN)。
C
CyC2018 已提交
102 103

```java
C
CyC2018 已提交
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
public class ThreeSumBinarySearch implements ThreeSum {

    @Override
    public int count(int[] nums) {
        Arrays.sort(nums);
        int N = nums.length;
        int cnt = 0;
        for (int i = 0; i < N; i++) {
            for (int j = i + 1; j < N; j++) {
                int target = -nums[i] - nums[j];
                int index = BinarySearch.search(nums, target);
                // 应该注意这里的下标必须大于 j,否则会重复统计。
                if (index > j) {
                    cnt++;
                }
            }
        }
        return cnt;
    }
C
CyC2018 已提交
123 124 125 126
}
```

```java
C
CyC2018 已提交
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
public class BinarySearch {

    public static int search(int[] nums, int target) {
        int l = 0, h = nums.length - 1;
        while (l <= h) {
            int m = l + (h - l) / 2;
            if (target == nums[m]) {
                return m;
            } else if (target > nums[m]) {
                l = m + 1;
            } else {
                h = m - 1;
            }
        }
        return -1;
    }
C
CyC2018 已提交
143 144 145
}
```

C
CyC2018 已提交
146
##  3. ThreeSumTwoPointer
C
CyC2018 已提交
147

C
CyC2018 已提交
148
更有效的方法是先将数组排序,然后使用双指针进行查找,时间复杂度为 O(N<sup>2</sup>)。
C
CyC2018 已提交
149 150

```java
C
CyC2018 已提交
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
public class ThreeSumTwoPointer implements ThreeSum {

    @Override
    public int count(int[] nums) {
        int N = nums.length;
        int cnt = 0;
        Arrays.sort(nums);
        for (int i = 0; i < N - 2; i++) {
            int l = i + 1, h = N - 1, target = -nums[i];
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            while (l < h) {
                int sum = nums[l] + nums[h];
                if (sum == target) {
                    cnt++;
                    while (l < h && nums[l] == nums[l + 1]) l++;
                    while (l < h && nums[h] == nums[h - 1]) h--;
                    l++;
                    h--;
                } else if (sum < target) {
                    l++;
                } else {
                    h--;
                }
            }
        }
        return cnt;
    }
C
CyC2018 已提交
178 179 180
}
```

C
CyC2018 已提交
181
# 倍率实验
C
CyC2018 已提交
182

C
CyC2018 已提交
183
如果 T(N) \~ aN<sup>b</sup>logN,那么 T(2N)/T(N) \~ 2<sup>b</sup>
C
CyC2018 已提交
184

C
CyC2018 已提交
185
例如对于暴力的 ThreeSum 算法,近似时间为 \~N<sup>3</sup>/6。进行如下实验:多次运行该算法,每次取的 N 值为前一次的两倍,统计每次执行的时间,并统计本次运行时间与前一次运行时间的比值,得到如下结果:
C
CyC2018 已提交
186

C
CyC2018 已提交
187 188 189 190 191 192 193 194
| N | Time(ms) | Ratio |
| :---: | :---: | :---: |
| 500 | 48 | / |
| 1000 | 320 | 6.7 |
| 2000 | 555 | 1.7 |
| 4000 | 4105 | 7.4 |
| 8000 | 33575 | 8.2 |
| 16000 | 268909 | 8.0 |
C
CyC2018 已提交
195

C
CyC2018 已提交
196
可以看到,T(2N)/T(N) \~ 2<sup>3</sup>,因此可以确定 T(N) \~ aN<sup>3</sup>logN。
C
CyC2018 已提交
197 198

```java
C
CyC2018 已提交
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
public class RatioTest {

    public static void main(String[] args) {
        int N = 500;
        int loopTimes = 7;
        double preTime = -1;
        while (loopTimes-- > 0) {
            int[] nums = new int[N];
            StopWatch.start();
            ThreeSum threeSum = new ThreeSumSlow();
            int cnt = threeSum.count(nums);
            System.out.println(cnt);
            double elapsedTime = StopWatch.elapsedTime();
            double ratio = preTime == -1 ? 0 : elapsedTime / preTime;
            System.out.println(N + "  " + elapsedTime + "  " + ratio);
            preTime = elapsedTime;
            N *= 2;
        }
    }
C
CyC2018 已提交
218 219 220 221
}
```

```java
C
CyC2018 已提交
222
public class StopWatch {
C
CyC2018 已提交
223

C
CyC2018 已提交
224
    private static long start;
C
CyC2018 已提交
225 226


C
CyC2018 已提交
227 228 229
    public static void start() {
        start = System.currentTimeMillis();
    }
C
CyC2018 已提交
230 231


C
CyC2018 已提交
232 233 234 235
    public static double elapsedTime() {
        long now = System.currentTimeMillis();
        return (now - start) / 1000.0;
    }
C
CyC2018 已提交
236 237
}
```
C
CyC2018 已提交
238 239 240 241




C
CyC2018 已提交
242
</br><div align="center">关注公众号 CyC2018 获取更多精彩内容!在公众号后台回复关键字 **资料** 可领取一份技术面试复习思维导图,帮你理清多而杂的面试知识点。
C
CyC2018 已提交
243
<div align="center"><img width="180px" src="https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg"></img></div>