【算法和数据结构】1.8--数据结构之图(C++实现)

来源:互联网 发布:nginx php 张宴 编辑:程序博客网 时间:2024/05/01 18:31

       图,一种复杂的数据结构,在实际生活中起着举足轻重的作用。如路网(线路规划),社交网络描述等等。现在我们就以下面这个简单无向图(图1)为例,来说明使用邻接矩阵 实现图在计算机中的存储和操作的方法。
这里写图片描述
              图1

       不同于树,我们在习惯上称呼如图中数字编号的店为顶点而非节点。在树的概念中,存在一个特殊的节点称之为根,其起着好似渔网的主绳的作用,缺之不可。而在图这种数据结构中,更加倾向于平等地看待每一个顶点。

        在下面给出的实现中,我们用一维数组来表示邻接矩阵。若图有n个顶点,则数组大小即为n*n。第row行,col列的元素实际上存储在数组下标为row*n+col的位置。

        关于邻接矩阵的定义这里不再赘述,下图2给出图1所示简单无向图的邻接矩阵,希望能够帮助读者理解。
这里写图片描述
                图2

        下面给出以邻接矩阵表示图的C++实现,其中封装了对图的常用操作(具体见代码注释):

/*节点类*/class Node{public:    Node(char identifier = 0);    char m_identifier;       //顶点编号    bool m_isVisited;        //顶点访问标志位:true表示已经被访问};Node::Node(char identifier){    m_identifier = identifier;    m_isVisited = false;}/*图类*/class Graph{public:    Graph(int capacity);    ~Graph();    void resetNode();              //重置所有顶点的访问标志位为false,未访问    bool addNode(Node *pNode);     //添加新顶点    bool addEdgeForUndirectedGraph(int row, int col, int val = 1); //添加边以构造无向图,val表示权值,默认连通    bool addEdgeForDirectedGraph(int row, int col, int val = 1);   //添加边以构造有向图,val表示权值,默认连通    void printMatrix();   //打印邻接矩阵    void depthFirstTraverse(int nodeIndex);   //深度优先遍历,指定第一个点    void widthFirstTraverse(int nodeIndex);   //广度优先遍历,指定第一个点private:    bool getValueOfEdge(int row, int col, int &val);  //获取边权值    void widthFirstTraverseImplement(vector<int> preVec);  //利用vector实现广度优先遍历    int m_iCapacity;     //图容量,即申请的数组空间最多可容纳的顶点个数    int m_iNodeCount;    //图的现有顶点个数    Node *m_pNodeArray;  //存放顶点的数组    int *m_pMatrix;      //为了方便,用一维数组存放邻接矩阵};Graph::Graph(int capacity){    m_iCapacity = capacity;    m_iNodeCount = 0;    m_pNodeArray = new Node[m_iCapacity];    m_pMatrix = new int[m_iCapacity*m_iCapacity];    for (int i = 0;i < m_iCapacity*m_iCapacity;i++)  //初始化邻接矩阵    {        m_pMatrix[i] = 0;    }}Graph::~Graph(){    delete []m_pNodeArray;    delete []m_pMatrix;}void Graph::resetNode(){    for (int i = 0;i < m_iNodeCount;i++)    {        m_pNodeArray[i].m_isVisited = false;    }}bool Graph::addNode(Node *pNode){    if (pNode == NULL)        return false;    m_pNodeArray[m_iNodeCount].m_identifier = pNode->m_identifier;    m_iNodeCount++;    return true;}bool Graph::addEdgeForUndirectedGraph(int row, int col, int val){    if (row < 0 || row >= m_iCapacity)        return false;    if (col < 0 || col >= m_iCapacity)        return false;    m_pMatrix[row*m_iCapacity + col] = val;    m_pMatrix[col*m_iCapacity + row] = val;    return true;}bool Graph::addEdgeForDirectedGraph(int row, int col, int val){    if (row < 0 || row >= m_iCapacity)        return false;    if (col < 0 || col >= m_iCapacity)        return false;    m_pMatrix[row*m_iCapacity + col] = val;    return true;}void Graph::printMatrix(){    for (int i = 0;i < m_iCapacity;i++)    {        for (int k = 0;k < m_iCapacity;k++)            cout << m_pMatrix[i*m_iCapacity + k] << " ";        cout << endl;    }}void Graph::depthFirstTraverse(int nodeIndex){    int value = 0;    //访问第一个顶点    cout << m_pNodeArray[nodeIndex].m_identifier << " ";    m_pNodeArray[nodeIndex].m_isVisited = true;    //访问其他顶点    for (int i = 0;i < m_iCapacity;i++)    {        getValueOfEdge(nodeIndex, i, value);        if (value != 0)   //当前顶点与指定顶点连通        {            if (m_pNodeArray[i].m_isVisited == true)  //当前顶点已被访问                continue;            else           //当前顶点没有被访问,则递归            {                depthFirstTraverse(i);            }        }        else       //没有与指定顶点连通        {            continue;        }    }}void Graph::widthFirstTraverse(int nodeIndex){    //访问第一个顶点    cout << m_pNodeArray[nodeIndex].m_identifier << " ";    m_pNodeArray[nodeIndex].m_isVisited = true;    vector<int> curVec;    curVec.push_back(nodeIndex);      //将第一个顶点存入一个数组    widthFirstTraverseImplement(curVec);}void Graph::widthFirstTraverseImplement(vector<int> preVec){    int value = 0;    vector<int> curVec;    //定义数组保存当前层的顶点    for (int j = 0;j < (int)preVec.size();j++)  //依次访问传入数组中的每个顶点    {        for (int i = 0;i < m_iCapacity;i++)  //传入的数组中的顶点是否与其他顶点连接        {            getValueOfEdge(preVec[j], i, value);            if (value != 0)   //连通            {                if (m_pNodeArray[i].m_isVisited==true)  //已经被访问                {                    continue;                }                else   //没有被访问则访问                {                    cout << m_pNodeArray[i].m_identifier << " ";                    m_pNodeArray[i].m_isVisited = true;                    //保存当前点到数组                    curVec.push_back(i);                }            }        }    }    if (curVec.size()==0)   //本层次无被访问的点,则终止    {        return;    }    else    {        widthFirstTraverseImplement(curVec);    }}bool Graph::getValueOfEdge(int row, int col, int &val){    if (row < 0 || row >= m_iCapacity)        return false;    if (col < 0 || col >= m_iCapacity)        return false;    val = m_pMatrix[row*m_iCapacity + col];    return true;}

        特别需要提醒的是:

  1. 在实现图遍历时应用了C++标准数组模板vector,读者在使用时应该包含相关头文件。
  2. 构造边的时候封装了两个函数,一个是对于有向图,一个是对于无向图,读者对比两种图形的邻接矩阵和实现可以很容易理解。

        另外,在前面对于二叉树的介绍中,我们使用了数组和链表两种方式来表示树。其实对于图来说,也拥有着两种表示方式。上述邻接矩阵的表示方法即使用数组,另外还有使用链表的如邻接表、十字链表、邻接多重表等等。他们在表示有向图、无向图和运算代价方面各有优劣,这里不再做详细介绍。有兴趣的读者朋友可以查阅相关资料。

0 0