题目
蚁后发布了一项新任务:小蚁同学,我需要玉米库的玉米,再要配点水果,去帮我找来吧。小蚁正准备出发,蚁后又说:哎呀,回来,我还没说完呢,还有若干要求如下:
1. 小蚁同学,你需要尽可能以最少的花费拿到食物((图1中路线上的数值表示每两个储物间的花费));
2. 小蚁同学,你最多只能经过9个储藏间拿到食物(包含起止两个节点,多次通过同一节点按重复次数计算);
3. 小蚁同学,你必须经过玉米间,水果间(图1中标绿色节点);
4. 别忘了,食蚁兽也在路上活动呢,一旦与食蚁兽相遇,性命危矣!不过小蚁微信群 公告已经公布了敌人信息(图1中标红色路段);
5. 最后,千万别忘了,还有两段路是必须经过的,那里有我准备的神秘礼物等着你呢(附件图中标绿色路段)。
设计一种通用的路径搜索算法,来应对各种搜索限制条件,找到一条最优路径,顺利完成蚁后布置的任。
注:
1、蚁巢,有若干个储藏间(图中圆圈表示),储藏间之间有诸多路可以到达。
2、节点本身通行无花费;
3、该图为无向图,可以正反两方向通行,两方向都会计费,并且花费相同;
4、起止节点分别为附件图中S点和E点。
5、最优路径:即满足限制条件的路径。
思路
代码
#include "iostream"
#include "string"
#include "fstream"
#include "vector"
#include "queue"
#include "sstream"
#include "set"
#include "string.h"
#include "math.h"
#include "time.h"
#include <functional>
using namespace std;
#define INF 200000000
#define N 1000
struct Edge
{
int to; // 边终止节点
int cost; // 花费
int flag; // 1表示必须经过的边,0表示没有要求
Edge(int to1, int cost1, int flag1)
{
to = to1;
cost = cost1;
flag = flag1;
}
};
int nV; // 顶点数
int nE; // 边数
int nMostV; // 经过的定点数上限
int nMustV; // 必须经过的顶点数
int nMustE; // 必须经过的边数
set<string> mustE; // 必经边
vector<Edge> G[N]; // 图的邻接表形式
int G1[N][N]; // 图的邻接矩阵形式
int dist[N]; // 从源点出发的最短距离
bool mustV[N]; // 是否为必经点
vector<int> path; // 花费最短的路径
int maxE; // 边的最大花费
vector<int> V; // 求解顺序
typedef pair<int, int> P; // first是最短距离,second是顶点编号
vector<Edge> G2[N]; // 用于边权为1的图
int G3[N][N]; // 用于边权为1的图
vector<int> path1; // 顶点数最短的路径
int preV[N]; // 前向节点数组
bool vis[N]; // 当前节点是否访问过
vector<Edge> G4[N]; // 用于求2点之间所有最短路径
// 一个路径结果
struct Ans
{
vector<int> path; // 路径
int cost; // 花费
int start; // 起始点
void getCost()
{
cost = G1[start][path[0]]; // 实际花费
for(int i=0; i<path.size()-1; i++)
{
cost += G1[path[i]][path[i+1]];
}
}
};
void build(); // 建图
void dijkstra(int s, vector<Edge> G[N]); // 2点之间的所有最短路径
void dijkstra(int s, vector<Edge> G[N], int *preV); // 2点之间的1条最短路径
void findPath(int s, int t, int *preV, vector<int> &path); // 寻找s到t之间的路径,并将其加入总路径path
void subSolve(vector<Edge> G[N], int *preV, vector<int> &path); // 寻找全局路径
void printPath(vector<int> &path, bool end); // 打印路径
void solve(); // 主体
void solve1(); // 寻找满足条件的路径
void addEdge(int from, int to, int cost, int flag, vector<Edge> G[N])
{
Edge e(to, cost, flag);
G[from].push_back(e);
Edge e1(from, cost, flag);
G[to].push_back(e1);
}
void build()
{
int i;
ifstream fin;
fin.open("500点图7.txt");
cout << "顶点数:";
fin >> nV;
cout << nV << endl;
cout << "边数:";
fin >> nE;
cout << nE << endl;
cout << "经过点的上限:";
fin >> nMostV;
cout << nMostV << endl;
cout << "必经点(第一行为总数):" << endl;
fin >> nMustV;
cout << nMustV << endl;
int v = 0;
memset(mustV, false, sizeof(mustV));
for(i=0; i<nMustV; i++)
{
fin >> v;
cout << v << " ";
mustV[v] = true;
}
int nMustE = 0;
int nLimitE = 0;
// 输入图
for(i=0; i<nV; i++)
{
for(int j=i; j<nV; j++)
{
G1[i][j] = G1[j][i] = INF;
G3[i][j] = G3[j][i] = INF;
}
}
cout << endl << "图{边的起点,终点,花费,类型(1表示必经点,-1表示禁止经过的点,0表示无要求)}:";
cout << "略..." << endl;
int from, to, cost, flag;
for(i=0; i<nE; i++)
{
fin >> from >> to >> cost >> flag;
//cout << from << " " << to << " " << cost << " " << flag << endl;
if(flag != -1) // 如果不是禁止经过的边
{
addEdge(from, to, cost, flag, G);
G1[from][to] = G1[to][from] = cost;
addEdge(from, to, 1, flag, G2);
G3[from][to] = G3[to][from] = 1;
}
if(flag == 1)
{
nMustE++;
mustV[from] = mustV[to] = true;
stringstream sin, sin1;
sin << from << "," << to;
mustE.insert(sin.str());
sin1 << to << "," << from;
mustE.insert(sin1.str());
}
else if(flag == -1)
nLimitE++;
}
cout << "必经边数:" << nMustE << endl;
cout << "禁止边数:" << nLimitE << endl;
fin.close();
}
// 求2点之间的所有最短路径
void dijkstra(int s, vector<Edge> G[N])
{
fill(dist, dist + nV, INF);
priority_queue<P, vector<P>, greater<P> > q;
dist[s] = 0;
q.push(P(0, s));
while(!q.empty())
{
P p = q.top(); //从尚未使用的顶点中找到一个距离最小的顶点
q.pop();
int v = p.second;
if(dist[v] < p.first)
continue;
for(int i=0; i<G[v].size(); i++)
{
Edge &e = G[v][i];
int dis = dist[v] + e.cost;
if(dist[e.to] > dis)
{
dist[e.to] = dist[v] + e.cost;
q.push(P(dist[e.to], e.to));
G4[v].push_back(e);
}
else if(dist[e.to] == dis)
{
G4[v].push_back(e);
}
}
}
}
// 求2点之间的1条最短路径
void dijkstra(int s, vector<Edge> G[N], int *preV)
{
fill(dist, dist + nV, INF);
priority_queue<P, vector<P>, greater<P> > q;
dist[s] = 0;
q.push(P(0, s));
while(!q.empty())
{
P p = q.top(); //从尚未使用的顶点中找到一个距离最小的顶点
q.pop();
int v = p.second;
if(dist[v] < p.first)
continue;
for(int i=0; i<G[v].size(); i++)
{
Edge &e = G[v][i];
int dis = dist[v] + e.cost;
if(dist[e.to] > dis)
{
dist[e.to] = dist[v] + e.cost;
q.push(P(dist[e.to], e.to));
}
}
}
}
// 寻找s到t之间的路径,并将其加入总路径path
void findPath(int s, int t, int *preV, vector<int> &path)
{
vector<int> tmp;
for(int i=t; i != s; i = preV[i])
{
tmp.push_back(i);
}
for(int k=tmp.size()-1; k>=0; k--)
{
path.push_back(tmp[k]);
}
}
// 用于搜2点之间的多条最短路径
void dfs(int s, int t, Ans &A, vector< Ans > &paths, int start, int &min)
{
if (s == t)
{
A.start = start;
A.getCost();
if(A.path.size() < min)
min = A.path.size();
paths.push_back(A);
}
if(A.path.size() < min)
{
for (int i = 0; i < G4[s].size(); i++)
{
int u = G4[s][i].to;
if (!vis[u])
{
vis[u] = true;
A.path.push_back(u);
dfs(u, t, A, paths, start, min);
A.path.pop_back();
vis[u] = false;
}
}
}
}
void subSolve(vector<Edge> G[N], int *preV, vector<int> &path)
{
// 求无约束下的单源最短路径,用于得到必经点的顺序
dijkstra(0, G, preV);
// 得到必经点的顺序
if(V.empty())
{
priority_queue< P, vector<P>, greater<P> > q1;
int i;
for(i=0; i<nV; i++)
{
if(mustV[i])
{
q1.push(P(dist[i], i));
}
}
V.push_back(0);
cout << endl;
while(!q1.empty())
{
P p = q1.top();
q1.pop();
V.push_back(p.second);
}
V.push_back(nV - 1);
}
// 必经点顺序微调
// 将必经点中属于同一必经边的2个点放在一起
int n = V.size();
int i;
for(i=0; i<n-2; i++)
{
for(int j=i+2; j<n; j++)
{
stringstream sin;
sin << V[i] << "," << V[j];
if(mustE.count(sin.str())) // 如果是必经边
{
int tmp = V[j];
for(int k=j; k>i+1; k--)
V[k] = V[k-1];
V[i+1] = tmp;
break;
}
}
}
// 求解每对必经点之间的最短路径,合并为最终解
int last = V[0];
i = 1;
while(i < V.size())
{
int cur = V[i++];
stringstream sin;
sin << last << "," << cur;
if(mustE.count(sin.str()))
{
path.push_back(cur);
last = cur;
cur = V[i++];
}
// 修正的dijkstra算法,用于求2点之间所有最短路径
dijkstra(last, G);
// 搜寻花费最少的多条路径,用到剪枝
vector<Ans> paths;
Ans ans;
memset(vis, false, sizeof(vis));
int cnt = INF;
dfs(last, cur, ans, paths, last, cnt);
for(int v=0; v<nV; v++)
G4[v].clear();
// 选取花费最小且节点数最少的路径
int min = paths[0].cost;
int minId = 0;
for(int k=1; k<paths.size(); k++)
{
for(int j=0; j<paths[k].path.size(); j++)
{
if(paths[k].cost < min)
{
min = paths[k].cost;
minId = k;
}
}
}
for(int j=0; j<paths[minId].path.size(); j++)
{
path.push_back(paths[minId].path[j]);
}
last = cur;
}
}
void printPath(vector<int> &path, bool end)
{
int cost = 0;
cout << "0-->";
cost += G1[0][path[0]];
for(int i=0; i<path.size()-1; i++)
{
cout << path[i] << "-->";
cost += G1[path[i]][path[i+1]];
if(G1[path[i]][path[i+1]] == INF)
cout << " " << path[i] << ", " << path[i+1] << endl;
}
if(end)
{
cost += G1[0][path[0]];
cout << path[path.size()-1] << "-->";
if(G1[0][path[0]] == INF)
cout << " " << 0 << ", " << path[0] << endl;
}
cout << nV-1 << endl;
cout << endl << "花费:" << cost << endl << endl;
}
// 搜s到t的多条路径
void dfs(int s, int t, Ans &A, int n, vector< Ans > &paths, int start)
{
if (s == t)
{
A.start = start;
A.getCost();
paths.push_back(A);
}
if (A.path.size() < n)
{
for (int i = 0; i < G[s].size(); i++)
{
int u = G[s][i].to;
if (!vis[u])
{
vis[u] = true;
A.path.push_back(u);
dfs(u, t, A, n, paths, start);
A.path.pop_back();
vis[u] = false;
}
}
}
}
// 求每对必经点之间满足约束的路径
// 即当花费最少不满足时,退而求其次,求一个花费较大,但满足节点上限的路径
void solve1()
{
int cnt = 0;
int cnt1 = 0;
int i = 0;
int i1 = 0;
int n = path.size();
int n1 = path1.size();
vector<int> path2;
int last = V[0];
int k = 1;
while (k < V.size())
{
bool flag = true;
int cur = V[k++];
stringstream sin;
sin << last << "," << cur;
if (mustE.count(sin.str()))
{
path2.push_back(last);
path2.push_back(cur);
last = cur;
cur = V[k++];
i += 2;
i1 += 2;
flag = false;
}
cnt = cnt1 = 0;
vector<int> tmp;
while(i<n && path[i] != cur)
{
tmp.push_back(path[i]);
i++;
cnt++;
}
while(i1<n1 && path1[i1] != cur)
{
i1++;
cnt1++;
}
if(cnt > cnt1)
{
vector<Ans> paths;
Ans ans;
memset(vis, false, sizeof(vis));
dfs(last, cur, ans, cnt1+1, paths, last);
if (paths.size() > 0)
{
int minId = 0;
int minCost = paths[0].cost;
int n = paths.size();
int j;
for (j = 1; j < n; j++)
{
int cost = paths[j].cost;
if (paths[j].cost < minCost)
{
minId = j;
minCost = paths[j].cost;
}
}
if(flag)
path2.push_back(last);
for (j = 0; j < paths[minId].path.size()-1; j++)
{
path2.push_back( paths[minId].path[j] );
}
}
}
else
{
for(int k=0; k<tmp.size(); k++)
{
path2.push_back(tmp[k]);
}
}
last = cur;
}
if(path2.size()+2 <= nMostV)
{
cout << endl << "存在符合条件的路径:\n";
}
else
{
cout << endl << "不存在符合条件的路径,给出次优路径(去掉点数上限约束):\n";
}
printPath(path2, true);
}
void solve()
{
build();
subSolve(G, preV, path); // 得到满足约束,且花费少的路
int n = path.size();
if(n+1 <= nMostV) // 没有超过点数限制
{
cout << endl << "存在符合条件的路径:\n";
printPath(path, false); // 打印最优解
}
else // 超过点数限制
{
subSolve(G2, preV, path1); // 得到满足约束,且节点数少的路径
int n1 = path1.size();
if(n1+1 > nMostV) // 点数最少时也会超过顶点上限,不存在符合条件的解
{
cout << endl << "不存在符合条件的路径,给出次优路径(去掉点数上限约束):\n";
printPath(path, false);
}
else // 可能存在最优路径
{
solve1();
}
}
}
int main()
{
clock_t start, finish;
start = clock();
solve();
finish = clock();
cout << "用时:" << (double)(finish-start) / CLOCKS_PER_SEC << " s" << endl;
getchar();
return 0;
}
DearEidolon 提出了如下修改,暂未验证,路过的同学帮忙验证一下~
void dijkstra(int start, vector<Edge> GList[N])
{
fill(minDis, minDis + nV + 1, INF);
priority_queue<P, vector<P>, greater<P> > q;
minDis[start] = 0;
q.push(P(minDis[start], start));
vector<markStruct> markVector;
markStruct markstruct(P(minDis[start],start), start, Edge(start, 0));
markVector.push_back(markstruct);
while (!q.empty())
{
P p = q.top(); //从尚未使用的顶点中找到一个距离最小的顶点
q.pop();
int v = p.second;
if (minDis[v] < p.first)
continue;
for (int i = 0; i<GList[v].size()
Edge &e = GList[v][i];
int dis = minDis[v] + e.cost;
if (minDis[e.to] > dis)
{
if (minDis[e.to] != INF) //如果起始点到e.to的最近距离minDis[e.to]不是第一次更新,需要将上次放入GList的e删掉
for (vector<markStruct>::iterator iter1 = markVector.begin(); iter1 != markVector.end(); iter1++)
if (iter1->PreminDisTo == P(minDis[e.to], e.to))
for (vector<Edge>::iterator iter2 = GListSel[iter1->TempStartV].begin(); iter2 != GListSel[iter1->TempStartV].end(); )
if (iter2->cost == iter1->SelE.cost&&iter2->to == iter1->SelE.to)
iter2 = GListSel[iter1->TempStartV].erase(iter2);
else
++iter2;
minDis[e.to] = dis;
q.push(P(minDis[e.to], e.to));
GListSel[v].push_back(e);
markStruct markstruct(P(minDis[e.to], e.to), v, e); //每次放入GList一个e,就要标记一次这个e对应的P和v
markVector.push_back(markstruct);
}
else if (minDis[e.to] == dis)
{
GListSel[v].push_back(e);
markStruct markstruct(P(minDis[e.to], e.to), v, e); //每次放入GList一个e,就要标记一次这个e对应的P和v
markVector.push_back(markstruct);
}
q = priority_queue<P, vector<P>, greater<P> >(); //做一次dijkstra算法,清空一次最短距离容器q
markVector.swap(vector<markStruct>()); //做一次dijkstra算法,清空一次标记容器markVector
}
}