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; }
程序运行结果如图: