您的位置:首页 > 其它

最短路径问题以及包含过路费的问题--动态规划式的解法

2017-03-05 16:56 1046 查看
求最短路径的dijkstra算法(详细算法原理自行百度,本文简单从动态规划角度思考),可以看成是一个动态规划的方法。

动态规划算法通常基于一个递推公式及一个或多个初始状态。当前子问题的解将由上一次子问题的解推出。

dijkstra虽然没有递推公式,但是满足将大问题分解成子问题,当前问题的解由上一次子问题的解推出。

算法的思想是

使用了三个一维数组,分别是visit[k],pre_node[k],short[k]来分别表示是否经过k点,k点的前驱节点,到达k点的最短距离

初始状态,是所有点出了起点都没经过,所有点的前驱节点都是起点,到达所有点的最短距离是起点直达的距离(没有路径距离就是极大值)。

接着开始循环求子问题。

子问题是先找出没有经过的并且到起点最短距离不是极大值的点i(第一次肯定是离起点最近的点)

如果找不到这个点,跳出循环(结束循环的唯一条件,这样可以避免非联通图出错)

然后分别找出点i的邻点j,判断通过i到j的距离是否小于目前j到起点的距离

如果是,那就更新short[j]和pre_node[k]

最后可以通过pre_node[]来遍历最短路径

int minlength(int (*edge)[7])
{
int short_p[7];
int pre_node[7];
int visit[7];
for (int i = 1; i < 7; i++){
visit[i] = 0;
pre_node[i] = 1;//起点是1,初始化都从1开始,直接从起点到各个点是老路
short_p[i] = edge[1][i];
}
short_p[1] = 0;
visit[1] = 1;
//for (int v = 0; v < 7; v++){//无法避免不是联通图的情况
while (1){
int min = max_n;
int k = -1;
for (int i = 2; i < 7; i++){//先找出没进过的并且离起点最近的点
if (!visit[i] && short_p[i] < min){
min = short_p[i];
k = i;
}
}
if (k == -1)
break;//找不到就跳出去,因为可能图不连通
else
visit[k] = 1;
for (int ii = 2; ii < 7; ii++){//以上面找到的点为基准,找出相邻没过的点,检查过这个点到相邻点近还是老路近
if (!visit[ii] && edge[k][ii] + min < short_p[ii]){
short_p[ii] = edge[k][ii] + min;
pre_node[ii] = k;//更改前节点
}
}
}
return 0;
}
int main()
{
int edge[7][7] = { max_n };
for (int i = 0; i < 7; i++){
for (int j = 0; j < 7; j++)
edge[i][j] = max_n;
}
edge[1][2] = 8;
edge[1][5] = 3;
edge[2][3] = 1;
edge[2][5] = 4;
edge[2][6] = 5;
edge[3][4] = 4;
edge[3][6] = 1;
edge[4][6] = 3;
edge[5][6] = 2;
for (int i = 0; i < 7; i++){
for (int j = i+1; j < 7; j++)
edge[j][i] = edge[i][j];
}
minlength(edge);
}


上面只有距离唯一一个条件,是一维的,逻辑比较简单

如果每条路径还需要收过路费,就是一个二维的动态规划问题。

问题变为了

到达在符合经费预算的情况下到达目标点的最短距离

或者到达目标点花费最少

算法思想还是使用三个数组,不过是2纬数组,第二维的下标表示的是经费余额

visit[i][j]是否到达节点i并且经费余额为j的情况

short[i][j]表示在经费余额为j的情况下,到达节点i的最短距离

pair
<int,int
> pre_node[i][j] 用来存放当前情况的前一个情况(节点结合余额)

初始状态

visit[i][j]全没经历过 visit[v0][money]=1 v0为原点,money为所有预算

pair[i][j]设为初始值,和起点相邻的并且预算够过路费的标为 (v0,money), 起点的前驱还是起点

其余节点都是起点,余额为-1

short[i][j]都为极大值 short[v0][money] = 0 ,起点本身 距离0,且没花钱

开始循环求解子问题

考虑所有的点以及所有的花费(余额>=0)的情况,

找到满足预算路径最短的点(小于极大值)(一开始是起点的邻点,不一定是最近的点)

如果找不到就跳出循环

找到了 标记为点k,余额为p

找出所有与点k相邻的点ii,

如果余额允许到点ii AND !visit[ii][到达ii后的余额](该情况没经历过) AND 起点经过点k到点ii的距离 < 同等花费下原先的距离(short[ii][p-k到ii的花费])

更新同等花费下原先的距离

更新pre_node[][]

代码如下:

#include<iostream>
#include<algorithm>
#include<stack>
using namespace std;
const int max_n = 65535;
int short_p[7][21];//short_p[i][j]表示点i到起点,余额为j的最短距离
int visit[7][21];
int edge[7][7], mny[7][7];
std::pair<int, int> pre_node[7][21];
int minlength(int money)
{
for (int i = 1; i < 7; i++){
for (int j = 0; j < 21; j++){
visit[i][j] = 0;
short_p[i][j] = max_n;
pre_node[i][j]=make_pair(1, -1);
}
if (money - mny[1][i] >= 0){
short_p[i][money - mny[1][i]] = edge[1][i];
pre_node[i][money - mny[1][i]] = make_pair(1, money);
}
}
visit[1][money] = 1;//标记起点
short_p[1][money] = 0;//距起点距离为0,没花钱
pre_node[1][money] = make_pair(1, money);
while (true){
int min = max_n;
int k = -1, p = -1;
for (int i = 2; i < 7; i++){//先找出没进过的并且离起点最近的点
for (int j = 0; j < money; j++){
if (!visit[i][j] && short_p[i][j] < min){
min = short_p[i][j];
k = i;
p = j;
}
}
}
if (k == -1 || p == -1)
break;
else
visit[k][p] = 1;
for (int ii = 2; ii < 7; ii++){//以上面找到的点为基准,找出相邻没过的点,检查过这个点到相邻点近还是老路近
//又足够的余额去相邻的点,   并且没有遇到过同类情况   且             距离更短
if ((p - mny[k][ii] > 0) && !visit[ii][p - mny[k][ii]] && edge[k][ii] + min < short_p[ii][p - mny[k][ii]]){
short_p[ii][p - mny[k][ii]] = edge[k][ii] + min;
pre_node[ii][p - mny[k][ii]] = make_pair(k, p);//更改前节点
}
}
}
return 0;
}
int min_cost_pathprint(int node, int money)
{
if (node > 6 || node < 1)
return -1;
cout << "到节点 " << node << " 花费最少为: ";
int i = money;
for (; i >= 0; i--){
if (short_p[node][i] < max_n)
break;
}
cout << money - i << " 元" << endl;
return i;
}
int min_lengthpath(int node, int money)
{
if (node > 6 || node < 1)
return -1;
cout << "到节点 " << node << " 最短距离为: ";
int min = max_n;
int k;
for (int i = 0; i <= money; i++){
if (short_p[node][i] < min){
min = short_p[node][i];
k = i;
}
}
cout << min << endl;
return k;
}
void pathprint(int node, int last)
{
cout << "路径为:";
std::pair<int, int> pathpair;
stack<std::pair<int, int>> path;
int dst = node, mm = last;
do{
pathpair = make_pair(dst, mm);
path.push(pathpair);
int dst1 = pre_node[dst][mm].first;
int mm1 = pre_node[dst][mm].second;
dst = dst1;
mm = mm1;
} while (pathpair.first != 1);
while (path.size() > 1){
cout << '<' << path.top().first << ' ' << path.top().second << '>' << ' ';
path.pop();
}
cout << '<' << path.top().first << ' ' << path.top().second << '>' << endl;
}
int main()
{
for (int i = 0; i < 7; i++){
for (int j = 0; j < 7; j++){
edge[i][j] = max_n;
mny[i][j] = max_n;
}
}
//距离
edge[1][2] = 8;
edge[1][5] = 3;
edge[2][3] = 1;
edge[2][5] = 4;
edge[2][6] = 5;
edge[3][4] = 4;
edge[3][6] = 1;
edge[4][6] = 3;
edge[5][6] = 2;
//过路费
mny[1][2] = 1;
mny[1][5] = 3;
mny[2][3] = 7;
mny[2][5] = 6;
mny[2][6] = 2;
mny[3][4] = 2;
mny[3][6] = 4;
mny[4][6] = 3;
mny[5][6] = 5;
for (int i = 0; i < 7; i++){
for (int j = i + 1; j < 7; j++){
edge[j][i] = edge[i][j];
mny[j][i] = mny[i][j];
}
}
int money = 20;
minlength(money);
int t1 = min_lengthpath(4,money);
if (t1 >= 0)
pathprint(4, t1);
int t2 = min_cost_pathprint(4, money);
if (t2 >= 0)
pathprint(4, t2);
return 0;
}
/*收过路费 二维地杰斯特拉 结束*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息