南昌大學-計算計-2020-2021-2學期-演算法重點
阿新 • • 發佈:2021-07-07
2020-2021-2學期-演算法重點
重點總結
- 演算法特點、時間複雜度、空間複雜度、平均複雜度
- 演算法複雜度三種記號的嚴格數學定義、用定義求複雜度
- 迴圈次數統計求複雜度
- 展開法求遞推方程、特徵根求遞推方程
- 快速排序的平均複雜度
- 基數排序
- 動態規劃的兩個基本要素
- 貪心演算法的基本要素
- 分治法與動態規劃的異同
- 分治演算法複雜度的主定理(定理4.2)
- 簡單問題的分治法設計
- 簡單問題的二分法設計
- 揹包問題動態規劃法的手動計算(填表)
- 單源最短路徑貪婪法的手動計算
- 設計特定要求(時間複雜度)的演算法
各知識點的細化、例項
1 演算法特點、時間複雜度、空間複雜度、平均複雜度
1.1 演算法特點:
- 有限性
- 確定性,演算法的每個步驟都有精確的定義,要執行的步驟都是清晰的、無歧義的
- 輸入,一個演算法有0個或多個輸入
- 輸出,一個演算法有一個或多個輸出
- 能行性,演算法中有待實現的運算都是基本的運算,原則上可以由人用紙和筆在有限的時間內精確的完成
2 演算法複雜度三種記號的嚴格數學定義、用定義求複雜度
2.1 執行時間的上界—— O 記號
2.2 執行時間的下界—— Ω 記號
- 下界的階越高,評估的精確度越高。
2.3 執行時間的準確界—— θ 記號
2.4 三者的關係
- 如果 f(n) = O(g(n)),且 f(n) = Ω(g(n)) , 則 f(n) = θ (g(n))
- 如果 f(n) = O(g(n)) , 且 g(n) = O(h(n)) , 則 f(n) = O(h(n)) 。
- 如果 f(n) = Ω(g(n)) , 且 g(n) = Ω(h(n)) , 則 f(n) = Ω(h(n)) 。
- 如果 f(n) = θ(g(n)) , 且 g(n) = θ(h(n)) , 則 f(n) = θ(h(n)) 。
3 迴圈次數統計求複雜度
3.1 洗牌
4 展開法求遞推方程、特徵根求遞推方程
4.1 特徵根求遞推方程
4.2 遞推法求解遞迴方程
5 快速排序的平均複雜度
5.1 快速排序的平均複雜度為 nlogn
6 基數排序
6.1演算法思想:
- 第一步,按照元素關鍵字的最低位數字d1 排序,排序之後合併成一個連結串列
- 第二步,按照元素關鍵字的次低位數字d2 排序,重複第一步的工作,得到的新連結串列以關鍵字的最低兩位數字排序
- 依次類推,經過第k步,按照元素關鍵字最高位數字dk 排序,重複第一步的工作。
6.2 演算法程式碼形式:
// 基數排序 輸入:存放元素的連結串列 L,關鍵字的數字位數 k 輸出:按遞增順序排序的連結串列 L
template <class Type>
void radix_sort(Type* L , int k){
Type *Lhead[10] , *p ;
int i , j ;
for(i = 0 ; i < 10 ;i ++) Lhead[i] = new Type ; // 分配10個連結串列的頭結點
for(i = 0 ; i < k ; i ++) {
for(j = 0 ; j < 10 ; j ++)
Lhead[j]->prior = Lhead[j]->next = Lhead[j] ; // 將10個連結串列置空
while(L->next != L){
p = del_entry(L) ; // 刪去 L 的第一個元素,使 p 指向該元素
j = get_digital(p , i) ; // 從 p 所指的元素關鍵字取第 i 個數字
add_entry(Lhead[j] , p) ; // 把 p 所指的元素加入連結串列 Lhead[j] 的表尾
}
for(j = 0 ; j < 10 ; j ++) append(L,Lhead[j]) ; // 將10個連結串列連結到 L
}
for(i = 0 ; i < 10 ; i ++) delete(Lhead[i]) ; // 釋放10個連結串列的頭結點
}
// 取下並刪除雙迴圈連結串列的第一個元素
// 輸入:連結串列的頭結點指標 L 輸出:被取下第一個元素的連結串列 L ,以及指向被取下元素的指標
template <class Type>
Type* del_entry(Type *L){
Type *p ;
if(p != L){
p->prior->next = p->next ;
p->next->prior = p->prior ;
}else p = NULL ;
return p ;
}
// 將一個元素 插入雙向迴圈連結串列的鏈尾
// 輸入:連結串列的頭結點指標 L 輸出 , 插入元素的指標 p :插入一個元素後的連結串列 L
template <class Type>
void add_entry(Type *L , Type *p){
p->prior = L->prior ;
p->next = L ;
L->prior->next = p ;
L->prior = p ;
}
// 取 p 所指向元素的關鍵字的第 i 為數字(最低位為第 0 位)
// 輸入:指向某元素的指標 p ,希望去除的關鍵字的第 i 位數字的位置 i 輸出:該元素的關鍵字的第i為數字
template <class Type>
int get_digital(Type *p , int i){
int key ;
key = p->key ;
if(i != 0) key = key / power(10 , i) ;
return key % 10 ;
}
// 把連結串列 L1 的所有元素附加到連結串列 L 的末端
// 輸入:指向連結串列 L1 和 L 的頭結點指標 輸出:附加了新內容的連結串列 L
template <class Type>
void append(Type *L , Type *L1){
if(L1->next != L1){
L->prior->next = L1->next ;
L1->next->prior = L->prior ;
L1->prior->next = L ;
L->prior = L1->prior ;
}
}
6.3 例項
6.3.1 手動模擬寫出基數排序的過程
習題P84 T14:初始連結串列的內容為:3562,6381,0356,2850,9136,3715,8329,7481,寫出用基數排序演算法對他們進行排序的過程。
6.3.2 按任意基數進行排序的演算法
習題 P85 T15:將基數作為引數,編寫一個可以按照任意基數進行排序的演算法。
7 動態規劃的兩個基本要素
- 最優子結構
- 重疊子問題
8 貪心演算法的基本要素
- 最優子結構
- 貪婪策略(貪婪選擇性質)
9 分治法與動態規劃的異同
- 相同點
- 二者都要求原問題具有最優子結構性質。
- 都是將原問題分而治之,分解成若干個規模較小(小到很容易解決的程式)的子問題。然後將子問題的解合併,形成原問題的解
- 不同點
- 分治法將分解後的子問題看成相互獨立的。動態規劃將問題分解為相互間有聯絡,有重疊部分的子問題
- 分治法通常利用遞迴求解。動態規劃通常利用迭代法自底向上求解,但也能用具有記憶功能的遞迴法自頂向下求解。
- 分治法解決的問題不一定是最優化問題,而動態規劃解決的問題一定是最優化問題。
10 分治演算法複雜度的主定理(定理4.2)
11 簡單問題的分治法設計
11.1 用分治法重新設計二叉搜尋演算法
template <class Type>
int binarySearch(Type A[] , int low , int high , Type x){
int mid = (low + high) / 2 ;
if(A[mid] == x) return mid ;
else if (A[mid] > x) return binarySearch(A,mid+1 ,high,x) ;
else return binarySearch(A,low,mid-1,x) ;
}
11.2 用分治法求 AB
typedef long long ll ;
// 用分治法 求 A 的 B 次方
ll myPow(ll a , ll b){
if(b == 1) return a ;
else {
ll temp = myPow(a , b / 2) ;
if(b % 2) return temp * temp * a ;
else return temp * temp ;
}
}
// 用分治法 求 A 的 B 次方 對 C 取餘的結果
ll myPowModC(ll a , ll b , ll c){
if(b == 1) return a % c ;
else {
ll temp = myPowModC(a , b / 2 , c) ;
if(b % 2) return temp * temp * a % c ;
else return temp * temp % c ;
}
}
11.3 分治法求二叉樹的高度
int TreeDepth(Node * root){
if(!root) return 0 ;
int leftDepth = TreeDepth(root->left) ;
int rightDepth = TreeDepth(root->right) ;
return max(leftDepth , rightDepth) + 1 ;
}
11.4 稱硬幣的次數
/*
在n(n>=3)枚硬幣中有一枚重量不合格的硬幣(重量過輕或過重),如果只有一架天平可以用來稱重且稱重的硬幣數沒有限制,設計一個演算法找出這枚不合格的硬幣,使得稱重的次數最少?給出演算法的偽碼描述。如果每稱1次就作為1次基本運算,分析演算法最壞情況下的時間複雜度!(8分)
*/
// A 是 n 個的硬幣的集合
int Coin(A, n) {
if n 等於 1 then return 0 ;
if n 等於 2 then return 1 ;
k = n / 3 ; // 向下取整
將 A 中的硬幣分為三部分,X , Y , Z ,且 X 和 Y 中的硬幣數為 k , Z 中的硬幣數為 n - 2k ;
if W(X) != W(Y) { // W(X) , W(Y) 表示 X,Y 的重量
A 集合賦值為 X 和 Y 集合的並集 ;
return Coin(A , 2k) + 1 ;
}else{
A 集合賦值為 Z 集合 ;
return Coin(A , n -2k) + 1;
}
}
/*
最壞情況下的時間複雜度為 O(logn)
遞推方程為 f(n) = f(2n/3) +O(1) ; f(1) = 0 ; f(2) = 1 ;
*/
11.5 求嚴格第二大的分治演算法
12 簡單問題的二分法設計
// n 個互不相等的整數,按遞增順序存放於陣列A,若存在一個下標 0<=i<n,使A[i]=i,設計一個O(logn)演算法找到i
int Search(int a[], int left , int right){
mid = (l + r) / 2; ;
if(A[mid] == mid) return mid ;
else if(A[mid] > mid) return Search(a , left , mid - 1) ;
else return Search(a , mid + 1 , high) ;
}