归纳算法及例题分析

前言


算法中有很多会用到归纳的思想,如递归等算法的基础都是归纳。今天来看看归纳的主要思想以及基于归纳的一些算法设计。


归纳

证明当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

总结

只要一个问题有基础解,并能由更小的问题构造出来,那么这个问题就可以用归纳来解决。基于归纳的算法还有很多很多,主要是明白了这种归纳的思想,然后就可以使用函数递归实现了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章