自学内容网 自学内容网

数据结构——图

生活中人与人的关系是很复杂的,比如我的一个朋友们,他们可能也相互认识。在这种情况下,一对一、一对多的结构难以完整的描述出来这种人际关系。所以就有了接下来的主题——图。

_____________________________________________________________________________

图的基本概念

图(graph)是由顶点(vertex)边(edge)构成的一种数据结构。通常表示为:G(V,E),其中V是图G的顶点集合,E是图G的边集合。

注意一点,树没有结点叫空树,但是图不允许没有顶点,但是可以没有边。

下面我们介绍一些基本概念以及术语。 

1.无向图与有向图在有向图中,顶点对是有序的,顶点对<x,y>称为顶点x到顶点y的一条边(弧),和<y, x>是两条不同的边,比如下图G3和G4为有向图。在无向图中,顶点对(x, y) 是无序的,顶点对(x,y)称为顶点x和顶点y相关联的一条边,这条边没有特定方向,(x, y)和(y,x) 是同一条边,比如下图G1和G2为无向图。注意:无向边用"()"表示,有向边用"<>"表示。无向边(x, y)等于有向边<x,y>和<y,x>

2.完全图:在有n个顶点的无向图中,任意两个顶点之间有且仅有一条边,则称此图为无向完全图,并且边的条数为n*(n-1)/2.比如上图G1;在n个顶点的有向图中,任意两个顶点之间有且仅有方向相反的边,并且有n * (n-1)条边,则称此图为有向完全图,比如上图G4。

3.邻接顶点:无向图中G中,若(u, v)是E(G)中的一条边,则称u和v互为邻接顶点,并称边(u,v)依附于顶点u和v;在有向图G中,若<u, v>是E(G)中的一条边,则称顶点u邻接到v,顶点v邻接自顶 点u,并称边<u, v>与顶点u和顶点v相关联

4.顶点的度顶点v的度是指与它相关联的边的条数,记作deg(v)。在有向图中,顶点的度等于该顶点的入度与出度之和,其中顶点v的入度是以v为终点的有向边的条数,记作indev(v);顶点v的出度是以v为起始点的有向边的条数,记作outdev(v)。因此:dev(v) = indev(v) + outdev(v)注意:对于无向图,顶点的度等于该顶点的入度和出度,即dev(v) = indev(v) = outdev(v)。

5.路径:在图G = (V, E)中,若从顶点vi出发有一组边使其可到达顶点vj,则称顶点vi到顶点vj的顶 点序列为从顶点vi到顶点vj的路径

6.边的权和网:在一个图中,边标上具有某些含义的数值,称为该边的权值。这种图成为

7.路径长度:对于不带权的图,一条路径的路径长度是指该路径上的边的条数;对于带权的图,一 条路径的路径长度是指该路径上各个边权值的总和

8.简单路径与回路:若路径上各顶点v1,v2,v3,…,vm均不重复,则称这样的路径为简单路 径。若路径上第一个顶点v1和最后一个顶点vm重合,则称这样的路径为回路或环

9.子图:设图G = {V, E}和图G1 = {V1,E1},若V1属于V且E1属于E,则称G1是G的子图。

10.连通图:在无向图中,若从顶点v1到顶点v2有路径,则称顶点v1与顶点v2是连通的。如果图中任 意一对顶点都是连通的,则称此图为连通图

11.强连通图:在有向图中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj 到vi的路径,则称此图是强连通图。

12.生成树:一个连通图的最小连通子图称作该图的生成树。有n个顶点的连通图的生成树有n个顶点和n-1条边

13.有向树:一个顶点入度为0,其余顶点入度为1的有向图。

14.稠密图和稀疏图:边数少的图叫稀疏图,反之成为稠密图。

____________________________________________________________________________

图的存储结构

图中既有节点,又有边(节点与节点之间的关系),因此,在图的存储中,只需要保存:节点和边关系即可。节点保存比较简单,只需要一段连续空间即可,那边关系该怎么保存呢?

邻接矩阵法

因为节点与节点之间的关系就是连通与否,即为0或者1,因此邻接矩阵(二维数组)即是:先用一 个数组将节点保存,然后采用矩阵来表示节点与节点之间的关系

注意:

无向图的邻接矩阵是对称的,第i行(列)元素之和,就是顶点i的度。有向图的邻接矩阵则不一定是对称的,第i行(列)元素之后就是顶点i的出(入)度

2. 如果边带有权值,并且两个节点之间是连通的,上图中的边的关系就用权值代替,如果两个顶点不通,则使用无穷大代替

3. 用邻接矩阵存储图适合稠密图。

当要查找两个点之间的关系,可以通过下标直接查找矩阵的值,时间复杂度为O(1)进行查找两点关系。

但是如果要查找一个顶点有哪些相连的边,此时就要遍历数组,由于起点确定,时间复杂度为O(n)。

template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
class Graph
{
public:
    typedef Graph<V, W, MAX_W, Direction> Self;
    Graph() = default;
    Graph(const V * vertexs, size_t n)
    {
        _vertexs.reserve(n);
        for (size_t i = 0; i < n; ++i)
        {
            _vertexs.push_back(vertexs[i]);
            _vIndexMap[vertexs[i]] = i;
        }
        // MAX_W 作为不存在边的标识值
        _matrix.resize(n);
        for (auto& e : _matrix)
        {
            e.resize(n, MAX_W);
        }
    }
    size_t GetVertexIndex(const V & v)
    {
        auto ret = _vIndexMap.find(v);
        if (ret != _vIndexMap.end())
        {
            return ret->second;
        }
        else
        {
            throw invalid_argument("不存在的顶点");
            return -1;
        }
    }
    void _AddEdge(size_t srci, size_t dsti, const W & w)
    {
        _matrix[srci][dsti] = w;
        if (Direction == false)
        {
            _matrix[dsti][srci] = w;
        }
    }
    void AddEdge(const V & src, const V & dst, const W & w)
    {
        size_t srci = GetVertexIndex(src);
        size_t dsti = GetVertexIndex(dst);
        _AddEdge(srci, dsti, w);
    }

    void Print()
    {
        // 打印顶点和下标映射关系
        for (size_t i = 0; i < _vertexs.size(); ++i)
        {
            cout << _vertexs[i] << "-" << i << " ";
        }
        cout << endl << endl;
        cout << " ";
        for (size_t i = 0; i < _vertexs.size(); ++i)
        {
            cout << i << " ";
        }
        cout << endl;
        // 打印矩阵
        for (size_t i = 0; i < _matrix.size(); ++i)
        {
            cout << i << " ";
            for (size_t j = 0; j < _matrix[i].size(); ++j)
            {
                if (_matrix[i][j] != MAX_W)
                    cout << _matrix[i][j] << " ";
                else
                    cout << "#" << " ";
            }
            cout << endl;
        }
        cout << endl << endl;
        // 打印所有的边
        for (size_t i = 0; i < _matrix.size(); ++i)
        {
            for (size_t j = 0; j < _matrix[i].size(); ++j)
            {
                if (i < j && _matrix[i][j] != MAX_W)
                {
                    cout << _vertexs[i] << "-" << _vertexs[j] << ":" <<
                        _matrix[i][j] << endl;
                }
            }
        }
    }

private:
    map<V, size_t> _vIndexMap;
    vector<V> _vertexs; // 顶点集合
    vector<vector<W>> _matrix; //存储边集合的矩阵
};

____________________________________________________________________________

邻接表法

邻接表:把边的信息放在一个结构体内存储起来,随后每个顶点都维护一个链表,链表的每个节点表示一个边。

1.无向图邻接表存储

无向图中同一条边在邻接表中出现了两次。如果想知道顶点vi的度,只需要知道顶点 vi边链表集合中结点的数目即可。

2.有向图邻接表存储

有向图中每条边在邻接表中只出现一次,与顶点vi对应的邻接表所含结点的个数,就是该顶点的出度,也称出度表,要得到vi顶点的入度,必须检测其他所有顶点对应的边链表。

邻接表不适合查询确定的两个顶点是否连通,如果数据量大,时间复杂度会退化至O(N)

以下是C++实现的代码

namespace LinkTable
{
    template<class W>
    struct LinkEdge
    {
        int _srcIndex;
        int _dstIndex;
        W _w;
        LinkEdge<W>* _next;
        LinkEdge(const W& w)
            : _srcIndex(-1)
            , _dstIndex(-1)
            , _w(w)
            , _next(nullptr)
        {}
    };
    template<class V, class W, bool Direction = false>
    class Graph
    {
        typedef LinkEdge<W> Edge;
    public:
        Graph(const V* vertexs, size_t n)
        {
            _vertexs.reserve(n);
            for (size_t i = 0; i < n; ++i)
            {
                _vertexs.push_back(vertexs[i]);
                _vIndexMap[vertexs[i]] = i;
            }
            _linkTable.resize(n, nullptr);
        }
        size_t GetVertexIndex(const V& v)
        {
            auto ret = _vIndexMap.find(v);
            if (ret != _vIndexMap.end())
            {
                return ret->second;
            }
            else
            {
                throw invalid_argument("不存在的顶点");
                return -1;
            }
        }
        void AddEdge(const V& src, const V& dst, const W& w)
        {
            size_t srcindex = GetVertexIndex(src);
            size_t dstindex = GetVertexIndex(dst);
            // 0 1
            Edge* sd_edge = new Edge(w);
            sd_edge->_srcIndex = srcindex;
            sd_edge->_dstIndex = dstindex;
            sd_edge->_next = _linkTable[srcindex];
            _linkTable[srcindex] = sd_edge;
            // 1 0
            // 无向图
            if (Direction == false)
            {
                Edge* ds_edge = new Edge(w);
                ds_edge->_srcIndex = dstindex;
                ds_edge->_dstIndex = srcindex;
                ds_edge->_next = _linkTable[dstindex];
                _linkTable[dstindex] = ds_edge;
            }
        }
    private:
        map<string, int> _vIndexMap;
        vector<V> _vertexs; // 顶点集合
        vector<Edge*> _linkTable;  // 边的集合的临接表
    };
}

_____________________________________________________________________________

图的遍历

想要遍历图,和树一样分为深度优先DFS和广度优先BFS

DFS

如图所示

深度优先遍历,就是优先将邻接顶点的所有其它邻接顶点都遍历完,再遍历下一个邻接顶点,其实和树是一样的。

void _DFS(size_t srci, vector<bool>& visited)
{
    cout << srci << ":" << _vertexs[srci] << endl;
    visited[srci] = true;

    // 找一个srci相邻的没有访问过的点,去往深度遍历
    for (size_t i = 0; i < _vertexs.size(); ++i)
    {
        if (_matrix[srci][i] != MAX_W && visited[i] == false)
        {
            _DFS(i, visited);
        }
    }

}

void DFS(const V& src)
{
    size_t srci = GetVertexIndex(src);
    vector<bool> visited(_vertexs.size(), false);

    _DFS(srci, visited);
}

BFS

如图所示

广度优先遍历:以A为起点,输出完上一层再输出下一层。

void BFS(const V& src)
{
    size_t srci = GetVertexIndex(src);

    // 队列和标记数组
    queue<int> q;
    vector<bool> visited(_vertexs.size(), false);

    q.push(srci);
    visited[srci] = true;
    int levelSize = 1;

    size_t n = _vertexs.size();
    while (!q.empty())
    {
        // 一层一层出
        for (int i = 0; i < levelSize; ++i)
        {
            int front = q.front();
            q.pop();
            cout << front << ":" << _vertexs[front] << " ";
            // 把front顶点的邻接顶点入队列
            for (size_t i = 0; i < n; ++i)
            {
                if (_matrix[front][i] != MAX_W)
                {
                    if (visited[i] == false)
                    {
                        q.push(i);
                        visited[i] = true;
                    }
                }
            }
        }
        cout << endl;

        levelSize = q.size();
    }

    cout << endl;
}

———————————————————————————————————————————

最小生成树

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

构造最小生成树的准则有三条:

1. 只能使用图中权值最小的边来构造最小生成树

2. 只能使用恰好n-1条边来连接图中的n个顶点

3. 选用的n-1条边不能构成回路

Kruskal算法

核心:每次迭代时,选出一条具有最小权值,且两端点不在同一连通分量上的边,加入生成树。

struct Edge
{
    size_t _srci;
    size_t _dsti;
    W _w;

    Edge(size_t srci, size_t dsti, const W& w)
        :_srci(srci)
        , _dsti(dsti)
        , _w(w)
    {}

    bool operator>(const Edge& e) const
    {
        return _w > e._w;
    }

};

W Kruskal(Self& minTree)
{
    size_t n = _vertexs.size();

    minTree._vertexs = _vertexs;
    minTree._indexMap = _indexMap;
    minTree._matrix.resize(n);
    for (size_t i = 0; i < n; ++i)
    {
        minTree._matrix[i].resize(n, MAX_W);
    }

    priority_queue<Edge, vector<Edge>, greater<Edge>> minque;
    for (size_t i = 0; i < n; ++i)
    {
        for (size_t j = 0; j < n; ++j)
        {
            if (i < j && _matrix[i][j] != MAX_W)
            {
                minque.push(Edge(i, j, _matrix[i][j]));
            }
        }
    }

    // 选出n-1条边
    int size = 0;
    W totalW = W();
    UnionFindSet ufs(n);
    while (!minque.empty())
    {
        Edge min = minque.top();
        minque.pop();

        if (!ufs.InSet(min._srci, min._dsti))
        {
            cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] <<":"<<min._w << endl;
            minTree._AddEdge(min._srci, min._dsti, min._w);
            ufs.Union(min._srci, min._dsti);
            ++size;
            totalW += min._w;
        }
        else
        {
            cout << "构成环:";
            cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
        }
    }

    if (size == n - 1)
    {
        return totalW;
    }
    else
    {
        return W();
    }
}

Prim算法

W Prim(Self& minTree, const W& src)
{
    size_t srci = GetVertexIndex(src);
    size_t n = _vertexs.size();

    minTree._vertexs = _vertexs;
    minTree._indexMap = _indexMap;
    minTree._matrix.resize(n);
    for (size_t i = 0; i < n; ++i)
    {
        minTree._matrix[i].resize(n, MAX_W);
    }

    vector<bool> X(n, false);
    vector<bool> Y(n, true);
    X[srci] = true;
    Y[srci] = false;

    // 从X->Y集合中连接的边里面选出最小的边
    priority_queue<Edge, vector<Edge>, greater<Edge>> minq;
    // 先把srci连接的边添加到队列中
    for (size_t i = 0; i < n; ++i)
    {
        if (_matrix[srci][i] != MAX_W)
        {
            minq.push(Edge(srci, i, _matrix[srci][i]));
        }
    }

    cout << "Prim开始选边" << endl;
    size_t size = 0;
    W totalW = W();
    while(!minq.empty())
    {
        Edge min = minq.top();
        minq.pop();

        // 最小边的目标点也在X集合,则构成环
        if (X[min._dsti])
        {
            cout << "构成环:";
            cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
        }
        else
        {
            minTree._AddEdge(min._srci, min._dsti, min._w);
            cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
            X[min._dsti] = true;
            Y[min._dsti] = false;
            ++size;
            totalW += min._w;
            if (size == n - 1)
                break;

            for (size_t i = 0; i < n; ++i)
            {
                if (_matrix[min._dsti][i] != MAX_W && Y[i])
                {
                    minq.push(Edge(min._dsti, i, _matrix[min._dsti][i]));
                }
            }
        }
    }

    if (size == n - 1)
    {
        return totalW;
    }
    else
    {
        return W();
    }
}

———————————————————————————————————————————

最短路径问题

最短路径问题:从在带权有向图G中的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小

最短路径问题又分为

  • 单源最短路径:给出一个起始顶点,求出该顶点到所有顶点的最短路,算法:Dijkstra算法和Bellman-Ford算法。
  • 多源最短路径:求出任意两个顶点之间的最短路径,算法:loyd-Warshall算法。

Dijkstra算法

从起始顶点出发,每次选择当前路径最短的顶点,随后尝试其它顶点是否可以通过该顶点缩短路径。

void Dijkstra(const V& src, vector<W>& dist, vector<int>& pPath)
{
    size_t srci = GetVertexIndex(src);
    size_t n = _vertexs.size();
    dist.resize(n, MAX_W);
    pPath.resize(n, -1);

    dist[srci] = 0;
    pPath[srci] = srci;

    // 已经确定最短路径的顶点集合
    vector<bool> S(n, false);

    for (size_t j = 0; j < n; ++j)
    {
        // 选最短路径顶点且不在S更新其他路径
        int u = 0;
        W min = MAX_W;
        for (size_t i = 0; i < n; ++i)
        {
            if (S[i] == false && dist[i] < min)
            {
                u = i;
                min = dist[i];
            }
        }

        S[u] = true;
        // 松弛更新u连接顶点v  srci->u + u->v <  srci->v  更新
        for (size_t v = 0; v < n; ++v)
        {
            if (S[v] == false && _matrix[u][v] != MAX_W
                && dist[u] + _matrix[u][v] < dist[v])
            {
                dist[v] = dist[u] + _matrix[u][v];
                pPath[v] = u;
            }
        }
    }
}

Bellman-Ford算法

Bellman-Ford是更加暴力的做法,每次确定两个顶点i和j,查看src->i->j是否比src->j更短。并且图中有几个顶点,这个过程最多就会执行多少次,因此其复杂度到达了O(N^{3})。虽然复杂度高,但是可以处理负数权值。
 

// 时间复杂度:O(N^3) 空间复杂度:O(N)
bool BellmanFord(const V& src, vector<W>& dist, vector<int>& pPath)
{
    size_t n = _vertexs.size();
    size_t srci = GetVertexIndex(src);

    // vector<W> dist,记录srci-其他顶点最短路径权值数组
    dist.resize(n, MAX_W);

    // vector<int> pPath 记录srci-其他顶点最短路径父顶点数组
    pPath.resize(n, -1);

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

    //cout << "更新边:i->j" << endl;


    // 总体最多更新n轮
    for (size_t k = 0; k < n; ++k)
    {
        // i->j 更新松弛
        bool update = false;
        cout << "更新第:" << k << "轮" << endl;
        for (size_t i = 0; i < n; ++i)
        {
            for (size_t j = 0; j < n; ++j)
            {
                // srci -> i + i ->j
                if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
                {
                    update = true;
                    cout << _vertexs[i] << "->" << _vertexs[j] << ":" << _matrix[i][j] << endl;
                    dist[j] = dist[i] + _matrix[i][j];
                    pPath[j] = i;
                }
            }
        }

        // 如果这个轮次中没有更新出更短路径,那么后续轮次就不需要再走了
        if (update == false)
        {
            break;
        }
    }


    // 还能更新就是带负权回路
    for (size_t i = 0; i < n; ++i)
    {
        for (size_t j = 0; j < n; ++j)
        {
            // srci -> i + i ->j
            if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
            {
                return false;
            }
        }
    }

    return true;
}

loyd-Warshall算法

Floyd-Warshall算法的核心思想是逐步扩展中间顶点的集合来计算最短路径。算法通过考虑所有可能的中间顶点来更新每一对顶点之间的最短路径长度。

void FloydWarshall(vector<vector<W>>& vvDist, vector<vector<int>>& vvpPath)
{
    size_t n = _vertexs.size();
    vvDist.resize(n);
    vvpPath.resize(n);

    // 初始化权值和路径矩阵
    for (size_t i = 0; i < n; ++i)
    {
        vvDist[i].resize(n, MAX_W);
        vvpPath[i].resize(n, -1);
    }

    // 直接相连的边更新一下
    for (size_t i = 0; i < n; ++i)
    {
        for (size_t j = 0; j < n; ++j)
        {
            if (_matrix[i][j] != MAX_W)
            {
                vvDist[i][j] = _matrix[i][j];
                vvpPath[i][j] = i;
            }

            if (i == j)
            {
                vvDist[i][j] = W();
            }
        }
    }

    // abcdef  a {} f ||  b {} c
    // 最短路径的更新i-> {其他顶点} ->j
    for (size_t k = 0; k < n; ++k)
    {
        for (size_t i = 0; i < n; ++i)
        {
            for (size_t j = 0; j < n; ++j)
            {
                // k 作为的中间点尝试去更新i->j的路径
                if (vvDist[i][k] != MAX_W && vvDist[k][j] != MAX_W
                    && vvDist[i][k] + vvDist[k][j] < vvDist[i][j])
                {
                    vvDist[i][j] = vvDist[i][k] + vvDist[k][j];

                    // 找跟j相连的上一个邻接顶点
                    // 如果k->j 直接相连,上一个点就k,vvpPath[k][j]存就是k
                    // 如果k->j 没有直接相连,k->...->x->j,vvpPath[k][j]存就是x

                    vvpPath[i][j] = vvpPath[k][j];
                }
            }
        }

        // 打印权值和路径矩阵观察数据
        for (size_t i = 0; i < n; ++i)
        {
            for (size_t j = 0; j < n; ++j)
            {
                if (vvDist[i][j] == MAX_W)
                {
                    //cout << "*" << " ";
                    printf("%3c", '*');
                }
                else
                {
                    //cout << vvDist[i][j] << " ";
                    printf("%3d", vvDist[i][j]);
                }
            }
            cout << endl;
        }
        cout << endl;

        for (size_t i = 0; i < n; ++i)
        {
            for (size_t j = 0; j < n; ++j)
            {
                printf("%3d", vvpPath[i][j]);
            }
            cout << endl;
        }
    }
}

总代码

#pragma once
#include <vector>
#include <map>
#include <set>
#include <string>
#include <queue>
#include <functional>

// weight
namespace matrix
{
template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
class Graph
{
typedef Graph<V, W, MAX_W, Direction> Self;
public:
Graph() = default;

Graph(const V* a, size_t n)
{
_vertexs.reserve(n);
for (size_t i = 0; i < n; ++i)
{
_vertexs.push_back(a[i]);
_indexMap[a[i]] = i;
}

_matrix.resize(n);
for (size_t i = 0; i < _matrix.size(); ++i)
{
_matrix[i].resize(n, MAX_W);
}
}

size_t GetVertexIndex(const V& v)
{
auto it = _indexMap.find(v);
if (it != _indexMap.end())
{
return it->second;
}
else
{
//assert(false);
cout << "不存在顶点" << ":" << v << endl;
throw invalid_argument("顶点不存在");

return -1;
}
}


void _AddEdge(size_t srci, size_t dsti, const W& w)
{
_matrix[srci][dsti] = w;
// 无向图
if (Direction == false)
{
_matrix[dsti][srci] = w;
}
}

void AddEdge(const V& src, const V& dst, const W& w)
{
size_t srci = GetVertexIndex(src);
size_t dsti = GetVertexIndex(dst);
_AddEdge(srci, dsti, w);
}

void Print()
{
// 顶点
for (size_t i = 0; i < _vertexs.size(); ++i)
{
cout << "[" << i << "]" << "->" << _vertexs[i] << endl;
}
cout << endl;

// 矩阵
// 横下标
cout << "  ";
for (size_t i = 0; i < _vertexs.size(); ++i)
{
//cout << i << " ";
printf("%4d", i);
}
cout << endl;

for (size_t i = 0; i < _matrix.size(); ++i)
{
cout << i << " "; // 竖下标
for (size_t j = 0; j < _matrix[i].size(); ++j)
{
//cout << _matrix[i][j] << " ";
if (_matrix[i][j] == MAX_W)
{
//cout << "* ";
printf("%4c", '*');
}
else
{
//cout << _matrix[i][j] << " ";
printf("%4d", _matrix[i][j]);
}
}
cout << endl;
}
cout << endl;

for (size_t i = 0; i < _matrix.size(); ++i)
{
for (size_t j = 0; j < _matrix[i].size(); ++j)
{
if (i < j && _matrix[i][j] != MAX_W)
{
cout << _vertexs[i] << "->" << _vertexs[j] << ":" << _matrix[i][j] << endl;
}
}
}

}

void BFS(const V& src)
{
size_t srci = GetVertexIndex(src);

// 队列和标记数组
queue<int> q;
vector<bool> visited(_vertexs.size(), false);

q.push(srci);
visited[srci] = true;
int levelSize = 1;

size_t n = _vertexs.size();
while (!q.empty())
{
// 一层一层出
for (int i = 0; i < levelSize; ++i)
{
int front = q.front();
q.pop();
cout << front << ":" << _vertexs[front] << " ";
// 把front顶点的邻接顶点入队列
for (size_t i = 0; i < n; ++i)
{
if (_matrix[front][i] != MAX_W)
{
if (visited[i] == false)
{
q.push(i);
visited[i] = true;
}
}
}
}
cout << endl;

levelSize = q.size();
}

cout << endl;
}

void _DFS(size_t srci, vector<bool>& visited)
{
cout << srci << ":" << _vertexs[srci] << endl;
visited[srci] = true;

// 找一个srci相邻的没有访问过的点,去往深度遍历
for (size_t i = 0; i < _vertexs.size(); ++i)
{
if (_matrix[srci][i] != MAX_W && visited[i] == false)
{
_DFS(i, visited);
}
}

}

void DFS(const V& src)
{
size_t srci = GetVertexIndex(src);
vector<bool> visited(_vertexs.size(), false);

_DFS(srci, visited);
}

struct Edge
{
size_t _srci;
size_t _dsti;
W _w;

Edge(size_t srci, size_t dsti, const W& w)
:_srci(srci)
, _dsti(dsti)
, _w(w)
{}

bool operator>(const Edge& e) const
{
return _w > e._w;
}

};

W Kruskal(Self& minTree)
{
size_t n = _vertexs.size();

minTree._vertexs = _vertexs;
minTree._indexMap = _indexMap;
minTree._matrix.resize(n);
for (size_t i = 0; i < n; ++i)
{
minTree._matrix[i].resize(n, MAX_W);
}

priority_queue<Edge, vector<Edge>, greater<Edge>> minque;
for (size_t i = 0; i < n; ++i)
{
for (size_t j = 0; j < n; ++j)
{
if (i < j && _matrix[i][j] != MAX_W)
{
minque.push(Edge(i, j, _matrix[i][j]));
}
}
}

// 选出n-1条边
int size = 0;
W totalW = W();
UnionFindSet ufs(n);
while (!minque.empty())
{
Edge min = minque.top();
minque.pop();

if (!ufs.InSet(min._srci, min._dsti))
{
//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] <<":"<<min._w << endl;
minTree._AddEdge(min._srci, min._dsti, min._w);
ufs.Union(min._srci, min._dsti);
++size;
totalW += min._w;
}
else
{
//cout << "构成环:";
//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
}
}

if (size == n - 1)
{
return totalW;
}
else
{
return W();
}
}

W Prim(Self& minTree, const W& src)
{
size_t srci = GetVertexIndex(src);
size_t n = _vertexs.size();

minTree._vertexs = _vertexs;
minTree._indexMap = _indexMap;
minTree._matrix.resize(n);
for (size_t i = 0; i < n; ++i)
{
minTree._matrix[i].resize(n, MAX_W);
}

/*set<int> X;
set<int> Y;
X.insert(srci);
for (size_t i = 0; i < n; ++i)
{
if (i != srci)
{
Y.insert(i);
}
}*/

vector<bool> X(n, false);
vector<bool> Y(n, true);
X[srci] = true;
Y[srci] = false;

// 从X->Y集合中连接的边里面选出最小的边
priority_queue<Edge, vector<Edge>, greater<Edge>> minq;
// 先把srci连接的边添加到队列中
for (size_t i = 0; i < n; ++i)
{
if (_matrix[srci][i] != MAX_W)
{
minq.push(Edge(srci, i, _matrix[srci][i]));
}
}

cout << "Prim开始选边" << endl;
size_t size = 0;
W totalW = W();
while (!minq.empty())
{
Edge min = minq.top();
minq.pop();

// 最小边的目标点也在X集合,则构成环
if (X[min._dsti])
{
//cout << "构成环:";
//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
}
else
{
minTree._AddEdge(min._srci, min._dsti, min._w);
//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
X[min._dsti] = true;
Y[min._dsti] = false;
++size;
totalW += min._w;
if (size == n - 1)
break;

for (size_t i = 0; i < n; ++i)
{
if (_matrix[min._dsti][i] != MAX_W && Y[i])
{
minq.push(Edge(min._dsti, i, _matrix[min._dsti][i]));
}
}
}
}

if (size == n - 1)
{
return totalW;
}
else
{
return W();
}
}

void PrintShortPath(const V& src, const vector<W>& dist, const vector<int>& pPath)
{
size_t srci = GetVertexIndex(src);
size_t n = _vertexs.size();
for (size_t i = 0; i < n; ++i)
{
if (i != srci)
{
// 找出i顶点的路径
vector<int> path;
size_t parenti = i;
while (parenti != srci)
{
path.push_back(parenti);
parenti = pPath[parenti];
}
path.push_back(srci);
reverse(path.begin(), path.end());

for (auto index : path)
{
cout << _vertexs[index] << "->";
}
cout << "权值和:" << dist[i] << endl;
}
}
}

// 顶点个数是N  -> 时间复杂度:O(N^2)空间复杂度:O(N)
void Dijkstra(const V& src, vector<W>& dist, vector<int>& pPath)
{
size_t srci = GetVertexIndex(src);
size_t n = _vertexs.size();
dist.resize(n, MAX_W);
pPath.resize(n, -1);

dist[srci] = 0;
pPath[srci] = srci;

// 已经确定最短路径的顶点集合
vector<bool> S(n, false);

for (size_t j = 0; j < n; ++j)
{
// 选最短路径顶点且不在S更新其他路径
int u = 0;
W min = MAX_W;
for (size_t i = 0; i < n; ++i)
{
if (S[i] == false && dist[i] < min)
{
u = i;
min = dist[i];
}
}

S[u] = true;
// 松弛更新u连接顶点v  srci->u + u->v <  srci->v  更新
for (size_t v = 0; v < n; ++v)
{
if (S[v] == false && _matrix[u][v] != MAX_W
&& dist[u] + _matrix[u][v] < dist[v])
{
dist[v] = dist[u] + _matrix[u][v];
pPath[v] = u;
}
}
}
}

// 时间复杂度:O(N^3) 空间复杂度:O(N)
bool BellmanFord(const V& src, vector<W>& dist, vector<int>& pPath)
{
size_t n = _vertexs.size();
size_t srci = GetVertexIndex(src);

// vector<W> dist,记录srci-其他顶点最短路径权值数组
dist.resize(n, MAX_W);

// vector<int> pPath 记录srci-其他顶点最短路径父顶点数组
pPath.resize(n, -1);

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

//cout << "更新边:i->j" << endl;


// 总体最多更新n轮
for (size_t k = 0; k < n; ++k)
{
// i->j 更新松弛
bool update = false;
cout << "更新第:" << k << "轮" << endl;
for (size_t i = 0; i < n; ++i)
{
for (size_t j = 0; j < n; ++j)
{
// srci -> i + i ->j
if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
{
update = true;
cout << _vertexs[i] << "->" << _vertexs[j] << ":" << _matrix[i][j] << endl;
dist[j] = dist[i] + _matrix[i][j];
pPath[j] = i;
}
}
}

// 如果这个轮次中没有更新出更短路径,那么后续轮次就不需要再走了
if (update == false)
{
break;
}
}


// 还能更新就是带负权回路
for (size_t i = 0; i < n; ++i)
{
for (size_t j = 0; j < n; ++j)
{
// srci -> i + i ->j
if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
{
return false;
}
}
}

return true;
}

void FloydWarshall(vector<vector<W>>& vvDist, vector<vector<int>>& vvpPath)
{
size_t n = _vertexs.size();
vvDist.resize(n);
vvpPath.resize(n);

// 初始化权值和路径矩阵
for (size_t i = 0; i < n; ++i)
{
vvDist[i].resize(n, MAX_W);
vvpPath[i].resize(n, -1);
}

// 直接相连的边更新一下
for (size_t i = 0; i < n; ++i)
{
for (size_t j = 0; j < n; ++j)
{
if (_matrix[i][j] != MAX_W)
{
vvDist[i][j] = _matrix[i][j];
vvpPath[i][j] = i;
}

if (i == j)
{
vvDist[i][j] = W();
}
}
}

// abcdef  a {} f ||  b {} c
// 最短路径的更新i-> {其他顶点} ->j
for (size_t k = 0; k < n; ++k)
{
for (size_t i = 0; i < n; ++i)
{
for (size_t j = 0; j < n; ++j)
{
// k 作为的中间点尝试去更新i->j的路径
if (vvDist[i][k] != MAX_W && vvDist[k][j] != MAX_W
&& vvDist[i][k] + vvDist[k][j] < vvDist[i][j])
{
vvDist[i][j] = vvDist[i][k] + vvDist[k][j];

// 找跟j相连的上一个邻接顶点
// 如果k->j 直接相连,上一个点就k,vvpPath[k][j]存就是k
// 如果k->j 没有直接相连,k->...->x->j,vvpPath[k][j]存就是x

vvpPath[i][j] = vvpPath[k][j];
}
}
}

// 打印权值和路径矩阵观察数据
for (size_t i = 0; i < n; ++i)
{
for (size_t j = 0; j < n; ++j)
{
if (vvDist[i][j] == MAX_W)
{
//cout << "*" << " ";
printf("%3c", '*');
}
else
{
//cout << vvDist[i][j] << " ";
printf("%3d", vvDist[i][j]);
}
}
cout << endl;
}
cout << endl;

for (size_t i = 0; i < n; ++i)
{
for (size_t j = 0; j < n; ++j)
{
//cout << vvParentPath[i][j] << " ";
printf("%3d", vvpPath[i][j]);
}
cout << endl;
}
}
}

private:
vector<V> _vertexs;// 顶点集合
map<V, int> _indexMap;// 顶点映射下标
vector<vector<W>> _matrix;  // 邻接矩阵
};


原文地址:https://blog.csdn.net/odoaobo/article/details/143952014

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!