算法介绍
分支界限法:
分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。
在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。
此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。
与回溯法的区别:
(1)求解目标:回溯法的求解目标是找出解空间树中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出在某种意义下的最优解。
(2)搜索方式的不同:回溯法以深度优先的方式搜索解空间树,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树。
常见的两种分支限界法:
(1)队列式(FIFO)分支限界法
按照队列先进先出(FIFO)原则选取下一个结点为扩展结点。
(2)优先队列式分支限界法
按照优先队列中规定的优先级选取优先级最高的结点成为当前扩展结点。
问题实例
问题描述:
题目:
请用分支限界法求解下图中从s到t点的最短路径。也就是从顶点1到顶点11的最短路径。
问题分析:
使用分支界限法解决问题,关键是找到每个节点的边界条件,符合的记录,不符合的修剪。在本题中到达每个顶点的距离最短就是边界条件。通过使用队列的存储结构,利用每个顶点之间的连线(顶点的联系)进行广度优先遍历,一层层的扩散更新到达每个顶点的最短距离。最后可以采用逆推法求最短路径。
伪代码:
①设计三个结构体,一个用来存储每个节点的最短路径值,一个用来存储两个节点之间的关系,最后用一个结构体将其余两个结构体整合为一体。
②利用while循环先将与源顶点相连的顶点加入队列,然后对队列中的首元素进行判断。
③如果与首元素相连的顶点的最短路径值大于首元素的最短路径值+两点间的距离,则更新相连顶点的最短路径值,首元素出队列,直到队列为空。
④利用逆推法,从最后一顶点开始查询,如果该顶点的最短路径值-相连顶点的距离=相连顶点的最短路径值,则记录该相连顶点并对该相连顶点进行相同操作,直到源顶点。
⑤输出记录的顶点,结束。
如下:
代码:
#include <iostream>
#include <stdio.h>
#include<queue>
using namespace std;
int len[100];
int num=0;
typedef struct ArcCell{
int adj;//保存权值
int info;//存储最短路径长度
}ArcCell,AdjMaxtrix[100][100]; // 定义同性不同名的结构体
typedef struct{ //定义每个点的编号和最短路径数值
int data;
int length;
}VerType;
typedef struct{
VerType vexs[100];//顶点向量
AdjMaxtrix arcs;
int vexnum;//顶点数
int arcnum;//弧数
}Graph;
Graph G;
queue<int> q;
void CreateGraph()
{
int m,n,t;
G.vexnum=11;
G.arcnum=19;
// printf("输入顶点数和弧数:");
// scanf("%d%d",&G.vexnum,&G.arcnum);
// printf("输入顶点:");
for(int i=1;i<=G.vexnum;i++)
{
// scanf("%d",&G.vexs[i].data);
G.vexs[i].data=i;
G.vexs[i].length=10000;
}
for(int i=1;i<=G.vexnum;i++)
for(int j=1;j<=G.vexnum;j++)
{
G.arcs[i][j].adj=0;
}
// printf("输入弧及权重:\n");
// for(int i=1;i<=G.arcnum;i++)
// {
// scanf("%d%d%d",&m,&n,&t);
// G.arcs[m][n].adj=1;
// G.arcs[m][n].info=t;
// }
G.arcs[1][2].adj=1;
G.arcs[1][2].info=2;
G.arcs[1][3].adj=1;
G.arcs[1][3].info=3;
G.arcs[1][4].adj=1;
G.arcs[1][4].info=4;
G.arcs[2][5].adj=1;
G.arcs[2][5].info=7;
G.arcs[2][6].adj=1;
G.arcs[2][6].info=2;
G.arcs[2][3].adj=1;
G.arcs[2][3].info=3;
G.arcs[3][6].adj=1;
G.arcs[3][6].info=9;
G.arcs[3][7].adj=1;
G.arcs[3][7].info=2;
G.arcs[4][7].adj=1;
G.arcs[4][7].info=2;
G.arcs[5][8].adj=1;
G.arcs[5][8].info=3;
G.arcs[5][9].adj=1;
G.arcs[5][9].info=3;
G.arcs[6][9].adj=1;
G.arcs[6][9].info=3;
G.arcs[6][7].adj=1;
G.arcs[6][7].info=1;
G.arcs[7][9].adj=1;
G.arcs[7][9].info=1;
G.arcs[7][10].adj=1;
G.arcs[7][10].info=1;
G.arcs[8][11].adj=1;
G.arcs[8][11].info=3;
G.arcs[9][11].adj=1;
G.arcs[9][11].info=2;
G.arcs[10][11].adj=1;
G.arcs[10][11].info=2;
G.arcs[10][9].adj=1;
G.arcs[10][9].info=2;
}
// k = t k
int NextAdj(int v,int w)
{
for(int i=w+1;i<=G.vexnum;i++)
if(G.arcs[v][i].adj)
return i;
return 0;//not found;
}
int NextAdj2(int i)
{
for(int j=i-1;j>0;j--)
{
if(G.arcs[j][i].adj==1&&(G.vexs[i].length-G.arcs[j][i].info==G.vexs[j].length))
{
G.arcs[j][i].adj=0;
return j;
break;
}
}
}
void Print2()
{
int i=G.vexnum;
while(G.vexs[i].length!=0)
{
int j=NextAdj2(i);
len[num]=j;
num++;
i=j;
}
cout<<endl;
for(int x=num-1;x>=0;x--)
{
cout<<len[x]<<" ";
}
num=0;
cout<<endl;
}
//
//void clean ()
//{
//
// for(int i=1;i<=G.arcnum;i++)
// {
// for(int j=1;j<=G.arcnum;j++)
// {
// if(G.arcs[i][j].info!=0)
// G.arcs[i][j].adj=1;
//
// }
// }
//
//}
void ShortestPaths(int v)
{
int k=0;//从首个节点开始访问
int t;
G.vexs[v].length=0;
q.push(G.vexs[v].data);
while(!q.empty())
{
t=q.front();
k=NextAdj(t,k);// k是选择出来相连的结点
while(k!=0)
{
if(G.vexs[t].length+G.arcs[t][k].info<=G.vexs[k].length)//减枝操作
{
G.vexs[k].length=G.vexs[t].length+G.arcs[t][k].info;
q.push(G.vexs[k].data);
}
k=NextAdj(t,k);
}
q.pop();
}
}
void Print()
{
for(int i=1;i<=G.vexnum;i++)
printf("%d------%d\n",G.vexs[i].data,G.vexs[i].length);
}
int main()
{
CreateGraph();
ShortestPaths(1);
Print();
Print2();
return 0;
}
结果:
解决该问题的关键是如何更新到达每一个顶点的最短距离,即通过边界值进行判断。本题采用了出入队列的方式,对每个与队首元素相连的顶点进行最短路径值判断,进行更新。
问题改进: 一张图的最短路径并不一定只有一条,可使用回溯法寻找所有的路径。
记录整理一些学习中的问题,如果有不恰当和错误的地方,欢迎批评指正~