演算法設計與分析(fd)

小菜鳥的java成長史發表於2020-12-28

演算法設計與分析知識點總結

演算法

演算法是一組有窮的規則,它規定了解決某一特定型別問題的一系列運算。

演算法的五個重要特性:確定性、可行性(有效性)、輸入、輸出、有窮性

五個方面:設計、表示、證明、分析、測試

分析演算法的兩個階段:事前分析、事後測試

**演算法分析— 頻率計數

  • 統計演算法中各類運算的執行次數

    • 基本運算:指那些時間囿界於常數的運算
    • 複合運算:具有固定執行時間的程式塊,如一條語句、一個過程或函式等,它們的一次執行時間也可視為常量、單位時間。
  • 運算的執行次數是從演算法的控制流程得來的

  • 一般用頻率計數函式表示式中的最高次項表示演算法複雜性分析的最終結果 —— 限界函式,且忽略掉其係數,記為: g(n)

  • 演算法時間/空間複雜度的限界函式常用的有三個

    • 上界函式:如果存在兩個正常數c和n0,對於所有 的n≥n0,有|f(n)| ≤ c|g(n)|,則記作f(n) = Ο(g(n))。

    • 下界函式:如果存在兩個正常數c和n0,對於所有的n≥n0,有|f(n)| ≥ c|g(n)|,則記作f(n) = Ω(g(n))。

    • “均值”函式:如果存在正常數c1,c2和n0,對於所有的n≥n0,有 c1|g(n)| ≤|f(n)| ≤ c2|g(n)|, 則記作f(n) = Θ(g(n))

  • Ο(1) < Ο(logn) < Ο(n) < Ο(nlogn) < Ο(n2) < Ο(n3)Ο(2n) < Ο(n!) < Ο(nn)

  • o記號:對任意正常數c,存在常數n0>0,使對所有的n≥n0,有|f(n)| ≤ c|g(n)|,則記作:f(n) = o(g(n))。

    • 例:2n = o(n2),但2n2≠ o(n2)
  • ω記號:對任意正常數c,存在常數n0>0,使對所有的n≥n0,有c|g(n)|≤|f(n)| ,則記作:f(n) = ω(g(n))。

    • 例:n2/2 = ω(n),但n2/2≠ ω(n2)

證明的方法:數學歸納法、反證法、反例法

遞迴分治法

遞迴

  • 在定義一個過程或函式時出現呼叫本過程或本函式的成分,稱之為遞迴

    • 呼叫自身,稱之為直接遞迴
    • 過程或函式p呼叫過程或函式q,而q又呼叫p,稱之為間接遞迴
    • 如果一個遞迴過程或遞迴函式中遞迴呼叫語句是最後一條執行語句,則稱這種遞迴呼叫為尾遞迴
  • 在以下三種情況下,常常要用到遞迴的方法

    • 定義是遞迴的:許多數學公式、數列等的定義是遞迴的。例,求n!和Fibonacci數列等。

      • Fibonacci: int Fib(int n)
        {  if (n1 || n2)
            return 1;
          else
        return Fib(n-1)+Fib(n-2);
    • 資料結構是遞迴的:例如單連結串列就是一種遞迴資料結構

    • 求解方法是遞迴的:典型的有Hanoi問題求解

  • 遞迴設計-獲取遞迴模型例

    • 簡單選擇排序

      • void SelectSort(int a[],int n,int i)
        { int j,k;
        if (i==n-1) return; //滿足遞迴出口條件
        else
          { k=i; //k記錄a[i…n-1]中最小元素的下標
        for (j=i+1;j<n;j++) //在a[i…n-1]中找最小元素
          if (a[j]<a[k])
        k=j;
        if (k!=i) //若最小元素不是a[i]
        swap(a[i],a[k]); //a[i]和a[k]交換
        SelectSort(a,n,i+1);
          }
        }
    • 氣泡排序

      • void BubbleSort(int a[],int n,int i)
        { int j;
        bool exchange;
        if (in-1) return; //滿足遞迴出口條件
          else
          { exchange=false; //置exchange為false
            for (j=n-1;j>i;j–)
              if (a[j]<a[j-1]) //當相鄰元素反序時
              {  swap(a[j],a[j-1]);
         exchange=true; //發生交換置exchange為true
              }
            if (exchange
        false) //未發生交換時直接返回   
              return;
            else //發生交換時繼續遞迴呼叫
              BubbleSort(a,n,i+1);
          }
        }
    • n皇后問題

      • bool place(int i,int j) //測試(i,j)位置能否擺放皇后
        { if (i==1) return true; //第一個皇后總是可以放置
        int k=1;
        while (k<i) //k=1~i-1是已放置了皇后的行
        { if ((q[k]==j) || (abs(q[k]-j)==abs(i-k)))
        return false;
        k++;
        }
        return true;
        }
        void queen(int i,int n) //放置1~i的皇后
        { if (i>n)
        dispasolution(n); //所有皇后放置結束
        else
        { for (int j=1;j<=n;j++) //在第i行上試探每一個列j
        if (place(i,j)) //在第i行上找到一個合適位置(i,j)
        { q[i]=j;
        queen(i+1,n);
        }
        }
        }

分治法

  • 化整為零、分而治之

  • 抽象控制演算法

    • SOLUTION DandC(p,q) /* divide and conquer */
      {
      if(small(p,q))
      return conquer(p,q);
      else
      {
      m=divide(p,q);
      return combine( DandC(p,m), DandC(m+1,q) );
      }
      }
  • 例:二分檢索

    • int BinSearch1(p, q, x)
      { int k=(p+q)/2;
      if(q<p) return –1; /* 引數錯誤 */
      if(x==a[k]) return k;
      if(x<a[k]) return BinSearch1(p, k-1, x);
      if(x>a[k]) return BinSearch1(k+1, q, x);
      }

動態規劃法

適用條件

  • **最優化原理(最優子結構性質):不論過去狀態和決策如何,對前面的決策所形成的狀態而言,餘下的諸決策必須構成最優策略。簡而言之,一個最優化策略的子策略總是最優的。一個問題滿足最優化原理又稱其具有最優子結構性質。
  • 無後效性:將各階段按照一定的次序排列好之後,對於某個給定的階段狀態,它以前各階段的狀態無法直接影響它未來的決策,而只能通過當前的這個狀態。、
  • **子問題的重疊性:動態規劃演算法的關鍵在於解決冗餘,這是動態規劃演算法的根本目的。動態規劃實質上是一種以空間換時間的技術,它在實現的過程中,不得不儲存產生過程中的各種狀態,所以它的空間複雜度要大於其他的演算法。選擇動態規劃演算法是因為動態規劃演算法在空間上可以承受,而搜尋演算法在時間上卻無法承受,所以我們舍空間而取時間。

侷限性

  • 它沒有統一的處理方法,必須根據問題的各種性質並結合一定的技巧來處理;另外當變數的維數增大時,總的計算量及存貯量急劇增大。因而,受計算機的存貯量及計算速度的限制,當今的計算機仍不能用動態規劃方法來解決較大規模的問題

基本步驟

  • 劃分階段:按照問題的時間或空間特徵,把問題分為若干個階段。在劃分階段時,注意劃分後的階段一定要是有序的或者是可排序的,否則問題就無法求解。
  • 確定狀態和狀態變數:將問題發展到各個階段時所處於的各種客觀情況用不同的狀態表示出來。當然,狀態的選擇要滿足無後效性。
  • 確定決策並寫出狀態轉移方程:因為決策和狀態轉移有著天然的聯絡,狀態轉移就是根據上一階段的狀態和決策來匯出本階段的狀態。所以如果確定了決策,狀態轉移方程也就可寫出。但事實上常常是反過來做,根據相鄰兩個階段的狀態之間的關係來確定決策方法和狀態轉移方程
  • 尋找邊界條件:給出的狀態轉移方程是一個遞推式,需要一個遞推的終止條件或邊界條件。

基本要素

  • 最優子結構

    • 在分析問題的最優子結構性質時,所用的方法具有普遍性:首先假設由問題的最優解匯出的子問題的解不是最優的,然後再設法說明在這個假設下可構造出比原問題最優解更好的解,從而導致矛盾。
  • 重疊子問題

    • 遞迴演算法求解問題時,每次產生的子問題並不總是新問題,有些子問題被反覆計算多次。這種性質稱為子問題的重疊性質
    • 動態規劃演算法,對每一個子問題只解一次,而後將其解儲存在一個表格中,當再次需要解此子問題時,只是簡單地用常數時間檢視一下結果

**典型問題

  • 矩陣連乘問題:給定n個矩陣{A1,A2,…,An},其中Ai與Ai+1是可乘的,i=1,2…,n-1。如何確定計算矩陣連乘積的計算次序,使得依此次序計算矩陣連乘積需要的數乘次數最少

    • 計算最優值 :int MatrixChain::MChain()
      { //求A[0:n-1]的最優解值
      for (int i=0;i<n; i++) m[i][i]=0;
      for (int r=2; r<=n; r++)
      for (int i=0; i<=n-r; i++)
      {
      int j=i+r-1;
      m[i][j]=m[i+1][j]+p[i]*p[i+1]*p[j+1]; //m[i][j] 的初值
      s[i][j]=i;
      for (int k=i+1; k<j; k++)
      {
      int t=m[i][k]+m[k+1][j]+p[i]*p[k+1]*p[j+1];
      if (t<m[i][j])
      {m[i][j]=t; s[i][j]=k;}
      }
      }
      return m[0][n-1];
      }
  • 0-1揹包問題

  • 剪繩子問題

    • def jianshengzi(n):
      if n < 2:
      return 0
      if n == 2:
      return 1 #長度為2,只能剪成11
      if n == 3:
      return 2 #長度為3,剪成2
      1 > 111

    遞迴問題是 f(n) = max{f(i)*f(n-i)}

    for i in range(4,n+1):
    maxs = 0
    for j in range(1,i/2+1):
    mult = h[j] * h[i-j]
    if maxs < mult:
    maxs = mult
    h[i] = maxs # 每次J的迭代輪詢出該長度的最大值
    print h
    return h[n]

貪心演算法

貪心演算法總是作出在當前看來最好的選擇。也就是說貪心演算法並不從整體最優考慮,它所作出的選擇只是在某種意義上的區域性最優選擇。

基本要素

  • 貪心選擇性質:所求問題的整體最優解可以通過一系列區域性最優的選擇,即貪心選擇來達到。
  • 最優子結構性質:當一個問題的最優解包含其子問題的最優解時,稱此問題具有最優子結構性質。問題的最優子結構性質是該問題可用動態規劃演算法或貪心演算法求解的關鍵特徵。

問題

  • 活動安排問題

    • public static int greedySelector(int [] s, int [] f, boolean a[])
      {
      int n=s.length-1;
      a[1]=true;
      int j=1;
      int count=1;
      for (int i=2;i<=n;i++) {
      if (s[i]>=f[j]) {
      a[i]=true;
      j=i;
      count++;
      }
      else a[i]=false;
      }
      return count;
      }
      各活動的起始時間和結束時間儲存於陣列s和f中且按結束時間的非減序排列
  • 揹包問題(單位重量價值最高)

    • public static float knapsack(float c,float [] w, float [] v,float [] x)
      {
      int n=v.length;
      Element [] d = new Element [n];
      for (int i = 0; i < n; i++) d[i] = new Element(w[i],v[i],i);
      MergeSort.mergeSort(d);
      int i;
      float opt=0;
      for (i=0;i<n;i++) x[i]=0;
      for (i=0;i<n;i++) {
      if (d[i].w>c) break;
      x[d[i].i]=1;
      opt+=d[i].v;
      c-=d[i].w;
      }
      if (i<n){
      x[d[i].i]=c/d[i].w;
      opt+=x[d[i].i]*d[i].v;
      }
      return opt;
      }
  • 最優裝載

    • public static float loading(float c, float [] w, int [] x)
      {
      int n=w.length;
      Element [] d = new Element [n];
      for (int i = 0; i < n; i++)
      d[i] = new Element(w[i],i);
      MergeSort.mergeSort(d);
      float opt=0;
      for (int i = 0; i < n; i++) x[i] = 0;
      for (int i = 0; i < n && d[i].w <= c; i++) {
      x[d[i].i] = 1;
      opt+=d[i].w;
      c -= d[i].w;
      }
      return opt;
      }//載重量為c,集裝箱i的重量為Wi
  • 哈夫曼編碼:用最小堆實現優先佇列Q

    • 哈夫曼演算法的正確性

      • 貪心選擇性質
      • 最優子結構性質
  • 多機排程問題

    • 要求給出一種作業排程方案,使所給的n個作業在儘可能短的時間內由m臺機器加工處理完成

擬陣

  • (1) S 是一個有限集
    (2) L是一個有限非空集,其元素是 S 的部分子集
    (3) 具有遺傳性 ,即對於任意 A⊆B,若 B∈L,那麼一定有 A∈L
    (4) 具有交換性 ,即對任意 A∈L,B∈L且 |A|<|B| ( |A| 表示 A 中的元素個數,以下同義) , 一定存在元素 x∈B且 x∉A,使得 A ∪ { x } ∈L

  • 擬陣解決貪心問題:即求出權值最大獨立集,對於任何能夠轉換為擬M=(S,L)的問題,都可以通過如下演算法解決

    • Set Solve(M,w)//給出擬陣M與權值函式w (Set這裡意為返回一個集合)
      {
      清空A;
      將S按w(x)的大小降序排好;
      for(對每一個x∈S,按w(x)降序)
      {
      if(A∪{x}∈L)
      A=A∪{x};
      }
      return A;//即權值最大獨立集
      }
  • 最小生成樹問題。擬陣的角度分析最小生成樹問題。
    對於無向圖 G= (V , E) 【注 : V 是點集,E 是邊集】
    我們定義這樣一個M=(S,L):
    (1) S=E
    (2) L={ x | x⊆E 且圖 G’=(V,x) 中無環 }
    這個 M 顯然滿足子集系統的前兩個條件。這裡證明其具有遺傳性與交換性:
    遺傳性:對任意A∈L, B⊆A , 顯然B⊆E。假設 B 中有環,那麼 A 中也一定有環,與 A∈L 矛盾,故 B 中無環,因此B∈L , M具有遺傳性。
    交換性:對任意A∈L,B∈L,設|A|<|B|,顯然 GA =(V,A) 中有|V|-|A| 個連通分量 , GB=(V,B) 中有|V|-|B|個連通分量。
    由|A|<|B| 有 |V|-|A|>|V|-|B| , 可知此時 B 中一定存在一條邊 x 連線了 G_A 中的兩個不同的連通分量。顯然,G’=(V,A∪{x})中無環且A∪{x}∈L ,因此M具有交換性。
    綜上,M是一個擬陣。

回溯法

回溯法在問題的解空間樹中,按深度優先策略,從根結點出發搜尋解空間樹。演算法搜尋至解空間樹的任意一點時,先判斷該結點是否包含問題的解。如果肯定不包含,則跳過對該結點為根的子樹的搜尋,逐層向其祖先結點回溯;否則,進入該子樹,繼續按深度優先策略搜尋。

  • 具有限界函式的深度優先生成法稱為回溯法。

基本思想

  • 基本步驟

    • 針對所給問題,定義問題的解空間;
    • 確定易於搜尋的解空間結構;
    • 以深度優先方式搜尋解空間,並在搜尋過程中用剪枝函式避免無效搜尋。
  • 常用剪枝函式

    • 用約束函式在擴充套件結點處剪去不滿足約束的子樹
    • 用限界函式剪去得不到最優解的子樹

典型問題

  • 0-1揹包問題

    • bestV = 0
      curW = 0
      curV = 0
      bestx = None
      def backtrack(i):
      global bestV, curW, curV, x, bestx
      if i >= n:
      if bestV < curV:
      bestV = curV
      bestx = x[:]
      else:
      if curW + w[i] <= c:
      x[i] = True
      curW += w[i]
      curV += v[i]
      backtrack(i + 1)
      curW -= w[i]
      curV -= v[i]
      x[i] = False
      backtrack(i + 1)
    • if name == ‘main’:
      n = 3
      c = 30
      w = [16,15,15]
      v = [45,25,25]
      x = [False for i in range(n)]
      backtrack(0)
      print(bestV)
      print(bestx)
  • n後問題

    • def conflict(state, nextColumn):
      nextRow = rows = len(state)
      for row in range(rows):
      column = state[row]
      if abs(column - nextColumn) in (0, nextRow - row):
      return True
      return False

def queens(num, state=()):
for pos in range(num):
if not conflict(state, pos):
if len(state) == num - 1:
yield (pos,)
else:
for result in queens(num, state + (pos,)):
yield (pos,) + result
def prettyprint(solution):
def line(pos, length=len(solution)):
return ’ . ’ * (pos) + ’ x ’ + ’ . ’ * (length - pos - 1)
for pos in solution:
print(line(pos))
- if name == ‘main’:
solutions = queens(8)
for index, solution in enumerate(solutions):
print(“第%d種解決方案:” % (index + 1), solution)
prettyprint(solution)
print(’*’ * 100)

  • 推銷員問題

    • void iterativeBacktrack (){
      int t=1;
      while(t>0){
      if (f(n,t)<=g(n,t))
      for(int i=f(n,t);i<=g(n,t);i++){
      x[t]=h(i);
      if(constraint(t) && bound(t)){
      if(solution(t)) output(x);//輸出最優解
      else t++;//搜尋下一層節點
      }
      }
      else t–;//回溯到上一節點
      }
      }
  • 圖的m著色問題

    • GraphColor(int n,int m,int color[],bool c[][5])
      {
      int i,k;
      for (i=0; i<n; i++ ) //將解向量color[n]初始化為0
      color[i]=0;
      k=0;
      while (k>=0)
      { color[k]=color[k]+1; //使當前顏色數加1
      while ((color[k]<=m) && (!ok(color,k,c,n))) //當前顏色是否有效
      color[k]=color[k]+1; //無效,搜尋下一個顏色
      if (color[k]<=m) //求解完畢,輸出解
      {
      if (k==n-1)break; //是最後的頂點,完成搜尋
      else k=k+1; //否,處理下一個頂點
      }
      else //搜尋失敗,回溯到前一個頂點
      {
      color[k]=0;
      k=k-1;
      }
      }
      }
  • 批處理作業排程

    • private static void backtrack(int i) {
      if (i > n) {
      for (int j = 1; j <= n; j++) bestx[j] = x[j];
      bestf = f;
      } else
      for (int j = i; j <= n; j++) {
      f1+=m[x[j]][1];
      f2[i]=((f2[i-1]>f1)?f2[i-1]:f1)+m[x[j]][2];
      f+=f2[i];
      if (f < bestf) {
      MyMath.swap(x,i,j);
      backtrack(i+1);
      MyMath.swap(x,i,j);
      }
      f1-=m[x[j]][1];
      f-=f2[i];
      }
      }
    • public class FlowShop
      static int n, // 作業數
      f1, // 機器1完成處理時間
      f, // 完成時間和
      bestf; // 當前最優值
      static int [][] m; // 各作業所需的處理時間
      static int [] x; // 當前作業排程
      static int [] bestx; // 當前最優作業排程
      static int [] f2; // 機器2完成處理時間

分支限界法

分支限界法則以廣度優先或以最小耗費優先的方式搜尋解空間樹,將活結點存放在一個特殊的表中。其策略是:在擴充套件結點處,先生成其所有的兒子結點,將那些導致不可行解或導致非最優解的兒子捨棄,其餘兒子加入活結點表中。此後,從活結點表中按照一定的規則取出一個結點作為當前擴充套件結點。並重覆上述結點擴充套件過程。

  • 分支限界法常以廣度優先或以最小耗費(最大效益)優先的方式搜尋問題的解空間樹。

限界函式的構造

  • 一個限界函式f(d)通常可以由兩個部分構成:⑴從開始結點到結點d的已有耗損值g(d);⑵再從結點d到達目標的期望耗損值 h(d):f(d) = g(d) + h(d)
  • 計算位置:搜尋樹的結點
    值:極大化問題是以該點為根的子樹所有可行解的值的上界 ( 極小化問題為下界)
    性質:對極大化問題父結點代價不小於子結點的代價 (極小化問題相反)

分支限界法求解最大化問題的一般過程

  • 1.根據限界函式確定目標函式的界[down, up];
    2.將待處理結點表PT初始化為空;
    3.對根結點的每個孩子結點x執行下列操作
    3.1 估算結點x的目標函式值value;
    3.2 若(value>=down),則將結點x加入表PT中;
    4.迴圈直到某個葉子結點的目標函式值在表PT中最大
    4.1 i=表PT中值最大的結點;
    4.2 對結點i的每個孩子結點x執行下列操作
    4.2.1 估算結點x的目標函式值value;
    4.2.2 若(value>=down),則將結點x加入表PT中;
    4.2.3 若(結點x是葉子結點且結點x的value值在表PT中最大),
    則將結點x對應的解輸出,演算法結束;
    4.2.4 若(結點x是葉子結點但結點x的value值在表PT中不是最大),
    則令down=value,並且將表PT中所有小於value的結點刪除;

典型例題

  • 0-1揹包問題

    • 限界函式:??=?+(?−?)×(?_(?+1)∕?_(?+1) )

    • #coding : utf-8
      import numpy as np
      import queue
      import math
      w = [4,7,5,3]#weight
      v = [40,42,25,12]#value
      def bag(capacity):
      vec_len = 2**(len(v)+1) - 1#tree `s size
      vec_v = np.zeros(vec_len)
      vec_w = np.zeros(vec_len)
      vec_w[0]=capacity
      que = queue.Queue();
      que.put(0)
      best = 0

    • while(not que.empty()):
      current = que.get()#獲取佇列
      level = int(math.log(current+1,2))
      if(vec_v[current] > vec_v[best]):
      best = current

      left = 2current+1#left child index
      right = 2
      current+2#right child index

      if(left < vec_len and vec_w[current]-w[level] > 0 and vec_v[current]+v[level] > vec_v[best] ):
      vec_v[left] = int(vec_v[current]+v[level])
      vec_w[left] = vec_w[current]-w[level]
      que.put(left)寫入佇列
      if(right < vec_len and sum(v[level+1:-1])+vec_v[current] > vec_v[best]):
      vec_v[right] = vec_v[current]
      vec_w[right] = vec_w[current]
      que.put(right)
      print(vec_w,vec_v[best])

if name == ‘main’:
bag(10)

  • 旅行售貨員問題

    • import math

n = 4
x = [0, 1, 2, 3]

定義圖的字典形式

G = {
‘1’: {‘2’: 30, ‘3’: 6, ‘4’: 4},
‘2’: {‘1’: 30, ‘3’: 5, ‘4’: 10},
‘3’: {‘1’: 6, ‘2’: 5, ‘4’: 20},
‘4’: {‘1’: 4, ‘2’: 10, ‘3’: 20}
}

定義圖的陣列形式

graph = [
[0, 30, 6, 4],
[30, 0, 5, 10],
[6, 5, 0, 20],
[4, 10, 20, 0]
]
- bestcost = math.inf # 好吧 乾脆就無窮好了
nowcost = 0 # 全域性變數,現在的花費
def TSP(graph, n, s):
global cc, bestc
if (s == n):
if (graph[x[n - 1]][x[0]] != 0 and (cc + graph[x[n - 1]][x[0]] < bestcost)):
print(‘best way:’, x)
bestcost = cc + graph[x[n - 1]][x[0]]
print(‘bestcost’, bestcost)
else:
for i in range(s, n):
# 如果下一節點不是自身 而且 求得的值小於目前的最佳值
if (graph[x[i - 1]][x[i]] != 0 and cc+ graph[x[i - 1]][x[i]] < bestcost):
x[i], x[s] = x[s], x[i] # 交換一下
cc += graph[x[s - 1]][x[s]] # 將花費加入
TSP(graph, n, s + 1)
cc -= graph[x[s - 1]][x[s]] # 回溯上去還需要減去
x[i], x[s] = x[s], x[i] # 別忘記交換回來

TSP(graph, n, 1)

概率演算法

演算法分類

  • 數值概率演算法

  • 蒙特卡羅(Monte Carlo)演算法

    • 用隨機投點法計算pi值
  • 拉斯維加斯(Las Vegas)演算法

    • n 皇后問題

      • boolean queensLV(int x[]) {
        int k=1; // 擺放皇后棋子的編號
        int count=1;
        while ((k<=n)&&(count>0)) {
        count=0; //有多少個可選的位置
        int j=0;
        for (int i=1; i<=n; i++) {
        x[k]=i;
        if (place(k))
        if (random(++count)==0) j=i;
        }
        if (count>0) x[k++]=j;//第K個皇后有count個位置可選,從中任選1個。
        }
        return (count>0);
        }
        void nQueen() {
        x=new int[n+1];
        for (int i=0;i<=n;i++)
        x[i]=0;
        while (!queensLV(x));
        }
  • 舍伍德(Sherwood)演算法

基本特徵是對所求解問題的同一例項用同一概率演算法求解兩次可能得到完全不同的效果(所需時間或計算結果)

隨機數在概率演算法設計中扮演著十分重要的角色:偽隨機數(線性同餘法)

NP完全理論

P類問題:多項式時間內可解的問題

NP類問題:多項式時間內可驗證問題(指驗證其解的正確性)

歸約:一個問題A可以約化為問題B的含義即是,可以用問題B的解法解決問題A

近似演算法

在多項式時間複雜度內,求解到最優解一定距離的次優解。有確切最優解但是並不能保證得到最優解的演算法都可以稱之為近似演算法

典型問題

  • 頂點覆蓋問題

    • VertexSet approxVertexCover ( Graph g )
      { cset=;
      e1=g.e;
      while (e1 != ) {
      從e1中任取一條邊(u,v);
      cset=cset∪{u,v};
      從e1中刪去與u和v相關聯的所有邊;
      }
      return c
      }
      Cset用來儲存頂點覆蓋中的各頂點。初始為空,不斷從邊集e1中選取一邊(u,v),將邊的端點加入cset中,並將e1中已被u和v覆蓋的邊刪去,直至cset已覆蓋所有邊。即e1為空。
  • 旅行售貨員問題

    • void approxTSP (Graph g)
      {
      (1)選擇g的任一頂點r;
      (2)用Prim演算法找出帶權圖g的一棵以r為根的最小生成樹T;
      (3)前序遍歷樹T得到的頂點表L;
      (4)將r加到表L的末尾,按表L中頂點次序組成迴路H,作為計 算結果返回;
      }

XMind - Trial Version

相關文章