多段图的最短路径问题是求原点到终点的最小代价路径
分支限界法求解步骤:
1.确定合理的限界函数,根据限界函数确定目标函数的界[down,up]
2.从起点开始,按照广度优先策略搜索问题的解空间树,将在界范围内的节点加入PT表
3.从PT表中取出节点做拓展节点,重复第二步,直到找出最优解
下面我们引入一个实际例子:
根据求解步骤进行分析:
1.由此图我们容易想到一个简单的下界,从起点出发取两段之间路径最小值,直到到达终点,即down=2+4+5+3=14。通常我们可以使用贪心法求上界,即up=2+6+6+3=17。
2.结合求上下界的方法,我们可以得到一个目标函数:
lb=走过路径长度+该节点到下一段的最小距离+剩余各段之间的最小距离之和。
3.由于走到第n-1段(假设有n段)目标函数值等于按该节点路径从起点走到终点的最短路径,因此,若此时目标函数为所有目标函数中的最小值,则该目标函数值即为最优解。图解过程为:
完整代码:
#include<stdio.h>
#include<iostream>
#include<queue> //C++优先队列头文件
using namespace std;
#include<malloc.h>
#define INF 1000
const int maxNum_v=20; //最大节点数
const int maxNum_p=5; //最大段数
int down=0; //下界
int up=0; //上界
int n; //实际节点数
int p_num; //实际段数
typedef struct Node1
{
int nv; //当前节点
int path; //走过路径
int lb; //目标函数值
int p; //段号
bool operator < (const Node1 &x)const
{
return x.lb<lb; //小根堆优先队列
}
}Node1;
typedef struct Node2
{
int v;
Node2 *next;
}Node2;
typedef struct Node
{
int v;
int wight;
Node *next;
}Node;
Node *p[maxNum_v]; //存储边的邻接链表
Node2 *pv[maxNum_p]; //存储段的邻接链表
priority_queue<Node1> pq;
void creat() //建立上面两个邻接链表
{
printf("请输入节点数:");
scanf("%d",&n);
int queue[n]; //用队列依次录入每个节点
int front=0;
int tail=0;
queue[tail++]=0;
bool visited[n]; //设置访问标签,避免多个节点指向一个节点,后一个节点被重复录入的情况
for(int i=0;i<n;i++)
{
visited[i]=false;
}
visited[0]=true;
while(front!=tail)
{
p[queue[front]]=(Node *)malloc(sizeof(Node));
p[queue[front]]->next=NULL;
int num;
printf("请输入节点 %d 指向节点的数目:",queue[front++]);
scanf("%d",&num);
for(int j=0;j<num;j++)
{
Node *tmp;
tmp=(Node *)malloc(sizeof(Node));
printf("请输入 %d 节点指向节点和权值,格式如 2 3:",queue[front-1]);
scanf("%d %d",&tmp->v,&tmp->wight);
if(!visited[tmp->v])
{
queue[tail++]=tmp->v;
visited[tmp->v]=true;
}
tmp->next=p[queue[front-1]]->next;
p[queue[front-1]]->next=tmp;
getchar();
}
}
printf("请输入该多段图的段数:");
scanf("%d",&p_num);
for(int i=0;i<p_num;i++)
{
pv[i]=(Node2*)malloc(sizeof(Node2));
pv[i]->next=NULL;
int v_num;
printf("请输入第 %d 段的节点数:",i);
scanf("%d",&v_num);
printf("请输入节点(形如 1 2 3):");
for(int j=0;j<v_num;j++)
{
Node2 *tmp;
tmp=(Node2*)malloc(sizeof(Node2));
scanf("%d",&tmp->v);
tmp->next=pv[i]->next;
pv[i]->next=tmp;
}
}
}
void get_down() //获取下界函数
{
for(int i=0;i<p_num;i++)
{
Node2 *q=pv[i]->next;
int min=INF;
while(q!=NULL)
{
Node *pp=p[q->v]->next;
if(pp==NULL)
{
min=0;
break;
}
while(pp!=NULL)
{
if(pp->wight<min)
min=pp->wight;
pp=pp->next;
}
q=q->next;
}
down+=min;
}
}
void get_up() //获取上界函数
{
int start=0;
Node *q;
while(1)
{
int min=INF;
q=p[start]->next;
if(q==NULL)
break;
while(q!=NULL)
{
if(q->wight<min)
{
min=q->wight;
start=q->v;
}
q=q->next;
}
up+=min;
}
}
int get_lb(Node1 Node1) //计算目标函数值
{
int min=INF;
Node *q=p[Node1.nv]->next;
if(q==NULL)
return Node1.path;
while(q!=NULL)
{
if(q->wight<min)
min=q->wight;
q=q->next;
}
Node1.lb=Node1.path+min;
// printf("%d %d\n",Node1.path,min);
Node2 *pp;
for(int i=Node1.p+1;i<p_num-1;i++) //查找未经过段到下一段的最小值
{
min=INF;
pp=pv[i]->next; //段内节点
if(pp==NULL)
break;
while(pp!=NULL)
{
Node *qq=p[pp->v]->next; //根据节点找边
while(qq!=NULL)
{
if(qq->wight<min)
min=qq->wight;
qq=qq->next;
}
pp=pp->next;
}
Node1.lb+=min;
}
return Node1.lb;
}
int solve()
{
get_down();
get_up();
Node1 first; //初始化起点节点0
first.nv=0;
first.p=0;
first.path=0;
first.lb=down;
pq.push(first);
int ret=INF;
while(pq.size())
{
Node1 tmp=pq.top();
pq.pop();
if(tmp.p==p_num-2) //此时目标函数值等于走过路径大小,最小目标函数即为最优解
{
ret=tmp.lb;
break;
}
Node1 next;
Node *q=p[tmp.nv]->next;
while(q!=NULL)
{
next.nv=q->v;
next.p=tmp.p+1;
next.path=tmp.path+q->wight;
next.lb=get_lb(next);
// printf("%d %d %d %d\n",next.lb,next.nv,tmp.path,q->wight);
if(next.lb<=up) //小于上界的节点入队列
pq.push(next);
q=q->next;
}
}
return ret;
}
main()
{
creat();
printf("节点 节点 权值:\n"); //生成图信息
for(int i=0;i<n;i++)
{
Node *q=p[i]->next;
while(q!=NULL)
{
printf("%d %d %d\n",i,q->v,q->wight);
q=q->next;
}
}
printf("段 节点:\n");
for(int i=0;i<p_num;i++)
{
Node2 *q=pv[i]->next;
while(q!=NULL)
{
printf("%d %d\n",i,q->v);
q=q->next;
}
}
printf("最优解为:%d\n",solve());
// get_down();
// printf("%d\n",down);
// get_up();
// printf("%d\n",up);
}
运行结果:
录入阶段:
1.节点的邻接链表
2.段的邻接链表
数据输出阶段:
1.检查图信息
2.最终结果