图的创建和遍历

目录

catalog.png

图的定义

数据结构中,图由顶点构成如下:

图1

上图中数字代表顶点(Vertex),连接顶点的是边(Edge),通过表示顶点之间的逻辑关系。

无向图

  • 定义:若表示顶点关系的边没有方向(无向边),则此图为无向图,如图1。顶点Vi到Vj之间的边表示为(Vi,Vj),代表Vi,Vj邻接。

  • 顶点的度:

    与某个顶点有关的边的数量,代表此顶点的度。如图1,顶点1的度为2。

  • 完全无向图

    无向图中,如果每个顶点都存在到其他顶点的边,则此图为完全无向图:

UnGraph.png

​ 完全无向图有n(n-1)/2条边,n为顶点数。

有向图

  • 定义

    当顶点之间的边有方向时,图为有向图。如下:

directed.png

​ 并使用有序对<Vi,Vj>来表示顶点Vi,Vj之间的边,称为弧。Vi称为弧尾,Vj为弧头。例如,上图顶点A、D之间的边表示为:<A,D>表明方向A->D

  • 出度、入度

    以顶点Vi为弧尾的弧数量,表示Vi的出度,以顶点Vi为弧头的数量,表示Vi的入度。

  • 完全有向图

    任意顶点存在互为相反方向的边,称为完全有向图

图中的边或弧,存在对应的数字,这样的图称为网,边或弧对应的数字称为权。

network.png

上图的权值表示距离。

连通性

连通图

在无向图G中,存在从Vi到Vj的路径,则称Vi和Vj是连通的。如果G中任意两个顶点都是连通的,则称G为连通图。

connected.png

无向图中的极大连通子图称为连通分量,连通分量条件:

  • 子图
  • 子图要是连通的
  • 连通子图含有极大顶点数

强连通图

在有向图G中,对于任意Vi,Vj,存在Vi->Vj 和Vj->Vi的路径,则称G是强连通图。

有向图的极大强连通子图称为强连通分量

图的存储结构

邻接矩阵

使用两个数组来表示图。

  • 一个一维数组Vertex表示顶点
  • 一个二维数组edge表示边。
edge[i][j]= W       //表示权值
            0       //i=j
            Infinty // 表示边(i,j)不存在
Adjcency Matrix.png

邻接表

邻接表与邻接矩阵相比,使用链表代替二维数组。对于边数比顶点数少的情况,邻接矩阵存在着巨大的浪费,而使用链式结构可以很好的避免浪费。

  • 使用一个一维数组表示顶点
  • 使用单链表来表示每个顶点的邻接顶点
Adjcency List.png

其他结构

十字链表、多重邻接表

十字链表作用于有向图、多重邻接表作用于无向图,都是邻接表的进阶版本,不做详细介绍

邻接表代码实现

边节点结构:

typedef struct EdgeNode{                            //边节点
        int adjVertex;
        Tweight weight;                             //权重
        EdgeNode* next;
}EdgeNode;

顶点节点结构:

typedef struct VertexNode {                     //顶点节点
        Tvertex vertex;                             //顶点值
        EdgeNode* firstEdge;                        //指向的边
}VertexNode;

类声明:

template<typename Tvertex, typename Tweight>
class UndirectedGraph                               //邻接表实现无向图
{
private:
    typedef struct EdgeNode{                        //边节点
        int adjVertex;
        Tweight weight;                             //权重
        EdgeNode* next;
    }EdgeNode;

    typedef struct VertexNode {                     //顶点节点
        Tvertex vertex;                             //顶点值
        EdgeNode* firstEdge;                        //指向的边
    }VertexNode;

    std::vector<VertexNode> graph;                  //保存顶点的数组
    int verxNums;                                   //顶点数量
    int edgeNums;                                   //边数量
public:
    UndirectedGraph(int vn = 0,int en = 0):verxNums(vn),edgeNums(en){}
    void CreateGraph();
    void CreateGraph(std::vector<Tvertex> vertex, 
        std::pair<std::vector<int>, Tweight> edgeWeight[]);
    
};

图创建函数:

template<typename Tvertex, typename Tweight>
void UndirectedGraph<Tvertex, Tweight>::CreateGraph() {
     //初始化顶点数组
    for (int i = 0; i < verxNums; i++) {
        VertexNode* v = new VertexNode;
        std::cout << "Input vertex "<< i <<" value:" << std::endl;
        std::cin >> v->vertex;
        v->firstEdge = nullptr;
        graph.push_back(*v);
    }
    
    //为顶点添加相关的边信息
    for (int nums = 0; nums < edgeNums; nums++) {
        int i, j;
        Tweight weight;
        std::cout << "Input the i,j of (vi,vj): ";
        std::cin >> i >> j;
        std::cout << "Input the weight of (vi,vj): ";
        std::cin >> weight;

        EdgeNode* e = new EdgeNode;                 //添加(vi,vj)
        e->adjVertex = j;
        e->weight = weight;
        e->next = graph[i].firstEdge;               //插入边节点
        graph[i].firstEdge = e;

        e = new EdgeNode;                           //无向图还需要添加(vj,vi)
        e->adjVertex = i;                           
        e->weight = weight;
        e->next = graph[j].firstEdge;
        graph[j].firstEdge = e;
    }
}

为方便测试,编写了一个静态生成图的函数,内容基本一样,该函数从一个节点数组std::vector<Tvertex> vertex读入节点信息,一个pair结构数组std::pair<std::vector<int>, Tweight> edgeWeight[]表示边和权值:

template<typename Tvertex, typename Tweight>
void UndirectedGraph<Tvertex, Tweight>::CreateGraph(std::vector<Tvertex> vertex, 
    std::pair<std::vector<int>, Tweight> edgeWeight[]) {
    for (int i = 0; i < verxNums; i++) {
        VertexNode* v = new VertexNode;
        v->vertex = vertex[i];
        v->firstEdge = nullptr;
        graph.push_back(*v);
    }
    for (int nums = 0; nums < edgeNums; nums++) {

        int i, j;
        Tweight weight;
        i = edgeWeight[nums].first[0];
        j = edgeWeight[nums].first[1];
        weight = edgeWeight[nums].second;

        EdgeNode* e = new EdgeNode;                 //添加(vi,vj)
        e->adjVertex = j;
        e->weight = weight;

        e->next = graph[i].firstEdge;
        graph[i].firstEdge = e;

        e = new EdgeNode;
        e->adjVertex = i;                           //添加(vj,vi)
        e->weight = weight;

        e->next = graph[j].firstEdge;
        graph[j].firstEdge = e;
    }

    //显示相关信息
    for (auto v : graph) {
        std::cout << "Vertex: " << v.vertex << std::endl;
        std::cout << "Edges: ";
        for (auto p = v.firstEdge; p != nullptr;p = p->next) {
            std::cout << p->adjVertex << " ";
        }

        std::cout << std::endl;
        std::cout << std::endl;
    }
}

图的遍历

深度优先(Depth First Search)

与树的中根遍历模式一样类似

DFS.png

如上图是按照搜索右边的节点规则,进行深度优先遍历,从A开始不断深入搜索A->B->C->D->E->F->G->H,搜索完H发现没有未遍历的邻接点,开始回退,退回到D,然后访问I,完成所有节点的遍历。

深度优先遍历实现:

  • 与二叉树的遍历一样使用递归
  • 使用一个长度为顶点数的数组来标记顶点是否被访问

代码实现:

//声明
template<typename Tvertex, typename Tweight>
class UndirectedGraph                               //邻接表实现无向图
{
private:
    ...
    void DFS(std::vector<bool>& isVisted, int i);   //深度优先遍历递归调用函数,外部无需访问该方法申明为私有
    
public:
    ...         
    void DFSTraverse();
    ...
};

//定义
template<typename Tvertex, typename Tweight>
void UndirectedGraph<Tvertex, Tweight>::DFS(std::vector<bool>& isVisted,int i) {

    isVisted[i] = true;
    std::cout << graph[i].vertex << " ";
    for (auto p = graph[i].firstEdge; p != nullptr; p = p->next){   //遍历链表
        if (!isVisted[p->adjVertex]) {
            DFS(isVisted, p->adjVertex);                            //对链表元素继续DFS
        }
    }
    
}

template<typename Tvertex, typename Tweight>
void UndirectedGraph<Tvertex, Tweight>::DFSTraverse() {
    std::vector<bool> isVisted(verxNums,false);             //访问标识数组,初始化为false

    for (int i = 0; i < verxNums; i++) {                    //防止存在子图的情况,未遍历子图
        if (!isVisted[i]) {
            DFS(isVisted,i);
        }
    }
    std::cout << std::endl;
}

广度优先遍历

与树的层次遍历相似,一层一层往下遍历,对每一层做最大范围遍历。并且与树的层次遍历实现相同,使用队列保存未遍历元素

BFS.png

代码实现:

//声明
template<typename Tvertex, typename Tweight>
class UndirectedGraph                               //邻接表实现无向图
{
public:
    ...
    void BFSTraverse();
};

//定义
template<typename Tvertex, typename Tweight>
void UndirectedGraph<Tvertex, Tweight>::BFSTraverse() {
    std::vector<bool> isVisted(verxNums, false);
    std::queue<VertexNode> Q;
    VertexNode v;

    for (int i = 0; i < verxNums; i++) {        //防止存在子图的情况,未遍历子图
        if (!isVisted[i]) {
            Q.push(graph[i]);           
            isVisted[i] = true;
        }
        while (!Q.empty()) {
            v = Q.front();                      //去队头元素
            std::cout << v.vertex << " ";
            for (auto p = v.firstEdge; p != nullptr; p = p->next) {
                if (!isVisted[p->adjVertex]) {
                    Q.push(graph[p->adjVertex]);
                    isVisted[p->adjVertex] = true;
                }
            }
            Q.pop();        
            
        }
    }
    std::cout << std::endl; 
}

完整代码和测试

#pragma once
#include <iostream>
#include <vector>
#include <utility>
#include <queue>
#include <algorithm>


/********************************************
* 时间:2021/1/2
* 作者:tanghf
* 类简介:使用邻接表实现无向图
********************************************/
template<typename Tvertex, typename Tweight>
class UndirectedGraph                               //邻接表实现无向图
{
private:
    typedef struct EdgeNode{                        //边节点
        int adjVertex;
        Tweight weight;                             //权重
        EdgeNode* next;
    }EdgeNode;

    typedef struct VertexNode {                     //顶点节点
        Tvertex vertex;                             //顶点值
        EdgeNode* firstEdge;                        //指向的边
    }VertexNode;

    std::vector<VertexNode> graph;
    int verxNums;                                   //顶点数量
    int edgeNums;                                   //边数量

    void DFS(std::vector<bool>& isVisted, int i);   //深度优先遍历递归调用函数
public:
    UndirectedGraph(int vn = 0,int en = 0):verxNums(vn),edgeNums(en){}
    void CreateGraph(std::vector<Tvertex> vertex,       
        std::pair<std::vector<int>, Tweight> edgeWeight[]); //静态创建无向图
    void CreateGraph();                                     //动态输入创建无向图
    void DFSTraverse();
    void BFSTraverse();
};
    
    
template<typename Tvertex, typename Tweight>
void UndirectedGraph<Tvertex, Tweight>::CreateGraph(std::vector<Tvertex> vertex, 
    std::pair<std::vector<int>, Tweight> edgeWeight[]) {

    for (int i = 0; i < verxNums; i++) {
        VertexNode* v = new VertexNode;
        v->vertex = vertex[i];
        v->firstEdge = nullptr;
        graph.push_back(*v);
    }
    for (int nums = 0; nums < edgeNums; nums++) {

        int i, j;
        Tweight weight;
        i = edgeWeight[nums].first[0];
        j = edgeWeight[nums].first[1];
        weight = edgeWeight[nums].second;

        EdgeNode* e = new EdgeNode;                 //添加(vi,vj)
        e->adjVertex = j;
        e->weight = weight;

        e->next = graph[i].firstEdge;
        graph[i].firstEdge = e;

        e = new EdgeNode;
        e->adjVertex = i;                           //添加(vj,vi)
        e->weight = weight;

        e->next = graph[j].firstEdge;
        graph[j].firstEdge = e;
    }

    
    for (auto v : graph) {
        std::cout << "Vertex: " << v.vertex << std::endl;
        std::cout << "Edges: ";
        for (auto p = v.firstEdge; p != nullptr;p = p->next) {
            int adjVxIndex = p->adjVertex;
            std::cout << "(" << v.vertex << "," 
                << graph[adjVxIndex].vertex << "),weight = "<< p->weight<<" ";
        }

        std::cout << std::endl;
        std::cout << std::endl;
    }
}


template<typename Tvertex, typename Tweight>
void UndirectedGraph<Tvertex, Tweight>::CreateGraph() {
    //初始化顶点数组
    for (int i = 0; i < verxNums; i++) {
        VertexNode* v = new VertexNode;
        std::cout << "Input vertex " << i << " value:" << std::endl;
        std::cin >> v->vertex;
        v->firstEdge = nullptr;
        graph.push_back(*v);
    }

    //为顶点添加相关的边信息
    for (int nums = 0; nums < edgeNums; nums++) {
        int i, j;
        Tweight weight;
        std::cout << "Input the i,j of (vi,vj): ";
        std::cin >> i >> j;
        std::cout << "Input the weight of (vi,vj): ";
        std::cin >> weight;

        EdgeNode* e = new EdgeNode;                 //添加(vi,vj)
        e->adjVertex = j;
        e->weight = weight;
        e->next = graph[i].firstEdge;               //插入边节点
        graph[i].firstEdge = e;

        e = new EdgeNode;                           //无向图还需要添加(vj,vi)
        e->adjVertex = i;
        e->weight = weight;
        e->next = graph[j].firstEdge;
        graph[j].firstEdge = e;
    }

    //输出添加的信息
    for (auto v : graph) {
        std::cout << "Vertex: " << v.vertex << std::endl;
        std::cout << "Edges: ";
        for (auto p = v.firstEdge; p != nullptr; p = p->next) {
            std::cout << p->adjVertex << " ";
        }

        std::cout << std::endl;
        std::cout << std::endl;
    }
}

template<typename Tvertex, typename Tweight>
void UndirectedGraph<Tvertex, Tweight>::DFS(std::vector<bool>& isVisted,int i) {

    isVisted[i] = true;
    std::cout << graph[i].vertex << " ";
    for (auto p = graph[i].firstEdge; p != nullptr; p = p->next){
        if (!isVisted[p->adjVertex]) {
            DFS(isVisted, p->adjVertex);
        }
    }
    
}


template<typename Tvertex, typename Tweight>
void UndirectedGraph<Tvertex, Tweight>::DFSTraverse() {
    std::vector<bool> isVisted(verxNums,false);

    for (int i = 0; i < verxNums; i++) {
        if (!isVisted[i]) {
            DFS(isVisted,i);
        }
    }
    std::cout << std::endl;
}

template<typename Tvertex, typename Tweight>
void UndirectedGraph<Tvertex, Tweight>::BFSTraverse() {
    std::vector<bool> isVisted(verxNums, false);
    std::queue<VertexNode> Q;
    VertexNode v;

    for (int i = 0; i < verxNums; i++) {
        if (!isVisted[i]) {
            Q.push(graph[i]);
            isVisted[i] = true;
        }
        while (!Q.empty()) {
            v = Q.front();
        
            std::cout << v.vertex << " ";
            for (auto p = v.firstEdge; p != nullptr; p = p->next) {
                if (!isVisted[p->adjVertex]) {
                    Q.push(graph[p->adjVertex]);
                    isVisted[p->adjVertex] = true;
                }
            }
            Q.pop();
            
        }
    }
    std::cout << std::endl; 
}

对如下图进行测试:

minispantree.png

main:

int main() {
    UndirectedGraph<string,int> g(9,15);
    pair <vector<int>, int> edgeWeight[15] = {
        {{0,1},10},{{0,5},11},{{1,2},18},
        {{1,6},16},{{1,8},12},{{2,8},8},
        {{2,3},22},{{3,6},24},{{3,8},21},
        {{3,7},16},{{3,4},20},{{4,5},26},
        {{4,7},7},{{5,6},17},{{6,7},19},
    };
    vector<string> vertex = { "v0","v1","v2","v3","v4","v5","v6","v7","v8" };

    g.CreateGraph(vertex,edgeWeight);
    cout << "DFS:" << endl;
    g.DFSTraverse();
    cout << "BFS:" << endl;
    g.BFSTraverse();
    
}
result.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • 目录 第六章 图 第一节 基本概念 1.1 定义和术语1.2 基本操作 第二节 存储结构 2.1 邻接矩阵2.2 ...
    Zal哥哥阅读 674评论 0 0
  • 图是由顶点的有穷非空集合和顶点之间的边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图中的顶点的集...
    keeeeeenon阅读 535评论 0 2
  • 主要知识点 图的概述 图的存储结构 图的遍历 最小生成树 最短路径 拓扑排序 关键路径 一、图的概念 图的定义: ...
    JiaJianHuang阅读 1,701评论 0 0
  • https://zh.visualgo.net/graphds 浅谈图形结构https://zh.visualgo...
    狼之独步阅读 4,137评论 0 0
  • 图的相关概念 图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为G(V,E),其中,G表示一...
    潘雪雯阅读 195评论 0 0