算法總結

算法分析與設計總結

經過八個周的時間,算法分析與設計這門課程結束,感覺自己對寫程序的方法有了新的認識,下面對這門課程所學到的知識和自己的感受做一個總結。

在算法分析與設計這門課中,主要學習了遞歸策略、分治策略、動態規劃、貪心算法、回溯算法和分支限界法這六種算法,下面分別說一下我所學到的。

一、遞歸策略

遞歸在大一的C++程序設計這門課中有所接觸,但在這門課中更加明確。程序中直接或間接調用自身的算法,我們稱之爲遞歸,它通常把一個大型複雜的問題轉化成爲一個與原問題相似的規模較小的問題來求解,這樣代碼會比較少,但解題的運行效率比較低,容易造成堆棧溢出。在用遞歸解決問題時,首先要有出口,即遞歸的結束條件,若沒有則會造成死鎖;此外,要找到大規模問題與小規模問題之間的關聯,可能是枚舉的方式,也可能是數學上的聯繫,有了這兩點,問題就會基本解決。最初接觸的遞歸的例子就是斐波那契數列,代碼很短,具體如下:

int fib(int n)

{

If(n<=1) return  1;

return fib(n-1)+fib(n-2);

}

算法運行時先判斷n,n大於1時,會調用求n-1n-2時,直至到10的情況。顯然,因爲遞歸的次數太多,這樣寫效率並會很低。在做遞歸相關的容易題目時,出口很好找,唯一需要的是找關聯,當然問題簡單,並不需要太多的思考。

二、分治算法

分治策略採用的是分而治之的觀點,它同樣是將一個數據規模爲n的問題分解成若干個小問題,但它與遞歸策略的區別是遞歸策略是將問題分解成相似的子問題,而分治策略是子問題與原問題形式相同,且子問題相互獨立,合併之後就會得到原問題的解。因此,分治法體現了分解、解決、合併這一過程,在解決問題時,需要考慮問題分解成幾個子問題合適,如何合併等問題。分治法解決的問題有以下幾個特徵:

1. 該問題的規模縮小到一定程度能夠容易的解決;

2. 該問題可以分解成若干個規模較小的相同的問題;

3. 利用該問題分解出的子問題的解可以合併爲該問題的解;

4. 該問題分解出的各個子問題的解是相互獨立的。

運用分治策略比較典型的例子是二分法,具體代碼如下:

Template<class type>

int binarysearch (type a[], const type& x,int n)

{

int left =0;

int right =n-1;

while(left<=right)

{

int middle=(left+right)/2;

if(x==a[middle]) return middle;

if(>a[middle]) left=middle+1;

else right=middle-1;

}

return  -1;         //沒有找到

}

二分搜索算法中,對一個已經進行升序排列的序列查找某個元素時,每次查找都會減少當前所找序列的一半,直到找到該元素時停止,但我感覺二分法並沒有體現出合併這一步,這個步驟在循環賽日程表有更明顯的體現,由每個的表格分爲四部分,直到分解爲的小表格,最後利用對稱的性質,合併成整個表格。分治算法的題目在上學期已經做過了,當時感覺只是比照着二分法來寫,感覺差不多,而容易的題目並沒有需要對解合併的題目,所以對合並解並沒有什麼練習,感觸不多。

三、貪心算法

貪心算法是通過每一步的選擇,最終得到問題的解,但這個解不一定是最優解,因爲在每一步上,所選擇的標準在當時看來是最優的,可以說它是局部最優。在用貪心算法解決問題時,最重要也是最困難的一步是選擇貪心標準,貪心標準是到達問題解的直接路徑,因此,貪心算法具有一定的速度優勢,效率較高。在做題時,要考慮題目是否可以用貪心算法來求解,它得到的解可能是問題最優解的近似值。貪心算法比較好理解的題目是揹包問題,具體代碼如下:

struct bag{

int w; //物品的重量

int v; //物品的價值

double c; //性價比

}

a[1001];

bool cmp(bag a, bag b){

return a.c >= b.c;            //按價值排序

}

sort(a, a+n, cmp);

double knapsack(int n, bag a[], double c)

{

double cleft = c;//揹包的剩餘容量

int i = 0;

double b = 0;//獲得的價值

while(i<n && a[i].w<cleft)

{

cleft -= a[i].w;

b += a[i].v;

i++;

}

if (i<n) b += 1.0*a[i].v*cleft/a[i].w;

return b;

}

揹包問題考慮的是裝滿揹包,且價值最大,在這樣的情況下,考慮以性價比爲貪心標準,在最後揹包的剩餘容量不足以裝下一件物品時,進行切割。此外,可以定義結構體來包含每件物品的屬性。在做題目時,主要考慮貪心標準,題目相對簡單,主要還是加深理解。

四、動態規劃

動態規劃解決多階段決策問題的一種方法,它要求每一步得到的解都必須是最優的,因此,在用動態規劃解決問題時,每一步都需要考慮全面,把當前步的所有的可能的解列出來,確定出當前步驟的最優解,在某些具有情況下,可以依據情境中的判定條件,捨棄那些不可能成爲最優解的局部解。因此,動態規劃每個階段與下一階段是有聯繫的,每一階段的決策都會影響到下一步。因而它具有如下的性質:最優決策序列的子序列,一定是局部最優決策子序列;包含有非局部最優的決策子序列,一定不是最優決策序列。

在學習的過程中,個人比較喜歡數字三角形,它除了是用動態規劃來解決的,還有一點就是它是從最底層向上來決定每一階段的最優解的,這樣節省存儲空間,不會出現溢出的情況,此外,還需注意記憶化搜索這種方法,它可以解決計算過的值重複計算的問題,節省時間。除了數學三角形,0-1揹包問題也是比較經典的算法,該問題也是要求價值最大,但物品不能被分割,顯然,此問題是可以用貪心算法來求解的,依舊是依據性價比來選擇物品裝入揹包,這裏選擇的物品的組合就不一定是該問題的最優解了。用動態規劃來解,找到兩階段之間的聯繫,即狀態轉移方程,可得到最優解。

在做題時,感覺不是那麼簡單,主要是找聯繫比較難找,有的舉例子根本就沒找到聯繫,所以,自我感覺尋找兩階段之間的聯繫是解題最關鍵的一步,需要仔細考慮。

五、回溯算法

回溯算法與下面要提到的分支限界法都是搜索技術,但也存在不同,回溯法是基於深度優先的方法進行的搜索方式,它可以系統的搜索一個問題的任意解或所有解,因此有“通用的解題法”之稱。但通常情況下,回溯法會跳過有些解,這些解要保證不會使結果發生變化,這樣,時間複雜度會降低。

回溯法中遍歷是在樹結構的基礎上進行的,這種樹結構被稱爲解空間樹,因爲這棵樹上的所有結點中至少有一個包含最優解。回溯是從樹的根節點開始的,每個開始結點成爲一個活結點,同時成爲當前的擴展結點。在當前的擴展結點,搜索向深度方向進入一個新的結點。這個新結點成爲一個新的活結點,併成爲當前的擴展結點。若在當前擴展結點處不能再向深度方向移動,則當前的擴展結點成爲死結點,即該活結點成爲死結點。此時回溯到最近的一個活結點處,並使得這個活結點成爲當前的擴展結點。這就是回溯法的基本過程,直至滿足終止條件。上文中提到回溯過程中會跳過某些解,跳過解的策略成爲剪枝函數,有兩種:用約束函數在擴展結點處減去不滿足約束條件的子樹;用限界函數減去不能得到最優解的子樹,這樣就避免了搜索過程中的無效搜索,提高效率。解空間樹分爲排列數和子集樹兩種,代表爲旅行商問題和0-1揹包問題。

在做這一部分的題目時,主要是變量比較多,需要考慮剪枝函數和終止條件,在做分割字符串時做的比較麻煩,需要仔細考慮。

六、分支限界法

分支限界法和回溯法的不同在於分支限界法是利用廣度優先的方法,因此基本過程也有不同,分支限界法中的活結點一旦成爲擴展結點,就一次性產生其所有兒子結點。在這些兒子結點中,導致不可行解或導致非最優解的兒子結點被捨棄,其餘兒子結點被加入活結點表中。從活結點表中取下一結點成爲當前擴展結點,並重覆上述結點擴展過程。這個過程一直持續到找到所需的解或活結點表爲空時爲止。此外,在避免無效搜索上也有所不同,分支限界法在求解過程中會計算解的界限,依據具體情況來決定去掉某些分支。

分支限界法的例子中最熟悉的就是單元最短路徑問題,它在上學期的數據結構中已有接觸,因此這種算法的思想也知道一點,只是當時並不明確。0-1揹包問題也可以用分支限界法來解決,由此可見,問題的解決方案有時可以是多種多樣的。這一部分沒有相關的練習題目,只是通過課本上的例子加深瞭解。

以上是我學習算法分析與設計時所瞭解的知識與自己的理解的總結,例子並沒有全面概述,找的都比較簡單,且一些知識並沒有總結上,主要是這六種算法的思想。此外,還加入了一些自己在做題時的感想,在總結過程中在看做過的題目能很快記起內容,感覺還需要再理解這些算法,不能浮於表面。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章