常用算法五(分支限界法)

来源:互联网 发布:IT界伍飞宇 编辑:程序博客网 时间:2024/05/16 14:05

一、基本描述

    类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法。但在一般情况下,分支限界法与回溯法的求解目标不同。回溯法的求解目标是找出T中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解

   (1)分支搜索算法

    所谓“分支”就是采用广度优先的策略,依次搜索E-结点的所有分支,也就是所有相邻结点,抛弃不满足约束条件的结点,其余结点加入活结点表。然后从表中选择一个结点作为下一个E-结点,继续搜索。

     选择下一个E-结点的方式不同,则会有几种不同的分支搜索方式。

   1)FIFO搜索

   2)LIFO搜索

   3)优先队列式搜索

(2)分支限界搜索算法 

二、分支限界法的一般过程

    由于求解目标不同,导致分支限界法与回溯法在解空间树T上的搜索方式也不相同。回溯法以深度优先的方式搜索解空间树T,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树T

    分支限界法的搜索策略是:在扩展结点处,先生成其所有的儿子结点(分支),然后再从当前的活结点表中选择下一个扩展对点。为了有效地选择下一扩展结点,以加速搜索的进程,在每一活结点处,计算一个函数值(限界),并根据这些已计算出的函数值,从当前活结点表中选择一个最有利的结点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。

    分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。问题的解空间树是表示问题解空间的一棵有序树,常见的有子集树和排列树。在搜索问题的解空间树时,分支限界法与回溯法对当前扩展结点所使用的扩展方式不同。在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,那些导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被子加入活结点表中。此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所求的解或活结点表为空时为止。

三、回溯法和分支限界法的一些区别

    有一些问题其实无论用回溯法还是分支限界法都可以得到很好的解决,但是另外一些则不然。也许我们需要具体一些的分析——到底何时使用分支限界而何时使用回溯呢?

回溯法和分支限界法的一些区别:

   方法对解空间树的搜索方式       存储结点的常用数据结构      结点存储特性常用应用

  回溯法深度优先搜索堆栈活结点的所有可行子结点被遍历后才被从栈中弹出找出满足约束条件的所有解

  分支限界法广度优先或最小消耗优先搜索队列、优先队列每个结点只有一次成为活结点的机会找出满足约束条件的一个解或特定意义下的最优解

旅行售货员问题用回溯法貌似更容易实现一些,网上代码很多。这里给出用分支限界法的java实现。

四、应用示例


问题描述:
某售货员要到若干城市去推销商品,已知各城市之间的路程(或旅费)。他要选定一条从驻地出发,经过每个城市一遍,最后回到驻地的路线,使总的路程( 或旅费)最小。各个城市之间可能是有向连通的、无向连通的、以及存在某个城市不连通的情况,你的程序应该能够处理所有可能的情况。如下图表示各个城市间无向连通。
  旅行售货员_分支限界法_java实现 - mpb-china - 我的博客









输入:

第一行为一个整数n(n<=10),表示城市的总个数。接下来是一个n*n的矩阵,用来表示城市间的连通情况以及花费,例如path[i][j]=lenlen=-1表示从城市i到城市j没有通路,len>0表示从ij的路程长度为len。对于上面图示的问题我们可以按照下面方式输入:

4
-1 30 6 4
30 -1 5 10
6 5 -1 20
4 10 20 -1

输出:

输出占一行,对于给定的问题,如果找到了最小路程(花费),输出该最小花费,如果没有通路可以到达每个城市,则输出-1

输入样例:

4
-1 30 6 4
30 -1 5 10
6 5 -1 20
4 10 20 -1

输出样例:

25

代码:

//旅行售货员问题,用分支限界法实现 2010-10-28

import java.util.Scanner;

public class Main
{

//Main
public static void main(String args[])
{
Scanner s=new Scanner(System.in);
int n=0;//结点的个数
String line=s.nextLine();//读入n
n=Integer.parseInt(line);
a=new float[n][n];
int []vv=new int[n];

for(int i=0;i<n;i++)
{
line=s.nextLine();
String []sArray=line.split(" ");
for(int j=0;j<sArray.length;j++)
{
a[i][j]=Integer.parseInt(sArray[j]);
}
}
System.out.println(bbTsp(vv));
}//Main


static float [][]a;//图的邻接矩阵
//static float a[][]={{-1,-1,-1,2},{2,-1,-1,-1},{1,3,-1,-1},{-1,-1,1,-1}};
//static float a[][]={{-1,30,6,4},{30,-1,5,10},{6,5,-1,20},{4,10,20,-1}};
//static float a[][]={{5,5,5,5},{5,5,5,5},{5,5,5,5},{5,5,5,5}};

private static class HeapNode implements Comparable
{
float lcost,//子树费用下界
cc,//当前费用
rcost;//X[s:n-1]中顶点最小出边费用和
int s;//根节点到当前结点的路径为X[0:s]
int []x;//需要进一步搜索的结点是x[s+1:n-1]

//HeapNode的构造函数
HeapNode(float lc,float ccc,float rc,int ss,int []xx)
{
lcost=lc;
cc=ccc;
s=ss;
x=xx;
}//HeapNode 构造函数

public int compareTo(Object x)
{
float xlc=((HeapNode)x).lcost;
if(lcost<xlc)
return -1;
if(lcost==xlc)
return 0;
return 1;
}

}//class HeapNode

public static int  bbTsp(int []v)
{
int n=v.length;
MinHeap heap=new MinHeap(100);
float []minOut=new float[n];//minOut[i]是顶点i的最小出边费用
float minSum=0;//最小出边费用和

//计算最小出边费用和
for(int i=0;i<n;i++)
{
float min=Float.MAX_VALUE;
for(int j=0;j<n;j++)
{
if(a[i][j]!=-1&&a[i][j]<min)
min=a[i][j];//有回路
}//for j

if(min==Float.MAX_VALUE)
{
return -1;//无回路
}//if

minOut[i]=min;
minSum+=min;
}//for i

//初始化
int []x=new int[n];
for(int i=0;i<n;i++)
{
x[i]=i;
}
HeapNode enode=new HeapNode(0,0,minSum,0,x);
float bestc=Float.MAX_VALUE;

//搜索排列空间树
while(enode!=null&&enode.s<n-1)
{
//System.out.println(bestc);
x=enode.x;
if(enode.s==n-2)//叶子结点
{
if(a[x[n-2]][x[n-1]]!=-1&&
a[x[n-1]][1]!=-1||
bestc==Float.MAX_VALUE)//当前最优解还不存在的情况
{
bestc=enode.cc+a[x[n-2]][x[n-1]]+a[x[n-1]][0];
enode.cc=bestc;
enode.lcost=bestc;
enode.s++;
heap.put(enode);
}
}//if(enode.s==n-2)

//if(enode.s!=n-2)
else
{
for(int i=enode.s+1;i<n;i++)
{
if(a[x[enode.s]][x[i]]!=-1)
{
float cc=enode.cc+a[x[enode.s]][x[i]];
float rcost=enode.rcost-minOut[x[enode.s]];
float b=cc+rcost;
if(b<bestc)
{
int []xx=new int[n];
for(int j=0;j<n;j++)
xx[j]=x[j];
xx[enode.s+1]=x[i];
xx[i]=x[enode.s+1];
HeapNode node=new HeapNode(b,cc,rcost,enode.s+1,xx);
heap.put(node);
}//if(b<bestc)
}//if 可行儿子结点
}//for
}//else,if(enode.s!=n-2)

enode=(HeapNode)heap.removeMin();
}//while
for(int i=0;i<n;i++)
v[i]=x[i];
return (int)bestc;
}//Class bbTsp



//构造最小堆
public static class MinHeap
{
private HeapNode[] heapArray; // 堆容器
private int maxSize; // 堆的最大大小
private int currentSize=0; // 堆大小

//构造函数
public MinHeap(int _maxSize)
{
maxSize = _maxSize;
heapArray = new HeapNode[maxSize];
currentSize = 0;
}

//自上而下调整
public void filterDown(int start, int endOfHeap)
{
int i = start;
int j = 2 * i + 1; // j是i的左子女位置
HeapNode temp = heapArray[i];

while (j <= endOfHeap)
{ // 检查是否到最后位置
if (j < endOfHeap // 让j指向两子女中的小者
&& heapArray[j].cc > heapArray[j + 1].cc)
{
j++;
}
if (temp.cc <= heapArray[j].cc)
{ // 小则不做调整
break;
} else
{ // 否则小者上移,i,j下降
heapArray[i] = heapArray[j];
i = j;
j = 2 * j + 1;
}
}
heapArray[i] = temp;
}//filterDown

//自下而上的调整:从结点start开始到0为止,自下向上比较,如果子女的值小于双亲结点的值则互相交换
public void filterUp(int start)
{
int j = start;
int i = (j - 1) / 2;
HeapNode temp = heapArray[j];

while (j > 0)
{ // 沿双亲结点路径向上直达根节点
if (heapArray[i].cc <= temp.cc)
{// 双亲结点值小,不调整
break;
} else {// 双亲结点值大,调整
heapArray[j] = heapArray[i];
j = i;
i = (i - 1) / 2;
}
heapArray[j] = temp; // 回送
}
}//filterUp

//插入结点
public void put(HeapNode node) 
{
HeapNode newNode = node;
heapArray[currentSize] = newNode;
filterUp(currentSize);
currentSize++;

}//put

//删除堆中的最小值
public HeapNode removeMin() 
{
HeapNode root = heapArray[0];
heapArray[0] = heapArray[currentSize - 1];
currentSize--;
filterDown(0, currentSize - 1);
return root;
}
}//class MinHeap
}//class Main