1. 程式人生 > 其它 >南昌大學-計算計-2020-2021-2學期-演算法重點

南昌大學-計算計-2020-2021-2學期-演算法重點

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) ;
}

13 揹包問題動態規劃法的手動計算(填表)

13.1 揹包問題動態規劃演算法

13.2 例題 6.6

13.3 習題6.8

13.4 習題6.9

14 單源最短路徑貪婪法的手動計算

14.1 例題 5.3

14.2 習題 5.3

15 設計特定要求(時間複雜度)的演算法