[轉]: 五大基本算法——回溯法

一、基本概念

回溯法是一種選優搜索法(試探法)。

基本思想:將問題P的狀態空間E表示成一棵高爲n的帶全有序樹T,把求解問題簡化爲搜索樹T。搜索過程採用深度優先搜索。搜索到某一結點時判斷該結點是否包含原問題的解,如果包含則繼續往下搜索,如果不包含則向祖先回溯。

通俗來說,就是利用一個樹結構來表示解空間,然後從樹的根開始深度優先遍歷該樹,到不滿足要求的葉子結點時向上回溯繼續遍歷。

幾個結點:
擴展結點:一個正在產生子結點的結點稱爲擴展結點
活結點:一個自身已生成但未全部生成子結點的結點
死結點:一個所有子結點已全部生成的結點

二、基本步驟

1、分析問題,定義問題解空間。

2、根據解空間,確定解空間結構,得搜索樹

3、從根節點開始深度優先搜索解空間(利用剪枝避免無效搜索)。

4、遞歸搜索,直到找到所要求的的解。

三、兩類常見的解空間樹

1、子集樹
當問題是:從n個元素的集合S中找出滿足某種性質的子集時,用子集樹。
子集樹必然是一個二叉樹。常見問題:0/1揹包問題、裝載問題。

//遍歷子集樹僞代碼
void backtrack(int t){
    if(t>n){
        output(x);
    } else{
        for(int i=0;i<=1;i++){
            x[t] = i;
            if(legal(t))
                backtrack(t+1);
        }
    }
}

遍歷子集樹時間複雜度:O(2^n)

2、排列樹
當問題是:確定n個元素滿足某種排列時,用排列數。常見問題:TSP旅行商問題,N皇后問題。

//遍歷排列樹僞代碼
void backtrack(int t){
    if(t>n){
        output(x);
    }else{
        for(int i=0;i<=1;i++){
            swap(x[t],x[i]);
            if(legal(t)){
                backtrack(t+1);
            } 
            swap(x[t],x[i]);
        }
    }
}

遍歷排列樹時間複雜度:O(n!)

通俗地講,結合Java集合的概念,選擇哪種樹其實就是看最後所得結果是放入一個List(有序)裏,還是放入一個Set(無序)裏。

四、剪枝函數

剪枝函數能極大提高搜索效率,遍歷解空間樹時,對於不滿足條件的分支進行剪枝,因爲這些分支一定不會在最後所求解中。

常見剪枝函數:

約束函數(對解加入約束條件)、限界函數(對解進行上界或下界的限定)

滿足約束函數的解纔是可行解。

五、求解模式

設問題的解是一個n維向量(a1,a2。。an),約束條件是ai之間滿足某種條件,記爲func(ai)

非遞歸回溯模式

int a[n],i;
//初始化數組a[];
i=1;
//有路可走,並且未達目標
while(i>0){
    if(i>n){
        //搜索到一個解,輸出;
    } else {
        //a[i]第一個可能得值
        while(a[i]在不滿足約束條件且在搜索空間內){
            a[i]下一個可能的值
        }
    }
}

遞歸回溯模式

一般情況下使用遞歸函數來實現回溯法比較簡單,其中i爲搜索的深度。

int a[n];
func(int i){
	if(i>n){
		//輸出結果;
	}else{
		for(j=下界; j<=上界; j=j+1){	//枚舉i所有可能得路徑
			if(func(j)){
				a[i]=j;
			}
		}
	}
}

常見案例問題

1、0/1揹包問題

2、TSP旅行商問題

3、最優裝載問題

4、N-皇后問題

具體問題可百度詳細內容。

 

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