图的遍历与最短路径算法
1. 图的构造部分
采用邻接矩阵存储边。节点编号为数字,从0~n-1,n为节点个数
class Graphs { public: Graphs(int n){ m_VeticeNum = n; m_Edge.resize(n); m_Edge[0].resize(n); }
void InitEdge(vector<vector<int>> edge) { m_Edge = edge; } private: int m_VeticeNum; //节点个数,为了方便,设定节点编号为0~m_VeticeNum-1 vector<vector<int>> m_Edge; };
初始化时,需要指定n,并传入邻接矩阵。
2. 图的遍历
对于图的遍历部分,原理参考图的深度优先遍历和广度优先遍历。下面是代码实现:
int Graphs::findNeighbor(int v, int idx) //找到节点v,从idx之后的第一个邻接点 { for (int i = idx + 1; i < m_VeticeNum; i++) { if (m_Edge[v][i] < INT_MAX && m_Edge[v][i] >0) return i; } return -1; } //找到从idx以后的v的下一个邻接点
DFS:
void Graphs::DFS(int v) { visited[v] = true; int w = findNeighbor(v, 0);//找到第一个邻接点 while (w != -1) { if (!visited[w]) { cout << "访问节点:" << w << endl; visited[w] = true; DFS(w); } w = findNeighbor(v, w); } }
BFS:
void Graphs::BFS(int v) { queue<int> q; q.push(v); visited[v] = true; cout << "访问节点:" << v << endl; while (!q.empty()) { int node = q.front(); q.pop(); //找邻接点 int w = findNeighbor(node, 0); while (w != -1) { if (!visited[w]) { q.push(w); cout << "访问节点:" << w << endl; visited[w] = true; } w = findNeighbor(node, w); } } }
3. Dijkstra算法
Dijkstra算法原理参考最短路径-Dijkstra和Floyd。其中的算法步骤个人认为下面的更好理解:
有两个集合,一个是已经更新的有最短路径的集合S,一个是待选择的最短路径集合U,初始时数组distance[]初始化为无穷,算法流程如下:
(1) 初始节点v,将v加入S,更新distance[v]=0,然后在所有U中寻找节点u1,u1与v相邻且distance[v]+edge[v][u1]最小,当distance[v]+edge[v][u1]<distance[v]时,更新distance[u1];
(2) 将u添加到S,然后以新的节点u为跳板,在U中寻找新的节点u2,更新distance[u2]+edge[u1][u2]<distance[u2],且distance[u2]最小的节点u3
(3) 重复步骤2。
下面看具体代码:
1 vector<int> Graphs::Dijkstra(int v0) //返回最短距离 2 { 3 vector<bool> S(m_VeticeNum, 0); //记录已经求出最短路径的节点 4 vector<int> dis(m_VeticeNum, INT_MAX); //记录最短距离 5 S[v0] = true; 6 int u = v0; 7 dis[v0] = 0; 8 for (int i = 1; i < m_VeticeNum; i++) //每次添加一个节点到S,共需要m_VeticeNum-1次 9 { 10 //找出新的最短路径点加入S 11 int minidx=0; 12 int minDis = INT_MAX; 13 for (int j = 0; j < m_VeticeNum; j++) 14 { 15 if (!S[j]) 16 { 17 if(m_Edge[u][j]>0 && m_Edge[u][j] <INT_MAX && dis[u] + m_Edge[u][j] < dis[j]) 18 dis[j] = dis[u] + m_Edge[u][j]; //更新距离 19 if (minDis > dis[j] && S[j]==false) 20 { 21 minDis = dis[j]; 22 minidx = j; 23 } 24 } 25 } 26 u = minidx; 27 S[u] = true; 28 } 29 return dis; 30 }
求出来的是从节点V0到所有其他节点的最短路径。
如果要求出最短距离的路径,再添加一个数组pre[],用于记录前一个节点,只需要在18行更新距离后,记录前一个节点,即pre[j]=u;
4. Floyd算法
Floyd算法原理参考Floyd。但是该博文没有讲路径求解,路径记录参考博文Floyd算法求多元最短路径。
下面是代码:
vector<vector<int>> Graphs::Floyd(void) { vector<vector<int>> e(m_VeticeNum, vector<int>(m_VeticeNum,0)); /* 如果需要记录路径,path[i][j]表示从i到j时到达j的前一步路径 vector<vector<int>> path(m_VeticeNum, vector<int>(m_VeticeNum, 0)); for (int i = 0; i < m_VeticeNum; i++) { for (int j = 0; j < m_VeticeNum; j++) path[i][j] = i; } */ e = m_Edge; //初始的路径 for (int k = 0; k < m_VeticeNum; k++) //对于每一个中间节点k { for (int i = 0; i < m_VeticeNum; i++) //从节点i到节点j,中间经过k { for (int j = 0; j < m_VeticeNum; j++) { if (e[i][k] > 0 && e[i][k] < INT_MAX && e[k][j]>0 && e[k][j]<INT_MAX && e[i][j]>e[i][k] + e[k][j]) { e[i][j] = e[i][k] + e[k][j]; //path[i][j]=path[k][j]; } } } } return e; }
该函数求出来的是任意节点到i到节点j的最短路径。
5. 相关例题
下面我们看一个最短路径的例子:leetcode 743 网络延迟时间。从题意可以分析出,该题目要求节点K到其他所有节点距离的最大值。采用Djikstra算法即能求出节点K到所有其他节点的最小距离。此处给出的输入不是邻接矩阵,而是所有边,可以直接在所有边上遍历,并实时更新dis数组,同时每次添加一个新的节点到已访问过的节点中,代码如下:
int networkDelayTime(vector<vector<int>>& times, int N, int K) { vector<int> visited(N,false); //visited[i]表示节点i-1是否已访问 vector<int> dis(N,INT_MAX); //表示到各个节点的距离 dis[K-1]=0; visited[K-1]=true; //初始化dis for(int i=0;i<size(times);i++) { if(times[i][0]==K) dis[times[i][1]-1]=times[i][2]; } int u=K; for(int i=0;i<N-1;i++) //N个节点,每次添加一个到visited,需要N-1次 { int mindis=INT_MAX; int idx=0; for(int j=0;j<size(times);j++) { if(times[j][0]==u && visited[times[j][1]-1]==false && dis[u-1]+times[j][2]<dis[times[j][1]-1]) dis[times[j][1]-1]=dis[u-1]+times[j][2]; //更新dis数组 if(mindis>dis[times[j][1]-1] && visited[times[j][1] - 1] == false) { mindis=dis[times[j][1]-1]; //寻找一个未访问的节点添加到visited idx=times[j][1]; } } if(mindis==INT_MAX) //没有距离可以更新,说明有不可达的节点 break; u=idx; visited[u-1]=true; //将新的最近节点添加到visited } int maxdis=0; for(int i=0;i<N;i++) if(dis[i]>maxdis) maxdis=dis[i]; return maxdis==INT_MAX? -1:maxdis; }