08.图算法C++(笔记)

1.图的基本概念

图是由顶点集合及顶点间的关系组成的一种数据结构。

表示为 G = (V, E)。V代表的是顶点集合,E代表边集合

树也是一种特殊的图,这个图无环。

树关注的时节点存的值,图更关注的是顶点及边的权值。

权值:边中的附带的数据信息。

顶点和边:图中结点称为顶点,第i个顶点记作vi。两个顶点vi和vj相关联称作顶点vi和顶点vj之间有一条边,图中的第k条边记作ek,ek = (vi,vj)或<vi,vj>.

有向图和无向图

有向图 :顶点对<x, y>是有序的,顶点对<x,y>称为顶点x到顶点y的一条边(弧),<x, y>和<y, x>是两条不同的边。

在这里插入图片描述

无向图 :顶点对(x, y)是无序的,顶点对(x,y)称为顶点x和顶点y相关联的一条边,这条边没有特定方向,(x, y)和(y,x)是同一条边。

在这里插入图片描述

完全图

在有n个顶点的无向图中,若有n * (n-1)/2条边,即任意两个顶点之间有且仅有一条边,则称此图为无向完全图;

在n个顶点的有向图中,若有n * (n-1)条边,即任意两个顶点之间有两条边,且仅这两条边方向相反,则称此图为有向完全图。

邻接顶点

无向图中:两个顶点U、V有一条边连接。成为U、V互为临界顶点。

有向图中:两个顶点U、V。若是U指向V,则称为U邻接到V,V邻接自顶点U。并称这条边与顶点U、V相关联。

顶点的度

顶点V的度指的是与它相关联的边的条数

特殊:

  1. 对于有向图,顶点的度等于该顶点的入度+出度。
    出度:从顶点出去的边的条数。
    入度:指向这个顶点的边数。
  2. 对于无向图:入度=出度=顶点的度

路径

从顶点U到顶点V的一组边称为路径。路径不止一条。

路径长度:对于不带权图为路径的边个数。带权图为路径所有边权值的和

最短路径:所有路径,路径长度最小的路径。

简单路径与回路:

  • 简单路径:路径上经过顶点不重复。
  • 回路:路径上经过的顶点有重复。

子图、连通图、强连通图

两张图A、B,若A的顶点时B的部分顶点,A的边是B的部分边,则称为A是B的子图。

连通图:无向图中,若顶点A、B存在路径,称为A、B连通。若图中的任意两点都是连通的,则称此图为连通图。

强连通图:有向图中,若图中的任意两点都是连通的(A指向B,且B指向A),则称此图为连通图。

生成树

无向图中一个连通图的最小连通子图称为生成树。(用最少的边把所有顶点连接起来)。n个顶点的连通图的生成树有n-1条边。

最小生成树:所有生成树中,路径长度最小的生成树。

2.图的存储结构

因为图中既有节点,又有边(节点与节点之间的关系),因此,在图的存储中,只需要保存:节点和边的关系即可。

节点通过数组保存,边的关系通过下面的方式保存。

图的创建

可以通过输入的方式来创建,或者是通过文件的方式来读取。

这里采用手动添加边来创建图,方便测试。

邻接矩阵

邻接矩阵(二维数组)即是:先用一个数组将定点保存,然后采用矩阵来表示节点与节点之间的关系。

在这里插入图片描述

如果矩阵位置为0代表无关,位置为1代表连接。A对应的下标为0,D对应的下标为3。这里采用map映射的方式来建立顶点与顶点下标的关系。

容易得出:无向图为对称矩阵。

此外:如果这个图是带权图,则矩阵内的数据为所带权值。

邻接矩阵的存储方式的优点:

  1. 非常适合边比较多的情况。
  2. O(1)判断两个节点的连接关系。

不足:

  1. O(N)查找一个节点连接的所有边(N为节点个数)。
C++邻接矩阵创建图
#pragma once

#include <vector>
#include <map>
#include <iostream>

// v:顶点 w:权值 max_w:最大权值 Direction:判断图是有向图还是无向图

namespace matrix{
    //邻接矩阵保存边关系
    template<class v,class w,w max_w = INT_MAX,bool bDirection = false>
    class Graph{
    private:
        std::vector<v> m_vertexs;               // 顶点集合
        std::map<v,int> m_mapIndex;             // 顶点与下标的映射
        std::vector<std::vector<w>> m_matrix;   // 邻接矩阵
        //获取顶点下标
        size_t GetPointIndex(const v& point) const{
            typename std::map<v,int>::const_iterator ptr = m_mapIndex.find(point);
            if(ptr!= m_mapIndex.end())
                return ptr->second;
            else{
                throw std::invalid_argument("point not found");
                return -1;
            }
        }
    public:
        // 图的创建
        Graph(const std::vector<v>& points){
            m_vertexs.resize(points.size());
            for(size_t i = 0; i < points.size(); ++i){
                m_vertexs[i] = points[i];
                m_mapIndex[points[i]] = i;
            }

            m_matrix.resize(points.size());
            // 邻接矩阵
            for(int i = 0; i < m_matrix.size(); ++i){
                m_matrix[i].resize(points.size(),max_w);
            }
        }

        //添加边关系,输入两个点,以及这两个点连线边的权值
        void AddEdge(const v& src, const v& dest, const w& weight){
            size_t posSrc = GetPointIndex(src);
            size_t posDest = GetPointIndex(dest);
            //区分有向图和无向图
            m_matrix[posSrc][posDest] = weight;
            if(!bDirection){
                //无向图,添加两条边关系
                m_matrix[posDest][posSrc] = weight;
            }
        }
        
        void Print(){
            //打印顶点对应坐标
            for(size_t i = 0; i < m_vertexs.size(); ++i){
                std::cout << "[" << i << "]->" << m_vertexs[i] << std::endl;
                std::cout << std::endl;
            }

            //打印边
            std::cout << "  ";
            for(size_t i = 0; i < m_vertexs.size(); ++i){
                std::cout << m_vertexs[i] << " ";
            }
            std::cout << std::endl;

            //打印矩阵
            for(size_t i = 0; i < m_matrix.size(); ++i){
                std::cout << m_vertexs[i] << " ";
                for(size_t j = 0; j < m_matrix[i].size(); ++j){
                    if(m_matrix[i][j] == max_w){
                        std::cout << "*" << " ";
                    }
                    else{
                        std::cout << m_matrix[i][j] << " ";
                    }
                }
                std::cout << std::endl;
            }
        }
    };
}
#include "Graph.hpp"

using namespace std;

void TestGraph(){
    vector<char>points = {'A','B','C','D'};
    matrix::Graph<char,int,INT_MAX,true> graph(points);
    graph.AddEdge('A','B',1);
    graph.AddEdge('A','D',4);
    graph.AddEdge('B','D',2);
    graph.AddEdge('B','C',9);
    graph.AddEdge('C','D',8);
    graph.AddEdge('C','B',5);
    graph.AddEdge('C','A',3);
    graph.AddEdge('D','C',6);
    graph.Print();
}

int main(){
    TestGraph();
    return 0;
}

在这里插入图片描述

邻接表

使用数组表示顶点的集合,使用链表表示边的关系。

创建指针数组,自己连接的顶点挂在下面。同样的,对节点进行编号。

在这里插入图片描述

邻接表的优点:

  1. 适合保存稀疏的边关系。
  2. 适合查找一个顶点连接出的边

不足:

  1. 不适合确定两个顶点是否相连,判断权值。

注意:如果图是有向图,则如果使用邻接表存储边的关系,需要保存出边表和入边表。

C++邻接表创建图
#pragma once

#include <vector>
#include <map>
#include <iostream>

// v:顶点 w:权值 max_w:最大权值 Direction:判断图是有向图还是无向图

// 邻接表保存边关系
namespace link_table{
    template<class w>
    struct Edge{
        int detPos;
        int srcPos;
        w weight;
        Edge<w> *next;
        Edge(int _srcPos, int _detPos, const w& _weight)
            :detPos(_detPos),srcPos(_srcPos),weight(_weight),next(nullptr)
        {}
    };

    template<class v,class w,bool Direction = false>
    class Graph{
        typedef Edge<w> Edge;
    private:
        std::vector<v> m_vertexs;      // 顶点集合
        std::map<v,int> m_mapIndex;    // 顶点与下标的映射
        std::vector<Edge*> m_tables;    // 领接表
        // 获取顶点下标
        size_t GetPointIndex(const v& point) const{
            typename std::map<v,int>::const_iterator ptr = m_mapIndex.find(point);
            if(ptr!= m_mapIndex.end())
                return ptr->second;
            else{
                throw std::invalid_argument("point not found");
                return -1;
            }
        }
    public:
        //图的创建
        Graph(const std::vector<v>& points){
            m_vertexs.resize(points.size());
            for(size_t i = 0; i < points.size(); ++i){
                m_vertexs[i] = points[i];
                m_mapIndex[points[i]] = i;
            }
            m_tables.resize(points.size());
        }

        //添加边关系,输入两个点,以及这两个点连线边的权值
        void AddEdge(const v& src, const v& dest, const w& weight){
            size_t posSrc = GetPointIndex(src);
            size_t posDest = GetPointIndex(dest);

            Edge* pEdge = new Edge(posSrc,posDest,weight);
            //头插
            pEdge->next = m_tables[posSrc];
            m_tables[posSrc] = pEdge;

            if(!Direction){
                //无向图,添加两条边关系
                Edge* pEdge = new Edge(posDest,posSrc,weight);
                //头插
                pEdge->next = m_tables[posDest];
                m_tables[posDest] = pEdge;
            }
        }

        void Print(){
            //打印顶点对应坐标
            for(size_t i = 0; i < m_vertexs.size(); ++i){
                std::cout << "[" << i << "]->" << m_vertexs[i] << std::endl;
                std::cout << std::endl;
            }

            for(size_t i = 0; i < m_tables.size(); ++i){
                std::cout << m_vertexs[i] << "[" << i << "]:";
                Edge* pNode = m_tables[i];
                while(pNode!= nullptr){
                    std::cout << m_vertexs[pNode->detPos]
                    << "[" << pNode->detPos << "]"
                    << "(" << pNode->weight << ")" << " ";
                    pNode = pNode->next;
                }
                std::cout << "nullptr" << std::endl;
            }
        }
    };
}
void TestGraph2(){
    vector<char>points = {'A','B','C','D'};
    link_table::Graph<char,int,true> graph(points);
    graph.AddEdge('A','B',1);
    graph.AddEdge('A','D',4);
    graph.AddEdge('B','D',2);
    graph.AddEdge('B','C',9);
    graph.AddEdge('C','D',8);
    graph.AddEdge('C','B',5);
    graph.AddEdge('C','A',3);
    graph.AddEdge('D','C',6);
    graph.Print();
}

int main(){
    //TestGraph();
    TestGraph2();
    return 0;
}

在这里插入图片描述

3.图的遍历

广度优先遍历 BFS

广度优先队列:以某个顶点为起点,一层一层进行遍历。需要借助队列。

在这里插入图片描述

具体遍历方式与二叉树的层序遍历方式类似,不同的是要通过标记的方式防止节点的重复遍历。

namespace matrix{
    //邻接矩阵保存边关系
    template<class v,class w,w max_w = INT_MAX,bool Direction = false>
    class Graph{
    public:
        // 图的创建
        Graph(const std::vector<v>& points){
            m_vertexs.resize(points.size());
            for(size_t i = 0; i < points.size(); ++i){
                m_vertexs[i] = points[i];
                m_mapIndex[points[i]] = i;
            }

            m_matrix.resize(points.size());
            // 邻接矩阵
            for(int i = 0; i < m_matrix.size(); ++i){
                m_matrix[i].resize(points.size(),max_w);
            }
        }

        //添加边关系,输入两个点,以及这两个点连线边的权值
        void AddEdge(const v& src, const v& dest, const w& weight){
            size_t posSrc = GetPointIndex(src);
            size_t posDet = GetPointIndex(dest);
            //区分有向图和无向图
            m_matrix[posSrc][posDet] = weight;
            if(!Direction){
                //无向图,添加两条边关系
                m_matrix[posDet][posSrc] = weight;
            }
        }
        
        void Print(){
            //打印顶点对应坐标
            for(size_t i = 0; i < m_vertexs.size(); ++i){
                std::cout << "[" << i << "]->" << m_vertexs[i] << std::endl;
                std::cout << std::endl;
            }

            //打印边
            std::cout << "  ";
            for(size_t i = 0; i < m_vertexs.size(); ++i){
                std::cout << m_vertexs[i] << " ";
            }
            std::cout << std::endl;

            //打印矩阵
            for(size_t i = 0; i < m_matrix.size(); ++i){
                std::cout << m_vertexs[i] << " ";
                for(size_t j = 0; j < m_matrix[i].size(); ++j){
                    if(m_matrix[i][j] == max_w){
                        std::cout << "*" << " ";
                    }
                    else{
                        std::cout << m_matrix[i][j] << " ";
                    }
                }
                std::cout << std::endl;
            }
        }
        
        void BFS(const v& src){//传入起点
            size_t srcPos = GetPointIndex(src);
            std::queue<size_t> q;
            q.push(srcPos);
            //标记数组
            std::vector<bool> visited(m_vertexs.size(),false);
            visited[srcPos] = true;     //入队列标记
            int iLevelSize = 1;         //每层节点个数
            int iLevel = 1;             //层数
            while(!q.empty()){
                for(int i = 0; i < iLevelSize; ++i){
                    int iFront = q.front();
                    std::cout << iFront << ":" << m_vertexs[iFront] << " ";
                    q.pop();
                    //这个节点周围的节点入队
                    for(size_t i = 0; i < m_vertexs.size(); i++){
                        if(m_matrix[iFront][i]!= max_w && !visited[i])
                        {
                            // 入队时,标记已访问节点
                            q.push(i);
                            visited[i] = true;
                        }
                    }
                }
                iLevelSize = q.size();
                std::cout << "第" << iLevel++ << "层" << std::endl;
            }
            std::cout << std::endl;
        }
     private:
        std::vector<v> m_vertexs;               // 顶点集合
        std::map<v,int> m_mapIndex;             // 顶点与下标的映射
        std::vector<std::vector<w>> m_matrix;   // 邻接矩阵
        //获取顶点下标
        size_t GetPointIndex(const v& point) const{
            typename std::map<v,int>::const_iterator ptr = m_mapIndex.find(point);
            if(ptr!= m_mapIndex.end())
                return ptr->second;
            else{
                throw std::invalid_argument("point not found");
                return -1;
            }
        }
    };
}

// 邻接表保存边关系
namespace link_table{
    template<class w>
    struct Edge{
        int detPos;
        int srcPos;
        w weight;
        Edge<w> *next;
        Edge(int _srcPos, int _detPos, const w& _weight)
            :detPos(_detPos),srcPos(_srcPos),weight(_weight),next(nullptr)
        {}
    };

    template<class v,class w,bool Direction = false>
    class Graph{
        typedef Edge<w> Edge;
    public:
        //图的创建
        Graph(const std::vector<v>& points){
            m_vertexs.resize(points.size());
            for(size_t i = 0; i < points.size(); ++i){
                m_vertexs[i] = points[i];
                m_mapIndex[points[i]] = i;
            }
            m_tables.resize(points.size());
        }

        //添加边关系,输入两个点,以及这两个点连线边的权值
        void AddEdge(const v& src, const v& dest, const w& weight){
            size_t posSrc = GetPointIndex(src);
            size_t posDest = GetPointIndex(dest);

            Edge* pEdge = new Edge(posSrc,posDest,weight);
            //头插
            pEdge->next = m_tables[posSrc];
            m_tables[posSrc] = pEdge;

            if(!Direction){
                //无向图,添加两条边关系
                Edge* pEdge = new Edge(posDest,posSrc,weight);
                //头插
                pEdge->next = m_tables[posDest];
                m_tables[posDest] = pEdge;
            }
        }

        void Print(){
            //打印顶点对应坐标
            for(size_t i = 0; i < m_vertexs.size(); ++i){
                std::cout << "[" << i << "]->" << m_vertexs[i] << std::endl;
                std::cout << std::endl;
            }

            for(size_t i = 0; i < m_tables.size(); ++i){
                std::cout << m_vertexs[i] << "[" << i << "]:";
                Edge* pNode = m_tables[i];
                while(pNode!= nullptr){
                    std::cout << m_vertexs[pNode->detPos]
                    << "[" << pNode->detPos << "]"
                    << "(" << pNode->weight << ")" << " ";
                    pNode = pNode->next;
                }
                std::cout << "nullptr" << std::endl;
            }
        }

        void BFS(const v& src){//传入起点
            size_t srcPos = GetPointIndex(src);
            std::queue<size_t> q;
            q.push(srcPos);
            //标记数组
            std::vector<bool> visited(m_vertexs.size(),false);
            visited[srcPos] = true;
            int iLevelSize = 1;         //每层节点数
            int iLevel = 1;             //层数
            while(!q.empty()){
                for(int i = 0;i < iLevelSize; ++i)
                {
                    int iFront = q.front();
                    std::cout << iFront << ":" << m_vertexs[iFront] << " ";
                    q.pop();
                    //这个节点周围的节点入队
                    Edge* pEdge = m_tables[iFront];
                    while(pEdge)
                    {
                        if(!visited[pEdge->detPos])
                        {
                            // 入队时,标记已访问节点
                            q.push(pEdge->detPos);
                            visited[pEdge->detPos] = true;
                        }
                        pEdge = pEdge->next;
                    }
                }
                iLevelSize = q.size();
                std::cout << "第" << iLevel++ << "层" << std::endl;
            }
            std::cout << std::endl;
        }
    private:
        std::vector<v> m_vertexs;      // 顶点集合
        std::map<v,int> m_mapIndex;    // 顶点与下标的映射
        std::vector<Edge*> m_tables;    // 领接表
        // 获取顶点下标
        size_t GetPointIndex(const v& point) const{
            typename std::map<v,int>::const_iterator ptr = m_mapIndex.find(point);
            if(ptr!= m_mapIndex.end())
                return ptr->second;
            else{
                throw std::invalid_argument("point not found");
                return -1;
            }
        }
    };
}
#include "Graph.hpp"

using namespace std;

void TestGraph(){
    vector<char>points = {'A','B','C','D'};
    matrix::Graph<char,int,INT_MAX,true> graph(points);
    graph.AddEdge('A','B',1);
    graph.AddEdge('A','D',4);
    graph.AddEdge('B','D',2);
    graph.AddEdge('B','C',9);
    graph.AddEdge('C','D',8);
    graph.AddEdge('C','B',5);
    graph.AddEdge('C','A',3);
    graph.AddEdge('D','C',6);
    graph.BFS('A');
}

void TestGraph2(){
    vector<char>points = {'A','B','C','D'};
    link_table::Graph<char,int,true> graph(points);
    graph.AddEdge('A','B',1);
    graph.AddEdge('A','D',4);
    graph.AddEdge('B','D',2);
    graph.AddEdge('B','C',9);
    graph.AddEdge('C','D',8);
    graph.AddEdge('C','B',5);
    graph.AddEdge('C','A',3);
    graph.AddEdge('D','C',6);
    graph.BFS('A');
}

int main(){
    TestGraph();
    //TestGraph2();
    return 0;
}

在这里插入图片描述

广度优先遍历序列

在这里插入图片描述

1.邻接矩阵

​ 同一个图的邻接矩阵表示方式唯一,因此广度优先遍历序列唯一;

2.邻接表

​ 同一个图的邻接表表示方式不唯一,因此广度优先遍历序列不唯一;

复杂度分析
1.邻接矩阵

(1)访问|V|个顶点需要O(|V|)的时间;

(2)查找每个顶点的邻接点都需要O(|V|)的时间,而总共有|V|个顶点;

(3)两个加起来是O(|V|)+O(|V|2),所以时间复杂度=O(|V|2);

2.邻接表

(1)访问|V|个顶点需要O(|V|)的时间;

(2)查找各个顶点的邻接点共需要O(|E|)的时间;

(3)所以时间复杂度=O(|V|+|E|);

深度优先遍历 DFS

深度优先遍历时,没遍历一个节点后,将这个节点标记已访问,防止重复访问。

  1. 访问顶点v;

  2. 从v的未被访问的邻接点中选取一个顶点w**(选取规则是找相邻编号最小的结点)**,从w出发进行深度优先遍历;

  3. 重复上述两步,直至图中所有和v有路径相通的顶点都被访问到。

namespace matrix{
    //邻接矩阵保存边关系
    template<class v,class w,w max_w = INT_MAX,bool Direction = false>
    class Graph{
    public:
        // 图的创建
        Graph(const std::vector<v>& points){
            m_vertexs.resize(points.size());
            for(size_t i = 0; i < points.size(); ++i){
                m_vertexs[i] = points[i];
                m_mapIndex[points[i]] = i;
            }

            m_matrix.resize(points.size());
            // 邻接矩阵
            for(int i = 0; i < m_matrix.size(); ++i){
                m_matrix[i].resize(points.size(),max_w);
            }
        }

        //添加边关系,输入两个点,以及这两个点连线边的权值
        void AddEdge(const v& src, const v& dest, const w& weight){
            size_t posSrc = GetPointIndex(src);
            size_t posDet = GetPointIndex(dest);
            //区分有向图和无向图
            m_matrix[posSrc][posDet] = weight;
            if(!Direction){
                //无向图,添加两条边关系
                m_matrix[posDet][posSrc] = weight;
            }
        }
       	
        void Print(){
            //打印顶点对应坐标
            for(size_t i = 0; i < m_vertexs.size(); ++i){
                std::cout << "[" << i << "]->" << m_vertexs[i] << std::endl;
                std::cout << std::endl;
            }

            //打印边
            std::cout << "  ";
            for(size_t i = 0; i < m_vertexs.size(); ++i){
                std::cout << m_vertexs[i] << " ";
            }
            std::cout << std::endl;

            //打印矩阵
            for(size_t i = 0; i < m_matrix.size(); ++i){
                std::cout << m_vertexs[i] << " ";
                for(size_t j = 0; j < m_matrix[i].size(); ++j){
                    if(m_matrix[i][j] == max_w){
                        std::cout << "*" << " ";
                    }
                    else{
                        std::cout << m_matrix[i][j] << " ";
                    }
                }
                std::cout << std::endl;
            }
        }
        
        //深度优先遍历
        void DFS(const v& src){
            size_t srcPos = GetPointIndex(src);
            std::vector<bool> visited(m_vertexs.size(),false);
            _DFS(srcPos,visited);
            std::cout << std::endl;
        }
    private:
        std::vector<v> m_vertexs;               // 顶点集合
        std::map<v,int> m_mapIndex;             // 顶点与下标的映射
        std::vector<std::vector<w>> m_matrix;   // 邻接矩阵
        //获取顶点下标
        size_t GetPointIndex(const v& point) const{
            typename std::map<v,int>::const_iterator ptr = m_mapIndex.find(point);
            if(ptr!= m_mapIndex.end())
                return ptr->second;
            else{
                throw std::invalid_argument("point not found");
                return -1;
            }
        }
        void _DFS(size_t srcPos, std::vector<bool>& visited){
            std::cout << srcPos << ":" << m_vertexs[srcPos] << " ";
            visited[srcPos] = true;
            for(size_t i = 0; i < m_vertexs.size(); i++)
            {
                if(m_matrix[srcPos][i]!= max_w && !visited[i]){
                    _DFS(i,visited);
                }

            }
        }
    };
}

// 邻接表保存边关系
namespace link_table{
    template<class w>
    struct Edge{
        int detPos;
        int srcPos;
        w weight;
        Edge<w> *next;
        Edge(int _srcPos, int _detPos, const w& _weight)
            :detPos(_detPos),srcPos(_srcPos),weight(_weight),next(nullptr)
        {}
    };

    template<class v,class w,bool Direction = false>
    class Graph{
        typedef Edge<w> Edge;
    public:
        //图的创建
        Graph(const std::vector<v>& points){
            m_vertexs.resize(points.size());
            for(size_t i = 0; i < points.size(); ++i){
                m_vertexs[i] = points[i];
                m_mapIndex[points[i]] = i;
            }
            m_tables.resize(points.size());
        }

        //添加边关系,输入两个点,以及这两个点连线边的权值
        void AddEdge(const v& src, const v& dest, const w& weight){
            size_t posSrc = GetPointIndex(src);
            size_t posDest = GetPointIndex(dest);

            Edge* pEdge = new Edge(posSrc,posDest,weight);
            //头插
            pEdge->next = m_tables[posSrc];
            m_tables[posSrc] = pEdge;

            if(!Direction){
                //无向图,添加两条边关系
                Edge* pEdge = new Edge(posDest,posSrc,weight);
                //头插
                pEdge->next = m_tables[posDest];
                m_tables[posDest] = pEdge;
            }
        }

        void Print(){
            //打印顶点对应坐标
            for(size_t i = 0; i < m_vertexs.size(); ++i){
                std::cout << "[" << i << "]->" << m_vertexs[i] << std::endl;
                std::cout << std::endl;
            }

            for(size_t i = 0; i < m_tables.size(); ++i){
                std::cout << m_vertexs[i] << "[" << i << "]:";
                Edge* pNode = m_tables[i];
                while(pNode!= nullptr){
                    std::cout << m_vertexs[pNode->detPos]
                    << "[" << pNode->detPos << "]"
                    << "(" << pNode->weight << ")" << " ";
                    pNode = pNode->next;
                }
                std::cout << "nullptr" << std::endl;
            }
        }

		void DFS(const v& src){
            size_t srcPos = GetPointIndex(src);
            std::vector<bool> visited(m_vertexs.size(),false);
            _DFS(srcPos,visited);
            std::cout << std::endl;
        }

    private:
        std::vector<v> m_vertexs;      // 顶点集合
        std::map<v,int> m_mapIndex;    // 顶点与下标的映射
        std::vector<Edge*> m_tables;    // 领接表
        // 获取顶点下标
        size_t GetPointIndex(const v& point) const{
            typename std::map<v,int>::const_iterator ptr = m_mapIndex.find(point);
            if(ptr!= m_mapIndex.end())
                return ptr->second;
            else{
                throw std::invalid_argument("point not found");
                return -1;
            }
        }

        void _DFS(size_t srcPos, std::vector<bool>& visited){
            std::cout << srcPos << ":" << m_vertexs[srcPos] << " ";
            visited[srcPos] = true;
            for(size_t i = 0; i < m_vertexs.size(); i++)
            {
                if(m_tables[srcPos]!= nullptr){
                    Edge* pEdge = m_tables[srcPos];
                    while(pEdge)
                    {
                        if(!visited[pEdge->detPos])
                        {
                            _DFS(pEdge->detPos,visited);
                        }
                        pEdge = pEdge->next;
                    }
                }
            }
        }
    };
}
#include "Graph.hpp"

using namespace std;

void TestGraph(){
    vector<char>points = {'A','B','C','D'};
    matrix::Graph<char,int,INT_MAX,true> graph(points);
    graph.AddEdge('A','B',1);
    graph.AddEdge('A','D',4);
    graph.AddEdge('B','D',2);
    graph.AddEdge('B','C',9);
    graph.AddEdge('C','D',8);
    graph.AddEdge('C','B',5);
    graph.AddEdge('C','A',3);
    graph.AddEdge('D','C',6);
    graph.DFS('C');
}

void TestGraph2(){
    vector<char>points = {'A','B','C','D'};
    link_table::Graph<char,int,true> graph(points);
    graph.AddEdge('A','B',1);
    graph.AddEdge('A','D',4);
    graph.AddEdge('B','D',2);
    graph.AddEdge('B','C',9);
    graph.AddEdge('C','D',8);
    graph.AddEdge('C','B',5);
    graph.AddEdge('C','A',3);
    graph.AddEdge('D','C',6);
    graph.Print();
    graph.DFS('A');
}

int main(){
    TestGraph();
    //TestGraph2();
    return 0;
}

在这里插入图片描述

注意:
如果图是连通图,则上述的遍历方式可以将图全部遍历完毕。

如果不是连通图,最后看标记数组是否全部已经被标记。如果还有点没有标记,则说明这个图不是连通图。此时更改点继续遍历。

4. 最小生成树算法(Kruskal、 Prim算法)

定义:

无向图中一个连通图的最小连通子图称为生成树。(用最少的边把所有顶点连接起来)。n个顶点的连通图的生成树有n-1条边。

路径长度:对于不带权图为路径的边个数。带权图为路径所有边权值的和

最小生成树:所有生成树中,路径长度最小的生成树。

所以生成树一定是连通图。这个定义是在无向图的基础上开展的。

连通图:无向图中,若顶点A、B存在路径,称为A、B连通。若图中的任意两点都是连通的,则称此图为连通图。

构造最小生成树

若连通图由n个顶点组成,则其生成树必含n个顶点和n-1条边。因此构造最小生成树的准则有三条:

  1. 只能使用图中的权值最小的边来构造最小生成树
  2. 只能使用恰好n-1条边来连接图中的n个顶点
  3. 选用的n-1条边不能构成回路

构造最小生成树的方法:Kruskal算法和Prim算法。这两个算法都采用了逐步求解的贪心策略。

Kruskal算法

克鲁斯卡尔算法查找最小生成树的方法是:将连通网中所有的边按照权值大小做升序排序,从权值最小的边开始选择,只要此边不和已选择的边一起构成环路,就可以选择它组成最小生成树。对于 N 个顶点的连通网,挑选出 N-1 条符合条件的边,这些边组成的生成树就是最小生成树。

判断图是否产生闭环,可以采用并查集的方式。如果这个边的顶点在并查集中,则说明如果添加这条边的话就构成环。

举个例子,下图是一个连通网,克鲁斯卡尔算法查找 图中 对应的最小生成树,需要经历以下几个步骤:

在这里插入图片描述

  1. 将连通网中的所有边按照权值大小做升序排序:

在这里插入图片描述

  1. 从 B-D 边开始挑选,由于尚未选择任何边组成最小生成树,且 B-D 自身不会构成环路,所以 B-D 边可以组成最小生成树。

在这里插入图片描述

  1. D-T 边不会和已选 B-D 边构成回路,可以组成最小生成树:

在这里插入图片描述

  1. A-C 边不会和已选 B-D、D-T 边构成环路,可以组成最小生成树:

在这里插入图片描述

  1. C-D 边不会和已选 A-C、B-D、D-T 边构成环路,可以组成最小生成树:

在这里插入图片描述

  1. C-B 边会和已选 C-D、B-D 边构成环路,因此不能组成最小生成树:
    在这里插入图片描述

  2. B-T、A-B 两条边都会和已选 A-C、C-D、B-D、D-T 构成环路,都不能组成最小生成树。而 S-A 不会和已选边构成环路,可以组成最小生成树。

在这里插入图片描述

  1. 如下图所示,对于一个包含6个顶点的连通网,我们已经选择了 5 条边,这些边组成的生成树就是最小生成树。

在这里插入图片描述

引入并查集 #include "UnionFindSet.hpp"

#include "UnionFindSet.hpp"
#include <vector>
#include <map>
#include <queue>
#include <iostream>

template <class w>
struct Edge
{
    int detPos;
    int srcPos;
    w weight;
    Edge<w> *next;
    Edge(int _srcPos, int _detPos, const w &_weight)
        : detPos(_detPos), srcPos(_srcPos), weight(_weight), next(nullptr)
    {}
};

struct rules{
    template <class w>
    bool operator()(const Edge<w> &left, const Edge<w> &right) const{
        return left.weight > right.weight;      //从小到大
    }
};

// v:顶点 w:权值 max_w:最大权值 Direction:判断图是有向图还是无向图
namespace matrix
{
    // 邻接矩阵保存边关系
    template <class v, class w, w max_w = INT_MAX, bool Direction = false>
    class Graph
    {
    public:
        // 图的创建
        Graph() = default;
        Graph(const std::vector<v> &points)
        {
            m_vertexs.resize(points.size());
            for (size_t i = 0; i < points.size(); ++i)
            {
                m_vertexs[i] = points[i];
                m_mapIndex[points[i]] = i;
            }

            m_matrix.resize(points.size());
            // 邻接矩阵
            for (int i = 0; i < m_matrix.size(); ++i)
            {
                m_matrix[i].resize(points.size(), max_w);
            }
        }

        // 添加边关系,输入两个点,以及这两个点连线边的权值
        void AddEdge(const v &src, const v &dest, const w &weight)
        {
            size_t posSrc = GetPointIndex(src);
            size_t posDet = GetPointIndex(dest);
            _AddEdge(posSrc,posDet,weight);
        }

        void Print()
        {
            // 打印顶点对应坐标
            for (size_t i = 0; i < m_vertexs.size(); ++i)
            {
                std::cout << "[" << i << "]->" << m_vertexs[i] << std::endl;
                std::cout << std::endl;
            }

            // 打印边
            std::cout << "  ";
            for (size_t i = 0; i < m_vertexs.size(); ++i)
            {
                std::cout << m_vertexs[i] << " ";
            }
            std::cout << std::endl;

            // 打印矩阵
            for (size_t i = 0; i < m_matrix.size(); ++i)
            {
                std::cout << m_vertexs[i] << " ";
                for (size_t j = 0; j < m_matrix[i].size(); ++j)
                {
                    if (m_matrix[i][j] == max_w)
                    {
                        std::cout << "*"
                                  << " ";
                    }
                    else
                    {
                        std::cout << m_matrix[i][j] << " ";
                    }
                }
                std::cout << std::endl;
            }
        }

        // 最小生成树,返回最小生成树权值,传入一个图,这个参数是输入输出参数,函数结束后,minTree是图的最小生成树
        w Kruskal(Graph<v, w, max_w, Direction> &minTree){
            size_t size = m_vertexs.size();
            //初始化minTree
            minTree.m_vertexs = m_vertexs;
            minTree.m_mapIndex = m_mapIndex;
            minTree.m_matrix.resize(size);
            for (int i = 0; i < size; ++i){
                minTree.m_matrix[i].resize(size, max_w);
            }
            std::priority_queue<Edge<w>, std::vector<Edge<w>>, rules> minQueue;     //从小到大
            // 将所有的边添加到优先级队列中
            for (size_t i = 0; i < size; ++i){
                //因为最小生成树,只在无向图中成立,所以只要遍历邻接矩阵一半即可
                for (size_t j = 0; j < i; ++j){
                    if (m_matrix[i][j]!= max_w){
                        minQueue.push(Edge<w>(i, j, m_matrix[i][j]));
                    }
                }
            }

            // 选出n-1条边
            int iDstSize = 0;
            w totalW = w();     //总共的权值
            // 创建并查集来标记是否成环,大小为图顶点个数
            UnionFindSet ufs(size);
            while (!minQueue.empty())
            {
                Edge<w> minEdge = minQueue.top();
                minQueue.pop();
                //判断这条边顶点是否在并查集中,在并查集中构成环,不符合最小生成树的定义
                if(!ufs.IsInSet(minEdge.srcPos, minEdge.detPos))
                {
                    //打印选的边测试
                    std::cout << m_vertexs[minEdge.srcPos] << "->" << m_vertexs[minEdge.detPos] << "权值:" << minEdge.weight << std::endl;
                    minTree._AddEdge(minEdge.srcPos, minEdge.detPos, minEdge.weight);
                    ufs.Union(minEdge.srcPos, minEdge.detPos);
                    iDstSize++;
                    totalW += minEdge.weight;
                }
            }
            if(iDstSize == size-1)
            {
                // 找到最小生成树
                return totalW;
            }
            else{
                // 没有找到最小生成树,返回权值的默认值,如果权值是整数则返回0
                return w();
            }
        }
    private : 
        std::vector<v> m_vertexs; // 顶点集合
        std::map<v, int> m_mapIndex;          // 顶点与下标的映射
        std::vector<std::vector<w>> m_matrix; // 邻接矩阵
        // 获取顶点下标
        size_t GetPointIndex(const v &point) const
        {
            typename std::map<v, int>::const_iterator ptr = m_mapIndex.find(point);
            if (ptr != m_mapIndex.end())
                return ptr->second;
            else
            {
                throw std::invalid_argument("point not found");
                return -1;
            }
        }

        void _AddEdge(size_t srcPos, size_t detPos,const w& weight)
        {
            //区分有向图和无向图
            m_matrix[srcPos][detPos] = weight;
            if (!Direction)
            {
                //无向图,添加两条边关系
                m_matrix[detPos][srcPos] = weight;
            }
        }
    };
}

Prim算法

与Kruskal算法类似,Prim算法也是通用最小生成树算法的一个特例。

Prim算法的工作原理与Dijkstra最短路径算法类似,也是使用局部贪心算法。

算法说明:

  • 开始指出一个起点,从起点开始找最小生成树。

  • Prim算法将所有顶点分成两部分:已选入的点,未选入的点。

  • 从未选入部分顶点中选出一个点,再从选入的点钟选择一个点。

  • 若两点,直接相连构成边,且这条边是所选择中权值最小的。则将选择的点添加到已选择部分顶点集合中。

例如:

在这里插入图片描述

  1. 设起点从a开始

  2. 开始将顶点分成两部分X、Y

    X = {a} Y = {b, c, d, e, f, g, h, i}

  3. X集合中选一个点a;Y中选择一个点,两个点要直接相连。

    Y中只能选b、h。又要选权值最小的边,所以这个点只能选择b。

此后,X = {a,b};Y = {c,d,e,f,g,h,i}

在这里插入图片描述

特殊情况:如果权值相同任意选择点即可。

后续重复这个过程即可,第二步选择的顶点组如下:

X = a Y = h 权值:8

或者

X = b Y = c 权值:8

这两条权值相同,任意选择即可。

在这里插入图片描述

此时 X = {a,b,c} Y = {d,e,f,g,h,i}

直到把Y集合所有点选择完毕即可。这样可以避免选择成环。不需要并查集来判断成环。

但是,这里处理时使用优先级队列,因为优先级队列开始时把顶点周围的所有边添加进入,而优先级队列又不支持指定删除元素。

所以当优先级队列弹出最小权值边的时候,还需要判断这条边的两个顶点是不是在同一个集合X中,如果在集合X,说明这条边构成环。

#include <vector>
#include <map>
#include <queue>
#include <iostream>

template <class w>
struct Edge
{
    int detPos;
    int srcPos;
    w weight;
    Edge<w> *next;
    Edge(int _srcPos, int _detPos, const w &_weight)
        : detPos(_detPos), srcPos(_srcPos), weight(_weight), next(nullptr)
    {}
};

struct rules{
    template <class w>
    bool operator()(const Edge<w> &left, const Edge<w> &right) const{
        return left.weight > right.weight;      //从小到大
    }
};

// v:顶点 w:权值 max_w:最大权值 Direction:判断图是有向图还是无向图
namespace matrix
{
    // 邻接矩阵保存边关系
    template <class v, class w, w max_w = INT_MAX, bool Direction = false>
    class Graph
    {
    public:
        // 图的创建
        Graph() = default;
        Graph(const std::vector<v> &points)
        {
            m_vertexs.resize(points.size());
            for (size_t i = 0; i < points.size(); ++i)
            {
                m_vertexs[i] = points[i];
                m_mapIndex[points[i]] = i;
            }

            m_matrix.resize(points.size());
            // 邻接矩阵
            for (int i = 0; i < m_matrix.size(); ++i)
            {
                m_matrix[i].resize(points.size(), max_w);
            }
        }

        // 添加边关系,输入两个点,以及这两个点连线边的权值
        void AddEdge(const v &src, const v &dest, const w &weight)
        {
            size_t posSrc = GetPointIndex(src);
            size_t posDet = GetPointIndex(dest);
            _AddEdge(posSrc,posDet,weight);
        }

        void Print()
        {
            // 打印顶点对应坐标
            for (size_t i = 0; i < m_vertexs.size(); ++i)
            {
                std::cout << "[" << i << "]->" << m_vertexs[i] << std::endl;
                std::cout << std::endl;
            }

            // 打印边
            std::cout << "  ";
            for (size_t i = 0; i < m_vertexs.size(); ++i)
            {
                std::cout << m_vertexs[i] << " ";
            }
            std::cout << std::endl;

            // 打印矩阵
            for (size_t i = 0; i < m_matrix.size(); ++i)
            {
                std::cout << m_vertexs[i] << " ";
                for (size_t j = 0; j < m_matrix[i].size(); ++j)
                {
                    if (m_matrix[i][j] == max_w)
                    {
                        std::cout << "*"
                                  << " ";
                    }
                    else
                    {
                        std::cout << m_matrix[i][j] << " ";
                    }
                }
                std::cout << std::endl;
            }
        }

        // 最小生成树,返回最小生成树权值,传入一个图,这个参数是输入输出参数,函数结束后,minTree是图的最小生成树
        //Src:Prim算法传入的起始点
        w Prim(Graph<v, w, max_w, Direction> &minTree, const v &Src){
            size_t posSrc = GetPointIndex(Src);
            //初始化minTree
            size_t size = m_vertexs.size();
            minTree.m_vertexs = m_vertexs;
            minTree.m_mapIndex = m_mapIndex;
            minTree.m_matrix.resize(size);
            for(size_t i = 0; i < size; ++i){
                minTree.m_matrix[i].resize(size, max_w);
            }
            std::vector<bool> X(size,false);        //已经加入的顶点的集合
            std::vector<bool> Y(size,true);         //未加入的顶点的集合
            X[posSrc] = true;
            Y[posSrc] = false;

            //从两个集合中选择两个点,构成权值最小的边
            std::priority_queue<Edge<w>, std::vector<Edge<w>>, rules> minQueue;
            //将这个顶点连接的边 入队列
            for(size_t i = 0;i < size; i++){
                if(m_matrix[posSrc][i]!= max_w){
                    minQueue.push(Edge<w>(posSrc, i, m_matrix[posSrc][i]));
                }
            }

            //选择权值最小的边,添加到最小生成树中
            size_t iEdgeSize = 0;
            w weight = w();
            while(!minQueue.empty()){
                Edge<w> minEdge = minQueue.top(); 
                minQueue.pop();

                //如果这条边的两个顶点在一个集合X中,说明构成环
                if(X[minEdge.detPos]){
                    //构成环
                    continue;
                }

                std::cout << m_vertexs[minEdge.srcPos] << "->" << m_vertexs[minEdge.detPos] << "权值:" << minEdge.weight << std::endl;

                minTree._AddEdge(minEdge.srcPos, minEdge.detPos, minEdge.weight);
                X[minEdge.detPos] = true;
                Y[minEdge.detPos] = false;
                iEdgeSize++;
                weight += minEdge.weight;
                //size个点,选size-1条边就可以结束循环了
                if(iEdgeSize == size-1){
                    break;
                }
                //posSrc --- posDst,把posDst点所有边(除了posSrc---posDst)加入优先级队列
                //所以posDst的另一个点不在集合X即可
                for (size_t i = 0; i < size; i++)
                {
                    if(m_matrix[minEdge.detPos][i]!= max_w &&!X[i]){
                        minQueue.push(Edge<w>(minEdge.detPos, i, m_matrix[minEdge.detPos][i]));
                    }
                }
            }
            if(iEdgeSize == size-1){
                return weight;
            }
            else{
                return w();
            }
        }
    private : 
        std::vector<v> m_vertexs; // 顶点集合
        std::map<v, int> m_mapIndex;          // 顶点与下标的映射
        std::vector<std::vector<w>> m_matrix; // 邻接矩阵
        // 获取顶点下标
        size_t GetPointIndex(const v &point) const
        {
            typename std::map<v, int>::const_iterator ptr = m_mapIndex.find(point);
            if (ptr != m_mapIndex.end())
                return ptr->second;
            else
            {
                throw std::invalid_argument("point not found");
                return -1;
            }
        }

        void _AddEdge(size_t srcPos, size_t detPos,const w& weight)
        {
            //区分有向图和无向图
            m_matrix[srcPos][detPos] = weight;
            if (!Direction)
            {
                //无向图,添加两条边关系
                m_matrix[detPos][srcPos] = weight;
            }
        }
    };
}
void TestGraphPrim()
{
    std::vector<char>vet{ 'a','b','c','d','e','f','g','h','i' };
	matrix::Graph<char, int> g(vet);
	g.AddEdge('a', 'b', 4);
	g.AddEdge('a', 'h', 8);
	g.AddEdge('b', 'c', 8);
	g.AddEdge('b', 'h', 11);
	g.AddEdge('c', 'i', 2);
	g.AddEdge('c', 'f', 4);
	g.AddEdge('c', 'd', 7);
	g.AddEdge('d', 'f', 14);
	g.AddEdge('d', 'e', 9);
	g.AddEdge('e', 'f', 10);
	g.AddEdge('f', 'g', 2);
	g.AddEdge('g', 'h', 1);
	g.AddEdge('g', 'i', 6);
	g.AddEdge('h', 'i', 7);
	matrix::Graph<char, int> primTree;
    int iWeight = g.Prim(primTree,'a');
	std::cout << "Prim:" << iWeight << std::endl;
	primTree.Print();
}

int main(){
    TestGraphPrim();
    return 0;
}

最后找到的最小生成树为:
在这里插入图片描述

5.最短路径问题(Dijkstra,Bellman-Ford,Floyd-Warshall算法 )

1. 最短路径

在网图和非网图中,最短路径的含义是不同的。由于非网图没有边上的权值,所谓最短路径,其实指的就是两个顶点之间经过的边数最少的路劲(即可以理解为把每一条边的权值看作是1)。

对于网图来说,最短路径,是指两顶点之间经过的边上的权值之和最少的路径,并且我们称路径上的第一个顶点是源点,最后一个顶点是终点。

求带权有向图G的最短路径问题一般可分为两类:

  • 一是单源最短路径,即求图中某一个顶点到其它顶点的最短路径,可以通过经典的 Dijkstra(迪杰斯特拉)算法求解;

  • 二是求每对顶点间的最短路径,可通过Floyd(弗洛伊德)算法来求解。

2. 迪杰斯特拉(Dijkstra)算法

Dijkstra算法思路是设置一个集合S记录已求得的最短路径的顶点,初始时把源点V0(图中的某个顶点)放入S,集合S每并入一个新顶点 Vi,都要修改源点V0到集合 V-S 中顶点当前的最短路径长度值。

在构造过程中需要两个辅助数组:

  • dist[ ] :记录从源点V0到其他各顶点当前的最短路径长度,它的初态为:若从 V0 到 Vi有直接路径(即V0 和 Vi邻接),则dist[ i ]为这两个顶点边上的权值;否则置 dist[ i ] 为 ∞。

  • path[ ]:path[ i ]表示从源点到顶点 i 之间的最短路径的前驱结点。在算法结束时,可以根据其值追溯到源点 V0 到 Vi 的最短路径。

假设从顶点V0 = 0出发,邻接矩阵Edge表示带权无向图,Edge[i] [j]表示无向边(i,j)的权值,若不存在无向边(i,j),则Edge[i] []为 ∞。

Dijkstra算法步骤如下:

  1. 初始化:集合S初始化为{0},dist[] 的初始值dist[i] = Edge[0] [i],path[] 的初始值path[i] = -1,i = 1,2,…,n-1。

  2. 从顶点集合V - S 中选出Vj,满足dist[j] = Min{dist[i] | Vi ∈ V - S},Vj就是当前求的一条从V0出发的最短路径的终点,令S = S∪{j}。

  3. 修改从V0 出发到集合V - S上任一顶点Vk 可达的最短路径长度:

    若 dist[j] + Edge[j] [k] < dist[k],则更新 dist[k] = dist[j] + Edge[j] [k] ,并修改path[k] = j(即修改顶点Vk的最短路径的前驱节点)。

  4. 重复 2 ~ 3操作共n-1次,直到所有的顶点都包含在S中。

解释下步骤3),每当一个顶点加入S后,可能需要修改源点V0 到集合 V-S中的可达顶点当前的最短路径长度。

下面举一个例子。如下图所示,源点为V0,初始时S = {V0},dist[1] = 6, dist[2] = 3,当V2并入集合S后,dist[1] 需要更新为 5(其比6小,即说明两点之间不是直线最短,要根据两点之间路径的权值之和来看)。

在这里插入图片描述

下面来讲解利用Dijkstra算法来求下图中的顶点 0 出发至其余顶点的最短路径的过程。

在这里插入图片描述

初始化:集合S初始化为{V0},V0可达V1和V2,其余顶点不可达,因此dist[]数组和path[]数组的设置如下:

i0123456
dist[i]63
path[i]-1-1-1-1-1-1-1

第一轮

选出最小dist[2],将顶点V2并入集合S,此时已找到V0到V2的最短路径,S = {V0,V2}。

当V2加入到S后,从V0到集合V-S中可到达顶点的最短路径长度可能会发生变化;因此需要更新dist[]数组。

V2可达V1,因V0->V2->V1的距离5比dist[1] = 6小,更新dist[1] = 5,并修改path[1] = 2(即V1的最短路径的前驱为V2);

V2可达V3,V0->V2->V3的距离8比dist[3] = ∞ 小,更新dist[3] = 8,path[3] = 2;

V2可达V5,V0->V2->V5的距离10小于dist[5] = ∞,更新dist[5] = 10,path[5] = 2。

V2再无到达其余的顶点的路径,结束这一轮,此时dist[]数组和path[]数组如下:

i0123456
dist[i]53810
path[i]-12-12-12-1

第二轮

选出最小值dist[1],将顶点V1并入集合S,此时已找到V0到V1的最短路径,S = {V0,V2,V1}。

然后更新dist[]数组和path[]数组,V1 可达V3,V0->V2->V1->V3的距离6小于dist[3] = 8,更新dist[3] = 6,path[3] = 1;

V1可达V2,但V2已经在集合S中,故不进行操作;

V1可达V4,V0->V2->V1->V4的距离9小于dist[4] = ∞,更新dist[4] = 9,path[4] = 1。

V1已无到达其余顶点的路径,结束此轮,此时dist[]数组和path[]数组如下:

i0123456
dist[i]536910
path[i]-12-1112-1

第三轮

选出最小值dist[3],将顶点V3并入集合S,此时已找到V0到V3的最短路径,S = {V0,V2,V1,V3};接着更新dist[]数组和path[]数组。

V3可到达V4,V0->V2->V1->V3->V4的距离为9,等于dist[4] = 9,不做更新;

V3可到达V5,V0->V2->V1->V3->V5的距离为12,大于dist[5] = 10,不做更新。

V3再无达到其余顶点的路径,结束此轮,此时dist[]数组和path[]数组如下:

i0123456
dist[i]536910
path[i]-12-1112-1

第四轮

选出最小值dist[4],将顶点V4并入集合S,此时已找到V0到V4的最短路径,S = {V0,V2,V1,V3,V4};继续更新dist[]数组和path[]数组。

V4可到V5,V0->V2->V1->V4->V5的距离11,大于dist[5] = 10,故不进行更新操作;

V4可到V6,V0->V2->V1->V4->V6的距离11,小于dist[6] = ∞,更新 dist[6] = 11,path[6] = 4。

V4再无达到其余顶点的路径,结束此轮,此时dist[]数组和path[]数组如下:

i0123456
dist[i]53691011
path[i]-12-11124

第五轮

选出最小值 dist[5],将顶点 V5 并入集合S,此时已找到 V0 到 V5的最短路径,S = { V0,V2,V1,V3,V4,V5}。然后dist[]数组和path[]数组。

V5 可到 V6, V0 -> V2 -> V5 -> V6 的最短路径 13 大于 dist[6],故不进行更新操作。

V6 再无达到其余顶点的路径,结束此轮,此时dist[]数组和path[]数组如下:

i0123456
dist[i]53691011
path[i]-12-11124

第六轮

选出最小值 dist[6],将顶点 V6 并入集合,此时全部顶点都已包含在S中,结束算法。

整个算法每一轮的结果如下:

在这里插入图片描述

总结:Dijkstra算法就是最开始选离源点V0最近的点,然后选好点后,

再从选好的点,看其邻接点的距离dist[]是否减小,减小就修改dist[]和path[];否则就不进行修改操作。

Dijkstra算法基于贪心策略,用邻接矩阵表示图时,来使用Dijkstra算法,其时间复杂度为O(n*n)。

当边上带有负权值时,Dijkstra算法并不适用。

使用dist[]数组和path[]数组,求最短路径。 这里介绍一个例子,其他顶点依次类推。

V0 到 V6的最短路径,先利用dist[6] = 11得出V0 到 V6的距离,然后利用path[]的得出路径。

path[6] = 4,顶点V6的前驱顶点是V4,再由path[4] = 1,表示V4的前驱是V1,path[1] = 2,表示V1的前驱是V2,path[2] = -1,结束。

最后可以得到V0到V6的最短路径为V6<-V4<-V1<-V2<-V0,即V0->V2->V1->V4->V6

贪心策略也叫贪心算法(greedy algorithm)或贪婪算法,是一种强有力的穷举搜索策略,它通过一系列选择来找到问题的最优解。

在每个决策点,它都会做出当时看来是最优的选择,一旦选择后就无需回溯。

简单来说,贪心策略是一种“步步为营”的策略——只要做好眼前的每一步,就自然会在未来得到最好的结果,并且做过的决策就是是最好的决策,无需再次检查。

很多时候,贪心法并不能保证得到最优解,它能得到的是较为接近最优解的较好解,因此贪心法经常被用来解决一些对结果精度要求不高的问题。

#include <vector>
#include <map>
#include <queue>
#include <algorithm>
#include <iostream>

// v:顶点 w:权值 max_w:最大权值 Direction:判断图是有向图还是无向图
namespace matrix
{
    // 邻接矩阵保存边关系
    template <class v, class w, w max_w = INT_MAX, bool Direction = false>
    class Graph
    {
    public:
        // 图的创建
        Graph() = default;
        Graph(const std::vector<v> &points)
        {
            m_vertexs.resize(points.size());
            for (size_t i = 0; i < points.size(); ++i)
            {
                m_vertexs[i] = points[i];
                m_mapIndex[points[i]] = i;
            }

            m_matrix.resize(points.size());
            // 邻接矩阵
            for (int i = 0; i < m_matrix.size(); ++i)
            {
                m_matrix[i].resize(points.size(), max_w);
            }
        }

        // 添加边关系,输入两个点,以及这两个点连线边的权值
        void AddEdge(const v &src, const v &dest, const w &weight)
        {
            size_t posSrc = GetPointIndex(src);
            size_t posDet = GetPointIndex(dest);
            _AddEdge(posSrc,posDet,weight);
        }

        void Print()
        {
            // 打印顶点对应坐标
            for (size_t i = 0; i < m_vertexs.size(); ++i)
            {
                std::cout << "[" << i << "]->" << m_vertexs[i] << std::endl;
                std::cout << std::endl;
            }

            // 打印边
            std::cout << "  ";
            for (size_t i = 0; i < m_vertexs.size(); ++i)
            {
                std::cout << m_vertexs[i] << " ";
            }
            std::cout << std::endl;

            // 打印矩阵
            for (size_t i = 0; i < m_matrix.size(); ++i)
            {
                std::cout << m_vertexs[i] << " ";
                for (size_t j = 0; j < m_matrix[i].size(); ++j)
                {
                    if (m_matrix[i][j] == max_w)
                    {
                        std::cout << "*"
                                  << " ";
                    }
                    else
                    {
                        std::cout << m_matrix[i][j] << " ";
                    }
                }
                std::cout << std::endl;
            }
        }

        // 单源最短路径
        // src:源顶点,dist:保存src到各个顶点的最短距离,path:保存最短路径的节点
        void Dijkstra(const v &src, std::vector<w> &dist, std::vector<int> &path)
        {
            size_t srcPos = GetPointIndex(src);
            size_t size = m_vertexs.size();
            dist.resize(size, max_w);
            path.resize(size, -1);

            dist[srcPos] = 0;       //源顶点到自己本身最短距离为0
            path[srcPos] = srcPos;  //源顶点的最短路径的父节点为自己

            std::vector<bool> S(size, false);       //已经确定最短路径的顶点的集合
            //循环判断所有的顶点
            for (size_t time = 0; time < size; time++){
                //选不在S集合 最短路径的顶点,更新其他路径
                //选p点,p点不在s集合中
                int p = 0;
                w min = max_w;  //最小权值
                for(size_t i = 0; i < size; i++){
                    if(!S[i] && dist[i] < min){
                        p  = i;
                        min = dist[i];
                    }
                }

                //把p点加入S集合
                S[p] = true;
                //松弛更新 src->p + p->p的邻接节点 与 src->p邻接节点的权值相比较,小则更新
                for(size_t adP = 0;adP < size; adP++){
                    //找到p点所有的邻接顶点
                    if(!S[adP] && m_matrix[p][adP] != max_w){
                        if (dist[adP] > dist[p] + m_matrix[p][adP])
                        {
                           dist[adP] = dist[p] + m_matrix[p][adP];
                           //更新这个顶点最短路径的父节点为p点
                           path[adP] = p;
                        }
                        
                    }
                }
            }
        }
        //打印最短路径
        void PrintShortPath(const v& src, const std::vector<w>& dist, const std::vector<int>& path)
        {
            size_t srcPos = GetPointIndex(src);
            size_t size = m_vertexs.size();
            //计算src点到其他点的最短路径长度,src到src=0不用计算
            for(size_t i = 0; i < size; i++)
            {
                if(i == srcPos)
                    continue;
                //src 到顶点i的最短路径长度
                std::vector<int> minPath;
                size_t pos = i;
                std::cout << "最短路径为:";
                while(pos!= srcPos)
                {
                    minPath.push_back(pos);
                    pos = path[pos];
                }
                minPath.push_back(srcPos);
                std::reverse(minPath.begin(), minPath.end());
                for (auto index : minPath)
                {
                    std::cout << m_vertexs[index] << "->";
                }
                std::cout << "长度:" << dist[i] << std::endl;
            }
        }
    private : 
        std::vector<v> m_vertexs; // 顶点集合
        std::map<v, int> m_mapIndex;          // 顶点与下标的映射
        std::vector<std::vector<w>> m_matrix; // 邻接矩阵
        // 获取顶点下标
        size_t GetPointIndex(const v &point) const
        {
            typename std::map<v, int>::const_iterator ptr = m_mapIndex.find(point);
            if (ptr != m_mapIndex.end())
                return ptr->second;
            else
            {
                throw std::invalid_argument("point not found");
                return -1;
            }
        }

        void _AddEdge(size_t srcPos, size_t detPos,const w& weight)
        {
            //区分有向图和无向图
            m_matrix[srcPos][detPos] = weight;
            if (!Direction)
            {
                //无向图,添加两条边关系
                m_matrix[detPos][srcPos] = weight;
            }
        }
    };
}
#include "Graph.hpp"

using namespace std;

void TestGraphDijkstra(){
    std::vector<char> vet{ '0','1','2','3','4','5','6' };
	matrix::Graph<char, int> g(vet);
	g.AddEdge('0', '1', 6);
	g.AddEdge('0', '2', 3);
    g.AddEdge('1', '2', 2);
    g.AddEdge('1', '3', 1);
    g.AddEdge('1', '4', 4);
    g.AddEdge('2', '3', 5);
    g.AddEdge('2', '5', 7);
    g.AddEdge('3', '4', 3);
    g.AddEdge('3', '5', 6);
    g.AddEdge('4', '5', 2);
    g.AddEdge('4', '6', 2);
    g.AddEdge('5', '6', 3);
    vector<int> dist; 
    vector<int> parentPath;
	g.Dijkstra('0', dist, parentPath);
	g.PrintShortPath('0',dist, parentPath);
}

int main(){
    TestGraphDijkstra();
    return 0;
}

算法的空间复杂度:开辟了二个大小等于顶点个数的数组,O(N)

时间复杂度:N个顶点,每个顶点都需要遍历一遍这个顶点的邻接顶点。时间复杂度O(N^2);

3.贝尔曼-福特(Bellman-Ford)单源最短路径算法

Bellman-Ford 算法是一种用于计算带权有向图中单源最短路径(SSSP:Single-Source Shortest Path)的算法。该算法由 Richard Bellman 和 Lester Ford 分别发表于 1958 年和 1956 年,而实际上Edward F. Moore 也在 1957 年发布了相同的算法,因此,此算法也常被称为 Bellman-Ford-Moore 算法。

Bellman-Ford 算法 和 Dijkstra 算法 同为解决单源最短路径的算法。对于带权有向图 G = (V, E),其中 V 为顶点数量,E 为边的数量,Dijkstra 算法要求图 G 中边的权值均为非负,而 Bellman-Ford 算法 能适应一般的情况(即存在负权边的情况)。一个实现的很好的 Dijkstra 算法比 Bellman-Ford 算法的运行时间要低。

Bellman-Ford 算法采用 动态规划(Dynamic Programming)进行设计,实现的时间复杂度为 O(VE),由于使用邻接矩阵存储图关系,因此E = V*V。

Dijkstra 算法采用 贪心算法(Greedy Algorithm)范式进行设计,普通实现的时间复杂度为 O(V2),若基于 斐波那契堆(Fibonacci heap) 的最小优先队列实现版本则时间复杂度为 O(E + VlogV)。

Bellman-Ford 算法描述:

在这里插入图片描述

根据上图可知,起点s到图中的其他顶点。要么直接相连,要么通过其他的顶点间接相连。

简述为:s->j(代表图中的所有边) 或者 s->i+i->j

  1. 以s->i这条边为起始边,开始找最终边,如果找到 (s->i->j)< s->j 时,进行松弛变化,将j的最小权值 修改为 s->i->j 边的权值和。
  2. 所有边都需要 进行与其他边的 松弛判断。

注意:

这里可能出现权值与路径不匹配的问题。

例如:

开始计算 s->t 的最小值为6,此时计算s->t->z的值为2。(图c)

之后s->t的最小值变成了s->y->x->t的权值和2(图d);此时必须修改上一步的结果(图e);如果没有修改就会出现路径和最小权值不匹配的情况。

只要更新出最短路径,就可能影响其他路径。解决这个问题的方式就是再整体更新一次。一旦更新有可能会影响其他路径,但是最多更新顶点次数,所以直接暴力更新顶点次数次即可。
如果这次遍历没有更新边,则后面的重复暴力就可以停止了。

负权回路问题

Bellman-Ford算法可以解决负权图的单源最短路径问题。但是解决不了负权回路问题。

负权回路:s->s最短路径权值计算为负数。

例如:

在这里插入图片描述

如上图s->s最短路径为s->t->y->s 为-1,那么只要重读次数足够多,s->s的最短路径可以一直变小。

重复两次为-2,重读三次为-3,有根据上文可知更新最短路径,就可能影响其他路径。所以此时最短路径会一直更新,Bellman-Ford算法没办法解决。

#include <vector>
#include <map>
#include <queue>
#include <algorithm>
#include <iostream>

// v:顶点 w:权值 max_w:最大权值 Direction:判断图是有向图还是无向图
namespace matrix
{
    // 邻接矩阵保存边关系
    template <class v, class w, w max_w = INT_MAX, bool Direction = false>
    class Graph
    {
    public:
        // 图的创建
        Graph() = default;
        Graph(const std::vector<v> &points)
        {
            m_vertexs.resize(points.size());
            for (size_t i = 0; i < points.size(); ++i)
            {
                m_vertexs[i] = points[i];
                m_mapIndex[points[i]] = i;
            }

            m_matrix.resize(points.size());
            // 邻接矩阵
            for (int i = 0; i < m_matrix.size(); ++i)
            {
                m_matrix[i].resize(points.size(), max_w);
            }
        }

        // 添加边关系,输入两个点,以及这两个点连线边的权值
        void AddEdge(const v &src, const v &dest, const w &weight)
        {
            size_t posSrc = GetPointIndex(src);
            size_t posDet = GetPointIndex(dest);
            _AddEdge(posSrc,posDet,weight);
        }

        void Print()
        {
            // 打印顶点对应坐标
            for (size_t i = 0; i < m_vertexs.size(); ++i)
            {
                std::cout << "[" << i << "]->" << m_vertexs[i] << std::endl;
                std::cout << std::endl;
            }

            // 打印边
            std::cout << "  ";
            for (size_t i = 0; i < m_vertexs.size(); ++i)
            {
                std::cout << m_vertexs[i] << " ";
            }
            std::cout << std::endl;

            // 打印矩阵
            for (size_t i = 0; i < m_matrix.size(); ++i)
            {
                std::cout << m_vertexs[i] << " ";
                for (size_t j = 0; j < m_matrix[i].size(); ++j)
                {
                    if (m_matrix[i][j] == max_w)
                    {
                        std::cout << "*"
                                  << " ";
                    }
                    else
                    {
                        std::cout << m_matrix[i][j] << " ";
                    }
                }
                std::cout << std::endl;
            }
        }

        // 单源最短路径
        // src:源顶点,dist:保存src到各个顶点的最短距离,path:保存最短路径的节点
        // 如果出现负权回路。函数返回false值
        bool BellmanFord(const v &src, std::vector<w> &dist, std::vector<int> &path)
        {
            size_t size = m_vertexs.size();
            size_t srcPos = GetPointIndex(src);

            // vertor<w> dist,记录srcPos-其他顶点最短路径权值数组
            dist.resize(size, max_w);

            // vector<w> path,记录srcPos-其他顶点最短路径的父节点数组
            path.resize(size, -1);

            // 先更新srci->srci为缺省值0
            dist[srcPos] = w();

            //总体最多更新size轮
            for (size_t time = 0; time < size; time++)
            {
                // i->j 更新松弛
                bool bUpdate = false;
                for (size_t i = 0; i < size; i++){
                    for(size_t j = 0; j < size; j++){
                        //srcPos -> i + i -> j 松弛关系
                        if(m_matrix[i][j] != max_w && (dist[i] + m_matrix[i][j] < dist[j])){
                            dist[j] = dist[i] + m_matrix[i][j];
                            path[j] = i;
                            bUpdate = true;
                        }
                    }
                }
                if(!bUpdate){
                    break;  //这次没有更新出最短路径,可以退出循环了
                }
            }

            //判断负权回路,如果退出循环还可以更新,就是负权回路返回false
            //还能更新就是带负权回路
            for (size_t i = 0; i < size; i++){
                for(size_t j = 0; j < size; j++){
                    if(m_matrix[i][j]!= max_w && (dist[i] + m_matrix[i][j] < dist[j])){
                        return false;
                    }
                }
            }
            return true;
        }

        //打印最短路径
        void PrintShortPath(const v& src, const std::vector<w>& dist, const std::vector<int>& path)
        {
            size_t srcPos = GetPointIndex(src);
            size_t size = m_vertexs.size();
            //计算src点到其他点的最短路径长度,src到src=0不用计算
            for(size_t i = 0; i < size; i++)
            {
                if(i == srcPos)
                    continue;
                //src 到顶点i的最短路径长度
                std::vector<int> minPath;
                size_t pos = i;
                std::cout << "最短路径为:";
                while(pos!= srcPos)
                {
                    minPath.push_back(pos);
                    pos = path[pos];
                }
                minPath.push_back(srcPos);
                std::reverse(minPath.begin(), minPath.end());
                for (auto index : minPath)
                {
                    std::cout << m_vertexs[index] << "->";
                }
                std::cout << "长度:" << dist[i] << std::endl;
            }
        }
    private : 
        std::vector<v> m_vertexs; // 顶点集合
        std::map<v, int> m_mapIndex;          // 顶点与下标的映射
        std::vector<std::vector<w>> m_matrix; // 邻接矩阵
        // 获取顶点下标
        size_t GetPointIndex(const v &point) const
        {
            typename std::map<v, int>::const_iterator ptr = m_mapIndex.find(point);
            if (ptr != m_mapIndex.end())
                return ptr->second;
            else
            {
                throw std::invalid_argument("point not found");
                return -1;
            }
        }

        void _AddEdge(size_t srcPos, size_t detPos,const w& weight)
        {
            //区分有向图和无向图
            m_matrix[srcPos][detPos] = weight;
            if (!Direction)
            {
                //无向图,添加两条边关系
                m_matrix[detPos][srcPos] = weight;
            }
        }
    };
}
#include "Graph.hpp"

using namespace std;

void TestBellmanFord()
{
    vector<char> str = { 's','y','z','t','x' };
	matrix::Graph<char, int, INT_MAX, true> g(str);
	g.AddEdge('s', 't', 6);
	g.AddEdge('s', 'y', 7);
	g.AddEdge('y', 'z', 9);
	g.AddEdge('y', 'x', -3);
	g.AddEdge('z', 's', 2);
	g.AddEdge('z', 'x', 7);
	g.AddEdge('t', 'x', 5);
	g.AddEdge('t', 'y', 8);     //测试负权回路 8 改为 -8
	g.AddEdge('t', 'z', -4);
	g.AddEdge('x', 't', -2);
    //测试负权回路
    //g.AddEdge('y', 's', 1);
	vector<int> dist;
	vector<int> parentPath;
	if(g.BellmanFord('s', dist, parentPath))
	    g.PrintShortPath('s', dist, parentPath);
    else
        std::cout << "有负权回路" << std::endl;
}

int main(){
    TestBellmanFord();
    return 0;
}

4.弗洛伊德(Floyd-Warshall)多源最短路径算法

多源最短路径:源顶点是图中的所有顶点,求图中任意两点的最短路径。

注意:

  1. Floyd-Warshall可以解决负数权值问题。
  2. 如果以所有点为源点,使用 Dijkstra算法 也可以算出图中任意两点的最短路径。但是 Dijkstra算法 不能带负数权值,Bellman-Ford算法 效率太低。

因为Floyd-Warshall算法要以图中任意顶点为源顶点。

根据上面分析可知,dist(记录源顶点到其他顶点的最短路径)数组应该是二维数组。

path(通过双亲表示法记录最短路径的节点)也应该是二维数组。

算法思路

Floyd算法考虑的是一条最短路径的中间节点,即简单路径p={v1,v2,…,vn}上除v1和vn的任意节点。

设k是p的一个中间节点,那么从i到j最短路径p就被分成 i->k 和 k->j 的两段最短路径p1,p2

p1是从 i->k 且 中间节点属于{1,2,…,k-1} 而取得的一条最短路径,

p2是从 k->j 且 中间节点属于{1,2,…,k-1} 而取得的一条最短路径。
在这里插入图片描述

简单来讲,设图中有n个顶点,

任意两点之间可能没有顶点,也可能有顶点。任意两点之间最多有(n-2)个 k 顶点。

Floyd-Warshall 算法的原理是动态规划;

设Di,j,k为从 i 到 j 的只以(1…k)集合中的节点为为中间节点 的 最短路径 的长度。

  1. 若最短路径经过点k,则Di,j,k = Di,k,k-1 + Dk,j,k-1

  2. 若最短路径不经过点k,则Di,j,k = Di,j,k-1

因此,Di,j,k = min(Di,j,k-1,Di,k,k-1 + Dk,j,k-1

简单来讲:如果 k 是 i->j 之间的点 且 i->k->j < i->j 说明经过中点k会使路径更短,更新 i->j 的路径长度,并且修改Path数组,记录更新后的父顶点。

例如:

在这里插入图片描述

左边的是权值矩阵,右边是父节点矩阵。与其他算法类似,只不过 Floyd-Warshall 算法要不图中的每个顶点都做一遍源节点,所以是二维数组。

#include <vector>
#include <map>
#include <queue>
#include <algorithm>
#include <iostream>

// v:顶点 w:权值 max_w:最大权值 Direction:判断图是有向图还是无向图
namespace matrix
{
    // 邻接矩阵保存边关系
    template <class v, class w, w max_w = INT_MAX, bool Direction = false>
    class Graph
    {
    public:
        // 图的创建
        Graph() = default;
        Graph(const std::vector<v> &points)
        {
            m_vertexs.resize(points.size());
            for (size_t i = 0; i < points.size(); ++i)
            {
                m_vertexs[i] = points[i];
                m_mapIndex[points[i]] = i;
            }

            m_matrix.resize(points.size());
            // 邻接矩阵
            for (int i = 0; i < m_matrix.size(); ++i)
            {
                m_matrix[i].resize(points.size(), max_w);
            }
        }

        // 添加边关系,输入两个点,以及这两个点连线边的权值
        void AddEdge(const v &src, const v &dest, const w &weight)
        {
            size_t posSrc = GetPointIndex(src);
            size_t posDet = GetPointIndex(dest);
            _AddEdge(posSrc,posDet,weight);
        }

        void Print()
        {
            // 打印顶点对应坐标
            for (size_t i = 0; i < m_vertexs.size(); ++i)
            {
                std::cout << "[" << i << "]->" << m_vertexs[i] << std::endl;
                std::cout << std::endl;
            }

            // 打印边
            std::cout << "  ";
            for (size_t i = 0; i < m_vertexs.size(); ++i)
            {
                std::cout << m_vertexs[i] << " ";
            }
            std::cout << std::endl;

            // 打印矩阵
            for (size_t i = 0; i < m_matrix.size(); ++i)
            {
                std::cout << m_vertexs[i] << " ";
                for (size_t j = 0; j < m_matrix[i].size(); ++j)
                {
                    if (m_matrix[i][j] == max_w)
                    {
                        std::cout << "*"
                                  << " ";
                    }
                    else
                    {
                        std::cout << m_matrix[i][j] << " ";
                    }
                }
                std::cout << std::endl;
            }
        }
        
        // 求多源最短路径
        void FloydWarShall(std::vector<std::vector<w>> &dist, std::vector<std::vector<int>> &path)
        {
            size_t size = m_vertexs.size();

            //初始化顶点矩阵 与 路径矩阵
            dist.resize(size);
            path.resize(size);
            for (size_t i = 0; i < size; i++)
            {
                dist[i].resize(size, max_w);
                path[i].resize(size, -1);
            }
            
            //直接相连的边更新初始化
            for (size_t i = 0; i < size; i++)
            {
                for (size_t j = 0; j < size; j++)
                {
                    if (m_matrix[i][j]!= max_w)
                    {
                        dist[i][j] = m_matrix[i][j];
                        path[i][j] = i; //i->j起点是i点
                    }
                    if(i == j){
                        //i->i时路径长度为0
                        dist[i][i] = 0;
                    }
                }
            }

            //最短路径的更新i->{其他顶点}->j
            //k作为中间点,尝试更新i->j的路径
            for (size_t k = 0; k < size; k++)
            {
                for (size_t i = 0; i < size; i++)
                {
                    for (size_t j = 0; j < size; j++)
                    {
                        //经过了k点
                        if (dist[i][k]!= max_w && dist[k][j]!= max_w)
                        {
                            if(dist[i][k] + dist[k][j] < dist[i][j])
                            {
                                //经过k更小,则更新长度
                                dist[i][j] = dist[i][k] + dist[k][j];
                                //找上一个与j邻接的节点
                                //k->j入过k与j直接相连,则path[i][j] = k
                                //但是k->j不一定直接相连 k->...->x->j,则path[i][j]=x,就是path[k][j]
                                path[i][j] = path[k][j];
                            }
                        }
                    }
                }
            }
        }


        //打印最短路径
        void PrintShortPath(const v& src, const std::vector<w>& dist, const std::vector<int>& path)
        {
            size_t srcPos = GetPointIndex(src);
            size_t size = m_vertexs.size();
            //计算src点到其他点的最短路径长度,src到src=0不用计算
            for(size_t i = 0; i < size; i++)
            {
                if(i == srcPos)
                    continue;
                //src 到顶点i的最短路径长度
                std::vector<int> minPath;
                size_t pos = i;
                std::cout << "最短路径为:";
                while(pos!= srcPos)
                {
                    minPath.push_back(pos);
                    pos = path[pos];
                }
                minPath.push_back(srcPos);
                std::reverse(minPath.begin(), minPath.end());
                for (auto index : minPath)
                {
                    std::cout << m_vertexs[index] << "->";
                }
                std::cout << "长度:" << dist[i] << std::endl;
            }
        }
    private : 
        std::vector<v> m_vertexs; // 顶点集合
        std::map<v, int> m_mapIndex;          // 顶点与下标的映射
        std::vector<std::vector<w>> m_matrix; // 邻接矩阵
        // 获取顶点下标
        size_t GetPointIndex(const v &point) const
        {
            typename std::map<v, int>::const_iterator ptr = m_mapIndex.find(point);
            if (ptr != m_mapIndex.end())
                return ptr->second;
            else
            {
                throw std::invalid_argument("point not found");
                return -1;
            }
        }

        void _AddEdge(size_t srcPos, size_t detPos,const w& weight)
        {
            //区分有向图和无向图
            m_matrix[srcPos][detPos] = weight;
            if (!Direction)
            {
                //无向图,添加两条边关系
                m_matrix[detPos][srcPos] = weight;
            }
        }
    };
}
void TestFloydWarShall(){
    vector<char> str = { '1','2','3','4','5' };
	matrix::Graph<char, int, INT_MAX, true> g(str);
	g.AddEdge('1', '2', 3);
	g.AddEdge('1', '3', 8);
	g.AddEdge('1', '5', -4);
	g.AddEdge('2', '4', 1);
	g.AddEdge('2', '5', 7);
	g.AddEdge('3', '2', 4);
	g.AddEdge('4', '1', 2);
	g.AddEdge('4', '3', -5);
	g.AddEdge('5', '4', 6);


	vector<vector<int>> vDist; vector<vector<int>> vPath; 
	g.FloydWarShall(vDist, vPath);

	// 打印任意两点之间的最短路径
	for (size_t i = 0; i < str.size(); ++i)
	{
		g.PrintShortPath(str[i], vDist[i], vPath[i]);
		cout << endl;
	}
}

int main(){
    TestFloydWarShall();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值