图的基本概念及实现

线性表中的元素是“一对一”的关系,树中的元素是“一对多”的关系,而图释“多对多”的关系。图是一种复杂的非线性的结构,图中的每个元素可以有零个或多个前驱,也可以有零个或多个后继,也就是说,元素之间的关系式任意的。

一、图的概念

定义:图其实是由顶点集合和顶点间的关系组成的一种数据结构。通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

图的分类
图可以分为无向图和有向图
这里写图片描述
左图为无向图,是由顶点和边构成的,右图为有向图,是由顶点和弧构成,弧有弧头和弧尾的区别。

按照边可以将图分为稀疏图和稠密图,这是个模糊的概念,也是相对的。
(x,y)表示无向的边,

二、图的存储

1.领接矩阵
领接矩阵用两个数组保存数据。一个一维数组存储图顶点信息,一个二维数组存储图中的边或弧的信息。
在无向图中,二维数组是个对称矩阵。

特点
0代表无边,1代表有边。
顶点的度是所在行数组之和。
求取顶点领接边,将行内元素遍历下。
这里写图片描述
有向图的领接矩阵,各行之和是出度,各列之和是入度。
带权的图称为网,用领接矩阵表示:
这里写图片描述
领接矩阵对于边数相对顶点较少的图,是很浪费空间的。

领接矩阵的优点&缺点
优点:连通
缺点:从顶点到某一点的路径不方便表示。

代码:

//领接矩阵存储
template <class V,class W,bool IsDirect=false>//默认情况下是无向图
class Graph
{
public:
    Graph()
    {}
    Graph(const V* array,size_t size)
    {
        //构造顶点
        _v.resize(size);
        for (size_t i = 0; i < size; ++i)
        {
            _v[i] = array[i];
        }

        //动态开辟二维数组
        _edges.resize(size);
        for (size_t i = 0; i < size; ++i){
            _edges[i].resize(size);
        }
    }

    ///获取顶点元素在其数组中的下标
    size_t GetIndexOfV(const V& value)
    {
        int size = _v.size();
        for (size_t i = 0; i < size; ++i){
            if (_v[i] == value)
                return i;
        }
        return -1;//返回-1表示找不到
    }

    //获取顶点的度
    size_t GetDevOfV(const V& value)
    {
        int size = _v.size();
        int dev = 0;
        int index = GetIndexOfV(value);
        if (index != -1)//该顶点存在
        {
            for (size_t i = 0; i < size; ++i){
                if (_edges[index][i] != 0)//有边
                    ++dev;
            }
            //有向图
            if (IsDirect){
                for (size_t j = 0; j < size; j++){
                    if (_edges[j][index] != 0)
                        ++dev;
                }
            }
        }
        return dev;
    }

    //添加顶点v1和v2对应的权值为weight的边
    void AddEdge(const V& v1, const V& v2, const W& weight)
    {
        int index1 = GetIndexOfV(v1);
        int index2 = GetIndexOfV(v2);

        if (index1 != -1 && index2 != -1)//两个顶点均存在
        {
            _edges[index1][index2] = weight;
            //无向图
            if (!IsDirect){
                _edges[index2][index1] = weight;
            }
        }
    }

    //图的打印
    void PrintGraph()
    {
        int size = _v.size();
        for (size_t i = 0; i < size; ++i)
        {
            for (size_t j = 0; j < size; ++j)
            {
                printf("%2d ", _edges[i][j]);
            }
            printf("\n");
        }
        printf("\n");
    }

private:
    vector<V> _v;//图的顶点
    vector<vector<W>> _edges;//任意两个顶点之间的边的权值
};

测试代码:

void test1()
{
    int array[] = { 0, 1, 2, 3 };
    Graph<int, int> g(array, sizeof(array) / sizeof(array[0]));
    g.AddEdge(0, 1, 10);
    g.AddEdge(2, 1, 30);
    g.AddEdge(1, 2, 20);
    g.AddEdge(0, 3, 40);

    g.GetDevOfV(1);
    g.PrintGraph();
}

2.领接表
数组和链表结合的存储方法,顶点用一个一维数组存储,每个顶点vi的所有领接点构成一个线性表。
这里写图片描述

代码:

template<class W>
struct LinkEdge
{
    size_t _src;//边的起点
    size_t _dst;//边的终点
    W _weight;//边的权值
    LinkEdge* _pNext;//指向下一条边

    LinkEdge()
    {}

    //有参构造函数
    LinkEdge(const size_t& src, const size_t& dst, const W& weight)
        :_src(src)
        , _dst(dst)
        , _weight(weight)
        , _pNext(NULL)
    {}
};

//领接表存储
template <class V,class W,bool IsDirect=false>
class Graph
{
    typedef LinkEdge<W> Node;
    typedef Node* PNode;

public:
    Graph()
    {}

    Graph(const V* array, size_t size)
        :_v(array,array+size)
    {
        _linkEdges.resize(size);
    }

    //获取顶点元素在其数组中的下标
    size_t GetIndexOfV(const V& value)
    {
        int size = _v.size();
        int index = 0;
        for (size_t i = 0; i < size; ++i)
        {
            if (_v[i] == value)
                return i;
        }
        return -1;
    }

    //添加顶点为v1和v2所对应权值为weight的边(采用头插)
    void AddEdge(const V& v1, const V& v2, const W& weight)
    {
        int index1 = GetIndexOfV(v1);//起点的下标
        int index2 = GetIndexOfV(v2);//终点的下标
        PNode pSrc = _linkEdges[index1];//起点
        PNode pDst = _linkEdges[index2];//终点

        if (index1 != -1 && index2 != -1)//顶点均存在
        {
            PNode newNode =new Node(index1, index2, weight);
            newNode->_pNext = pSrc;
            _linkEdges[index1] = newNode;

            if (!IsDirect)//无向图
            {
                PNode newNode =new Node(index2, index1, weight);
                newNode->_pNext = pDst;
                _linkEdges[index2] = newNode;
            }
        }
    }

    //获取顶点的度
    int GetVDev(const V& value)
    {
        int dev = 0;
        int index = GetIndexOfV(value);
        PNode pCur= _linkEdges[index];
        while (pCur)
        {
            ++dev;
            pCur = pCur->_pNext;
        }
        return dev;
    }

    void PrintGraph()
    {
        int size = _v.size();
        for (size_t i = 0; i < size; ++i)
        {
            PNode pCur = _linkEdges[i];
            while (pCur)
            {
                cout << _v[pCur->_src] << "--->" << _v[pCur->_dst] <<" 权值"<< pCur->_weight<<endl;
                pCur = pCur->_pNext;
            }
        }
    }

    //获取边的权值
    W GetVWeight(const V& v1,const V& v2)
    {
        int index1 = GetIndexOfV(v1);//起点的下标
        int index2 = GetIndexOfV(v2);//终点的下标

        if (index1 != -1 && index2 != -1)
        {
            PNode pCur = _linkEdges[index1];
            while (pCur)
            {
                if (pCur->_dst == index2)
                    return pCur->_weight;
                pCur = pCur->_pNext;
            }
        }
    }

private:
    vector<V> _v;//存储图的顶点
    vector<PNode> _linkEdges;//存储边,结点
};

测试代码:

void test2()
{
    int array[] = { 0, 1, 2, 3 };
    Graph<int, int> g(array, sizeof(array) / sizeof(array[0]));

    g.AddEdge(0, 1, 10);
    g.AddEdge(0, 2, 20);
    g.AddEdge(1, 2, 30);
    g.AddEdge(2, 1, 40);
    g.AddEdge(3, 2, 50);

    cout<<"以0为顶点的度为:"<< g.GetVDev(0)<<endl;
    g.PrintGraph();

    cout<<"0,1顶点的边的权值为:"<<g.GetVWeight(0, 1)<<endl;
}

猜你喜欢

转载自blog.csdn.net/zwe7616175/article/details/80208376