C++图(邻接矩阵实现+必要算法)

Posted 桃陉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++图(邻接矩阵实现+必要算法)相关的知识,希望对你有一定的参考价值。


一.基础知识

说起图来大家都很容易理解,图就是由若干顶点以及连接顶点的边所构成的图形。如下图所示:


接下来我们介绍一些图中的基本概念

∙ \\bullet 顶点(Vertex):自面意思,比如说上图的1、2、3、4、5、6

∙ \\bullet 边(edge):自面意思,连接两个顶点的线条

∙ \\bullet 权重(weight):每条边上所赋的一个值

∙ \\bullet 无向图:顶点之间没有连接方向,我们重点讨论的就是无向图。(拓扑排序中使用有向图)

∙ \\bullet 路径:路径就是从一个节点到另一个节点所经过线路,比如说从1到6有两条路径(针对无向图)分别为:1-5-4-6、1-2-3-4-6。


图的表示方式一共有两种:

∙ \\bullet 邻接矩阵表示:使用二维矩阵,用行row中的一个数表示一个顶点,列col中的一个数表示另一个顶点。对应的值为它们连线的权重,如果没有连接,那么置为0或者无穷大。在具体实现中我将矩阵多增加了一行一列,这样操作时可以使下标从1开始,这里我们将权重都先记为1,上图的邻接矩阵如下:

很容易可以发现,邻接矩阵是对称矩阵。

∙ \\bullet 邻接表表示:使用链表数组,每个顶点引出取一条链表,每个节点包含它们的下标值、权重与下一个节点地址。类似于我之前说的 哈希表的链表数组实现,只是需要多加一个权重而已。


下面简略提一下我们所实现的图中的一些算法

函数名称作用
深度优先搜索DFS算法对图进行遍历
广度优先搜索BFS算法对图进行遍历
单源最短路径Dijkstra算法从一个点出发到各个点的最短路径
最小生成树Prim算法用n-1条边连接n个顶点,使得总体权重最小

下面我们会详细的介绍每个算法的实现!


二.分步实现代码

定义的一些常量:

VISITED表示该顶点已访问,UNVISITED表示该顶点未经访问,INF用来模拟上面所说的无穷大,也就是表示这里没有边连接。

const int VISITED=1;
const int UNVISITED=0;
const int INF = 10000;

图的虚类表示一些图中需要实现的基本函数:

//图的虚类
class Graph
{
    private:
        void operator = (const Graph&) {}   //赋值
        Graph(const Graph&) {}  //拷贝函数
    public:
        Graph(){}
        virtual ~Graph(){}
        virtual void Init(int n)=0; //初始化
        virtual int n()=0;  //返回图的顶点数
        virtual int e()=0;  //返回图的边数
        virtual int first(int v)=0;  //返回第一个
        virtual int next(int v,int w)=0; //返回下一个
        virtual void setEdge(int val,int v2,int wght)=0;  //给两个点之间的边来设计权重
        virtual void delEdge(int v1,int v2)=0;  //删掉两点之间的边
        virtual bool isEdge(int i,int j)=0; //判断是否为存在的边
        virtual int weight(int v1,int v2)=0; //取权重

        virtual int getMark(int v)=0; //取出标记
        virtual void setMark(int v,int val)=0; //进行标记
};

边的结构体:

struct edge
{
    int start; //起始点
    int end; //终点
    int wt; //权重
};

图中承接虚类的基本函数实现:

//使用邻接矩阵进行存储
class Graphm: public Graph
{
    private:
        int numVertex,numEdge; //点数、边数
        int **matrix;  //邻接矩阵
        int *mark;  //指向存放有无访问该点的数组
        bool ishui; //判断是否为回路
    public:
        //构造函数
        Graphm(int numVert){Init(numVert);}
        //析构函数
        ~Graphm()
        {
            delete []mark;
            for(int i=0;i<numVertex;i++)
                delete []matrix[i];
            delete matrix;
        }
        //初始化函数
        void Init(int n)
        {
            int i;
            numVertex=n;
            numEdge=0;
            mark=new int[n];
            //开始都标记为为访问
            for (i=0;i<numVertex;i++)
                mark[i]=UNVISITED;
            matrix=(int**) new int*[numVertex];
            for (i=0;i<numVertex;i++)
                matrix[i]=new int[numVertex];
            for(i=0;i<numVertex;i++)
                for(int j=0;j<numVertex;j++)
                    matrix[i][j]=0;
            ishui=true;
        }
        //取出图中顶点数
        int n(){return numVertex;}
        //取出图中的边数
        int e(){return numEdge;}

        //返回v的第一个邻居
        int first(int v)
        {
            for(int i=0;i<numVertex;i++)
                if(matrix[v][i]!=0) return i;
            return numVertex;  //如果不存在,就返回n

        }
        //返回v在w之后的第一个邻居
        int next(int v,int w)
        {
            for (int i=w+1;i<numVertex;i++)
                if(matrix[v][i]!=0) return i;
            return numVertex;
        }
        //设置v1和v2之间的边权重为wt
        void setEdge(int v1,int v2,int wt)
        {
            if(matrix[v1][v2]==0) numEdge++; //如果没有边,则增加一条
            matrix[v1][v2]=matrix[v2][v1]=wt; 
        }
        //删除v1和v2之间的边
        void delEdge(int v1,int v2)
        {
            if(matrix[v1][v2]!=0) numEdge--;
            matrix[v1][v2]=0;
        }
        //判断v1和v2间是否有边
        bool isEdge(int v1,int v2)
        {
            return matrix[v1][v2]!=0;
        }
        int weight(int v1,int v2) {return matrix[v1][v2];}
        int getMark(int v) {return mark[v];}
        void setMark(int v,int val) {mark[v]=val;}
        void initMark(){for(int i=0;i<numVertex;i++) mark[i]=UNVISITED;}

		//打印邻接矩阵
        void print()
        {
            for (int i=0;i<numVertex;i++){
                for (int j=0;j<numVertex;j++)
                    cout<<matrix[i][j]<<" ";
                cout<<endl;
            }
        }
};

深度优先搜索遍历图:

给定了图以及开始的顶点v,如果顶点v没有被访问过,那么我们输出v并计数加1,然后将顶点v置为已访问。然后以v为顶点开始寻找一条路径进行搜索,搜索到底时退后一步寻找其他路径,直到所有顶点都被访问为止。

在下面代码中的具体实现就是使用了for循环,递归调用函数。

void DFS(Graphm* G,int v)
    {
        if(G->getMark(v)==UNVISITED)
        {    
            if(num==G->n()-2) cout<<v;
            else cout<<v<<" ";
            num++;
        }
        G->setMark(v,VISITED);
        for(int w=G->first(v);w<G->n();w=G->next(v,w))
            if(G->getMark(w)==UNVISITED)
                DFS(G,w);
    }

广度优先搜索遍历图:

使用了队列这一数据结构,同样是给定图以及开始顶点start,我们想将start存入队列进行访问后,将start出队并将其所有相邻顶点入队,重复此操作直到所有顶点访问完成为止。

注意:在代码最后我们加了一个判断图中是否存在为访问顶点的判断,当存在时说明图中有环。

void BFS(Graphm* G,int start)
        {
            int v,w;
            queue<int>q;
            int cnt=0;
            q.push(start);
            G->setMark(start,VISITED);
            while (!q.empty())
            {
                v=q.front();
                if(cnt==G->n()-2) cout<<v;
                else cout<<v<<" ";
                q.pop();
                for (w=G->first(v);w<G->n();w=G->next(v,w))
                    if(G->getMark(w)==UNVISITED)
                    {
                        G->setMark(w,VISITED);
                        q.push(w);
                    }
                cnt++;
            }
            //BFS搜索后判断是否有未访问的点
            for(int i=1;i<numVertex;i++)
            {
                if(G->getMark(i)==UNVISITED) {ishui=false;break;}
            }
        }

求单源最短路径:

给定图以及存放路径长度的数组D、开始顶点s、存放具体路径的字符串数组n。先挑选一条从s出发的边,然后比较是直接连接更近还是走其他的边更近,经过比较后选出两点间最短的路径。

void Dijkstra(Graph* G,int *D,int s,string n[])
{
    int v,i,w;
    for(i=0;i<G->n();i++)
    {
        v=minVertex(G,D);
        if(D[v]==INF) return;
        G->setMark(v,VISITED);
        for(w=G->first(v);w<G->n();w=G->next(v,w))
        {
            if(D[w]>(D[v]+G->weight(v,w)))
            {
                D[w]=D[v]+G->weight(v,w);
                n[w]=n[v]+" "+ std::to_string(w);
            }
        }
    }
}
//取最小
int minVertex(Graphm* G,int* D)
{
    int i,v=-1;
    for (i=0;i<G->n();i++)
        if(G->getMark(i)==UNVISITED) {v=i;break;}
    for (i++;i<numVertex;i++)
        if((G->getMark(i)==UNVISITED) && (D[i]<D[v]))
            v=i;
    return v;
}

求最小生成树:

给定图以及开始顶点s、开始顶点s与各点距离数组D、表示边的数组e。首先开始添加连接顶点s的一条最短边,然后将这两个顶点作为一个整体,添加连接它们的一条最短边,一直进行扩充直到包含所有顶点为止。

void Prim(Graphm* G,int *D,int s,struct edge e[])
{
    int *V=NULL;
    V = new int[G->n()];
    // int V[G->n()]={0};
    int ans=0;
    int i,w;
    for(i=1;i<G->n();i++)
    {
        int v=minVertex(G,D);
        G->setMark(v,VISITED);
        if(v!=s) 
        { 
            // cout<<V[v]<<" "<<v<<" "<<G->weight(v,V[v])<<endl;
            if(V[v]<v) {e[ans].start=V[v],e[ans].end=v,e[ans].wt=G->weight(V[v],v);}
            else 
               {e[ans].end=V[v],e[ans].start=v,e[ans].wt=G->weight(V[v],v);}
            ans++;
        }
        if(D[v]==INF) return;
        for(w=G->first(v);w<G->n();w=G->next(v,w))
        {
            if(D[w]>G->weight(v,w))
            {
                D[w]=G->weight(v,w);
                V[w]=v;
            }
        }
    }
}
int minVertex(Graphm* G,int* D)
{
    int i,v=-1;
    for (i=0;i<G->n();i++)
        if(G->getMark(i)==UNVISITED) {v=i;break;}
    for (i++;i<numVertex;i++)
        if((G->getMark(i)==UNVISITED) && (D[i]<D[v]))
            v=i;
    return v;
}

三.完整代码

Graph.h

#ifndef _GRAPH_H_
#define _GRAPH_H_以上是关于C++图(邻接矩阵实现+必要算法)的主要内容,如果未能解决你的问题,请参考以下文章

基于邻接矩阵的Dijkstra算法——C++实现

基于邻接矩阵的Dijkstra算法——C++实现

基于邻接矩阵的Dijkstra算法——C++实现

求算法,用邻接矩阵和邻接表创建一个图,实现深度和广度搜索,菜单形式,c语言的代码。无向无权的图。

对于 C++ 中的图问题,邻接列表或邻接矩阵哪个更好?

用c语言编程 1创建图的邻接矩阵和邻接表 2验证图的深度优先、广度优先遍历算法 3验证最短路径