分支限界的基本思想
分支限界和回溯法都是在解空间树中进行搜索,但是他们的搜索方式不同,对于回溯法,他是从根节点开始以深度优先的策略进行搜索;而分支限界则是从根节点开始,以宽度优先为的方式进行搜索
分支限界通常用来找问题的一个最优解,而回溯法用来找所有的可行解
两种分支限界法
队列式(FIFO):按照队列先进先出原则选取下一个结点为扩展结点
优先级队列式:按照优先队列规定的优先级选取优先级最高的结点成为当前扩展结点
分支限界对回溯法的改进之处
回溯法对于问题状态空间树的搜索过程是盲目进行的,当问题求最优解的时候,搜索页只有找到了一个可行解后目标函数的界才有意义,在寻找第一个可行解的时候,搜索的过程仍是盲目的;
而分支限界不再像单纯的回溯法那样盲目搜索,也不是遇到死结点才往回走,而是预先分别估算沿着扩展结点的各个儿子节点向下搜索路径中目标函数可能取得的界,把他们保存在一张节点表里面,依据节点表里面不断更新的信息,不断调整自己的搜索方向,有选择有目的的搜索
优点:可以求得最优解且平均速度块,检查的子问题较少,能较快求得最佳解
缺点:要存储很多叶子节点限界和对应耗费矩阵,花费的空间多
优先级队列怎么用
import java.util.PriorityQueue;
public class TheUseOfPrir {
public static void main(String[] args) {
final PriorityQueue<Student> queue=new PriorityQueue<>();
Student p1=new Student(95,"张三");
Student p2=new Student(89,"李四");
Student p3=new Student(89,"李四");
Student p4=new Student(67,"王五");
Student p5=new Student(92,"赵六");
queue.add(p1);
queue.add(p2);
queue.add(p3);//add 和offer效果一样。
queue.offer(p4);//add 方法实现,其实就是调用了offer
queue.offer(p5);
for (Student Student : queue) {
System.out.println(Student.toString());
}
System.out.println("---------------------");
while(!queue.isEmpty()){
System.out.println(queue.poll());//出队的时候是按优先级出的
}
}
}
class Student implements Comparable{
private int score;
private String name;
public Student(int age,String name){
this.score=age;
this.name=name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString(){
return "姓名:"+name+"-"+score+"分";
}
@Override
public int compareTo(Object o) {
Student current=(Student)o;
if(current.getScore()>this.score){
return 1;//从大到小排
}else if(current.getScore()==this.score){
return 0;
}
return -1;
}
}
0-1背包问题
商品类记录每一件物品的价值(price)与重量(weight),还有单位价值(bw = price/weight)
class Produce{
int id=0;
int price;
int weight;
int bw;//物品的单位价值
Produce(int price,int weight,int id)
{
this.id=id;
this.price=price;
this.weight=weight;
bw=price/weight;
}
}
背包类,记录背包的状态,最初什么也不装的背包是一种状态,装了某一个物品和不装该物品又是不同的状态
//背包类,用来记录当前的背包状态
class Bag{
int totalPrice;//背包内的总价值
int remindWeight;//背包内的剩余空间
int maxPrice;//最大价值
int maxWeight;//最大承载重量
int i;//当前商品的序列值
boolean boon = false;//是否爆炸
String stute;
Bag fatherBag;//前一个背包的状态。装入一个商品或不装入一个商品为一个背包状态
//构造函数,初始的背包,什么也没放
Bag(int maxWeight){
this.maxWeight = maxWeight;
fatherBag = null;
totalPrice = 0;
remindWeight = maxWeight;
this.i = -1;
stute="";
}
//不放入物品的背包状态 即右儿子节点
Bag(Bag fatherBag, int i){
totalPrice = fatherBag.totalPrice;
remindWeight = fatherBag.remindWeight;
maxPrice = fatherBag.totalPrice;
maxWeight = fatherBag.maxWeight;
this.i = i;
this.fatherBag = fatherBag;
stute=fatherBag.stute+"0";
}
//放入物品的背包状态 即左儿子节点
Bag(Bag fatherBag,Produce produce, int i){
totalPrice = fatherBag.totalPrice+produce.price;
remindWeight = fatherBag.remindWeight - produce.weight;
maxPrice = fatherBag.totalPrice + produce.price;
if(remindWeight < 0){
boon = true;//装不下
}
maxWeight = fatherBag.maxWeight;
this.i = i;
this.fatherBag = fatherBag;
stute=fatherBag.stute+"1";
}
}
商品链表,存放商品
List<Produce> list = new ArrayList<>();//商品列表
使用优先级队列来实现分支限界,优先级队列如下,优先级是这个状态下背包所能装的最大价值所决定的(maxPrice)
//优先级队列,按照背包的最大价值为优先级存放背包状态
PriorityQueue<Bag> heap = new PriorityQueue<Bag>(
(a,b)->{return (b.maxPrice - a.maxPrice) == 0?b.remindWeight-a.remindWeight:b.maxPrice-a.maxPrice;}
);
maxPrice使用getUpPrice(Bag tempBag, List list)函数来求,在当前背包状态对于后面还没装的物品,不断放入直到放不下,对于那个放不下的物品放入其最大能放入比例部分
//获取最大价值
public void getUpPrice(Bag tempBag, List<Produce> list){
int remind = tempBag.remindWeight;//剩余空间
for(int i = tempBag.i + 1; i<list.size(); i++){
int tempWeight = remind - list.get(i).weight;
if(tempWeight >= 0){//装得下
remind = tempWeight;
tempBag.maxPrice+=list.get(i).price;
}else {//装不下,则装入能装入比例
tempBag.maxPrice += list.get(i).bw*remind;
break;
}
}
}
分支限界法是一次性生成弹出节点的左儿子节点和右儿子节点,一个背包状态为一个节点,并把生成的节点按优先级放入优先级队列
//初始化商品列表
for(int i = 0; i<price.length; i++){
Produce produce=new Produce(price[i],weight[i],i);
list.add(produce);
}
//商品按照价值从大到小排序
Collections.sort(list,(a,b)->b.bw-a.bw);
Bag root = new Bag(maxWeight);//什么都没放的背包
getUpPrice(root, list);
heap.add(root);
while (!heap.isEmpty()){
Bag temp = heap.poll();
//到达叶子节点
if(temp.i>=list.size()-1){
printAnswer(temp);
break;
}
//不放入,右儿子节点
Bag notPut = new Bag(temp, temp.i+1);
if(!notPut.boon){//剪枝
getUpPrice(notPut, list);
heap.add(notPut);
}
//放入,左儿子节点
Bag putIn=new Bag(temp,list.get(temp.i+1),temp.i+1);
if(!putIn.boon){//剪枝
getUpPrice(putIn, list);
heap.add(putIn);
}
}
完整代码如下:
import java.util.*;
public class BagPro {
int[] price = {10,14,20,9}; //每个东西的价格
int[] weight = {5,7,10,3}; //每个东西的重量
int maxWeight = 19; //最多存储重量,背包容量
List<Produce> list = new ArrayList<>();//商品列表
int maxValue = 0;
//优先级队列,按照背包的最大价值为优先级存放背包状态
PriorityQueue<Bag> heap = new PriorityQueue<Bag>(
(a,b)->{return (b.maxPrice - a.maxPrice) == 0?b.remindWeight-a.remindWeight:b.maxPrice-a.maxPrice;}
);
BagPro(){
//初始化商品列表
for(int i = 0; i<price.length; i++){
Produce produce=new Produce(price[i],weight[i],i);
list.add(produce);
}
//商品按照价值从大到小排序
Collections.sort(list,(a,b)->b.bw-a.bw);
Bag root = new Bag(maxWeight);//什么都没放的背包
getUpPrice(root, list);
heap.add(root);
while (!heap.isEmpty()){
Bag temp = heap.poll();
//到达叶子节点
if(temp.i>=list.size()-1){
printAnswer(temp);
System.out.println("最大价值为"+maxValue);
break;
}
//不放入,右儿子节点
Bag notPut = new Bag(temp, temp.i+1);
if(!notPut.boon){//剪枝
getUpPrice(notPut, list);
heap.add(notPut);
}
//放入,左儿子节点
Bag putIn=new Bag(temp,list.get(temp.i+1),temp.i+1);
if(!putIn.boon){
getUpPrice(putIn, list);
heap.add(putIn);
}
}
}
//获取最大价值
public void getUpPrice(Bag tempBag, List<Produce> list){
int remind = tempBag.remindWeight;//剩余空间
for(int i = tempBag.i + 1; i<list.size(); i++){
int tempWeight = remind - list.get(i).weight;
if(tempWeight >= 0){//装得下
remind = tempWeight;
tempBag.maxPrice+=list.get(i).price;
}else {//装不下,则装入能装入比例
tempBag.maxPrice += list.get(i).bw*remind;
break;
}
}
}
//打印结果
private void printAnswer(Bag temp)
{
for(int i=0;i<list.size();i++)
{
if(temp.stute.charAt(i)=='1')
maxValue+=list.get(i).price;
System.out.println("放入价值为"+list.get(i).price+",重量为 "+list.get(i).weight+"的商品");
}
}
public static void main(String[] args) {
BagPro test = new BagPro();
}
}
//背包类,用来记录当前的背包状态
class Bag{
int totalPrice;//背包内的总价值
int remindWeight;//背包内的剩余空间
int maxPrice;//最大价值
int maxWeight;//最大承载重量
int i;//当前商品的序列值
boolean boon = false;//是否爆炸
String stute;
Bag fatherBag;//前一个背包的状态。装入一个商品或不装入一个商品为一个背包状态
//构造函数,初始的背包,什么也没放
Bag(int maxWeight){
this.maxWeight = maxWeight;
fatherBag = null;
totalPrice = 0;
remindWeight = maxWeight;
this.i = -1;
stute="";
}
//不放入物品的背包状态 即右儿子节点
Bag(Bag fatherBag, int i){
totalPrice = fatherBag.totalPrice;
remindWeight = fatherBag.remindWeight;
maxPrice = fatherBag.totalPrice;
maxWeight = fatherBag.maxWeight;
this.i = i;
this.fatherBag = fatherBag;
stute=fatherBag.stute+"0";
}
//放入物品的背包状态 即左儿子节点
Bag(Bag fatherBag,Produce produce, int i){
totalPrice = fatherBag.totalPrice+produce.price;
remindWeight = fatherBag.remindWeight - produce.weight;
maxPrice = fatherBag.totalPrice + produce.price;
if(remindWeight < 0){
boon = true;//装不下
}
maxWeight = fatherBag.maxWeight;
this.i = i;
this.fatherBag = fatherBag;
stute=fatherBag.stute+"1";
}
}
class Produce{
int id=0;
int price;
int weight;
int bw;//物品的单位价值
Produce(int price,int weight,int id)
{
this.id=id;
this.price=price;
this.weight=weight;
bw=price/weight;
}
}
图解
学习
队列式分支限界法
可以通过画分支限界法状态空间树的搜索图来理解具体思想和流程
设N=3, W=(16,15,15), P=(45,25,25), C=30(背包容量)
每一层按顺序对应一个物品放入背包(1)还是不放入背包(0)
步骤:
① 用一个队列存储活结点表,初始为空
② A为当前扩展结点,其儿子结点B和C均为可行结点,将其按从左到右顺序加入活结点队列,并舍弃A。
③ 按FIFO原则,下一扩展结点为B,其儿子结点D不可行,舍弃;E可行,加入。舍弃B
④ C为当前扩展结点,儿子结点F、G均为可行结点,加入活结点表,舍弃C
⑤ 扩展结点E的儿子结点J不可行而舍弃;K为可行的叶结点,是问题的一个可行解,价值为45
⑥ 当前活结点队列的队首为F, 儿子结点L、M为可行叶结点,价值为50、25
⑦ G为最后一个扩展结点,儿子结点N、O均为可行叶结点,其价值为25和0
⑧ 活结点队列为空,算法结束,其最优值为50
注:活结点就是不可再进行扩展的节点,也就是两个儿子还没有全部生成的节点
装载问题
解题思路
- 使用BBNode类存储解空间树里面的每个节点
static class BBNode{
BBNode parent;
boolean isLeftChild;
BBNode(BBNode parent, boolean isLeftChild){
//保存父节点和左节点标识用来输出最优解
this.parent = parent;
this.isLeftChild = isLeftChild;
}
}
- 使用HeapNode类来存储优先队列里的节点,上界upperLimitWeight是当前的重量加上还没装的重量,用来确定优先级,level是这个节点在解空间树里的所在层数
//优先级队列里的节点
static class HeapNode implements Comparable<HeapNode>{
BBNode liveNode;
int upperLimitWeight;//上界
int level;
public HeapNode(BBNode liveNode, int upperLimitWeight, int level){
this.liveNode = liveNode;
this.level = level;
this.upperLimitWeight = upperLimitWeight;
}
@Override
public int compareTo(HeapNode o) {
if(this.upperLimitWeight<o.upperLimitWeight){
return 1;
}else if(this.upperLimitWeight>o.upperLimitWeight){
return -1;
}
return 0;
}
}
- 往优先级队列里添加新节点
public static void addLiveNode(int upLimit, int level, BBNode parent, boolean isLeft) {
// put this live node in to MaxHeap of live node;
BBNode b = new BBNode(parent, isLeft);
HeapNode node = new HeapNode(b, upLimit, level);
heap.add(node);
}
- 却定每个物品添加进去后的剩余物品重量
int[] remains = new int[n+1];//剩余重量
remains[n] = 0;
for(int j = n-1; j>=0; j--){
remains[j] = remains[j+1]+conatinerWeight[j+1];
}
- 对解空间树进行搜索,从第一个物品开始,如果能装到进去,则计算出他的上界并当成左儿子节点添加进去,因为分支限界是一次性产生左右儿子节点,而本题的右儿子节点可以直接添加,所有在添加完左儿子后添加有儿子节点到优先级队列,添加完后弹出优先级最高的节点重复之前动作直到到达叶子节点
//搜索解空间树直到叶节点
while (i!=n+1){
if(expandWeight+conatinerWeight[i]<=capacity){
//能装下
int nodePriority = expandWeight+conatinerWeight[i]+remains[i];
addLiveNode(nodePriority,i+1,expandNode,true);
}
addLiveNode(expandWeight+remains[i],i+1,expandNode,false);
HeapNode nextnode = heap.poll();
i = nextnode.level;
expandNode = nextnode.liveNode;
expandWeight = nextnode.upperLimitWeight - remains[i-1];
}
- 完整代码如下
import java.util.PriorityQueue;
public class ZZProblem {
static PriorityQueue<HeapNode> heap = null;
static int bestWeight = 0;
//解空间树里的节点
static class BBNode{
BBNode parent;
boolean isLeftChild;
BBNode(BBNode parent, boolean isLeftChild){
this.parent = parent;
this.isLeftChild = isLeftChild;
}
}
//优先级队列里的节点
static class HeapNode implements Comparable<HeapNode>{
BBNode liveNode;
int upperLimitWeight;//上界
int level;
public HeapNode(BBNode liveNode, int upperLimitWeight, int level){
this.liveNode = liveNode;
this.level = level;
this.upperLimitWeight = upperLimitWeight;
}
@Override
public int compareTo(HeapNode o) {
if(this.upperLimitWeight<o.upperLimitWeight){
return 1;
}else if(this.upperLimitWeight>o.upperLimitWeight){
return -1;
}
return 0;
}
}
public static void maxLoading(int[] conatinerWeight, int capacity, int[] bestSolution){
heap = new PriorityQueue<HeapNode>();
int n = conatinerWeight.length-1;
BBNode expandNode = null;//扩展节点
int i = 0;//扩展节点的层数
int expandWeight = 0;
int[] remains = new int[n+1];//剩余重量
remains[n] = 0;
for(int j = n-1; j>=0; j--){
remains[j] = remains[j+1]+conatinerWeight[j+1];
}
//搜索解空间树直到叶节点
while (i!=n+1){
if(expandWeight+conatinerWeight[i]<=capacity){
//能装下
int nodePriority = expandWeight+conatinerWeight[i]+remains[i];
addLiveNode(nodePriority,i+1,expandNode,true);
}
addLiveNode(expandWeight+remains[i],i+1,expandNode,false);
HeapNode nextnode = heap.poll();
i = nextnode.level;
expandNode = nextnode.liveNode;
expandWeight = nextnode.upperLimitWeight - remains[i-1];
}
for(int j = n; j>=0; j--) {
bestSolution[j] = expandNode.isLeftChild? 1:0;
expandNode = expandNode.parent;
}
System.out.println("最大装载重量为"+expandWeight);
}
public static void addLiveNode(int upLimit, int level, BBNode parent, boolean isLeft) {
// put this live node in to MaxHeap of live node;
BBNode b = new BBNode(parent, isLeft);
HeapNode node = new HeapNode(b, upLimit, level);
heap.add(node);
}
public static void main(String[] args) {
int[] weight = {10,30,40};
int capacity = 50;
int [] bestSolution = new int[weight.length];
maxLoading(weight, capacity, bestSolution);
for(int i = 0; i<3; i++){
if(bestSolution[i] == 1){
System.out.println("装了物品"+(i+1));
}
}
}
}
旅行售货员问题
解题思路
-
问题的限界是当前所花费的费用和 + 未到的城市的所有出边和,用这个来确定优先级,很明显,这个值越小,按照当前这个方案搜索下去的解是最优解的可能性越大
-
使用HeapNode类来存储优先级队列里面的每一个节点
其中几个属性的含义如下:
cc:当前的总花费
s:当前的城市编号
x:当前的路径
rcost:是剩下的城市的最小出边和
lcost:就是确定优先级的上界,也就是当前所花费的费用和 + 未到的城市的所有出边和(rcost+cc)
public static class HeapNode implements Comparable{
float lcost;//子树费用的下界
float cc;//当前费用
float rcost;//x[s:n-1]中顶点最小出边费用和
int s;//根节点到当前节点的路径为x[0:s]
int[] x;//需要进一步搜索的顶点是x[s+1:n-1]
//lcost = rcost+cc
//构造方法
public HeapNode(float lcost,float cc,float rcost,int s,int[] x){
this.lcost=lcost;
this.cc=cc;
this.s=s;
this.x=x;
this.rcost = rcost;
}
public int compareTo(Object x){
if(((HeapNode)x).lcost>this.rcost){
return 1;
}
return -1;
}
}
- 核心思路如下
① 针对每一个节点(城市),找到他们的最小出边
//minOut[i]=i的最小出边费用
float[] minOut=new float[n+1];
float minSum=0;//最小出边费用和
for(int i=1;i<=n;i++){//针对每个节点,找到最小出边
//计算minOut[i]和minSum
float min=Float.MAX_VALUE;
for(int j=1;j<=n;j++){
if(a[i][j]<Float.MAX_VALUE&&a[i][j]<min)
min=a[i][j];
}
if(min==Float.MAX_VALUE)
return Float.MAX_VALUE;
minOut[i]=min;
minSum+=min;
}
② 搜索解空间树,对应当前的节点找他能到达的节点并计算能到达的节点的当前花费,以及他的下界,如果这个下界比当前的最优解小,才有可能是新的最优解,插入优先级队列,并从优先级队列里弹出头节点进行扩展
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.add(node);
Collections.sort(heap);
}
}
}
//取下一个扩展结点
enode=heap.poll();
③ 一直重复直到到达叶节点的上一个节点(这时候剩下两个节点),需要判断的是这两个节点是否连通以及叶子节点和起始节点是否连通,如果满足这两个条件再去判断这个路径是否比之前求出的最少费用少,如果是则成为新的最优路径
x=enode.x;
if(enode.s==n-2){
//当前扩展结点是叶节点的父节点
//再加两条边构成回路,所构成回路是否优于当前最优解
if(a[x[n-2]][x[n-1]]!=-1
&&a[x[n-1]][1]!=-1
&&enode.cc+a[x[n-2]][x[n-1]]+a[x[n-1]][1]<bestc){
//找到费用更小的回路
bestc=enode.cc+a[x[n-2]][x[n-1]]+a[x[n-1]][1];
}
}
④ 跳出循环得到最优值和最优解
for(int i=0;i<n;i++)
v[i+1]=x[i];
return bestc;
- 完整代码如下
import java.util.Collections;
import java.util.LinkedList;
import java.util.PriorityQueue;
public class BBTSP {
float[][] a;//图G的邻接矩阵
public BBTSP(float[][] a){
this.a=a;
}
public static class HeapNode implements Comparable{
float lcost;//子树费用的下界
float cc;//当前费用
float rcost;//x[s:n-1]中顶点最小出边费用和
int s;//根节点到当前节点的路径为x[0:s]
int[] x;//需要进一步搜索的顶点是x[s+1:n-1]
//lcost = rcost+cc
//构造方法
public HeapNode(float lcost,float cc,float rcost,int s,int[] x){
this.lcost=lcost;
this.cc=cc;
this.s=s;
this.x=x;
this.rcost = rcost;
}
public int compareTo(Object x){
if(((HeapNode)x).lcost>this.rcost){
return 1;
}
return -1;
}
}
public float bbTsp(int[] v){
int n=v.length-1;//节点数
PriorityQueue<HeapNode> heap = new PriorityQueue<>();
//minOut[i]=i的最小出边费用
float[] minOut=new float[n+1];
float minSum=0;//最小出边费用和
for(int i=1;i<=n;i++){//针对每个节点,找到最小出边
//计算minOut[i]和minSum
float min=Float.MAX_VALUE;
for(int j=1;j<=n;j++){
if(a[i][j]<Float.MAX_VALUE&&a[i][j]<min)
min=a[i][j];
}
if(min==Float.MAX_VALUE)
return Float.MAX_VALUE;
minOut[i]=min;
minSum+=min;
}
//初始化
int[] x=new int[n];
for(int i=0;i<n;i++)
x[i]=i+1;
HeapNode enode=new HeapNode(0,0,minSum,0,x);
float bestc=Float.MAX_VALUE;
//搜索排列空间树
while(enode!=null&&enode.s<n-1){
//非叶节点
x=enode.x;
if(enode.s==n-2){
//当前扩展结点是叶节点的父节点
//再加两条边构成回路,所构成回路是否优于当前最优解
if(a[x[n-2]][x[n-1]]!=-1&&a[x[n-1]][1]!=-1&&enode.cc+a[x[n-2]][x[n-1]]+a[x[n-1]][1]<bestc){
//找到费用更小的回路
bestc=enode.cc+a[x[n-2]][x[n-1]]+a[x[n-1]][1];
}
}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.add(node);
}
}
}
}
//取下一个扩展结点
enode=heap.poll();
}
//将最优解复制到v[1...n]
for(int i=0;i<n;i++)
v[i+1]=x[i];
return bestc;
}
public static void main(String[] args) {
int n=4;
float[][] a={{0,0,0,0,0},{0,-1,30,6,4},{0,30,-1,5,10},{0,6,5,-1,20},{0,4,10,20,-1}};//a下标从1开始,0用来凑数;-1表示不同,1表示连通
// int n=5;
// float[][] a={{0,0,0,0,0,0},{0,-1,5,-1,7,9},{0,5,-1,10,3,6},{0,-1,10,-1,8,-1},{0,7,3,8,-1,4},{0,9,6,-1,4,-1}};//a下标从1开始,0用来凑数;-1表示不同,1表示连通
BBTSP b=new BBTSP(a);
int []v=new int[n+1];
System.out.println("最短回路长为:"+b.bbTsp(v));
System.out.print("最短回路为:");
for(int i=1;i<=n;i++){
System.out.print(v[i]+" ");
}
}
}