Loading

数据结构-图

基本概念

图表示多对多的关系。包含

  • 一组顶点:通常用V(Vertex)表示顶点集合
  • 一组边:通常用E(Edge)表示边的集合
    • 边是顶点对:(v,w)是集合E的元素,其中v,w是集合V的元素
    • 有向边<v,w>表示从v指向w的边(单行线)
    • 不考虑重边和自回路

抽象数据类型定义

类型名称:图
数据对象集:G(V,E)由一个非空的有限顶点集合V和一个有限边集合E组成
操作集:对于任意图G,以及v,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的最小生成树

图的表示

图有两种表示方法,邻接矩阵和邻接表

我们可以创建一个二维数组G[N][N],若<vi,vj>是G中的边,则G[i][j]=1,否则为0。对于有向图,邻接矩阵是对称的,而对于无向图则不是。对于网络,G[i][j]的值是边<vi,vj>的权重,如果没有边,则为-∞。

邻接矩阵的优点

  • 直观、简单、好理解
  • 方便检查任意一对顶点间是否存在边
  • 方便找任一顶点的所有“邻接点”(有边直接相连的顶点)
  • 方便计算任一顶点的“度”(从该点出发的边数为“出度”,指向该点的边数为“入度”)
    • 无向图:对应行(或列)非0元素的个数
    • 有向图:对应行非0元素的个数是“出度”,对应列非0元素的个数是“入度”

邻接矩阵的缺点

  • 浪费空间:存稀疏图(点很多而边很少)有大量无效元素
    • 对稠密图(特别是完全图)还是很合算的
  • 浪费时间:统计稀疏图中一共有多少条边

同时,我们可以创建一个指针数组G[N],对应矩阵每行一个链表,只存非0元素。对于网络,链表结构中要增加权重的域。

邻接表的优点

  • 方便找任一顶点的所有“邻接点”
  • 节约稀疏图的空间
    • 需要N个头指针+2E个结点(每个结点至少2个域)
  • 方便计算任一顶点的“度”
    • 无向图:任一顶点的度就是链表的长度
    • 有向图:只能计算“出度”,需要构造“逆邻接表”(存指向自己的边)来方便计算“出度”

邻接表的缺点

  • 不方便检查任意一对顶点间是否存在边

邻接矩阵表示

结构体定义
#define MaxVertexNum 100
#define INFINITY 65535

typedef int Vertex;    		//用顶点下标表示顶点,为整型
typedef int WeightType;
typedef char DataType;

typedef struct ENode *PtrToENode;
struct ENode{		//边的定义
	Vertex V1,V2;		//有向边<v1,v2>
	WeightType Weight;		//权重
};
typedef PtrToENode Edge;

typedef struct GNode *PtrToGNode;
struct GNode{		//图结点的定义
	int Nv;		//顶点数
	int Ne;		//边数
	WeightType G[MaxVertexNum][MaxVertexNum];		//邻接矩阵
	DataType Data[MaxVertexNum];		//存顶点的数据,如果顶点不存数据,可以省略
};
typedef PtrToGNode MGraph;
图的初始化
MGraph CreateGraph(int VertexNum){     //初始化一个有VertexNum个顶点但没有边的图
    Vertex V,W;
    MGraph Graph;

    Graph=(MGraph)malloc(sizeof(struct GNode));      //建立图
    Graph->Nv=VertexNum;
    Graph->Ne=0;

    for(V=0;V<Graph->Nv;V++)      //初始化邻接矩阵,顶点编号为0-(Nv-1)
       for(W=0;W<Graph->Nv;W++)
          Graph->G[V][W]=INFINITY;

    return Graph;
}
边的插入
void InsertEdge(MGraph Graph, Edge E){
    Graph->G[E->V1][E->V2]=E->Weight;      //插入边<V1,V2>
    Graph->G[E->V2][E->V1]=E->Weight;      //若是无向图,还要插入边<V2,V1>
}
图的建立
MGraph BuildGraph(){
    MGraph Graph;
    Edge E;
    Vertex V;
    int Nv,i;

    scanf("%d",&Nv);     //读入顶点个数
    Graph=CreateGraph(Nv);     //初始化有Nv个顶点但没有边的图

    scanf("%d",&(Graph->Ne));     //读入边数
    if(Graph->Ne!=0){
       E=(Edge)malloc(sizeof(struct ENode));     //建立边结点
       for(i=0;i<Graph->Ne;i++){
          scanf("%d %d %d",&(E->V1),&(E->V2),&(E->Weight));     //读入边
          InsertEdge(Graph,E);    //插入边
       }
    }

    for(V=0;V<Graph->Nv;V++)
       scanf("%d",&(Graph->Data[V]));      //如果顶点有数据,则读入数据

    return Graph;
}

邻接表表示

结构体定义
#define MaxVertexNum 100

typedef int Vertex;     //顶点下标表示顶点,为整型
typedef int WeightType;
typedef char DataType;

typedef struct ENode *PtrToENode;
struct ENode{     //边的定义
    Vertex V1,V2;     //有向边<V1,V2>
    WeightType Weight;
};
typedef PtrToENode Edge;

typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode{     //邻接点的定义
    Vertex AdjV;      //邻接点下标
    WeightType Weight;      //边权重
    PtrToAdjVNode Next;     //指向下一个邻接点的指针
};

typedef struct Vnode{      //顶点表头结点的定义
    PtrToAdjVNode FirstEdge;      //边表头指针
    DataType Data;       //存顶点的数据
}AdjList[MaxVertexNum];       //AdjList是邻接表类型的数组

typedef struct GNode *PtrToGNode;
struct GNode{     //图结点的定义
    int Nv;     //顶点数
    int Ne;     //边数
    AdjList G;     //邻接表
};
typedef PtrToGNode LGraph;
图的初始化
LGraph CreateGraph(int VertexNum){      //初始化一个有VertexNum个顶点但没有边的图
    Vertex V;
    LGraph Graph;

    Graph=(LGraph)malloc(sizeof(struct GNode));     //建立图
    Graph->Nv=VertexNum;
    Graph->Ve=0;

    for(V=0;V<Graph->Nv;V++)        //初始化邻接表头指针,定点编号0-(Graph->Nv-1)
       Graph->G[V].FirstEdge=NULL;

    return Graph;
}
边的插入
void InsertEdge(LGraph Graph, Edge E){
    PtrToAdjVNode NewNode;
    //插入边<V1,V2>
    NewNode=(PtrToAdjVNode)malloc(sizeof(struct AdjVNode));     //为V2建立新的邻接点
    NewNode->AdjV=E->V2;
    NewNode->Weight=E->Weight;
    //将V2插入V1的表头
    NewNode->Next=Graph->G[E->V1].FirstEdge;
    Graph->G[E->V1].FirstEdge=NewNode;
    //若是无向图,还要插入边<V2,V1>
    NewNode=(PtrToAdjVNode)malloc(sizeof(struct AdjVNode));     //为V1建立新的邻接点
    NewNode->AdjV=E->V1;
    NewNode->Weight=E->Weight;
    //将V1插入V2的表头
    NewNode->Next=Graph->G[E->V2].FirstEdge;
    Graph->G[E->V1].FirstEdge=NewNode;
}
图的建立
LGraph BuildGraph(){
    LGraph Graph;
    Edge E;
    Vertex V;
    int Nv,i;

    scanf("%d",&Nv);        //读入顶点个数
    Graph=CreateGraph(Nv);      //初始化有Nv个顶点但没有边的图

    scanf("%d",&(Graph->Ne));       //读入边数
    if(Graph->Ne!=0){
        E=(Edge)malloc(sizeof(struct ENode));       //建立边结点
        for(i=0;i<Graph->Ne;i++){
            scanf("%d %d %d",&(E->V1),&(E->V2),&(E->Weight));       //读入边的起点、终点、权重
            InsertEdge(Graph,E);        //插入边
        }
    }

    for(V=0;V<Graph->Nv;V++)        //如果顶点有数据,读入数据
        scanf("%d",&(Graph->G[V].Data));

    return Graph;
}

图的遍历

图的遍历有两种方法,深度优先搜索(DFS)和广度优先搜索(BFS)。

深度优先搜索

图的深度优先搜索类似于树的先序遍历。若有N个顶点、E条边,时间复杂度为

  • 用邻接表存储图,有O(N+E)
  • 用邻接矩阵存储图,有O(N^2)

伪代码如下:

void DFS(Vertex V){
    visited[V]=true;
    for(V的每个邻接点W)
        if(!visited[W])
            DFS(W);
}

C语言代码如下:

void Visit(Vertex V){
    printf("正在访问顶点%d",V);
}

void DFS(LGraph Graph, Vertex V, void (*Visit)(Vertex)){        //以V为出发点对邻接表存储的图Graph进行DFS
    PtrToAdjVNode W;

    Visit(V);       //访问第V个顶点
    Visited[V]=true;        //Visited[]为全局变量,初始化为false,已访问则标记为true

    for(W=Graph->G[V].FirstEdge;W;W=W->Next)        //对V的每个邻接点W->AdjV
        if(!Visited[W->AdjV])       //若W->AdjV未被访问
            DFS(Graph,W->AdjV,Visit);       //则递归访问
}
广度优先搜索

图的广度优先搜索类似于树的层序遍历,需要使用队列。若有N个顶点、E条边,时间复杂度为

  • 用邻接表存储图,有O(N+E)
  • 用邻接矩阵存储图,有O(N^2)

伪代码如下:

void BFS(Vertex V){
    visited[V]=true;
    Enqueue(V,Q);
    while(!IsEmpty(Q)){
        V=Dequeue(Q);
        for(V的每个邻接点W)
            if(!visited[W]){
                visited[W]=true;
                Enqueue(W,Q);
            }
    }
}

C语言代码如下:

bool IsEdge(MGraph Graph, Vertex V, Vertex W){      //判断<V,W>是否是图Graph中的一条边,即W是否是V的邻接点
    return Graph->G[V][W]<INFINITY?true:false;
}

void BFS(MGraph Graph, Vertex S, void (*Visit)(Vertex)){        //以S为出发点对邻接矩阵存储的图Graphj进行BFS
    Queue Q;
    Vertex V,W;

    Q=CreateQueue(MaxSize);     //创建空队列

    Visit(S);
    Visited[S]=true;
    AddQ(Q,S);      //S入队

    while(!IsEmpty(Q)){
        V=DeleteQ(Q);       //出队
        for(W=0;W,Graph->Nv;W++)        //对于图中的每个顶点W
            if(!Visited[W]&&IsEdge(Graph,V,W)){     //如果W是V的临界点且未被访问过
                Visit(W);
                Visited[W]=true;
                AddQ(Q,W);
            }
    }
}
posted @ 2020-07-05 18:45  Kinopio  阅读(102)  评论(0编辑  收藏  举报