歸納算法及例題分析

前言


算法中有很多會用到歸納的思想,如遞歸等算法的基礎都是歸納。今天來看看歸納的主要思想以及基於歸納的一些算法設計。


歸納

證明當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

總結

只要一個問題有基礎解,並能由更小的問題構造出來,那麼這個問題就可以用歸納來解決。基於歸納的算法還有很多很多,主要是明白了這種歸納的思想,然後就可以使用函數遞歸實現了。

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