【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_
#include<iostream>
#include<queue>
#include<string>
#include<sstream>
using namespace std;

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; //进行标记
};


int num=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;
            }
        }

        //深度优先搜索遍历图
        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);
        }
        //广度优先搜索遍历图
        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;}
            }
        }

        //求单源最短路径
        void Dijkstra(Graphm* 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;
        }

        //Prim算法就最小生成树
        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;
                    }
                }
            }
        }
        //判断是否有回路
        bool isHui()
        {
            return ishui;
        }
};


#endif

main.cpp

#include"Graph.h"
#include<algorithm>
using namespace std;

bool cmp(edge &a,edge &b) { return a.wt<=b.wt;}

int main()
{
    //p为顶点数,m为边数
    int p=0,m=0;
    cin>>p>>m;
    Graphm graph(p+1);
    Graphm* G=&graph;
    //建立图中的边
    cout<<"input egde:"<<endl;
    for(int i=0;i<m;i++)
    {
        int a=0,b=0,c=0;
        cin>>a>>b>>c;
        graph.setEdge(a,b,c);
    }
    cout<<"BFS:"<<endl;
    graph.BFS(G,1);
    cout<<endl;
    graph.initMark();

    cout<<"DFS:"<<endl;
    graph.DFS(G,1);
    cout<<endl;
    graph.initMark();

    int *distance=NULL;
    string *n=NULL;
    distance = new int[p+1];
    n = new string[p+1];

    for (int i=0;i<p+1;i++) n[i]="1 "+std::to_string(i);
    for (int i=0;i<p+1;i++)
    {
        if(i==1) distance[i]=0;
        else if(G->weight(1,i)==0) distance[i]=INF;
        else  distance[i]=G->weight(1,i);
    }
    cout<<"Dijkstra:"<<endl;
    graph.Dijkstra(G,distance,1,n);
    for (int i=2;i<p+1;i++)
    {
        cout<<n[i]<<" "<<distance[i]<<endl;
    }
    graph.initMark();
    
    edge *e=NULL;
    e = new edge[p-1];

    for (int i=0;i<p+1;i++)
    {
        if(i==1) distance[i]=0;
        else distance[i]=INF;
    }
    cout<<"Prim:"<<endl;
    graph.Prim(G,distance,1,e);
    //根据最小树中边的权重进行排序,权重小的放在前面
    sort(e,e+5,cmp);
    for(int i=0;i<p-1;i++)
    {
        cout<<e[i].start<<" "<<e[i].end<<" "<<e[i].wt<<endl;
    }

    cout<<"Is it a huilu?"<<endl;
    if(graph.isHui()==true) cout<<"YES";
    else cout<<"NO";
    
    system("pause");
    return 0;
}

/*
6 7
1 2 1
1 5 2
2 3 3
2 5 4
3 4 5
4 5 6
4 6 7
*/

四.结果展示

操作介绍:首先输入顶点数p以及边数m;然后依次输入m条边,格式为起点、终点、权重;首先返回BFS结果、然后返回DFS结果、然后返回Dijstra结果,格式为出发点到终点的路径最后为总长度;然后返回Prim结果,一共m-1条边,格式为起点,终点,权重;最后判断图中是否有回路。
在这里插入图片描述

  • 13
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论
以下是C++使用邻接矩阵实现Dijkstra算法的示例代码: ```c++ #include <iostream> #include <limits.h> #define V 9 // 找到距离最小的未访问节点 int min_distance(int dist[], bool visited[]) { int min_dist = INT_MAX, min_index = -1; for (int v = 0; v < V; v++) { if (!visited[v] && dist[v] <= min_dist) { min_dist = dist[v]; min_index = v; } } return min_index; } // 打印最短路径 void print_path(int parent[], int j) { if (parent[j] == -1) { std::cout << j << " "; return; } print_path(parent, parent[j]); std::cout << j << " "; } // Dijkstra算法 void dijkstra(int graph[V][V], int src) { int dist[V], parent[V]; bool visited[V]; for (int i = 0; i < V; i++) { dist[i] = INT_MAX; visited[i] = false; parent[i] = -1; } dist[src] = 0; for (int count = 0; count < V - 1; count++) { int u = min_distance(dist, visited); visited[u] = true; for (int v = 0; v < V; v++) { if (!visited[v] && graph[u][v] && dist[u] != INT_MAX && dist[u] + graph[u][v] < dist[v]) { dist[v] = dist[u] + graph[u][v]; parent[v] = u; } } } std::cout << "最短路径:" << std::endl; for (int i = 0; i < V; i++) { std::cout << src << " -> " << i << " 距离:" << dist[i] << " 路径:"; print_path(parent, i); std::cout << std::endl; } } int main() { int graph[V][V] = { { 0, 4, 0, 0, 0, 0, 0, 8, 0 }, { 4, 0, 8, 0, 0, 0, 0, 11, 0 }, { 0, 8, 0, 7, 0, 4, 0, 0, 2 }, { 0, 0, 7, 0, 9, 14, 0, 0, 0 }, { 0, 0, 0, 9, 0, 10, 0, 0, 0 }, { 0, 0, 4, 14, 10, 0, 2, 0, 0 }, { 0, 0, 0, 0, 0, 2, 0, 1, 6 }, { 8, 11, 0, 0, 0, 0, 1, 0, 7 }, { 0, 0, 2, 0, 0, 0, 6, 7, 0 } }; dijkstra(graph, 0); return 0; } ``` 上述代码中,我们使用邻接矩阵表示,dist数组存储源点到各个节点的距离,parent数组存储最短路径上每个节点的前驱节点,visited数组表示该节点是否被访问过。在Dijkstra算法中,我们通过找到距离最小的未访问节点,以及对其邻接点进行松弛操作,来逐步求解源点到各个节点的最短距离。最后打印出最短路径和距离即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

比奇堡咻飞兜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值