前言
算法中有很多会用到归纳的思想,如递归等算法的基础都是归纳。今天来看看归纳的主要思想以及基于归纳的一些算法设计。
归纳
证明当n = 1时命题成立。
证明如果在n = k时命题成立,那么可以推导出在n = k+1时命题也成立。(m代表任意自然数)
这种方法的原理在于:首先证明在某个起点值时命题成立,然后证明从一个值到下一个值的过程有效。当这两点都已经证明,那么任意值都可以通过反复使用这个方法推导出来。
算法样例
1、多项式求解
Pn(x) = anxn+an−1xn+…+a1x+a0
求Pn(x)
归纳假设:我们已求解出Pn-1(x) = an−1xn+…+a1x+a0
则Pn(x) = Pn-1(x) + an * x^n
Tn = Tn + n + 1
Input:a0, a1, a2 … an Output: ans Begin int P(n){ if(n == 0){ return a[0]; } else{ P(n) = P(n - 1) + a[n] * x ^ n; } } ans = P(n) end
增强归纳
同时归纳P(n)与x的n次方
Input:a0, a1, a2 … an Output: ans Begin int x[n] X[0] = 1 X[1] = x int P(n){ if(n == 0){ return a[0]; } else{ x[n] = x[n - 1] * x; P(n) = P(n - 1) + a[n] * x[n]; } } Ans = P(n) End
Tn = Tn-1 + 3
2n次乘法,n次加法
O(3n)
优化
Pn-1(x) = anxn −1+an−1xn+…+a1x
Pn(x) = P(n) * x + a[N - n]
Tn = Tn- 1 + 2
N 次乘法,n次加法
Input: a1, a2, ... an Output: P Begin P = an; for(i = 1 to n){ P = x * P + a[n - i]; } } end
O(2n)
2. 最大导出子图
找G的最大导出子图H, H 中所有顶点的度大于或等于k
导出子图
含有原图一部分点,但点在的边一定在
生成子图
包含原图所有点,但不一定所有边
归纳假设
顶点数少于n的,我们可以找到它的导出子图
对于n的图
遍历所有点
若全都度>= k 则其就是最大的导出子图
若有点小于k,删去该点及所连边,检查剩下的图
剩下的图边数少于n,根据归纳假设,可求得最大导出子图
Input: all deg of v Output: biggest induced subgraph G Begin Graph(n){ flag = 1; if(n = k){ there is no biggest induced subgraph } for(every v in G){ if(deg(v) < k){ flag = 0; delete v and line linked v } } if(flag == 0){ Graph(n - 1); } else{ return; } } T = Graph(n) End
3. 一对一映射
集合A, f为A到A的映射,A中找子集S使f在S上是one-to-one function
归纳假设
假设n-1规模的集合我们可以找到相应的S
对于n
遍历所有元素
若每个元素都被映射到
则f为one-to-one的
A便是S
若有元素没有被映射到
则S中一定没有这个元素
删去这个元素
剩下n-1个,根据归纳
可求解
伪代码:
Algorithm Mapping Input: f[n] //mapping funciton for 1 to n Output: S // a set of 1...n c[N] = {0, 0, ... } //The in degree of element i begin for(i = 0; i < n; i++){ c[f(i)]++; } for(i = 0; i < n; i++){ if(c[i] = 0){ put i in queue } } while(!queue.empty()){ remove i from top of queue S = S - {i}; c[f[i]]--; if(c[f(i)] == 0){ put f[i] in queue } } } end
4. 社会名流
人与人关系有知道与不知道,若一个人被所有人知道,但自己不知道其他所有人,则他是社会名流。
归纳假设:
n-1规模的人中可以求解社会名流问题
随便拿两个人A和B
问A是否知道B
若知道,则A一定不是社会名流,删除
若不知道,则B一点不是社会名流,删除
还剩n-1个人
根据归纳假设,可以求解
不断删去,最后剩下的人有可能是,也可能不是
就是个社会名流的candidate
让他与其他所有人每个问两个问题,就可以确定其是不是
O(3(n-1))
Input: know[i][j] // i know j Output: celebrity begin //choose the candidate i = 1; j = 2; next = 3; for(k = 0; k < n - 1; k++){ if(know[i][j]){ i = next; } else{ j = next; } next++; } if(i == n + 1){ candidate = j } else{ candidate = i; } flag = 1; know[candidate][candidate] = 0; for(i = 0; i < n; i++){ if(know[candidate][i]){ flag = 0; break; } if(know[i][candidate] != 1){ flag = 0; break; } } if(flag == 0){ celebrity = 0; // there is no celebrity } else{ celebrity = candidate; } end
5. 计算平衡因子
计算一棵树(n个节点)上所有节点的平衡因子
归纳假设:已知节点数<n的树上所有节点的平衡因子
发现这个归纳没法求n
增强的归纳假设:已知节点数<n的树上每个节点的平衡因子和高度
对于n个节点的树
知道其根节点的左子树与右子树
根据归纳假设
知道左子树高度
知道右子树高度
可以求根的平衡因子
algorithm: balnum(node*) Input: a node point Output: balance num of this node begin node *balnum(node *p) { if(p -> right == NULL && p -> left == NULL){ p -> height = 0; p -> balnum = 0; } p -> height = max(balnum(p -> left) -> height, balnum(p -> right) -> height); p -> balnum = balnum(p -> left) -> height - balnum(p -> right) -> height; } end
6. 最大连续子序列
给定序列 x1, x2, x3 … xn
求和最大的连续的子序列
归纳假设:已知规模<n的序列 的最大子序列
加入xn,没法推得规模n的序列的最大子序列
增强的归纳假设:已知的规模<n的序列的最大子序列与后缀最大的子序列
加入xn
若xn加入使后缀序列和大于原先的最大子序列
则新序列最大后缀子序列加入xn,且最大子序列为最大后缀子序列
若xn加入使后缀序列和还是小于原先的最大子序列,但>0
则新序列最大后缀子序列加入xn,且最大子序列仍为原来的
若xn加入使后缀序列和<0
则新序列最大后缀子序列为0,且最大子序列仍为原来的
algorithm: maxSequence input: seq[N] Output: maxSeq begin maxSeq = 0, maxBackward = 0; for(i = 0; i < n; i++){ if(seq[i] + maxBackward > maxSeq){ maxSeq = maxBackward + seq[i]; } else{ if(seq[i] + maxBackward > 0){ maxBackward = seq[i] + maxBackward; } else{ maxBackward = 0; } } } end
优化
A[i]为以第i个元素结尾的最大子序列的和
I -> I + 1
若A[i] > 0, 则A[I + 1] = x[I + 1] + A[i]
Xi 加入其中
若A[i] < 0, 则A[I + 1] = x[I + 1]
最后遍历A[i], 求最大值
algorithm: maxSequence input: seq[N] Output: maxSeq begin maxSeq[0] = seq[0]; for(i = 1; i < N; i++){ if(maxSeq[i - 1] < 0){ maxSeq[i] = seq[i]; } else{ maxSeq[i] = maxSeq[i - 1] + seq[i]; } } end
7. 汉诺塔问题
有A、B、C三个桩子,A上放有n个不同大小的圆盘,按大小递减顺序自下而上。1) 设计算法,用最少的移动步数将A上的圆盘都移到C,每次只能移一个,且任一桩子上的圆盘都必须满足圆盘大小自下而上递减的顺序。2) 给定数组p,其元素取值只有1,2,3三种,代表了n个圆盘目前的位置,1代表A,2代表B,3代表C,p[i]的值为第i个圆盘的位置。例如p={3,3,2,1}表示共有4个圆盘,第一个和第二个都在C,第三个在B,第4个在A。如果p代表了1)中的最优移动过程中某一步的状态,则求出这是第几步。注意诸如p=[2,2]是不可能出现的,此时算法得到-1
1)
归纳假设:已知n-1个盘怎么移动
n个盘 从A到C 分三步
n-1个盘从A到B
第n个从A到C
N-1个盘从A到C
Algorithm: Hanoi Tower Input: Disk number Output: movement move(diskId, N, M) { move diskId disk from N to M } hanoi(n, A, B, C){ if(n == 1){ move(1, A, C); } else{ hanoi(n - 1, A, C, B); move(diskId, A, C); hanoi(n - 1, B, A, C); } } end
2)
汉诺塔
n个盘移动 需要2^n - 1次
反向逆推
考虑第n个盘
第n个在1时,说明最后一个盘还没有移动
考虑1 ---- n-1 移动为A -> B
第n个在3时, 说明最后一个盘已经到了指定位置
已经至少移了2^(n - 1) 次
考虑1 ---- n-1 移动为B -> C
递归求解
8. 逆时针填充矩阵
设计算法将1到n2按顺时针方向由内而外填入一个n*n的矩阵
例 n=2
2 3
1 4
n=5
25 10 11 12 13
24 9 2 3 14
23 8 1 4 15
22 7 6 5 16
21 20 19 18 17
逆时针一圈一圈往里填
数字递减
转向时判断
归纳假设: 已经知道k填在哪
对于k-1看k位置的下右上左顺序来填写
伪代码
Algorithm: matrix
Input: n
Output: matrix
Begin
CreateA(int n){
//初始化矩阵为长宽n+2的二维数组,最外层一圈填值为0,其余为-1
int[][] a = new int[n+2][n+2];
for (int i = 1; i < a.length-1; i++) {
for (int j = 1; j < a[i].length-1; j++) {
a[i][j] = -1;
}
}
//方向数组 该数组为本算法的关键
int move[][] = {{0,1},{1,0},{0,-1},{-1,0}};
int direction = 0;
int k= 25;
Go(k, 1, 1, n, a, move, direction);
//输出填好的蛇形矩阵
for (int i = 1; i < a.length-1; i++) {
for (int j = 1; j < a[i].length-1; j++) {
System.out.print("\t"+a[i][j]);
}
System.out.println();
}
}
// 填写矩阵,矩阵最外层的一圈为0,其他值为-1,当遇到-1的时候才给矩阵里填值
public static void Go(int k,int x,int y,int n,int a[][],int move[][],int direction){
a[x][y] = k--;
if(k> n*n)
return;
while(a[x+move[direction][0]][y+move[direction][1]]!= -1){
//方向由 下、右、上、左循环
direction = (direction+1)%4;
}
//x,y为即将走上的下一个格子
x = x+move[direction][0];
y = y+move[direction][1];
Go(k, x, y, n, a, move, direction);
}
}
end
总结
只要一个问题有基础解,并能由更小的问题构造出来,那么这个问题就可以用归纳来解决。基于归纳的算法还有很多很多,主要是明白了这种归纳的思想,然后就可以使用函数递归实现了。