美文网首页
大师兄的数据结构学习笔记(九): 图

大师兄的数据结构学习笔记(九): 图

作者: superkmi | 来源:发表于2020-11-05 11:29 被阅读0次

    大师兄的数据结构学习笔记(八): 哈夫曼树与哈夫曼编码
    大师兄的数据结构学习笔记(十): 伸展树

    一、什么是图(Graph)

    • 图表示多对多的关系。
    • 线性结构和树都可以看成是图的特殊情况。
    1. 关于顶点
    • 图包含一组顶点。
    • 通常用V(Vertex)表示顶点集合。
    2. 关于边
    • 图包含一组边。
    • 通常用E(Edge)表示边的集合。
    • 边表示的是顶点和顶点之间的某种关系:

    1) 无向边

    • (v,w) \in E,其中v,w \in V
    • 如果可以从v到w, 也可以从w到v。

    2) 有向边
    • <v,w> \in E,其中v,w \in V
    • 表示从v指向w的边(单行线)。


    • 在图中,不考虑重边和自回路。
    3. 抽象数据类型
    • 类型名称:图(Graph)
    • 数据额对象集:G(V,E)由一个非空的有限顶点集合V和一个有限边集合E组成。
    • 常见操作集:对于任意图G\in Graph, 以及v \in V, e\in E
    操作集 说明
    Graph Create() 建立并返回空图。
    Graph InsertVertex(Graph G,Vertex v) 将v插入G。
    Graph InsertEdge(Graph G,Edge e) 将e插入G。
    void DFS(Graph G,Vertex v) 从顶点v出发深度优先遍历图G。
    void BFS(Graph G,Vertex v) 从顶点v出发宽度优先遍历图G。
    void ShortestPath(Graph G,Vertex v,int Dist[]) 计算图G中顶点v到任意其他顶点的最短距离。
    void MST(Graph G) 计算图G的最小生成树。
    4. 常见术语
    • 无向图(undirected edge): 图中所有的边都是无方向的。
    • 有向图(directed edge): 图中的边可能是有向边或无向边,但方向是有意义的。
    • 带权网络(weighted network): 图中的边是带权重的,用来表示顶点之间关系的细节。
    • 邻接点(adjacent node): 有边直接相连的顶点。
    • 度(degree): 在无向图中,与顶点v关联的边数,成为v的度数,记作deg(v)。
    • 出度: 从该点出发的边数。
    • 入度: 指向该点的边数。
    • 简单图(simple graph): 不含任何自环(self-loop)的图。
    • 通路(path): 由m+1个顶点与m条边交替而成的序列。
    • 环路(cycle): 对于长度m\geq1的通路\pi,起止顶点相同(v_0=v_m)。
    • 稀疏图(sparse graph): 点很多而边很少。
    • 完全图(complete graph): 任意两个顶点间,都有一条边。

    二、在程序中表示图

    1. 邻接矩阵(adjacency matrix)
    • 是图最基本的实现方式。
    • 使用方阵G[N][N]表示由N个顶点构成的图,其中每个单元各自负责描述一对顶点之间可能存在的邻接关系。
    • 若<V_i,V_j>是G中的边, G[i][j] = 1。
    • 否则G[i][j] = 0。
    优点 缺点
    1. 直观、简单、好理解。
    2. 方便检查任意一对顶点间是否存在边。
    3. 方便找任一顶点的所有邻接点。
    4. 方便计算任一顶点的度。
    1. 稀疏图浪费空间 - 存有大量无效元素(0)。
    2. 稀疏图浪费时间 - 统计总边数需要遍历所有结点。
    #ifndef NODE_H_
    #define NODE_H_
    
    class Node
    {
    public:
        Node(char data = 0); // 构造结点
        char m_cData; // 数据
        bool m_bIsVisted; // 是否被访问过
    };
    #endif // !NODE_H_
    
    #include "node.h"
    
    Node::Node(char data)
    {
        m_cData = data;
        m_bIsVisted = false; //默认未访问
    }
    
    #ifndef GRAPH_H_
    #define GRAPH_H_
    #include <vector>
    #include "node.h"
    using namespace std;
    
    class Graph
    {
    public:
        Graph(int capacity); //构造函数
        ~Graph();           //析构函数
        void resetNode(); //重置节点
        bool InsertVertex(Node* pNode); // 插入节点
        void DFS(int nodeIndex); //深度优先遍历
        void BFS(int nodeIndex); //宽度优先遍历
        void setValueToMatrixForDirectedGraph(int row, int col, int val = 1); //给有向图的邻接矩阵元素设置值
        void setValueToMatrixForUndirectedGraph(int row, int col, int val = 1);//给无向图的邻接矩阵的元素设置值
        void printMatrix(); //打印邻接矩阵
    private:
        int m_iCapacity; // 节点容量
        int m_iNodeCount; //当前节点数量
        Node* m_pNodeArray; //指向第一个节点的指针
        int* m_pMatrix; // 指向邻接矩阵首元素的指针
        bool getValueFromMatrix(int row, int col, int& val); // 获取临街矩阵中元素的值
        void breadthFirstTraverseImpl(vector<int> preVec);
    };
    
    #endif // !GRAPH_H_
    
    #include <iostream>
    #include "graph.h"
    using namespace std;
    
    //构造函数
    Graph::Graph(int capacity)
    {
        m_iCapacity = capacity;
        m_iNodeCount = 0;
        m_pNodeArray = new Node[m_iCapacity];
        m_pMatrix = new int[m_iCapacity * m_iCapacity];
        memset(m_pMatrix, 0, m_iCapacity * m_iCapacity * sizeof(int)); // 初始化matrix
    }
    
    //析构函数
    Graph::~Graph()
    {
        delete[]m_pNodeArray; // 释放节点
        delete[]m_pMatrix;    //释放矩阵
    }
    
    //重置节点
    void Graph::resetNode()
    {
        for (int i = 0; i < m_iNodeCount; i++)
        {
            m_pNodeArray[i].m_bIsVisted = false;
        }
    }
    
    // 插入节点
    bool Graph::InsertVertex(Node* pNode)
    {
        if (pNode == nullptr) return false;
        m_pNodeArray[m_iNodeCount] = pNode->m_cData;
        m_iNodeCount++;
        return true;
    }
    
    // 获取临街矩阵中元素的值
    bool Graph::getValueFromMatrix(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;
    }
    
    void Graph::breadthFirstTraverseImpl(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++) {
                getValueFromMatrix(preVec[j], i, value);
                if (value == 0) continue;
                if (m_pNodeArray[i].m_bIsVisted) continue;
                cout << m_pNodeArray[i].m_cData << " ";
                m_pNodeArray[i].m_bIsVisted = true;
                curVec.push_back(i);
            }
        }
    }
    
    //深度优先遍历
    void Graph::DFS(int nodeIndex)
    {
        int value = 0;
        cout << m_pNodeArray[nodeIndex].m_cData << " ";  // 打印值
        m_pNodeArray[nodeIndex].m_bIsVisted = true; // 修改状态
    
        for (int i = 0; i < m_iCapacity; i++)
        {
            getValueFromMatrix(nodeIndex, i, value);
            if (value == 0) continue;
            if (m_pNodeArray[i].m_bIsVisted) continue;
            DFS(i);
        }
    }
    
    //宽度优先遍历
    void Graph::BFS(int nodeIndex)
    {
        cout << m_pNodeArray[nodeIndex].m_cData << " ";
        m_pNodeArray[nodeIndex].m_bIsVisted = true;
        vector<int> curVec;
        curVec.push_back(nodeIndex);
        breadthFirstTraverseImpl(curVec);
    }
    
    //给有向图的邻接矩阵元素设置值
    bool Graph::setValueToMatrixForDirectedGraph(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;
    }
    
    //给无向图的邻接矩阵的元素设置值
    bool Graph::setValueToMatrixForUndirectedGraph(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;
    }
    
    //打印邻接矩阵
    void Graph::printMatrix()
    {
        for (int i = 0; i < m_iCapacity; i++)
        {
            for (int k = 0; k < m_iCapacity; k++)
            {
                cout << m_pNodeArray[i*m_iCapacity+k].m_cData << " ";
            }
            cout << endl;
        }
    }
    
    2. 邻接表(adjacency list)
    • G[N]为指针数组,对应矩阵每行一个链表,只存非0元素。
    优点 缺点
    1. 方便找任一丁点的所有邻接点
    2. 节约稀疏图空间。
    3. 方便计算无向图任一顶点的
    1. 只能计算有向图的出度,计算入度需要构造逆邻接表
    2. 不方便检查任意一对顶点间是否存在边。
    #ifndef QUEUE_H
    #define QUEUE_H
    
    const int queueSize = 100;
    
    template<class T>
    //表结构
    class queue
    {
    public:
        T data[queueSize];
        int front, rear;
    };
    
    #endif
    
    #ifndef GRAPH_H
    #define GRAPH_H
    #include "queue.h"
    
    // 边表结点
    struct ArcNode
    {
        int adjvex; // 邻接点
        ArcNode* next; 
    };
    
    
    // 顶点表结点
    struct VertexNode
    {
        int vertex;
        ArcNode* firstedge;
    };
    
    
    const int MaxSize = 10;
    int visited[MaxSize] = {0}; // 是否被访问
    
    template<class T>
    class Graph
    {
    public:
        Graph(T a[], int n, int e); //建立n个顶点,e条边的图
        ~Graph() {};                // 析构函数
        void DFS(int v);            // 深度优先搜索
        void BFS(int v);            // 广度优先搜索
    private:
        VertexNode adjlist[MaxSize];// 图中的顶点
        int vertexNum;              // 图中的顶点数
        int arcNum;                 // 图中的边数
    };
    
    #endif
    
    #include <iostream>
    #include "graph.h"
    using namespace std;
    
    template<class T>
    // 构造函数
    Graph<T>::Graph(T a[], int n, int e)
    {
        vertexNum = n;
        arcNum = e;
        // 初始化顶点
        for (int i = 0; i < vertexNum; i++)
        {
            adjlist[i].vertex = a[i];
            adjlist[i].firstedge = nullptr;
        }
        // 初始化边
        for (int i = 0; i < arcNum; i++)
        {
            int k, j;
            cin >> k >> j;
            ArcNode* s = new ArcNode;
            s->adjvex = j;
            s->next = adjlist[k].firstedge;
            adjlist[k].firstedge = s;
        }
    }
    
    template<class T>
    // 深度优先
    void Graph<T>::DFS(int v)
    {
        cout << adjlist[v].vertex;
        visited[v] = 1;
        ArcNode* p = adjlist[v].firstedge;
        while (p != nullptr)
        {
            int j = p->adjvex;
            if (visited[j]==0)
            {
                DFS(j);
            }
            p = p ->next;
        }
    }
    
    template<class T>
    // 广度优先
    void Graph<T>::BFS(int v)
    {
        int visited[MaxSize] = { 0 };
        queue<T> Q;
        Q.front = Q.rear = -1; // 初始化队列
        cout << adjlist[v].vertex;
        visited[v] = 1;
        Q.data[++Q.rear] = v;
        while (Q.front != Q.rear)
        {
            v = Q.data[++Q.front];
            ArcNode* p = adjlist[v].firstedge;
            while (p != nullptr)
            {
                int j = p->adjvex;
                if (visited[j] == 0)
                {
                    cout << adjlist[j].vertex;
                    visited[j] = 1;
                    Q.data[++Q.rear] = j;
                }
                p = p->next;
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:大师兄的数据结构学习笔记(九): 图

          本文链接:https://www.haomeiwen.com/subject/csvdvktx.html