[C++][数据结构][图][上]详细讲解


1.图的基本概念

  • 图是由顶点集合及顶点间的关系组成的一种数据结构 G = ( V , E ) G = (V, E) G=(VE)

    • 顶点集合:V = {x|x属于某个数据对象集}是有穷非空集合
    • 边的集合:E = {(x,y)|x,y属于V}或者E = {|x,y属于V && Path(x, y)}是顶点间关系的有穷集合
      • ( x , y ) (x, y) (x,y)表示x到y的一条双向通路,即** ( x , y ) (x, y) (x,y)是无方向的**
      • Path(x, y)表示从x到y的一条单向通路,即Path(x, y)是有方向的
    • [图:graph],[顶点:vertex],[边:edge]
  • 顶点和边

    • 图中结点称为顶点,第i个顶点记作 v i vi vi
    • 两个顶点 v i v_i vi v j v_j vj相关联称作顶点 v i v_i vi和顶点 v j v_j vj之间有一条边,图中的第k条边记作 e k e_k ek e k = ( v i , v j ) e_k = (v_i,v_j) ek=(vivj) < v i , v j > <v_i, v_j> <vi,vj>
  • 有向图和无向图:

    • 有向图中,顶点对 < x , y > <x, y> <x,y>是有序的,顶点对 < x , y > <x, y> <x,y>称为顶点x到顶点y的一条边(弧), < x , y > <x, y> <x,y> < y , x > <y, x> <y,x>是两条不同的边,比如下图G3和G4为有向图
    • 无向图中,顶点对 ( x , y ) (x, y) (x,y) 是无序的,顶点对 ( x , y ) (x,y) (x,y)称为顶点x和顶点y相关联的一条边,这条边没有特定方向, ( x , y ) (x, y) (x,y) ( y , x ) (y,x) (yx)是同一条边,比如下图G1和G2为无向图
    • **注意:**无向边(x, y)等于有向边 < x , y > <x, y> <x,y> < y , x > <y, x> <y,x>
  • 说明:

    • 树是一种特殊的图(无环连通)
      • 树关注节点(顶点)中存的值
    • 图不一定是树
      • 图关注顶点及边的权值
        请添加图片描述
  • 完全图

    • 有n个顶点的无向图中,若有 n ∗ ( n − 1 ) / 2 n * (n-1)/2 n(n1)/2条边,即任意两个顶点之间有且仅有一条边, 则称此图为无向完全图,比如上图G1;
    • 有n个顶点的有向图中,若有 n ∗ ( n − 1 ) n * (n-1) n(n1)条边,即任意两个顶点之间有且仅有方向相反的边,则称此图为有向完全图,比如上图G4
  • 邻接顶点

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

    • 顶点v的度是指与它相关联的边的条数,记作 d e g ( v ) deg(v) deg(v)
    • 有向图中,顶点的度等于该顶点的入度与出度之和
      • 顶点v的入度以v为终点的有向边的条数,记作 i n d e v ( v ) indev(v) indev(v)
      • 顶点v的出度以v为起始点的有向边的条数,记作 o u t d e v ( v ) outdev(v) outdev(v)
      • 因此: d e v ( v ) = i n d e v ( v ) + o u t d e v ( v ) dev(v) = indev(v) + outdev(v) dev(v)=indev(v)+outdev(v)
    • 对于无向图顶点的度等于该顶点的入度和出度
      • 即: d e v ( v ) = i n d e v ( v ) = o u t d e v ( v ) dev(v) = indev(v) = outdev(v) dev(v)=indev(v)=outdev(v)
  • 路径:在图 G = ( V , E ) G = (V, E) G=(VE)中,若从顶点 v i v_i vi出发有一组边使其可到达顶点 v j v_j vj,则称顶点 v i v_i vi到顶点 v j v_j vj的顶点序列为从顶点 v i v_i vi到顶点 v j v_j vj的路径

  • 路径长度

    • 对于不带权的图,一条路径的路径长度是指该路径上的边的条数
    • 对于带权的图,一条路径的路径长度是指该路径上各个边权值的总和
      请添加图片描述
  • 简单路径与回路

    • 若路径上各顶点 v 1 , v 2 , v 3 , … , v m v_1,v_2,v_3,…,v_m v1v2v3vm均不重复,则称这样的路径为简单路径
    • 若路径上第一个顶点 v 1 v_1 v1和最后一个顶点 v m v_m vm重合,则称这样的路径为回路或环
      请添加图片描述
  • 子图

    • 设图 G = ( V , E ) G = (V, E) G=(V,E)和图 G 1 = ( V 1 , E 1 ) G_1 = (V_1,E_1) G1=(V1E1),若 V 1 V_1 V1属于 V V V E 1 E_1 E1属于 E E E,则称 G 1 G_1 G1 G G G的子图
    • 顶点和边都是原图的一部分
      请添加图片描述
  • 连通图

    • 无向图中,若从顶点 v 1 v_1 v1到顶点 v 2 v_2 v2有路径,则称顶点 v 1 v_1 v1与顶点 v 2 v_2 v2是连通的
    • 如果图中任意一对顶点都是连通的,则称此图为连通图
  • 强连通图:在有向图中,若在每一对顶点 v i v_i vi v j v_j vj之间都存在一条从 v i v_i vi v j v_j vj的路径,也存在一条从 v j v_j vj v i v_i vi的路径,则称此图是强连通图

  • 生成树

    • 一个连通图的最小连通子图称作该图的生成树
    • 有n个顶点的连通图的生成树有n个顶点和 n − 1 n-1 n1条边

2.图的存储结构

1.邻接矩阵

  • 节点与节点之间的关系就是连通与否,即0或1

    • 邻接矩阵即:先用一个数组将定点保存,然后采用矩阵来表示节点与节点之间的关系
      请添加图片描述
  • 注意:

    • 无向图的邻接矩阵是对称的,第i行(列)元素之和,就是顶点i的度
    • 有向图的邻接矩阵则不一 定是对称的,第i行(列)元素之后就是顶点i的出(入)度
    • 如果边带有权值,并且两个节点之间是连通的,上图中的边的关系就用权值代替,如果两个顶点不通,则使用无穷大代替
  • 特点:

    • 优点:
      • 邻接矩阵存储方式非常适合稠密图
      • 邻接矩阵O(1)判断两个顶点的连接关系,并取到权值
    • 缺点:
      • 如果顶点比较多,边比较少时,矩阵中存储了大量的0成为系数矩阵,比较浪费空间
      • 相对而言,不适合查找一个顶点连接的所有的边 – O(N)
// V -> vertex  W -> weight
template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
class Graph
{
public:
    Graph(const V* arr, size_t n)
    {
        _vertexs.resize(n);
        for (size_t i = 0; i < n; i++)
        {
            _vertexs[i] = arr[i];
            _indexMap[arr[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
        {
            throw invalid_argument("Vertex isn't exist");
            return -1;
        }
    }

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

        _matrix[srci][dsti] = w;

        // 无向图
        if (Direction == false)
        {
            _matrix[dsti][srci] = 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 < _matrix.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 << "* ";
                }
                else
                {
                    cout << _matrix[i][j] << " ";
                }
            }
            cout << endl;
        }
    }
private:
    vector<V> _vertexs;        // 顶点集合
    map<V, int> _indexMap;     // 顶点映射下标
    vector<vector<W>> _matrix; // 邻接矩阵
};

2.邻接表

  • 使用数组表示顶点的集合使用链表表示边的关系
  • 特点:
    • 优点:
      • 适合存储稀疏图
      • 适合查找一个顶点连接出去的边
    • 缺点:
      • 不适合确定两个顶点是否相连及权值

1.无向图邻接表

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

    请添加图片描述

2.有向图邻接表

  • 注意有向图中每条边在邻接表中只出现一次
    • 与顶点 v i v_i vi对应的邻接表所含结点的个数,就是该顶点的出度,也称出度表
      • 一般情况下,有向图,存储一个出边表即可
    • 要得到 v i v_i vi顶点的入度,必须检测其他所有顶点对应的边链表,看有多少边顶点的 d s t dst dst取值是 i i i
template<class W>
struct Edge
{
    Edge(int dsti, const W& w)
        : _dsti(dsti)
        , _w(w)
    {}

    int _dsti; // 目标点下标
    W _w;      // 权值
    Edge* _next;
};

// V -> vertex  W -> weight
template<class V, class W, bool Direction = false>
class Graph
{
    typedef Edge<W> Edge;
public:
    Graph(const V* arr, size_t n)
    {
        _vertexs.resize(n);
        for (size_t i = 0; i < n; i++)
        {
            _vertexs[i] = arr[i];
            _indexMap[arr[i]] = i;
        }

        _tables.resize(n, nullptr);
    }
        
    size_t GetVertexIndex(const V& v)
    {
        auto it = _indexMap.find(v);
        if (it != _indexMap.end())
        {
            return it->second;
        }
        else
        {
            throw invalid_argument("Vertex isn't exist");
            return -1;
        }
    }

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

        Edge* eg = new Edge(dsti, w);
        eg->_next = _tables[srci]; // 头插
        _tables[srci] = eg;

        // 无向图
        if (Direction == false)
        {
            Edge* eg = new Edge(srci, w);
            eg->_next = _tables[dsti]; // 头插
            _tables[dsti] = eg;
        }
    }

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

        for (size_t i = 0; i < _tables.size(); i++)
        {
            cout << _vertexs[i] << "[" << i << "]->";
            Edge* cur = _tables[i];
            while (cur)
            {
                cout << "[" << _vertexs[cur->_dsti] << ":" \
	                << cur->_dsti << ":" << cur->_w << "]->";
                cur = cur->_next;
            }
            cout << "nullptr" << endl;
        }
    }
private:
    vector<V> _vertexs;        // 顶点集合
    map<V, int> _indexMap;     // 顶点映射下标
    vector<Edge*> _tables;     // 邻接表
};

3.总结

  • 邻接矩阵和邻接表相辅相成,是各有优缺点的互补结构
  • 37
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DieSnowK

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值