【图】图的相关概念以及图的存储

来源:互联网 发布:淘宝超级运动会是什么 编辑:程序博客网 时间:2024/06/05 13:56

图的定义

图是一种复杂的非线性结构。

在图形结构中,节点之间的关系是任意的,图中任意两个数据元素之间都有可能相关。

图G由两个集合V(顶点Vertex)和E(边Edge)组成,定义为G=(V,E)。

图的相关概念

1、无向图

无向边:若顶点Vi到Vj之间的边没有方向,则称这条边为无向边,用无序偶对(Vi,Vj)来表示。

如果图中任意两个顶点时间的边都是无向边,则称该图为无向图,
G=(V, {E})、0≤边≤n(n-1)/2。

这里写图片描述

上图表示:
G=(V,{E}) 其中顶点集合V={A,B,C,D};边集合E={(A,B),(B,C),(C,D),(D,A),(A,C)}

有向图

有向边:若从顶点Vi到Vj的边有方向,则称这条边为有向边,也称为弧。

用有序偶 < Vi,Vj > 来表示,Vi称为弧尾,Vj称为弧头。

如果图中任意两个顶点之间的边都是有向边,则称该图为有向图:
G=(V, {A})、0≤弧≤n(n-1)。

这里写图片描述

上图表示:
连接顶点A到D的有向边就是弧,A是弧尾,D是弧头,< A, D>表示弧。注意不能写成< D, A>。

对于如上有向图来说,G=(V,{E})其中顶点集合V={A,B,C,D};弧集合E={< A,D >, < B, A>,< C,A >,< B,C >}

3、完全图
完全无向图:若有n个顶点的无向图有n(n-1)/2 条边, 则此图为完全无向图。即,所有的边都是无向边。

完全有向图:有n个顶点的有向图有n(n-1)条边, 则此图为完全有向图。即,所有的边都是有向边。

4、邻接顶点

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

5、顶点的度

顶点的度:顶点v的度是指与它相关联的边的条数,记作deg(v)。

在有向图中,顶点的度等于该顶点的入度与出度之和,其中顶点v的入度是以v为终点的有向边的条数,记作indev(v);顶点v的出度是以v为起始点的有向边的条数,记作outdev(v)。因此:
dev(v) = indev(v) + outdev(v)

对于无向图,顶点的度等于该顶点的入度和出度,即dev(v) = indev(v) = outdev(v)。

记住,不管是无向图还是有向图,顶点数n,边数e和顶点的度数有如下关系:

这里写图片描述

因此,就拿有向图(b)来举例,由公式可以得到图G的边数e=(D(V1)+D(V2)+D(V3)+D(V4))/2=(3+2+2+1)/2=4

6、路径长度

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

7、连通图(无向图)

在无向图G中,如果图中任意两个顶点vi, vj属于V,vi和vj都是连通的,则图G是连通图。

连通分量:无向图中的极大连通子图

*8、强连通图(有向图)

在有向图G中,如果每一对顶点vi, vj属于V且vi不等于vj,从vi到vj与从vj到vi都存在路径,则图G是连通图。

强连通分量:有向图的极大强连通子图。

9、生成树

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

如果在生成树上添加一条边,必定构成一个环:因为这条边使得它依附的那两个顶点之间有了第二条路径。

一个有n个顶点的生成树有且仅有n-1条边。如果一个图有n个顶点和小于n-1条的边,则是非连通图。如果多余n-1条边,则一定有环。但有n-1条边的图不一定是生成树。

图的存储

(1)邻接矩阵

其实就是用两个数组来表示图。
一维数组存储图中数据元素(顶点)的信息,
二维数组(邻接矩阵)存储图中数据元素之间的关系(边或弧)的信息。

无向图

这里写图片描述

从上面可以看出,无向图的边数组是一个对称矩阵。
从这个矩阵中,很容易知道图中的信息。
(1)要判断任意两顶点是否有边无边就很容易了;
(2)要计算某个顶点的度,其实就是这个顶点vi在邻接矩阵中第i行或(第i列)的元素之和;
(3)求顶点vi的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]=1的vj就是邻接点;

而有向图有入度和出度之分:
顶点vi的入度为是第i列各数之和,顶点vi的出度是第i行的各数之和。

有向图

若图G是网图,有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:

这里写图片描述

这里写图片描述

可以看出:

(1)第i行权重大于0、小于无穷的个数之和为顶点vi的出度(OD(vi));

(2)第j列权重大于0、小于无穷的个数之和为顶点vj的入度(ID(vj));

(3)第i行和第i列权重大于0、小于无穷的个数之和即为顶点vi的度(TD(vi) = OD(vi) + ID(vj))。

领结矩阵缺点

邻接矩阵形式存储图结构,当e远远小于n^2时,大量的元素是0,造成空间浪费。

邻接矩阵 代码实现:
该类中有三个成员,
一维数组 vector< v> _ver;//存放结点 比如 char A B;
二维数组 vector< vector > _edge;//存放边的权值
bool变量 bool _IsDirected; //true 为有向图

//V表示顶点的类型,比如字符char,数字int//W表示权值,比如int template<class V, class W>class GraphMatrix{public:    //默认为无向图,构造函数必须给出顶点的数组及其大小    GraphMatrix(const V* pVer, size_t size, bool isDirected = false)        :_IsDirected(isDirected)    {        //构建两个数组        _ver.resize(size);        _edge.resize(size);        for (size_t i = 0; i < size; ++i)        {            _ver[i] = pVer[i];//构建一维数组顶点值            _edge[i].resize(size);//构建二维数组        }    }    //返回节点的下标    int GetIndexOfVertex(const V& v)    {        for (size_t i = 0; i < _ver.size(); ++i)        {            if (_ver[i] == v)                return i;        }        assert(false);        return -1;    }    //添加边,生成图    //对应无向图,需要在二维数组中添加两次,对称    void AddEdge(const V& v1, const V& v2, const W& weight)    {        int row = GetIndexOfVertex(v1);        int col = GetIndexOfVertex(v2);        if (_IsDirected)//有向图        {            _edge[row][col] = weight;        }        else//无向图        {            _edge[row][col] = weight;            _edge[col][row] = weight;        }    }    void Print()    {        for (size_t i = 0; i < _edge.size(); ++i)        {            for (size_t j = 0; j < _edge[i].size(); ++j)            {                cout << _edge[i][j] << " ";            }            cout << endl;        }        cout << endl;    }private:    vector<V> _ver;//存放结点 比如 char A B;    vector<vector<W> > _edge;//存放边的权值    bool _IsDirected; //true 为有向图};

(2) 邻接表

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

无向图

这里写图片描述

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

有向图

这里写图片描述

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

邻接表代码实现

实现邻接表,必须有单链表,先给出单链表的节点设计,
四个成员,权值,源节点,目的节点,next指针域。

template<class W>struct LinkEdge{    W _edge;//权值    size_t _srcIndex;//源节点    size_t _desIndex; //目的节点    struct LinkEdge<W>* _pNext;//next指针域    LinkEdge(size_t srcIdx, size_t desIdx, const W& weight)        : _edge(weight)        , _srcIndex(srcIdx)        , _desIndex(desIdx)        , _pNext(NULL)    {}};

插入一条记录,采用头插的方法,

template<class V, class W>class GraphLink{public:    GraphLink()    {}    GraphLink(const V* pVex, size_t size, bool isDirected)    {        _vex.resize(size);        _LinkTable.resize(size, NULL);        for (size_t i = 0; i < size; ++i)            _vex[i] = pVex[i];    }    //头插法    void _AddEdge(int srcIdx, int desIdx,const  W& weight)//为了防止代码重复    {        LinkEdge<W>* pEdge = new LinkEdge<W>(srcIdx, desIdx, weight);        pEdge->_pNext = _LinkTable[srcIdx];        _LinkTable[srcIdx] = pEdge;    }    //有向图插入权值,只插入一次,无向图插入两次    void AddEdge(const V& v1, const V& v2, const W& weight)    {        int srcIdx = GetIndexOfVertex(v1);        int desIdx = GetIndexOfVertex(v2);        assert(srcIdx != desIdx);        //判断是否为有向图:有向图插入权值,只插入一次        _AddEdge(srcIdx, desIdx, weight);        if (!_IsDirected)            _AddEdge(desIdx, srcIdx, weight);    }    //获取某个节点的度数:有向图出度和入度    int GetDev(const V& v)    {        //先求出度(有向图和无向图 都要求)        int VertexIdx = GetIndexOfVertex(v);        //遍历链表        int OutCount= 0;        LinkEdge<W> *pEdge = _LinkTable[VertexIdx];        while (pEdge)        {            ++OutCount;            pEdge = pEdge->_pNext;        }        //求入度        int InCount= 0;        if (_IsDirected)        {            for (size_t i = 0; i < _LinkTable.size(); ++i)            {                LinkEdge<W> *pEdge = _LinkTable[i];                if (i != VertexIdx)                {                    while (pEdge)                    {                        if (pEdge->_desIndex == VertexIdx)                            ++InCount;                    }                }            }        }        return InCount + OutCount;    }    void Print()    {        cout << " 图的构成:" << endl;        for (size_t i = 0; i < _LinkTable.size(); ++i)        {            LinkEdge<W>* pCur = _LinkTable[i];            cout << i;            while (pCur)            {                cout << " -->" << pCur->_desIndex << "[" << pCur->_edge << "]";                pCur = pCur->_pNext;            }            cout << " -->NULL" << endl;        }        cout << endl;    }private:    //获取顶点的下标    size_t GetIndexOfVertex(const V& v)    {        for (size_t i = 0; i < _vex.size(); ++i)        {            if (_vex[i] == v)                return i;        }        assert(false);        return -1;    }private:    vector<V> _vex;    vector<LinkEdge<W>*> _LinkTable;    bool _IsDirected;};
原创粉丝点击