算法引论
参考 : 《数据结构与算法分析》. Mark Allen Weiss. 机械工业出版社
第一章 : 引论
P和NP
P对NP问题是Steve Cook于1971年首次提出。“P/NP问题”,这里的P指多项式时间(Polynomial),一个复杂问题如果能在多项式时间内解决,那么它便被称为P问题,这意味着计算机可以在有限时间内完成计算;NP指非确定性多项式时间(nondeterministic polynomial),一个复杂问题不能确定在多项式时间内解决,假如NP问题能找到算法使其在多项式时间内解决,也就是证得了P=NP。比NP问题更难的则是NP完全和NP-hard,如围棋便是一个NP-hard问题。2010年8月7日,来自惠普实验室的科学家Vinay Deolalikar声称已经解决了“P/NP问题” ,并公开了证明文件。
- P类包含的是在多项式时间内可以解决的问题。
- NP类包含的是在多项式时间内“可验证”的问题。是指给定某一解决方案的“证书”,就能在问题输入规模的多项式时间内,验证该“证书”是正确的。
- P中任何问题都属于NP (即为NP类的子集),因为若某一问题属于P,则可以在不给出证书的情况下,在多项式时间内解决它。
- 通俗地说,如果一个问题属于NP,且与NP中的任何问题一样“难”的,则说它属于NPC类,即NP完全的(Non-deterministic Polynomial Complete)。
- 若NP 中的所有问题都能在多项式时间内归约为搜索问题A,那么则称问题A是NP- 完全的 (NPC)。
- N P = P + N P C NP = P + NPC NP=P+NPC
处理NP完全问题
-
在实践中,我们必须为这些各种各样的问题找到某种解决办法,因此人们对解决这些问题非常感兴趣。
-
(1)修改问题并寻找一种“近似”算法来给出接近但并非最佳的解。
-
(2)给出一种能够有效解决实际应用中所出现的问题的实例算法,但对于最坏情况下的输入,这种算法仍然是无法找到问题的解。这种方法最著名的例子是解决整数线性规划问题的程序,尽管它们有可能需要指数级别的时间,但实际应用中的输入数据也显然不是最坏情况下的输入。
-
(3)使用一种叫做“回溯法”的技术来避免检查所有可能的解,以期找到尽可能“高效”的指数级别算法。
级数
-
∑ i = 0 N A i = A N + 1 − 1 A − 1 \sum\limits_{i=0}^NA^i = \frac{A^{N+1} -1}{A-1} i=0∑NAi=A−1AN+1−1
-
∑ i = 0 N A i ≤ 1 1 − A , 若 0 < A < 1 \sum\limits_{i=0}^NA^i \leq \frac{1}{1-A}, 若0<A<1 i=0∑NAi≤1−A1,若0<A<1
-
∑ i = 1 N i = N ( N + 1 ) 2 \sum\limits_{i=1}^N i = \frac{N(N+1)}{2} i=1∑Ni=2N(N+1)
-
∑ i = 0 N A 2 = N ( N + 1 ) ( 2 N + 1 ) 6 \sum\limits_{i=0}^N A^2 = \frac{N(N+1)(2N+1)}{6} i=0∑NA2=6N(N+1)(2N+1)
-
∑ i = 1 N i k ≈ N k + 1 ∣ k + 1 ∣ , k ≠ − 1 \sum\limits_{i=1}^N i^k \approx \frac{N^{k+1}}{|k+1|} , k\neq -1 i=1∑Nik≈∣k+1∣Nk+1,k=−1
-
调和级数 H N = ∑ i = 1 N 1 i ≈ l o g e N H_N = \sum\limits_{i=1}^{N} \frac{1}{i} \approx log_e N HN=i=1∑Ni1≈logeN 其中近似值的误差趋于 γ ≈ 0.57721566 \gamma \approx 0.57721566 γ≈0.57721566 该值称为欧拉常数
在计算机科学中,除非有特别的声明,否则所有的对数都是以2为底的。
比如 : l o g N log N logN 实际就是 l o g 2 N log_2 N log2N
递归
- 递归逆序打印输出数。写一个递归程序,逆序打印一个正整数的各位数字。
public class Solution {
// 正序输出
void PrintOut (int n) {
if(n>9) {
PrintOut(n/10);
}
int m = n%10;
System.out.print(m+" ");
}
// 反序输出
void printOut(int n){
if ( n!=0) {
int m = n%10; //把低位先赋值给m
System.out.print(m+" "); //先输出低位数
printOut(n/10); //递归调用, 逐渐输出
}
}
public static void main(String[] args) {
Solution a = new Solution();
a.PrintOut(1200);
System.out.println();
a.printOut(1200);
}
}
输出结果 :
1 2 0 0
0 0 2 1
或者 :
public class Solution {
// 正序输出
void PrintOut(int n){
if ( n!=0) {
PrintOut(n/10); //先递归调用, 再逐渐输出
n = n%10; //把最高位先输出
System.out.print(n+" "); //先输出低位数
}
}
// 反序输出
void printOut(int n){
if ( n!=0) {
int m = n%10; //把低位先赋值给m
System.out.print(m+" "); //先输出低位数
printOut(n/10); //递归调用, 逐渐输出
//尾递归 : 如果在从递归调用返回时没有继续的操作要完成,那么这个递归方法就称为尾递归(tail recursive)
}
}
public static void main(String[] args) {
Solution a = new Solution();
a.PrintOut(1200);
System.out.println();
a.printOut(1200);
}
}
输出 :
1 2 0 0
0 0 2 1
- 将欧几里德算法改写为递归算法。
public class Solution {
// 欧几里得算法 (求最大公约数)
int Gcd(int m, int n) {
int Rem;
while(n>0) {
Rem = m%n;
m=n;
n=Rem;
}
return m;
}
// 或者用递归实现欧几里得算法
int gcd(int m, int n) {
if(n>0) {
return gcd(n,m%n);
}else {
return m;
}
}
public static void main(String[] args) {
Solution a = new Solution();
int b1 = a.Gcd(16,8);
int b2 = a.gcd(16,8);
System.out.println(b1);
System.out.println(b2);
}
}