第一章
- 算法的定義:一組有窮的規則,它規定了某一類型問題的一系列計算方法。
- 算法的重要特性:1.確定性、2.可行性、3.輸入、4.輸出、5.有限性。
確定性:算法的每一種運算必須確定,沒有二義性。比如:1/0之類的運算不允許存在。
可行性:算法的每一種運算在理論上課由人通過紙筆在有限時間內演算。
輸入:每個算法允許有0個或多個輸入,這些量是在算法開始前給出的,在一個特定的集合內。
輸出:每個算法允許有1個或多個輸出,這些是與輸入有某種特定關係的量。
有限性:算法必須在有限的時間內結束。 - 算法的基本內容:1.設計算法、2.表示算法、3.確認算法、4.分析算法、5.測試程序
- 算法分析目的:爲了可以知道爲完成一項任務所設計的算法的優劣,從而促使人們設計出更好的算法。
- 算法的全面分析分爲兩個階段:事前分析、時候測試。
事前分析:求出該算法的時間限界函數(時間複雜度、空間複雜度)
事後測試:收集該算法的執行時間和佔用空間的統計(作時空分佈圖)
時間複雜度:用來描述算法隨着輸入規模增加、運算時間增長速率的統計量。 - 從計算時間上,可將算法分爲兩大類:多項式時間算法、指數時間算法。
多項式時間算法:可以由多項式函數來計算時間限界的算法。
指數時間算法:只能由指數函數來計算時間限界的算法。 - 頻率計數:計算機執行程序中某一條語句的次數。
第二章
- 遞歸算法:一種自身調用自身或間接調用自身的算法。
- 大致可將遞歸通過思想分爲兩類:基於歸納法的遞歸稱爲遞歸算法、基於分治思想的遞歸稱爲分治算法。
- 系統在實現遞歸函數的調用時,應使用棧的方式管理調用遞歸函數時的返回的地址。
- 值的回傳方式:兩次值傳送方式、地址傳送方式。
1)兩次值傳送方式:按照指定類型爲變參設置存儲空間,在執行函數調用時,將實參值傳送給變參,在返回時將變參的值傳給實參。
2)地址傳送方式: 在內部將變參設置成一個地址,在執行函數調用時,首先執行地址傳送即將實參的地址傳送給變參,在函數執行過程中,對變參的操作實際上變爲對所對應地址的操作。 - 遞歸算法:基礎步、歸納步
- 計算階乘函數n!f(n) = f(n-1)+1 —>>O(n)
- 快速冪
//非遞歸
int quick_pow(int a,int b){//a^b
int cnt = 1;
while(b){
if(b&1) cnt *=a;
a*=a;
b>>=1;
}
return cnt;
}
//遞歸
int quick_pow(int a,int b){//a^b
int cnt = 1;
if(b){
cnt = quick_pow(a,b/2);
cnt *= cnt;
if(b&1) cnt*=a;
}
return cnt;
}
//O(logn)
- 插入排序 O(n^2) 穩定 棧深度n
- 多項式 遞歸 提取X化簡,O(n) 站深度n
- 漢諾塔問題
void hmove(char a, char b, char c, int n){
if(n == 0){
return ;
}
hmove(a, c, b, n - 1); //前n - 1塊從a移動到b上
printf("%c -> %c\n", a, c); //我自己移動一塊從a -> c
ans++; //我移動了幾次?
hmove(b, a, c, n - 1); //最後把 n - 1塊從b移動到c上
}//f(n) = 2f(n-1)+1;
//只能先移動到相鄰的柱子上
void hmove2(char a, char b, char c, int n){
if(n == 0){
return ;
}
hmove2(a, b, c, n - 1); //把前n - 1塊通過b移動到c上
ans2++; //把最後一塊移動到b上
printf("%c -> %c\n", a, b);
hmove2(c, b , a, n - 1); //把n - 1 塊通過b移動到a上
ans2++; //把最後一塊移動到c上
printf("%c -> %c\n", b, c);
hmove2(a, b, c, n - 1); //把剩下的n - 1塊通過b移動到c上
}//f(n) = 3f(n-1)+2;
- 分治法:分而治之,將問題分解成若干子問題,子問題的處理方法又與原問題相同。分爲:劃分步、治理步、組合步。 T(n)=kT(n/k)+bn -->T(n) = n+bnlogk(n) --> O(nlogk(n))
- 二分法
int find_2(int l,int r,int x){
int m;
while(l<r){
m = (l+r)>>1;
if(a[m] < x) l = m+1;
else r = m;
}
return l;
}//O(logn)
``
- 歸併排序 自上向下 穩定 O(nlogn)
第三章
- 貪心算法:在每一輪的循環中,通過少量的局部的計算,力爭去找到一個局部最優解,而不考慮整體是否達到最優。
- 貪心求解揹包問題:value/weight sort 非最佳算法;
比如:WSUM = 7, P1 = 10,W1 = 5; P2 = 6,W2 = 4; P3 = 5,W3 = 3; - 最短路徑算法
迪傑斯特拉算法 O(n^2)不可處理帶負權值邊 單源點
弗洛裏達 O(n^3) 多源點
福特 O(VE) 可求負權邊 - 最小生成樹:
Prim(普里姆算法) 取點 稠密圖 O(n^2)選用鄰接矩陣
Kruskal(克魯斯卡爾算法) 取邊 疏散圖 O(vlogv)選用鄰接表 並查集
第四章
- 動態規劃:是解決多階段決策過程的方法。多階段決策過程指的是它的活動過程不僅可以分成若干個階段,而且在任意一個階段以後的行爲都僅僅依賴上一個階段的過程狀態,而與上個階段之前的階段沒有關係。 具有馬爾科夫性(後效性)
- 每一個多階段決策過程都可以用多段圖模型求解。
- 0/1揹包問題 O(n^2)
- 最長公共子序列 LCS
dp[i][j] : s1[0:i]與s2[0:j]的LCS
則有DP方程 if (s1[i] == s2[j] ) dp[i][j] = dp[i-1][j-1]+1 else dp[i][j] = max(dp[i][j-1],dp[i-1][j])
打印通過多開一個記錄數組通過遞歸打印。 - 編輯距離 dp[i][j] = min(dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+bool(s[i]==s[j]))
- 區間DP POJ 1141 POJ1390
第五章
-
回溯算法:是一種選優搜索法,按選優條件向前搜索,以達到目標。回溯法採用試錯的思想,它嘗試分步的去解決一個問題。在分步解決問題的過程中,當它通過嘗試發現現有的分步答案不能得到有效的正確的解答的時候,它將取消上一步甚至是上幾步的計算,再通過其它的可能的分步解答再次嘗試尋找問題的答案。
-
使用回溯算法對於問題的解空間進行深度搜索時,通常有兩種搜索算法:遞歸回溯、迭代回溯。
遞歸回溯:使用遞歸函數對於解空間進行DFS的回溯算法 -
0/1揹包問題 DFS.
-
上界函數 用於剪除不包括最優解的子樹
-
完全子圖:給定無向圖 G(v,e) U(v’,e’)當v’屬於v,e’屬於e,且對於圖U任意兩個節點都可以直接相連,U是G的完全子圖,也可以說圖U是圖G的一個團體當且僅當圖U不包括在更大的完全子圖中時。
-
圖G的最大團體指的是包含圖G節點數最多的團體
-
找到最大團體算法 DFS
第六章
- 隨機化算法:在算法中使用了隨機函數,且隨機函數的返回值直接或者間接的影響了算法的執行流程或執行結果。
優點:1.所需要消耗的計算時間與空間開銷,通常會小於一個問題的已知的最好的確定性算法。2.迄今爲止的所有隨機算法,他們的實現都比較簡單,比較容易理解。 - 隨機化算法分類:
A. 數值隨機化算法 近似解 精度隨時間增加而增加 隨機數產生器
B. 蒙特卡洛算法 不確定是否爲準確解 獲得正確解的概率和時間正相關 大素數判定方法(根據:費馬小定理。如果正整數N是素數,對於所有小於N的正整數A都有 A^(N-1)%N == 1)
C. 拉斯維加斯算法 不確定能否找到,但一定是正確解 找到正確解概率和時間正相關 字符串查詢RK算法
D. 謝伍德算法 正確解 情況:當一個算法最壞和平均相差過大可以用 消除最壞情況和特定實例的關聯 快排,K小數
第七章
- DFS BFS
- 無向圖的割點:節點集合S是節點集合V的子集,如果刪去S集合會使得圖G變成非連通圖,那麼就稱節點S是圖G的割點集。如果S中只有一個節點,則稱爲割點。
- 如果再一個無相連通圖沒有割點,那麼我們稱它爲雙連通圖。
- 連通圖:任意兩點之間存在路徑
- 連通分量:無向圖中最大連通圖即爲連通分量,連通圖只有一個連通分量就是它自身。 Tarjan算法可求 O(E+V) DFS+棧實現
- 強連通圖:有向圖中任意兩個節點直接互有路徑。
- 簡單通路:通路中所有的頂點互不相同。初級通路必爲簡單通路,但反之不真。
- 割點定理:A.深度優先搜索樹中,節點V的任何一個子孫節點都不能通過向後邊達到節點V的祖先。那個這個點就是割點。B.深度有限搜索樹中,根節點至少有兩個以上的孩子節點,根就是割點。
- 割點的尋找:Tarjan算法 時間戳思想
- 網絡的最大流問題
c(a,b) >= f(a,b) 容量約束
f(a,b) = -f(b,a) 斜對稱
f(a,a) = 0 流量守恆
EK 算法
1是源點 M是匯點,樸素EK
void EK(){
19 //從1出發,不斷找可以到達m的增廣路
20 int ans = 0;
21 while(true){
22 //EK算法的核心是通過bfs不斷查找增廣路,同時建立反向弧
23 //每次循環都要對v數組和p數組進行清空,因爲是意圖查找一條新的增廣路了
24 memset(p, 0, sizeof(p));
25 memset(v, 0, sizeof(v));
26 queue<int> q;
27 q.push(1);
28 v[1] = INF;
29 //每次只找一條增廣路,同時修改c[i][j]的值
30 while(!q.empty()){
31 int f = q.front();
32 q.pop();
33 for(int i = 1; i <= m; i++){
34 if(v[i] == 0 && c[f][i] > 0){ //v[i]原本是記錄增廣路實時的殘量最小值,v[i]==0代表這個點還沒有走過,且從p到i的殘量大於0說明通路
35 v[i] = min(v[f], c[f][i]); //實時更新v[i]的值,v[f]存儲1條增廣路中i點前所有水管殘量的最小值,v[i]爲該條增廣路到i點爲止,路徑上的最小殘量
36 p[i] = f; //p[i]實時保存i點的前驅節點,這樣就當i==m時整條增廣路就被記錄下來
37 q.push(i); //將i點入隊
38 }
39 }
40 }
41 if(v[m] == 0) break; //如果v[m]==0則代表找不到增廣路了(中途出現了c[i][j]==0的情況)
42 ans += v[m];
43 int temp = m;
44 while(p[temp] != 0){ //類似並查集的查操作,不斷查上一個元素且將剩餘殘量減去最小殘聯,反向弧增加最小殘量
45 c[p[temp]][temp] -= v[m];
46 c[temp][p[temp]] += v[m];
47 temp = p[temp];
48 }
49 }
50 printf("%d\n", ans);
51 }
- 二分圖匹配