数据结构——图(Graph)

数据结构——图(Graph)


目录

1.图的基本概念

2.图的基本代码操作

3.图的进阶操作和应用


1.1 图的定义

任何一个图都是由顶点和边构成的。我们采用集合的思想,可以把任何一个图用两个集合表示出来:记作 G = ( V , E ) G = (V,E) G=(V,E),其中V是表示顶点的有限集合,E为边的有限集合。
允许E为空集,此时表示的含义是:图G只有顶点但是不存在边。


1.2 图的相关术语

1. 有向图和无向图

我们举一个例子来理解:
题目描述:
某省政府进行"畅通工程",现在给出每两个城市之间的道路和道路长度,问从给定的a城市出发,最短需要走多长才能到达b城市,题目保证a,b之间存在路径。

只看题目的话,我们就能发现,这里的路径是一个无向路径,因为a和b之间存在一条路,那么必然可以同时实现a -> bb -> a,所以我们会得到一个无向图。

有向图的概念自然就明了了,对于任何一条路径他的方向是确定的,只存在a -> b或者b -> a.


2.度、入度、出度

对于无向图,每一个顶点的度就可以理解为它所连接的边数。对于有向图,由于点与点之间存在了方向,所以我们定义:入度是以该顶点为终点的边的条数,出度则是以该顶点为起点的边的条数。该顶点的度为入度和出度之和。比如下图中,顶点4的入度为1,出度为2.

例题
在这里插入图片描述


3.路径和路径长度

在一个图G中,从顶点i到顶点j的路径是一个顶点序列 i = i 0 , i 1 , . . . . , i m = j i = i_0,i_1,....,i_m = j i=i0,i1,....,im=j路径长度是指一条路径上经过的边的数目。 比如这里的从顶点1到3的路径长度为2,经过了1 -> 22 -> 3


2.0 图的基本操作

分为这几种:

  1. 建图(建立存储结构)
  2. 销毁图
  3. 输出图
  4. 求顶点的度

2.1 建立一个图

图的建立其实就是存图。主要有两种存储结构,即邻接矩阵和邻接表。


1. 邻接矩阵存图

邻接矩阵表示的是节点之间的相邻关系,对于一个不带权的矩阵我们有:
A [ i ] [ j ] = 1 , i ≠ j A[i][j] = {1,i \neq j} A[i][j]=1,i=j
A [ i ] [ j ] = 0 , i = j A[i][j] = 0,i = j A[i][j]=0,i=j
对于有权图只需要在第一步里更改权值。
图的邻接矩阵对于查询两个顶点i,j之间是否有边是非常方便的。


看图


现在给出代码
前置代码:

#define MaxSize 1e3 + 10
#define INF 0x3f3f3f3f
typedef char VertexType[10];
typedef struct vertex
{
    int adjvex; //顶点编号
    VertexType data; //顶点的数据信息
} VType; //顶点类型

typedef struct graph
{
    int e,n; //n为点数,e为边数
    VType vexs[MaxSize]; //顶点集合
    int edge[MaxSize][MaxSize]; //边的集合
} MatGraph;  //图的邻接矩阵类型


- 建立图的邻接矩阵算法

void CreatGraph(MatGraph &g,int A[][MaxSize],int n,int e)
{
    g.n = n;g.e = e;
    for(int i = 0;i < n;i++){
        for(int j = 0;j < n;j++){
            g.edges[i][j] = A[i][j];
        }
    }
}

- 销毁图算法
邻接矩阵存图的空间是系统自己分配的,系统自动分配释放空间,所以在销毁图中不需要进行额外操作。

void DestoryGraph(MatGraph g)
{

}

- 输出图算法

void DisGraph(MatGraph g)
{
    for(int i = 0;i < n;i++){
        for(int j = 0;j < n;j++){
            if(g.edges[i][j] < INF) printf("%4d",g.edges[i][j]);
            else printf("%4s","∞")
        }
        printf("\n");
    }
}  

INF是一个很大的数,我们一般认为是0x3f3f3f3f,以后常用它来表示无穷。


- 求顶点度的算法
对于有向图和无向图,有两种不同的算法。
对于无向图:没有入度出度的说法,所以直接求总度数即可。

int Degreel(MatGraph g,int v)
{
    if(v < 0 || v >= g.n) return -1; //顶点编号错误
    for(int i = 0;i < g.n;i++){
        if(g.edges[v][i] > 0 && g.edges[v][i] < INF) d++;
    }  
    return d;  
}

对于有向图而言,度就存在一定的区别了,对于一个点,既有出度也有入度,所以一层循环显然是解决不到的。

int Degree2(MatGraph g,int v)
{
    int d1 = 0,d2 = 0,d;
    if(v < 0 || v > g.n) return -1;
    for(int i = 0;i < g.n;i++){  //计算出度
        if(g.edges[v][i] > 0 && g.edges[v][i] < INF) d1 ++;
    }
    for(int i = 0;i < g.n;i++){  //计算入度
        if(g.edges[i][v] > 0 && g.edges[i][v] < INF) d2 ++;
    }
    d = d1 + d2;
    return d;
}

2.邻接表存图

邻接表的存储思路为:我们希望把图中的每一个顶点作为一个独立的头节点,然后把和这个点相邻的点全部链起来,每一个节点对应的就是一条边。
其实邻接表的存储就是:把每一条边的关系用相邻点的链接表示,每一个节点都存在三个域:权值域、指针域、顶点域。分别存放需要的各种信息。


上图:

对应的邻接表存储:

可以直观看出,邻接矩阵更容易理解,而在实际应用中,邻接表的另一种非指针表达:链式前向星更为常用,这里不再给出描述,有兴趣的同学移步CSDN(笔者也没懂),所以这里只给出邻接表的代码实现。后面的应用我们主要使用邻接矩阵。


- 邻接表建图
首先给出邻接表的声明

typedef char VertexType[10]; //定义一个字符串类型,其实用C++的string 是一样的
typedef struct edgenode  //建立边节点
{
    int adjext;
    int weight;
    struct edgenode * nectarc;
} ArcNode;

typedef struct vexnode //建立头节点
{
    VertexType data; 
    ArcNode * firstarc; //头节点指向的第一条边
} VHeadNode;

typedef struct 
{
    int n,e;
    VHeadNode adjlist[Maxsize]; //建立头节点数组
} AdjGraph;


- 建立邻接表算法

void CreatGraph(AdjGraph * & G,int A[][Maxsize],int n,int e)
{
    ArcNode * p;
    G = (AdjGraph * )malloc(sizeof(AdjGraph));
    G -> n = n;
    G -> e = e;
    for(int i = 0;i < n;i++) G -> adjlist[i].firstace = NULL;
    for(int i = 0;i < G -> n;i++){
        for(int j = G -> n - 1;j >= 0;j--){
            if(A[i][j] > 0 && A[i][j] < INF){
                p = (ArcNode * )malloc(sizeof(ArcNode));
                p -> adjvex = j;
                p -> weight = A[i][j];
                p -> nextarc = G -> adjlist[i].firstarc;
                G -> adjlist[i].firstarc = p;     
            }
        }
    }
}

- 销毁图算法
由于分配链表空间时,我们申请了动态存储空间,所以程序结束时需要手动释放!

void DestoryGraph(AdjGraph * & G)
{
    ArcNode * pre,* p;
    for(int i = 0;i < G -> n;i++){
        pre = G -> adjlist[i].firstarc;
        if(pre != NULL){
            p = pre -> nextarc;
            while(p != NULL){
                free(pre);
                pre = p;
                p = p -> nextarc;
            }
            free(pre);
        }
    }
    free(G);
}

- 输出图的算法

void DisplayGraph(AdjGraph * G)
{
    ArcNode * p;
    for(int i = 0;i < G -> n;i++){
        printf("  [%2d]",i);
        p = G -> adjlist[i].firstarc;
        if(p != NULL) printf("->");
        while(p != NULL){
            printf(" %d%(d"),p -> adjvex,p -> weight);
            p = p -> nextarc;
        } 
        printf("\n");
    }
}

- 求顶点度算法
同样对于有向图和无向图有两种写法,下面给出无向图的写法:

int Degreel(AdjGraph * G,int v)
{
    int d = 0;
    ArcNode * p;
    if(v < 0 || v >= G.n) return -1;
    p = G -> adjlist[i].firstarc;
    while(p != NULL){
        d++;
        p = p -> nextarc;
    }
    return d;
}

对于有向图,和邻接矩阵的思路类似,分别统计入度和出度。

int Dgreel(AdjGraph * g,int v)
{
    int d1 = 0,d2 = 0,d;
    ArcNode * p;
    if(v < 0 || v >= G -> n) return -1;
    p = G -> adjlist[i].firstarc;
    while(p != NULL){
        d1 ++;
        p = p -> nextarc;
    }
    for(int i = 0;i < G -> n;i++){
        p = G -> adjlist[i].firstarc;
        while(p != NULL){
            if(p -> adjvex == v) d2++;
            p = p -> nextarc;
        }
    }
    d = d1 + d2;
    return d;
}

题目的讲解和相关算法的描述在我的录课中有比较全的讲解,大家可以移步bilibili

  • 12
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

CUCKyrie

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

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

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

打赏作者

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

抵扣说明:

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

余额充值