08.图算法C++(笔记)

1.图的基本概念

图是由顶点集合及顶点间的关系组成的一种数据结构。

表示为 G = (V, E)。V代表的是顶点集合,E代表边集合

树也是一种特殊的图,这个图无环。

树关注的时节点存的值,图更关注的是顶点及边的权值。

权值:边中的附带的数据信息。

顶点和边:图中结点称为顶点,第i个顶点记作vi。两个顶点vi和vj相关联称作顶点vi和顶点vj之间有一条边,图中的第k条边记作ek,ek = (vi,vj)或<vi,vj>.

有向图和无向图

有向图 :顶点对<x, y>是有序的,顶点对<x,y>称为顶点x到顶点y的一条边(弧),<x, y>和<y, x>是两条不同的边。

在这里插入图片描述

无向图 :顶点对(x, y)是无序的,顶点对(x,y)称为顶点x和顶点y相关联的一条边,这条边没有特定方向,(x, y)和(y,x)是同一条边。

在这里插入图片描述

完全图

在有n个顶点的无向图中,若有n * (n-1)/2条边,即任意两个顶点之间有且仅有一条边,则称此图为无向完全图;

在n个顶点的有向图中,若有n * (n-1)条边,即任意两个顶点之间有两条边,且仅这两条边方向相反,则称此图为有向完全图。

邻接顶点

无向图中:两个顶点U、V有一条边连接。成为U、V互为临界顶点。

有向图中:两个顶点U、V。若是U指向V,则称为U邻接到V,V邻接自顶点U。并称这条边与顶点U、V相关联。

顶点的度

顶点V的度指的是与它相关联的边的条数

特殊:

  1. 对于有向图,顶点的度等于该顶点的入度+出度。
    出度:从顶点出去的边的条数。
    入度:指向这个顶点的边数。
  2. 对于无向图:入度=出度=顶点的度

路径

从顶点U到顶点V的一组边称为路径。路径不止一条。

路径长度:对于不带权图为路径的边个数。带权图为路径所有边权值的和

最短路径:所有路径,路径长度最小的路径。

简单路径与回路:

  • 简单路径:路径上经过顶点不重复。
  • 回路:路径上经过的顶点有重复。

子图、连通图、强连通图

两张图A、B,若A的顶点时B的部分顶点,A的边是B的部分边,则称为A是B的子图。

连通图:无向图中,若顶点A、B存在路径,称为A、B连通。若图中的任意两点都是连通的,则称此图为连通图。

强连通图:有向图中,若图中的任意两点都是连通的(A指向B,且B指向A),则称此图为连通图。

生成树

无向图中一个连通图的最小连通子图称为生成树。(用最少的边把所有顶点连接起来)。n个顶点的连通图的生成树有n-1条边。

最小生成树:所有生成树中,路径长度最小的生成树。

2.图的存储结构

因为图中既有节点,又有边(节点与节点之间的关系),因此,在图的存储中,只需要保存:节点和边的关系即可。

节点通过数组保存,边的关系通过下面的方式保存。

图的创建

可以通过输入的方式来创建,或者是通过文件的方式来读取。

这里采用手动添加边来创建图,方便测试。

邻接矩阵

邻接矩阵(二维数组)即是:先用一个数组将定点保存,然后采用矩阵来表示节点与节点之间的关系。

在这里插入图片描述

如果矩阵位置为0代表无关,位置为1代表连接。A对应的下标为0,D对应的下标为3。这里采用map映射的方式来建立顶点与顶点下标的关系。

容易得出:无向图为对称矩阵。

此外:如果这个图是带权图,则矩阵内的数据为所带权值。

邻接矩阵的存储方式的优点:

  1. 非常适合边比较多的情况。
  2. O(1)判断两个节点的连接关系。

不足:

  1. O(N)查找一个节点连接的所有边(N为节点个数)。
C++邻接矩阵创建图
#pragma once

#include <vector>
#include <map>
#include <iostream>

// v:顶点 w:权值 max_w:最大权值 Direction:判断图是有向图还是无向图

namespace matrix{
    //邻接矩阵保存边关系
    template<class v,class w,w max_w = INT_MAX,bool bDirection = false>
    class Graph{
    private:
        std::vector<v> m_vertexs;               // 顶点集合
        std::map<v,int> m_mapIndex;             // 顶点与下标的映射
        std::vector<std::vector<w>> m_matrix;   // 邻接矩阵
        //获取顶点下标
        size_t GetPointIndex(const v& point) const{
            typename std::map<v,int>::const_iterator ptr = m_mapIndex.find(point);
            if(ptr!= m_mapIndex.end())
                return ptr->second;
            else{
                throw std::invalid_argument("point not found");
                return -1;
            }
        }
    public:
        // 图的创建
        Graph(const std::vector<v>& points){
            m_vertexs.resize(points.size());
            for(size_t i = 0; i < points.size(); ++i){
                m_vertexs[i] = points[i];
                m_mapIndex[points[i]] = i;
            }

            m_matrix.resize(points.size());
            // 邻接矩阵
            for(int i = 0; i < m_matrix.size(); ++i){
                m_matrix[i].resize(points.size(),max_w);
            }
        }

        //添加边关系,输入两个点,以及这两个点连线边的权值
        void AddEdge(const v& src, const v& dest, const w& weight){
            size_t posSrc = GetPointIndex(src);
            size_t posDest = GetPointIndex(dest);
            //区分有向图和无向图
            m_matrix[posSrc][posDest] = weight;
            if(!bDirection){
                //无向图,添加两条边关系
                m_matrix[posDest][posSrc] = weight;
            }
        }
        
        void Print(){
            //打印顶点对应坐标
            for(size_t i = 0; i < m_vertexs.size(); ++i){
                std::cout << "[" << i << "]->" << m_vertexs[i] << std::endl;
                std::cout << std::endl;
            }

            //打印边
            std::cout << "  ";
            for(size_t i = 0; i < m_vertexs.size(); ++i){
                std::cout << m_vertexs[i] << " ";
            }
            std::cout << std::endl;

            //打印矩阵
            for(size_t i = 0; i < m_matrix.size(); ++i){
                std::cout << m_vertexs[i] << " ";
                for(size_t j = 0; j < m_matrix[i].size(); ++j){
                    if(m_matrix[i][j] == max_w){
                        std::cout << "*" << " ";
                    }
                    else{
                        std::cout << m_matrix[i][j] << " ";
                    }
                }
                std::cout << std::endl;
            }
        }
    };
}
#include "Graph.hpp"

using namespace std;

void TestGraph(){
    vector<char>points = {'A','B','C','D'};
    matrix::Graph<char,int,INT_MAX,true> graph(points);
    graph.AddEdge('A','B',1);
    graph.AddEdge('A','D',4);
    graph.AddEdge('B','D',2);
    graph.AddEdge('B','C',9);
    graph.AddEdge('C','D',8);
    graph.AddEdge('C','B',5);
    graph.AddEdge('C','A',3);
    graph.AddEdge('D','C',6);
    graph.Print();
}

int main(){
    TestGraph();
    return 0;
}

在这里插入图片描述

邻接表

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

创建指针数组,自己连接的顶点挂在下面。同样的,对节点进行编号。

在这里插入图片描述

邻接表的优点:

  1. 适合保存稀疏的边关系。
  2. 适合查找一个顶点连接出的边

不足:

  1. 不适合确定两个顶点是否相连,判断权值。

注意:如果图是有向图,则如果使用邻接表存储边的关系,需要保存出边表和入边表。

C++邻接表创建图
#pragma once

#include <vector>
#include <map>
#include <iostream>

// v:顶点 w:权值 max_w:最大权值 Direction:判断图是有向图还是无向图

// 邻接表保存边关系
namespace link_table{
    template<class w>
    struct Edge{
        int detPos;
        int srcPos;
        w weight;
        Edge<w> *next;
        Edge(int _srcPos, int _detPos, const w& _weight)
            :detPos(_detPos),srcPos(_srcPos),weight(_weight),next(nullptr)
        {}
    };

    template<class v,class w,bool Direction = false>
    class Graph{
        typedef Edge<w> Edge;
    private:
        std::vector<v> m_vertexs;      // 顶点集合
        std::map<v,int> m_mapIndex;    // 顶点与下标的映射
        std::vector<Edge*> m_tables;    // 领接表
        // 获取顶点下标
        size_t GetPointIndex(const v& point) const{
            typename std::map<v,int>::const_iterator ptr = m_mapIndex.find(point);
            if(ptr!= m_mapIndex.end())
                return ptr->second;
            else{
                throw std::invalid_argument("point not found");
                return -1;
            }
        }
    public:
        //图的创建
        Graph(const std::vector<v>& points){
            m_vertexs.resize(points.size());
            for(size_t i = 0; i < points.size(); ++i){
                m_vertexs[i] = points[i];
                m_mapIndex[points[i]] = i;
            }
            m_tables.resize(points.size());
        }

        //添加边关系,输入两个点,以及这两个点连线边的权值
        void AddEdge(const v& src, const v& dest, const w& weight){
            size_t posSrc = GetPointIndex(src);
            size_t posDest = GetPointIndex(dest);

            Edge* pEdge = new Edge(posSrc,posDest,weight);
            //头插
            pEdge->next = m_tables[posSrc];
            m_tables[posSrc] = pEdge;

            if(!Direction){
                //无向图,添加两条边关系
                Edge* pEdge = new Edge(posDest,posSrc,weight);
                //头插
                pEdge->next = m_tables[posDest];
                m_tables[posDest] = pEdge;
            }
        }

        void Print(){
            //打印顶点对应坐标
            for(size_t i = 0; i < m_vertexs.size(); ++i){
                std::cout << "[" << i << "]->" << m_vertexs[i] << std::endl;
                std::cout << std::endl;
            }

            for(size_t i = 0; i < m_tables.size(); ++i){
                std::cout << m_vertexs[i] << "[" << i << "]:";
                Edge* pNode = m_tables[i];
                while(pNode!= nullptr){
                    std::cout << m_vertexs[pNode->detPos]
                    << "[" << pNode->detPos << "]"
                    << "(" << pNode->weight << ")" << " ";
                    pNode = pNode->next;
                }
                std::cout << "nullptr" << std::endl;
            }
        }
    };
}
void TestGraph2(){
    vector<char>points = {'A','B','C','D'};
    link_table::Graph<char,int,true> graph(points);
    graph.AddEdge('A','B',1);
    graph.AddEdge('A','D',4);
    graph.AddEdge('B','D',2);
    graph.AddEdge('B','C',9);
    graph.AddEdge('C','D',8);
    graph.AddEdge('C','B',5);
    graph.AddEdge('C','A',3);
    graph.AddEdge('D','C',6);
    graph.Print();
}

int main(){
    //TestGraph();
    TestGraph2();
    return 0;
}

在这里插入图片描述

3.图的遍历

广度优先遍历 BFS

广度优先队列:以某个顶点为起点,一层一层进行遍历。需要借助队列。

在这里插入图片描述

具体遍历方式与二叉树的层序遍历方式类似,不同的是要通过标记的方式防止节点的重复遍历。

namespace matrix{
    //邻接矩阵保存边关系
    template<class v,class w,w max_w = INT_MAX,bool Direction = false>
    class Graph{
    public:
        // 图的创建
        Graph(const std::vector<v>& points){
            m_vertexs.resize(points.size());
            for(size_t i = 0; i < points.size(); ++i){
                m_vertexs[i] = points[i];
                m_mapIndex[points[i]] = i;
            }

            m_matrix.resize(points.size());
            // 邻接矩阵
            for(int i = 0; i < m_matrix.size(); ++i){
                m_matrix[i].resize(points.size(),max_w);
            }
        }

        //添加边关系,输入两个点,以及这两个点连线边的权值
        void AddEdge(const v& src, const v& dest, const w& weight){
            size_t posSrc = GetPointIndex(src);
            size_t posDet = GetPointIndex(dest);
            //区分有向图和无向图
            m_matrix[posSrc][posDet] = weight;
            if(!Direction){
                //无向图,添加两条边关系
                m_matrix[posDet][posSrc] = weight;
            }
        }
        
        void Print(){
            //打印顶点对应坐标
            for(size_t i = 0; i < m_vertexs.size(); ++i){
                std::cout << "[" << i << "]->" << m_vertexs[i] << std::endl;
                std::cout << std::endl;
            }

            //打印边
            std::cout << "  ";
            for(size_t i = 0; i < m_vertexs.size(); ++i){
                std::cout << m_vertexs[i] << " ";
            }
            std::cout << std::endl;

            //打印矩阵
            for(size_t i = 0; i < m_matrix.size(); ++i){
                std::cout << m_vertexs[i] << " ";
                for(size_t j = 0; j < m_matrix[i].size(); ++j){
                    if(m_matrix[i][j] == max_w){
                        std::cout << "*" << " ";
                    }
                    else{
                        std::cout << m_matrix[i][j] << " ";
                    }
                }
                std::cout << std::endl;
            }
        }
        
        void BFS(const v& src){//传入起点
            size_t srcPos = GetPointIndex(src);
            std::queue<size_t> q;
            q.push(srcPos);
            //标记数组
            std::vector<bool> visited(m_vertexs.size(),false);
            visited[srcPos] = true;     //入队列标记
            int iLevelSize = 1;         //每层节点个数
            int iLevel = 1;             //层数
            while(!q.empty()){
                for(int i = 0; i < iLevelSize; ++i){
                    int iFront = q.front();
                    std::cout << iFront << ":" << m_vertexs[iFront] << " ";
                    q.pop();
                    //这个节点周围的节点入队
                    for(size_t i = 0; i < m_vertexs.size(); i++){
                        if(m_matrix[iFront][i]!= max_w && !visited[i])
                        {
                            // 入队时,标记已访问节点
                            q.push(i);
                            visited[i] = true;
                        }
                    }
                }
                iLevelSize = q.size();
                std::cout << "第" << iLevel++ << "层" << std::endl;
            }
            std::cout << std::endl;
        }
     private:
        std::vector<v> m_vertexs;               // 顶点集合
        std::map<v,int> m_mapIndex;             // 顶点与下标的映射
        std::vector<std::vector<w>> m_matrix;   // 邻接矩阵
        //获取顶点下标
        size_t GetPointIndex(const v& point) const{
            typename std::map<v,int>::const_iterator ptr = m_mapIndex.find(point);
            if(ptr!= m_mapIndex.end())
                return ptr->second;
            else{
                throw std::invalid_argument("point not found");
                return -1;
            }
        }
    };
}

// 邻接表保存边关系
namespace link_table{
    template<class w>
    struct Edge{
        int detPos;
        int srcPos;
        w weight;
        Edge<w> *next;
        Edge(int _srcPos, int _detPos, const w& _weight)
            :detPos(_detPos),srcPos(_srcPos),weight(_weight),next(nullptr)
        {}
    };

    template<class v,class w,bool Direction = false>
    class Graph{
        typedef Edge<w> Edge;
    public:
        //图的创建
        Graph(const std::vector<v>& points){
            m_vertexs.resize(points.size());
            for(size_t i = 0; i < points.size(); ++i){
                m_vertexs[i] = points[i];
                m_mapIndex[points[i]] = i;
            }
            m_tables.resize(points.size());
        }

        //添加边关系,输入两个点,以及这两个点连线边的权值
        void AddEdge(const v& src, const v& dest, const w& weight){
            size_t posSrc = GetPointIndex(src);
            size_t posDest = GetPointIndex(dest);

            Edge* pEdge = new Edge(posSrc,posDest,weight);
            //头插
            pEdge->next = m_tables[posSrc];
            m_tables[posSrc] = pEdge;

            if(!Direction){
                //无向图,添加两条边关系
                Edge* pEdge = new Edge(posDest,posSrc,weight);
                //头插
                pEdge->next = m_tables[posDest];
                m_tables[posDest] = pEdge;
            }
        }

        void Print(){
            //打印顶点对应坐标
            for(size_t i = 0; i < m_vertexs.size(); ++i){
                std::cout << "[" << i << "]->" << m_vertexs[i] << std::endl;
                std::cout << std::endl;
            }

            for(size_t i = 0; i < m_tables.size(); ++i){
                std::cout << m_vertexs[i] << "[" << i << "]:";
                Edge* pNode = m_tables[i];
           
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值