1. 程式人生 > >0033演算法筆記——【分支限界法】分支限界法與單源最短路徑問題

0033演算法筆記——【分支限界法】分支限界法與單源最短路徑問題

      1、分支限界法

(1)描述:採用廣度優先產生狀態空間樹的結點,並使用剪枝函式的方法稱為分枝限界法。

所謂“分支”是採用廣度優先的策略,依次生成擴充套件結點的所有分支(即:兒子結點)。
所謂“限界”是在結點擴充套件過程中,計算結點的上界(或下界),邊搜尋邊減掉搜尋樹的某些分支,從而提高搜尋效率。

    (2)原理按照廣度優先的原則,一個活結點一旦成為擴充套件結點(E-結點)R後,演算法將依次生成它的全部孩子結點,將那些導致不可行解或導致非最優解的兒子捨棄,其餘兒子加入活結點表中。然後,從活結點表中取出一個結點作為當前擴充套件結點。重複上述結點擴充套件過程,直至找到問題的解或判定無解為止。

    (3)分支限界法與回溯法

1)求解目標:回溯法的求解目標是找出解空間樹中滿足約束條件的所有解,而分支限界法的求解目標則是找出滿足約束條件的一個解,或是在滿足約束條件的解中找出在某種意義下的最優解。2)搜尋方式的不同:回溯法以深度優先的方式搜尋解空間樹,而分支限界法則以廣度優先或以最小耗費優先的方式搜尋解空間樹。

(4)常見的分支限界法

1)FIFO分支限界法(佇列式分支限界法)

     基本思想:按照佇列先進先出(FIFO)原則選取下一個活結點為擴充套件結點。

     搜尋策略:一開始,根結點是唯一的活結點,根結點入隊。從活結點隊中取出根結點後,作為當前擴充套件結點。對當前擴充套件結點,先從左到右地產生它的所有兒子,用約束條件檢查,把所有滿足約束函式的兒子加入活結點佇列中。再從活結點表中取出隊首結點(隊中最先進來的結點)為當前擴充套件結點,……,直到找到一個解或活結點佇列為空為止。

    2)LC(least cost)分支限界法(優先佇列式分支限界法)

    基本思想:為了加速搜尋的程序,應採用有效地方式選擇活結點進行擴充套件。按照優先佇列中規定的優先順序選取優先順序最高的結點成為當前擴充套件結點。

    搜尋策略:對每一活結點計算一個優先順序(某些資訊的函式值),並根據這些優先順序;從當前活結點表中優先選擇一個優先順序最高(最有利)的結點作為擴充套件結點,使搜尋朝著解空間樹上有最優解的分支推進,以便儘快地找出一個最優解。再從活結點表中下一個優先級別最高的結點為當前擴充套件結點,……,直到找到一個解或活結點佇列為空為止。

(5)分支限界法搜尋應用舉例

1)0-1揹包問題,當n=3時,w={16,15,15}, p={45,25,25}, c=30


     佇列式分支限界法(處理法則:先進先出):{}—>{A}—>{B,C}—>{C,D,E}(D是不可行解,捨棄)—>{C,E}—>{E,F,G}—>{F,G,J,K}(J是不可行解,捨棄)—>{F,G,K}—>{G,K,L,M}—>{K,L,M,N,O}—>{}

     優先佇列式分支限界法(處理法則:價值大者優先):{}—>{A}—>{B,C}—>{C,D,E}—>{C,E}—>{C,J,K}—>{C}—>{F,G}—>{G,L,M}—>{G,M}—>{G}—>{N,O}—>{O}—>{}

     2)旅行員售貨問題


     佇列式分支限界法(節點B開始):{ }—{B}—{C,D,E}—{D,E,F,G}—{E,F,G,H,I}—{F,G,H,I,J,K}—{G,H,I,J,K,L}—{H,I,J,K,L,M}—{I,J,K,L,M,N}—{J,K,L,M,N,O}—{K,L,M,N,O,P}—{L,M,N,O,P,Q}—{M,N,O,P,Q}—{N,O,P,Q}—{O,P,Q}—{P,Q}—{Q}—{ }

優先佇列式分支限界法:優先順序是結點的當前費用:{ }—{B}—{C,D,E}—{C,D,J,K}—{C,J,K,H,I}—{C,J,K,I,N}—{C,K,I,N,P}—{C,I,N,P,Q}—{C,N,P,Q,O}—{C,P,Q,O}—{C,Q,O}—{Q,O,F,G}—{Q,O,G,L}—{Q,O,L,M}—{O,L,M}—{O,M}—{M}—{ }

     2、單源最短路徑問題

問題描述

在下圖所給的有向圖G中,每一邊都有一個非負邊權。要求圖G的從源頂點s到目標頂點t之間的最短路徑。


     下圖是用優先佇列式分支限界法解有向圖G的單源最短路徑問題產生的解空間樹。其中,每一個結點旁邊的數字表示該結點所對應的當前路長。

     

演算法設計

演算法從圖G的源頂點s和空優先佇列開始。結點s被擴充套件後,它的兒子結點被依次插入堆中。此後,演算法從堆中取出具有最小當前路長的結點作為當前擴充套件結點,並依次檢查與當前擴充套件結點相鄰的所有頂點。如果從當前擴充套件結點i到頂點j有邊可達,且從源出發,途經頂點i再到頂點j的所相應的路徑的長度小於當前最優路徑長度,則將該頂點作為活結點插入到活結點優先佇列中。這個結點的擴充套件過程一直繼續到活結點優先佇列為空時為止。

    在演算法擴充套件結點的過程中,一旦發現一個結點的下界不小於當前找到的最短路長,則演算法剪去以該結點為根的子樹
    在演算法中,利用結點間的控制關係進行剪枝。從源頂點s出發,2條不同路徑到達圖G的同一頂點。由於兩條路徑的路長不同,因此可以將路長長的路徑所對應的樹中的結點為根的子樹剪去。 

     演算法具體程式碼如下:

     1、MinHeap2.h

#include <iostream>

template<class Type>
class Graph;

template<class T> 
class MinHeap 
{ 
	template<class Type>
	friend class Graph;
	public: 
		MinHeap(int maxheapsize = 10); 
		~MinHeap(){delete []heap;} 

		int Size() const{return currentsize;} 
		T Max(){if(currentsize) return heap[1];} 

		MinHeap<T>& Insert(const T& x); 
		MinHeap<T>& DeleteMin(T &x); 

		void Initialize(T x[], int size, int ArraySize); 
		void Deactivate(); 
		void output(T a[],int n);
	private: 
		int currentsize, maxsize; 
		T *heap; 
}; 

template <class T> 
void MinHeap<T>::output(T a[],int n) 
{ 
	for(int i = 1; i <= n; i++) 
	cout << a[i] << " "; 
	cout << endl; 
} 

template <class T> 
MinHeap<T>::MinHeap(int maxheapsize) 
{ 
	maxsize = maxheapsize; 
	heap = new T[maxsize + 1]; 
	currentsize = 0; 
} 

template<class T> 
MinHeap<T>& MinHeap<T>::Insert(const T& x) 
{ 
	if(currentsize == maxsize) 
	{ 
		return *this; 
	} 
	int i = ++currentsize; 
	while(i != 1 && x < heap[i/2]) 
	{ 
		heap[i] = heap[i/2]; 
		i /= 2; 
	} 

	heap[i] = x; 
	return *this; 
} 

template<class T> 
MinHeap<T>& MinHeap<T>::DeleteMin(T& x) 
{ 
	if(currentsize == 0) 
	{ 
		cout<<"Empty heap!"<<endl; 
		return *this; 
	} 

	x = heap[1]; 

	T y = heap[currentsize--]; 
	int i = 1, ci = 2; 
	while(ci <= currentsize) 
	{ 
		if(ci < currentsize && heap[ci] > heap[ci + 1]) 
		{ 
			ci++; 
		} 

		if(y <= heap[ci]) 
		{ 
			break; 
		} 
		heap[i] = heap[ci]; 
		i = ci; 
		ci *= 2; 
	} 

	heap[i] = y; 
	return *this; 
} 

template<class T> 
void MinHeap<T>::Initialize(T x[], int size, int ArraySize) 
{ 
	delete []heap; 
	heap = x; 
	currentsize = size; 
	maxsize = ArraySize; 

	for(int i = currentsize / 2; i >= 1; i--) 
	{ 
		T y = heap[i]; 
		int c = 2 * i; 
		while(c <= currentsize) 
		{ 
			if(c < currentsize && heap[c] > heap[c + 1]) 
				c++; 
			if(y <= heap[c]) 
				break; 
			heap[c / 2] = heap[c]; 
			c *= 2; 
		} 
		heap[c / 2] = y; 
	} 
} 

template<class T> 
void MinHeap<T>::Deactivate() 
{ 
	heap = 0; 
} 
     2、6d2.cpp
//單源最短路徑問題 分支 限界法求解
#include "stdafx.h"
#include "MinHeap2.h"
#include <iostream>
#include <fstream> 
using namespace std;

ifstream fin("6d2.txt"); 

template<class Type>
class Graph
{
	friend int main();
    public:
		void ShortesPaths(int);
	private:
        int		n,		   //圖G的頂點數
				*prev;     //前驅頂點陣列
 	    Type	**c,       //圖G的領接矩陣
				*dist;     //最短距離陣列
}; 

template<class Type>
class MinHeapNode
{
   friend Graph<Type>;
   public:
	   operator int ()const{return length;}
   private:
       int       i;		  //頂點編號
       Type  length;	  //當前路長
}; 

template<class Type>
void Graph<Type>::ShortesPaths(int v)//單源最短路徑問題的優先佇列式分支限界法
{ 
	MinHeap<MinHeapNode<Type>> H(1000);
	MinHeapNode<Type> E;

	//定義源為初始擴充套件節點
	E.i=v;
	E.length=0;
	dist[v]=0;

	while (true)//搜尋問題的解空間
	{
		for (int j = 1; j <= n; j++)
			if ((c[E.i][j]!=0)&&(E.length+c[E.i][j]<dist[j])) {

				 // 頂點i到頂點j可達,且滿足控制約束
				 dist[j]=E.length+c[E.i][j];
				 prev[j]=E.i;

				 // 加入活結點優先佇列
				 MinHeapNode<Type> N;
				 N.i=j;
				 N.length=dist[j];
				 H.Insert(N);
			}`
		try 
		{
			H.DeleteMin(E); // 取下一擴充套件結點
		}        
		catch (int) 
		{
			break;
		}  
		if (H.currentsize==0)// 優先佇列空
		{
			break;
		}
	}
}

int main()
{  
	int n=11;
	int prev[12] = {0,0,0,0,0,0,0,0,0,0,0,0};  

	int dist[12]={1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000};

	cout<<"單源圖的鄰接矩陣如下:"<<endl;
	int **c = new int*[n+1];

	for(int i=1;i<=n;i++)
	{
		c[i]=new int[n+1];
		for(int j=1; j<=n; j++)
		{
			fin>>c[i][j];
			cout<<c[i][j]<<" ";
		}
		cout<<endl;
	}

	int v=1;
    Graph<int> G;
    G.n=n;

    G.c=c;
    G.dist=dist;
    G.prev=prev;
    G.ShortesPaths(v);

    cout<<"從S到T的最短路長是:"<<dist[11]<<endl;
    for (int i = 2; i <= n; i++)
	{
		cout<<"prev("<<i<<")="<<prev[i]<<"   "<<endl;
	}

    for (int i = 2; i <= n; i++)
	{
		cout<<"從1到"<<i<<"的最短路長是:"<<dist[i]<<endl;
	}

	for(int i=1;i<=n;i++)
	{
		delete []c[i];
	}

	delete []c;
	c=0;	
	return 0;
}
     程式執行結果如圖: