图的遍历与最短路径算法

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

 

posted @ 2020-07-20 15:07  晨枫1  阅读(825)  评论(0编辑  收藏  举报