基本思想

  • 用一维数组存储顶点:描述顶点相关的数据
  • 用二维数组存储边:描述顶点间的关系和权

邻接矩阵法

设图 A = (V, E) 是一个有 n 个顶点的图,图的邻接矩阵为 Edgen, 则:

image.png

注:
解决工程问题时,习惯于对图中的每个顶点进行编号;
当需要权值时,取 W 非空表示结点间有连接。

无向图邻接矩阵法

  • 无向图的邻接矩阵是对称的

image.png

有向图邻接矩阵法

  • 有向图的邻接矩阵可能是不对称的

image.png

设计与实现

问题:如何具体表示顶点集数据?如何具体表示边集数组?

image.png

实现方式一

直接使用数组表示顶点集和边集
template <int N, typename V, typename E>
class MatrixGraph : public Graph<V, E>
{
protected:
    V m_vertexes[N];
    E m_edges[N][N];
    int m_edges[N][N];
    
public:
    // ...
};

效率分析

struct TV
{
    int a1[100];
    char a2[1000];
    
    TV() {/* init array */}
};

struct TE
{
    float a1[100];
    long a2[1000];
    
    TE() {/* init array */}
};

int main()
{
    MatrixGraph<1000, TV, TE> g;
    
    // ...
}

问题

  • 构造效率低下

    • 图对象构造时,频繁调用顶点类型和边类型的构造函数
  • 空间使用率低下

    • 图对象占用大量空间,而大多数空间处于闲置状态(如:含1000个顶点的图,只有少量几条边时)
  • 无法表示空值

    • 无法用统一的方式表示边为空的情形(例:当边相关类型为自定义类型时)

实现方式二

使用指针数组表示顶点集边集
template <int N, typename V, typename E>
class MatrixGraph : public Graph<V, E>
{
protected:
    V *m_vertexes[N];
    E *m_edges[N][N];
    int m_eCount;

public:
    // ...
};

问题的解决

  • 构造效率

    • 初始化图对象时,只需要将数组元素赋值为空
  • 空间使用率

    • 顶点数据元素和边数据元素在需要时动态创建
  • 空值的表示

    • 任意的顶点类型和边类型都使用 NULL 表示空值

编程实验: 图的邻接矩阵结构

文件:MatrixGraph.h

#ifndef MATRIXGRAPH_H
#define MATRIXGRAPH_H

#include "Graph.h"
#include "Exception.h"
#include "DynamicArray.h"

namespace DTLib
{

template <int N, typename V, typename E>
class MatrixGraph : public Graph<V, E>
{
public:
    MatrixGraph() = default;

    V getVertex(int i) const override
    {
        V ret;

        if (!getVertex(i, ret))
        {
            THROW_EXCEPTION(InvalidParameterExcetion, "Parameter i is invalid ...");
        }

        return ret;
    }

    bool getVertex(int i, V &value) const override
    {
        bool ret = ((0 <= i) && (i < vCount()));

        if (ret)
        {
            if (m_vertexes[i] != nullptr)
            {
                value = *(m_vertexes[i]);
            }
            else
            {
                THROW_EXCEPTION(InvalidOpertionExcetion, "No value assigned to this vertex ...");
            }
        }

        return ret;
    }

    bool setVertex(int i, const V &value) override
    {
        bool ret = ((0 <= i) && (i < vCount()));

        if (ret)
        {
            V *data = m_vertexes[i];

            if (data == nullptr)
            {
                data = new V();
            }

            if (data != nullptr)
            {
                *data = value;

                m_vertexes[i] = data;  // 异常安全!!!
            }
            else
            {
                THROW_EXCEPTION(NoEnoughMemoryException, "No memory to store new vertex value ...");
            }
        }

        return ret;
    }

    SharedPointer<Array<int>> getAdjacent(int i) const override
    {
        DynamicArray<int> *ret = nullptr;

        if ((0 <= i) && (i < vCount()))
        {
            int n = 0;

            for (int j=0; j<vCount(); ++j)
            {
                if (m_edges[i][j] != nullptr)
                {
                    ++n;
                }
            }

            ret = new DynamicArray<int>(n);

            if (ret != nullptr)
            {
                for (int j=0, k=0; j<vCount(); ++j)
                {
                    if (m_edges[i][j] != nullptr)
                    {
                        ret->set(k++, j);
                    }
                }
            }
            else
            {
                THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create ret obj ...");
            }
        }
        else
        {
            THROW_EXCEPTION(InvalidParameterExcetion, "Parameter i is invalid ...");
        }

        return ret;
    }

    E getEdge(int i, int j) const override
    {
        E ret;

        if (!getEdge(i, j, ret))
        {
            THROW_EXCEPTION(InvalidParameterExcetion, "Parameter <i, j> is invalid ...");
        }

        return ret;
    }

    bool getEdge(int i, int j, E &value) const override
    {
        bool ret = ((0 <= i) && (i < vCount()) &&
                    (0 <= j) && (j < vCount()));

        if (ret)
        {
            if (m_edges[i][j] != nullptr)
            {
                value = *(m_edges[i][j]);
            }
            else
            {
                THROW_EXCEPTION(InvalidOpertionExcetion, "No value assigned to this edge ...");
            }
        }

        return ret;
    }

    bool setEdge(int i, int j, const E &value) override
    {
        bool ret = ((0 <= i) && (i < vCount()) &&
                    (0 <= j) && (j < vCount()));

        if (ret)
        {
            E *ne = m_edges[i][j];

            if (ne != nullptr)
            {
                *ne = value;
            }
            else
            {
                ne = new E();

                if (ne != nullptr)
                {
                    *ne = value;

                    m_edges[i][j] = ne;  // 异常安全!!!

                    ++m_eCount;
                }
                else
                {
                    THROW_EXCEPTION(NoEnoughMemoryException, "No memory to store new edge value ...");
                }
            }
        }

        return ret;
    }

    bool removeEdge(int i, int j) override
    {
        bool ret = ((0 <= i) && (i < vCount()) &&
                    (0 <= j) && (j < vCount()));

        if (ret)
        {
            E *todel = m_edges[i][j];

            m_edges[i][j] = nullptr;

             if (todel != nullptr)
             {
                 --m_eCount;

                 delete todel;
             }
        }

        return ret;
    }

    int vCount() const override
    {
        return N;
    }

    int eCount() const override
    {
        return m_eCount;
    }

    int OD(int i) const override
    {
        int ret = 0;

        if ((0 <= i) && (i < vCount()))
        {
            for (int j=0; j<vCount(); ++j)
            {
                if (m_edges[i][j] != nullptr)
                {
                    ++ret;
                }
            }
        }
        else
        {
            THROW_EXCEPTION(InvalidParameterExcetion, "Parameter i is invald ...");
        }

        return ret;
    }

    int ID(int i) const override
    {
        int ret = 0;

        if ((0 <= i) && (i < vCount()))
        {
            for (int j=0; j<vCount(); ++j)
            {
                if (m_edges[j][i] != nullptr)
                {
                    ++ret;
                }
            }
        }
        else
        {
            THROW_EXCEPTION(InvalidParameterExcetion, "Parameter i is invald ...");
        }

        return ret;
    }

    ~MatrixGraph()
    {
        for (int i=0; i<vCount(); ++i)
        {
            for (int j=0; j<vCount(); ++j)
            {
                delete m_edges[i][j];
            }

            delete m_vertexes[i];
        }
    }

protected:
    V *m_vertexes[N] = {0};
    E *m_edges[N][N] = {0};
    int m_eCount = 0;

    MatrixGraph(const MatrixGraph &) = default;
    MatrixGraph &operator = (const MatrixGraph &) = default;
};

}

#endif // MATRIXGRAPH_H

文件:main.cpp

#include <iostream>
#include "BTreeNode.h"
#include "MatrixGraph.h"

using namespace std;
using namespace DTLib;

int main()
{
    MatrixGraph<3, int, int> g;

    g.setEdge(0, 1, 1);
    g.setEdge(1, 0, 2);
    g.setEdge(1, 2, 3);

    cout << "vCount : " << g.vCount() << endl;
    cout << "eCount : " << g.eCount() << endl;
    cout << "ID(1) : " << g.ID(1) << endl;
    cout << "OD(1) : " << g.OD(1) << endl;
    cout << "TD(1) : " << g.TD(1) << endl;

    SharedPointer<Array<int>> aj = g.getAdjacent(1);

    cout << "Adjacent(1) : " ;
    for (int i=0; i<aj->length(); ++i)
    {
        cout << (*aj)[i] << " ";
    }
    cout << endl;

    cout << "delete edge:" << endl;
    g.removeEdge(0, 1);

    cout << "eCount : " << g.eCount() << endl;

    g.setVertex(0, 100);

    cout << "V(0) : " << g.getVertex(0) << endl;

    cout << "W(0, 1) : " << g.getEdge(0, 1) << endl;

    return 0;
}

输出:

vCount : 3
eCount : 3
ID(1) : 1
OD(1) : 2
TD(1) : 3
Adjacent(1) : 0 2
delete edge:
eCount : 2
V(0) : 100
W(0, 1) : terminate called after throwing an instance of 'DTLib::InvalidOpertionExcetion'

小结

  • 邻接矩阵法使用数组对图相关的数据进行存储
  • 一维数组存储顶点相关的数据(空表示无相关数据)
  • 二维数组存储边相关的数据(空表示结点间无连接)
  • 代码实现时使用指针数组进行数据的存储(提高效率)

以上内容整理于狄泰软件学院系列课程,请大家保护原创!


TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧