五大基本常用演算法概述
一,貪心演算法的設計思想
• 從問題的某一個初始解出發逐步逼近給定的目標,每一步都作一個不可回溯的決策,儘可能地求得最好的解。當達到某演算法中的某一步不需要再繼續前進時,演算法停止。
二,貪心演算法的基本性質
1)貪心選擇性質
所謂貪心選擇性質是指所求問題的整體最優解可以通過一系列區域性最優的選擇,即貪心選擇來達到。這是貪心演算法可行的第一個基本要素,也是貪心法與動態規劃法的主要區別。
2) 最優子結構性質
該問題解的整體最優性依賴於其區域性子問題解的最優性。這種性質是可以採用貪心演算法解決問題的關鍵特徵。例如,活動安排問題,在選擇了一項活動後,它必須是最優的,否則不能得到全域性的最優。三,貪心演算法的適用性
• 貪心演算法對問題只需考慮當前區域性資訊就要做出決策,也就是說使用貪心演算法的前提是“區域性最優策略能導致產生全域性最優解”。
•該演算法的適用範圍較小, 若應用不當, 不能保證求得問題的最佳解。更準確的方法是通過數學方法證明問題對貪心策略的選用性。四,絕對貪心問題
例一:Dijkstra單源最短路徑問題(有向圖)
(Dijkstra)演算法思想 按路徑長度遞增次序產生最短路徑演算法: 把V分成兩組: (1)S:已求出最短路徑的頂點的集合 (2)V-S=T:尚未確定最短路徑的頂點集合輔助陣列:dist[ ] 存放V0到T中點距離
path[ ]存放已經加入S中點到V0最短路徑
例二:Kruskal最小生成樹問題(每次選權值最小邊,直到生成一個最小生成樹)
演算法的執行時間為 O(nlog n)。
原始碼:
- #include<stdio.h>
- #include<stdlib.h>
- #include<string.h>
- #define MAX_NAME 5
- #define MAX_VERTEX_NUM 20
- typedef char Vertex[MAX_NAME];/*頂點名字串*/
- typedef int AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];/*鄰接距陣*/
- struct MGraph/*定義圖*/
- {
- Vertex vexs[MAX_VERTEX_NUM];
- AdjMatrix arcs;
- int vexnum,arcnum;
- };
- typedef struct
- {
- Vertex adjvex; /*當前點*/
- int lowcost; /*代價*/
- }minside[MAX_VERTEX_NUM];
- int LocateVex(MGraph G,Vertex u)//定位
- {
- int i;
- for(i=0;i<G.vexnum;++i)if(strcmp(u,G.vexs[i])==0)return i;
- return -1;
- }
- void CreateGraph(MGraph &G)
- {
- int i,j,k,w;
- Vertex va,vb;
- printf("請輸入無向網G的頂點數和邊數(以空格為分隔)\n");
- scanf("%d %d",&G.vexnum,&G.arcnum);
- printf("請輸入%d個頂點的值(<%d個字元):\n",G.vexnum,MAX_NAME);
- for(i=0;i<G.vexnum;++i) /* 構造頂點集*/
- scanf("%s",G.vexs[i]);
- for(i=0;i<G.vexnum;++i) /*初始化鄰接矩陣*/
- for(j=0;j<G.vexnum;++j)
- G.arcs[i][j]=0x7fffffff;
- printf("請輸入%d條邊的頂點1 頂點2 權值(以空格作為間隔): \n",G.arcnum);
- for(k=0;k<G.arcnum;++k)
- {
- scanf("%s%s%d%*c",va,vb,&w);
- i=LocateVex(G,va);
- j=LocateVex(G,vb);
- G.arcs[i][j]=G.arcs[j][i]=w; /*對稱*/
- }
- }
- void kruskal(MGraph G)
- {
- int set[MAX_VERTEX_NUM],i,j;
- int k=0,a=0,b=0,min=G.arcs[a][b];
- for(i=0;i<G.vexnum;i++)
- set[i]=i;
- printf("最小代價生成樹的各條邊為:\n");
- while(k<G.vexnum-1)
- {
- for(i=0;i<G.vexnum;++i)
- for(j=i+1;j<G.vexnum;++j)
- if(G.arcs[i][j]<min)
- {
- min=G.arcs[i][j];
- a=i;
- b=j;
- }
- min=G.arcs[a][b]=0x7fffffff;
- if(set[a]!=set[b])
- {
- printf("%s-%s\n",G.vexs[a],G.vexs[b]);
- k++;
- for(i=0;i<G.vexnum;i++)
- if(set[i]==set[b])
- set[i]=set[a];
- }
- }
- }
- int main()
- {
- MGraph g;
- CreateGraph(g);
- kruskal(g);
- system("PAUSE");
- return 0;
- }
- /*結果如下
- 請輸入無向網G的頂點數和邊數(以空格為分隔)
- 6 10
- 請輸入6個頂點的值(<5個字元):
- v1
- v2
- v3
- v4
- v5
- v6
- 請輸入10條邊的頂點1 頂點2 權值(以空格作為間隔):
- v1 v2 6
- v1 v3 1
- v1 v4 5
- v2 v3 5
- v2 v5 3
- v4 v3 5
- v4 v6 2
- v3 v5 6
- v3 v6 4
- v5 v6 6
- 最小代價生成樹的各條邊為:
- v1-v3
- v4-v6
- v2-v5
- v3-v6
- v2-v3
- 請按任意鍵繼續. . .
- */
五,相對貪心問題
例一,取數遊戲。
•問題描述
有2個人輪流取2n個數中的n個數,取數之和大者為勝。設計演算法,讓先取數者獲勝,模擬取數過程。
•問題分析
這個遊戲一般假設取數者只能看到2n個數中兩邊的數(第一次取時,能看到6,5則取6),用貪心演算法的情況:
•例如
若一組資料為:6,16,27,6,12,9,2,11,6,5
用貪心策略每次兩人都取兩邊的數中較大的一個數,先取者勝.以A先取為例:
取數結果為:
A 6,27,12,5,11=61 勝
B 16,6,9,6,2=39
•但若選另一組資料:16,27,7,12,9,2,11,6
仍都用貪心演算法,先取者A敗。
取數結果為:
A 16,7,9,11=43
B 27,12,6,2=47 勝
其實,若我們只能看到兩邊的資料,則此題無論先取還是後取都無必勝的策略。這時一般的策略是用近似貪心演算法。
但若取數者能看到全部2n個數,則此問題可有一些簡單的方法,有的雖不能保證所取數的和是最大,但確是一個先取者必勝的策略。
六,動態規劃(各個問題包含公共子問題)
1)最優決策原理:要求問題具有最優子結構(即最優解包含子問題的最優解),是一種自底向上的求解思路,與遞迴正好相反,每次求解到一個階段時,該階段求解所依賴的子問題已經完全求解完畢,因此每一步的求解都是在直到全部所需資訊的情況下進行的,因此可以得到全域性最優解。
2)動態規劃的決策過程:最優決策是在最後階段形成的,然後向前倒推,直到初始階段;而決策的具體結果及所產生的狀態轉移,卻是由初始階段開始進行計算的,然後向後遞迴或迭代,直到最終結果。
3)適用條件
任何思想方法都有一定的侷限性,超出了特定條件,它就失去了作用。同樣,動態規劃也並不是萬能的。適用動態規劃的問題必須滿足最優化原理和無後效性。 1.最優化原理(最優子結構性質) 最優化原理可這樣闡述:一個最優化策略具有這樣的性質,不論過去狀態和決策如何,對前面的決策所形成的狀態而言,餘下的諸決策必須構成最優策略。簡而言之,一個最優化策略的子策略總是最優的。一個問題滿足最優化原理又稱其具有最優子結構性質。 2.無後效性:將各階段按照一定的次序排列好之後,對於某個給定的階段狀態,它以前各階段的狀態無法直接影響它未來的決策,而只能通過當前的這個狀態。換句話說,每個狀態都是過去歷史的一個完整總結。這就是無後向性,又稱為無後效性。3.子問題的重疊性:動態規劃將原來具有指數級複雜度的搜尋演算法改進成了具有多項式時間的演算法。其中的關鍵在於解決冗餘,這是動態規劃演算法的根本目的。 動態規劃實質上是一種以空間換時間的技術,它在實現的過程中,不得不儲存產生過程中的各種狀態,所以它的空間複雜度要大於其它的演算法
例題:著名的貨郎擔(旅行售貨商)問題
七,回溯
1)設計思想
回溯與分支限界技術實際上都是基於窮舉方法的,即按照一定規律,把問題所有可能的解組織成某種樹結構,形成可能的解空間樹或狀態空間樹。然後按照具體問題的約束條件,通過各種搜尋策略,遍歷可能的解空間樹。從而得到滿足問題條件的解或最優解。兩種演算法設計思路相近、本質一致。
2)回溯與分支限界法解決實際問題,大致可分為四個環節:
(1)確定問題的可能解空間,相當於找出進行窮舉的搜尋範圍。
(2)以一種便於搜尋的方式組織所有的可能解,一般是生成可能解空間樹。
(3)以某種方式搜尋可能的解空間樹,有兩種基本搜尋方式。即:深度優先搜尋方式和,這就是回溯技術;廣度優先搜尋,就是分支限界技術。
(4)在搜尋過程中利用判定函式,也稱為限界函式,通過“剪枝”來加速搜尋過程。
3)回溯法的設計原理
按照深度優先搜尋的方式,從根結點出發搜尋狀態空間樹。
• 每搜尋到達狀態空間樹的一個擴充套件結點,總是先判斷以該結點為根的子樹是否包含問題的解。如果肯定不包含,則跳過對該結點為根的子樹的進一步搜尋,該結點成為一個死結點,同時應向上層回溯至最近的一個活動結點。再以該活動結點作為當前新的擴充套件結點。以這種方式遞迴地在解空間中搜索,直至找到問題的解,或者解空間中已無活動結點為止,即此問題無解。
• 在回溯法中,為了避免生成那些不可能產生最佳解的問題狀態,要不斷地利用限界函式來處死那些實際上不可能產生所需解的活動結點,以減少問題的計算量。所以,回溯法應是具有限界函式的深度優先搜尋法。
用回溯法解題時,常遇到兩類典型的解空間樹,子集樹與排列樹。
例題:著名的八皇后問題
詳細解答參見博文------八皇后問題
八,分支限界(求問題在某種意義下的最優解)
1)分支限界法的設計原理
分支限界法類似於回溯法,也是一種在問題的狀態空間樹上搜索解的演算法。但是,分支限界法與回溯法有不同的求解目標。回溯法的求解目標是找出狀態空間樹中的所有回答結點或任一回答結點,分支限界法的求解目標則是找出使得某一目標函式達到極小或極大的一個問題結點。
2)分支限界法與回溯法有兩點不同:
(1)求解目標不同。回溯法的求解目標是找出解空間樹中滿足約束條件的所有解,而分支限界法的求解目標則是找出滿足約束條件的一個解或最優解。
(2)搜尋方式不同。回溯法以深度優先的方式搜尋解空間樹,而分支限界法則以廣度優先或以最小耗費優先的方式搜尋解空間樹。
3)常見的兩種分支限界法
(1)佇列式(FIFO)分支限界法
按照佇列先進先出(FIFO)原則選取下一個結點為擴充套件結點。
(2)優先佇列式分支限界法
按照優先佇列中規定的優先順序選取優先順序最高的結點成為當前擴充套件結點。
4)常見問題
裝箱問題、佈線問題、單源最短路徑問題、最大團問題、0-1揹包問題、旅行售貨問題