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;
}