前言
算法中有很多會用到歸納的思想,如遞歸等算法的基礎都是歸納。今天來看看歸納的主要思想以及基於歸納的一些算法設計。
歸納
證明當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
總結
只要一個問題有基礎解,並能由更小的問題構造出來,那麼這個問題就可以用歸納來解決。基於歸納的算法還有很多很多,主要是明白了這種歸納的思想,然後就可以使用函數遞歸實現了。