数学基础
指数
指数加法、乘法、除法、幂
XAXB=XA+B
XAXB=XA−B
(XA)B=XAB
XN+XN=2XN
对数
在计算机科学中,除非有特笔的声明,否则所有的对数都是以2为底的。
logAB=logCAlogCB
换底公式
logAB=logA+logB
级数
∑ni=02i=2N+1−1
∑ni=0Ai=AN+1−1A−1
∑ni=0i2=n(n+n1(2n+1)6
模运算
如果N整除 A-B,那么就说A与B模N同余,记为
A≡B(modN)
。
81≡61(mod/10)
证明方法
归纳法
基准情形
归纳假设
反证法
通过反例证明
算法
算法是什么?算法是为求解一个问题需要遵循的、被清楚指定的简单指令的集合。
为什么学习算法?1 算法是用来解决问题的。2 机器学习的时候会用到算法。3 找工作。
算法的特点:1 有穷性,需要在一定的时间范围内完成。(对立面:地老天荒)。2 确定性,第一步做什么,第二步做什么,清清楚楚。3可行性,在当前的计算条件、人力、物力条件下能实现的。4每个算法都要有输入输出。
算法分析
算法分析是分析算法的时间、空间复杂度。使用大O标记法。会涉及到上界、下界。法则:
如果
T1(N)=O(f(N))
且
T2(N)=O(g(N))
,那么
T1(N)+T2(N)=O(f(N)+g(N))
。
如果 T1(N)=O(f(N)) 且 T2(N)=O(g(N)) ,那么 T1(N)∗T2(N)=O(f(N)∗g(N)) 。
算法时间复杂度分析中,每一步就是指汇编中的一条指令。空间是指内存暂用字节数。
复杂度分析方法
数循环次数、均摊分析、递归式–主定理
复杂度标记
- O(1)
基本运算:+ - * / % 寻址 - O(logn)
二分查找 O( n1/2 )
枚举约数。例如查找100的约数有哪些?1,2,4,5,10,20,25,50,100。计算的时候只要从1计算到10即可,因为当找到约数2的时候,也找到了50。所以只需要 n1/2 。O(n)
线性查找- O(
n2
)
朴素最近点对 - O(
n3
)
普通矩阵乘法 Floyd最短路(不了解) - O(nlogn)
归并排序 快速排序期望复杂度 基于比较的排序算法下界 - O(
2n
)
枚举全部的子集 - O(n!)
枚举全排列
优秀的复杂度:O(1) < O(logn) < O(
n1/2
) < O(n) < O(nlogn)
可能可以优化的:O(
n2
) < O(
n3
) < O(
2n
) < O(n!)
分析方法之 数数
我之前看过N遍算法书,终究是没看明白时间复杂度与空间复杂度是怎么计算的。前段时间做了leetcode,算是有一点点感觉。最好详细阅读《数据结构域算法分析-java语言描述》第2版 的第2章。在书籍中有说明for循环、顺序语句、if/else语句怎么计算复杂度。也介绍了什么是大O标记法。我还需要再看看书。
分析方法之 均摊分析
因为每次操作数量不同,但是总操作数是一定的,所以可以用均摊分析法。例如Java中的ArrayList的实现。ArrayList是用数组实现的。在初始化的时候会分配一定的空间n。每次add、remove操作都是O(1)。但是当插入数量大于n的时候,会导致一次内存重新分配,会再分配n+ 12n 的空间,再把前n个数,拷贝到新数组。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
那add操作的时间复杂度是多少呢?例如初始n=10,当n=15、22、33、49、73、109….的时候会触发数组扩充操作。假设最终n=109,这期间需要进行 10+15+22+33+49+73=202次操作。平均到每个数字202/109=2。表示为O(1)(关于为什么2表示为O(1)需要看书了解)。
例题
求最大字序列和。给定一个数组nums,里面有正有负,求 ∑jk=inums[k] 的最大值。
解法一 暴力
枚举所有子数组可能的起始下标和结束下标。第4行循环大小为n。第5行循环大小为n-i,但最坏情况是n。第7行循环大小为j-i+1,最坏情况也是n。所以时间复杂度记为O( n3 )。
public static int maxSubSum(int[] nums) {
int n = nums.length;
int maxSum = 0;
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
int thisSum = 0;
for (int k = i; k <= j; k++) {
thisSum += nums[k];
}
maxSum = Math.max(thisSum, maxSum);
}
}
return maxSum;
}
解法二 优化枚举
解法一中有一些冗余计算。 ∑jk=inums[k]=∑j−1k=inums[k]+nums[j] 。优化以后时间复杂度O( n2 )。
public static int maxSubSum(int[] nums) {
int n = nums.length;
int maxSum = 0;
for (int i = 0; i < n; i++) {
int thisSum = 0;
for (int j = i; j < n; j++) {
thisSum += nums[j];
maxSum = Math.max(thisSum, maxSum);
}
}
return maxSum;
}
解法三 贪心
这里用到了数组中有正有负的条件。任意一个数加上负数和总是在变小。所以当发现thisSum<0,就让thisSum=0。这样的解法时间复杂度O(n),但是正确性不是很明显,总是需要证明的,同时也需要较高的编程人员水平(相对来说)。算法的优化总是在时间、空间、复杂度之间进行权衡。
public static int maxSubSum(int[] nums) {
int n = nums.length;
int maxSum = 0,thisSum = 0;
for (int i = 0; i < n; i++) {
thisSum += nums[i];
maxSum = Math.max(maxSum, thisSum);
if(thisSum <0 ) thisSum = 0;
}
return maxSum;
}