图的路径问题
图的路径
路径问题
在图的相关问题当中,我们经常会遇到路径问题,即在图中某两个节点之间的距离.
这个距离也许并不仅仅是相隔的节点数,也有可能代表一些其他的值,比如距离,时间.
此时广度优先搜索(BFS)是一种不错的选择.广度优先搜索按照层次逐层处理节点,这为我们计算顶点到某一节点的距离提供了便利.
我给出一个简单的实现方案:
class BFSTest {
public:
map<string, vector<string>> pointList;
map<string, bool> pointVisitted;
//有向图
void initPointList(vector<pair<string,string>> path) {
pointList.clear();
for (size_t i = 0; i < path.size(); i++) {
if(pointList[path[i].first] == pointList.end()){
vector<string> temp;
pointList[path[i].first] = temp;
}
pointList[path[i].first].push_back(path[i].second);
}
}
void initVisitted() {
for(auto it = pointVisitted.begin(); it != pointVisitted.end(); it++) {
it->second = false;
}
}
int graphBFS(string begin, string end) {
queue<pair<string,int>> findQueue;
pair<string,int> node;
findQueue.push(pair<string,int>(begin,0));
while(!findQueue.empty()) {
node = findQueue.front();
findQueue.pop();
pointVisitted[node.first] = true;
for (size_t i = 0; i < pointList[node.first].size(); i++) {
if(pointVisitted[pointList[node.first][i]] == false){
if(pointList[node.first][i] == end) {
return node.second + 1;
}
findQueue.push(pair<string,int>(pointList[node.first][i],node.second+1));
}
}
}
return -1;
}
int calcDistance(vector<pair<string,string>> path, vector<pair<string, string>> queries) {
vector<int> results;
initPointList(path);
for (size_t i = 0; i < queries.size(); i++) {
results.push_back(graphBFS(queries[i].first,queries[i].second));
}
}
}
Dijkstra算法
- 在实际应用中,我们遇到的问题可能比上面提到的要复杂的多.最直观的一个例子,节点之间的'距离'可能并不是我们在上文代码中实现的单位长度(也就是1).此时我们拥有的则是一个加权图.那么这种图的最短路径问题应该怎么解决呢?
- 让我们来想象这样一种情况,我们首先给出一道题.小明从A地出发,要去到D地.A到D之间的路径关系如下:
A->B = 5
A->C = 10
B->D = 5
C->D = 1
我们应该如何站在计算机的角度去考虑这个问题?闹钟算法是这么解决的,小米和朋友同时从A出发,去往可达的B,C.当小明到达B时,小明的朋友距离C还有5个单位距离.此时小明已经发现了D并向D走去.过了5个单位时间后小明已到达终点D,此时小明的朋友到达C点.
- 如果我们将题目中的距离关系改成这样呢?
A->B = 5
A->C = 8
B->D = 10
C->D = 2
按照刚才的思路,小明在从A到达B的时候,小明的朋友距离C还有3个单位距离.此时小明从B出发去往D.在3个单位距离的时间后,小明的朋友到达C,此时他从C出发去往D.在2个单位距离后小明的朋友到达D.此时小明距离D还有5个单位距离.
- 让我们概括一下刚才的思路:我们以时间为单位,在相同时间内我们沿每条路径移动相同的距离.每次到达新的节点的时候,我们都会停下来更新整个路径图.已经在走的路径长度被更新为剩余路径的长度,而新发现的路径则被加入到路径图中去.直到某一条路径到达终点为止.
代码实现:
bool compare(const cityAndDistance a,const cityAndDistance b) {
return a->second < b->second;
}
class shortestPath{
public:
typedef pair<string, int> cityAndDistance;
map<string, vector<cityAndDistance>> graph;
map<string, cityAndDistance> prev;
map<string, bool> visited;
void initGraph(vector<pair<string, pair<string,int>>> path) {
for (size_t i = 0; i < path.size(); i++) {
if(graph.find(path[i].first) != graph.end()) {
vector<cityAndDistance> temp;
graph[path[i].first] = temp;
}
graph[path[i].first].push_back(cityAndDistance(path[i].second));
}
}
//基于闹钟算法,每次到达一个新的节点就将已发现的节点距离更新.
void update(map<string,int>& queuePri) {
visited[first.first] = true;
auto first = queuePri.begin();
for(auto i = first+1;i != queuePri.end();i++){
i->second -= first->second;
}
}
//通过回溯找到最短路径
vector<string> backtrack(string begin, string end) {
vector<string> temp;
temp.push_back(end);
string nodeNow = end;
while(nodeNow != begin) {
nodeNow = prev[nodeNow].first;
temp.insert(temp.begin(), nodeNow);
}
return backtrack;
}
vector<string> dijkstra(string begin, string end) {
map<string,int> queuePri;
queuePri[begin] = 0;
while(!queuePri.empty()) {
//到达最短的点.
auto it = queuePri.begin();
//更新所有节点距离信息
update(queuePri);
//找到最终节点
if(it->first == end) {
return backtrack(begin,end);
}
//更新可达节点信息
for (size_t i = 0; i < graph[it->first].size(); i++) {
//如果节点被访问过则去除这一节点.
if(visited.find(graph[it->first][i].first)){
continue;
}
//发现新节点或者更短的路径
if(queuePri.find(graph[it->first][i].first) != queuePri.end() || queuePri[graph[it->first][i].first] > graph[it->first][i].second) {
//更新新节点或者最短路径
queuePri[graph[it->first][i].first] = graph[it->first][i].second;
//绑定该节点与前驱节点,
prev[graph[it->first][i].first] = cityAndDistance(it->first, graph[it->first][i].second);
}
}
//擦除已到达节点.
queuePri.erase(it);
//排序,使得每次最短的节点都在begin位置.
sort(queuePri.begin(),queuePri.end(),compare);
}
return vector<string>();
}
vector<string> findShortestPath(vector<pair<string, string, int>> path) {
initPointList(path);
return dijkstra(begin, end);
}
}