数据结构学习笔记之图

概念

图的定义

图(Graph)是由顶点的有穷非空集合和顶点之间的边的集合组成,通常
表示为: G(V,E)。其中,G 表示一个图,V 是图 G 中顶点的集合,E 是
图 G 中边的集合。

需要注意:

  1. 图中数据元素叫做顶点(Vertext)。

  2. 在图中,不允许没有顶点。若 V 是图的顶点的集合,那么,V 是非空
    有穷集合。

  3. 图的任意两个顶点之间都可能有关系,它们的关系用边来表示。边集可
    以是空的。

其他概念

无向边

若顶点 $V_i$ 到 $V_j$ 之间的边没有方向,这条边就叫做无向边(Edge),
用无序偶对 ($V_iV_j$) 来表示。

无向图

如果图中任意两个顶点之间的边都是无向边,则称该图为无项图(Undirected graphs)。

有向边

若从顶点 $V_i$ 到 $V_j$ 的边有方向,则称这条边为有向边,也称为弧 (Arc)。

这条有向边用有序偶 $<V_i,V_j>来表示,$V_j是弧尾(Tail),$V_j$是弧头(Head)

有向图

如果图中任意两个顶点之间的边都是有向边,这个图就是有向图。

无向边用小括号 “()”表示,有向边用尖括号“<>”表示。

简单图

在图中,若不存在顶点到其自身的边,且同一条边不重复出现,这样的图就是简单图。

数据结构_图_简单图

无向完全图

在无向图中,如何任意两点之间都存在边,这个图就是无向完全图。

n 个顶点的无向完全图有 ${n(n-1)} \over 2$ 条边。

有向完全图

在有向图中,如果任意两点之间都存在方向互为相反的两条弧,这个图就是有向完全图。

n个顶点的有向完全图有 $n(n-1) \over 2$ 条边。

稠密图和稀疏图

边或弧很少的图是稀疏图;边或弧很多的图是稠密图。

它们都是相对概念。

权、网

与图的边或弧相关的数字叫做权(Weight)。

权可以表示一个顶点到另一个顶点的距离或耗费。

带权的图叫做网(Network)。

子图

假设有两个图 $G = (V,\lbrace E \rbrace)$,$G' = (V',\lbrace E' \rbrace)$,$V' \subseteq V$,$E' \subseteq E$,那么,
G'G 的子图。

图的顶点与边间的关系(暂时不做笔记,有空再补充)

概念太多,记录太麻烦,暂时不做笔记,有空再补充。

连通图

连通图

图7-2-13图2根据定义,我认为它不是强连通图。

数据结构_图_连通图
数据结构_图_连通图_2
数据结构_图_连通图_3
数据结构_图_连通图_4

生成树

不理解。

数据结构_图_生成树_1
数据结构_图_生成树_2

有向树

不理解。

数据结构_图_有向树_1
数据结构_图_有向树_2

图的抽象数据类型

ADT 图(Graph)

Data
    顶点的有穷非空集合和边的集合
    
Operation
    CreateGraph(*G, V, VR):按照顶点集 V 和 边弧集 VR 的定义构造图 G。
    DestroyGraph(*G):图G存在则销毁它。
    LocateVex(G, u):若图 G 中存在顶点 u,则返回它在图中的位置。
    GetVex(G, v):返回图 G 中顶点 v 的值。
    PutVex(G, v, value):将图 G 中顶点 v 赋值 value。
    FirstAdjVex(G, *v):返回顶点 v 的一个邻接顶点,若顶点在 G 中无邻接顶点则返回空。
    NextAdjVex(G, v, *w):返回顶点 v 相对于顶点 w 的下一个邻接顶点,若 w 是 v 的最后
                          一个邻接点则返回“空”。
    InsertVex(*G, v):在图 G 中增添新顶点 v。
    DeleteVex(*G, v): 删除图  G 中顶点 v 及其相关的弧。
    InsertArc(*G, v, w):在图中增添弧 <v,w>,若 G 是无向图,还要增加对称弧 <w,v>。
    DeleteArc(*G, v, w):在图中删除弧 <v,w>,若 G 是无向图,还需要删除对称弧 <w,v>。
    DFSTraverse(G):对图 G 深度优先遍历,在遍历过程中对每个顶点调用。
    HFSTraverse(G):对图 G 广度优先遍历,在遍历过程中对每个顶点调用。
endADT

图的存储结构(难理解)

邻接矩阵

定义

图的邻接矩阵(Adjacency Matrix)存储方式是用两个数组表示图。一个一维数组存储图中顶点信息,一个
二维数组(称为邻接矩阵)存储图中的边或弧信息。

数据结构_图_邻接矩阵_定义
无向图实例
数据结构_图_邻接矩阵_解释
有向图实例
数据结构_图_邻接矩阵_有向图
网图
数据结构_图_邻接矩阵_网图定义
数据结构_图_邻接矩阵_网图

邻接矩阵存储结构

邻接矩阵存储结构代码:

备注:用65536代替 $\infty$

typedef char VertexType;
typedef int EdgeType;
#define MAXVEX 100;     // 最大顶点数
#define INFINITY 65535;  
typedef struct
{
    VertexType vexs[MAXVEX];    // 顶点表
    EdgeType arc[MAXVEX][MAXVEX];   // 邻接矩阵,可看作边表
    int numVertexes, numEdges;  // 图中当前顶点数和边数
}MGraph;

创建无向网图代码:

void CreateGraph(MGraph *G)
{
    int i, j, k, w;
    printf("%s", "输入顶点数和边数:\n");
    scanf("%d, %d", &G->numVertexes, &G->numEdges);     // 输入顶点数和边数
    for(i = 0; i < G->numVertexes; i++){
        scanf(&G->vexs[i]);
    }
    for(i = 0; i < G->numVertexes; i++){
        for(j = 0; j < G->numVertexes; j++){
            G->arc[i][j] = INFINITY;    // 邻接矩阵初始化
        }
    }
    for(k = 0; k < G->numEdges; k++){   // 读入numEdges条边,建立邻接矩阵
        printf("输入边(vi,vj)上的下标i、下标j和权w:\n");
        scanf("%d, %d, %d", &i, &j, &w);    // 输入边(vi,vj)上的权w
        G->arc[i][j] = w;
        G->arc[j][i] = G->arc[i][j];    // 因为是无向图,矩阵对称
    }
}

邻接表

概念

定义

数组与链表相结合的方法称为邻接表(Adjacency List)。

思路
  1. 图中顶点用一个一维数组存储。该数组存储两部分内容:顶点信息和指针。
    该指针指向该顶点的第一个邻接点。

  2. 图中的每个顶点 $v_i$ 的所有邻接点构成一个线性表,用单链表存储。
    这个线性表可能叫 边表,也可能叫 出边表

当图为无向图的时候,这个线性表叫做顶点 $v_i$ 的边表。

当图为有向图的时候,这个线性表叫做顶点 $v_i$ 作为弧尾的出边表。

摘抄书本
无向图的邻接表结构
数据结构_图_邻接表_无向图的邻接表结构

$v_0$ 的邻接点是 $v_1,v_2,v_3$,$v_0$的 firstEdge 存储的指针指向它的第一个邻接点 $v_1$。
$v_1$ 的 adjvex 存储的是 该顶点在顶点表中的下标,next 存储的指针指向 $v_0$ 的第二个邻接点 $v_2$。

有向图的邻接表结构
数据结构_图_邻接表_有向图的邻接表结构_1
数据结构_图_邻接表_有向图的邻接表结构_2
网图的邻接表结构
数据结构_图_邻接表_网图

邻接表存储结构(看不懂)

结点定义代码

typedef char VertexType;
typedef int EdgeType;

typedef struct EdgeNode     // 边表结点
{
    int adjvex;     // 邻接点域,存储该顶点对应的下标
    EdgeType weight;    // 权值
    struct EdgeNode *next;  // 链域,指向下一个连接点
}EdgeNode;

typedef struct VertexNode   // 顶点表结点
{
    VertexType data;    // 顶点域,存储顶点信息
    EdgeNode *firstEdge;    // 边表头指针
}VertexNode, AdjList[MAXVEX];

typedef struct
{
    AdjList adjList;
    int numVertexes, numEdges;      // 图中当前顶点数和边数
}GraphAdjList;

边表结点 EdgeNode 为什么如此定义?

无向图的邻接表创建代码

void CreateALGraph(GraphAdjList *G)
{
    int i, j, k;
    EdgeNode *e;
    printf("输入顶点数和边数:\n");
    scanf("%d, %d", &G->numVertexes, &G->numEdges);     // 输入顶点数和边数 
    
    for(i = 0; i < G->numVertexes; i++){
        scanf(&G->adjList[i].data);     // 输入顶点信息。这种写法,有问题吗?
        G->adjList[i].firstEdge = NULL;     // 将边表置为空表。为什么?
    }
    
    for(k = 0; k < G->numVertexes; i++){    // 建立边表
        printf("输入边(Vi,Vj)上的顶点序号:\n");
        scanf("%d, %d", &i, &j);    // 输入边(Vi,Vj)上的顶点序号
        e = (EdgeNode *)malloc(sizeof(EdgeNode));       // 申请内存空间,创建边表结点
        e->adjvex = j;      // 邻接序号为j
        e->next = G->adjList[i].firstEdge;  // 将e指针指向当前顶点指向的结点 ?
        G->adjList[i].firstEdge = e;    // 将当前顶点的指针指向e
        e = (EdgeNode *)malloc(sizeof(EdgeNode));   // 申请内存空间,创建边表结点
        e->adjvex = i;  // 邻接序号为i
        e->next = G->adjList[j].firstEdge;  // 将e指针指向当前顶点指向的结点
        G->adjList[j].firstEdge = e;    // 将当前顶点的指针指向e
    }
}

十字链表(看不懂)

摘抄

数据结构_图_十字链表_1

数据结构_图_十字链表_2

数据结构_图_十字链表_3

数据结构_图_十字链表_4

邻接多重表(看不懂)

摘抄

数据结构_图_邻接多重表_1

数据结构_图_邻接多重表_2

数据结构_图_邻接多重表_3

数据结构_图_邻接多重表_4

数据结构_图_邻接多重表_5

边集数组

摘抄

数据结构_图_边集数组_1

数据结构_图_边集数组_2

图的遍历

定义

从图中某一个顶点出发遍访图的其余所有顶点,且使所有顶点被访问且只访问一次。
这个过程,就叫做图的遍历(Traversing Graph)。

深度优先遍历(Depth_First_Search)

摘抄

数据结构_图_深度优先遍历_摘抄_1

虚线表示已经遍历过的点,实线表示未遍历过的点。

数据结构_图_深度优先遍历_摘抄_2

数据结构_图_深度优先遍历_摘抄_3

代码

邻接矩阵代码
typedef int Boolean;    // Boolean是布尔类型,其值是TRUE或FALSE
Boolean visited[MAX];   // 访问标志的数组

void DFS(MGraph G, int i)
{
    int j;
    visited[i] = TRUE;
    printf("%c", G.vexs[i]);
    
    /*2*/for(j = 0; j < G.numVertexes; j++){
        /*1*/if(G.arc[i][j] == 1 && !visited[j] ){
            DFS(G, j);  // 对未访问的顶点递归调用
        }
    }
}

void DFSTraverse(MGraph G)
{
    int i;
    
    for(i = 0; i < G.numVertexes; i++){
        visited[i] = FALSE;     // 初始所有顶点状态都是未访问过状态
    }
    
    for(i = 0; i < G.numVertexes; i++){
        if(!visited[i]){
            DFS(G, i);
        }
    }
}

第1行,G.arc[i][j] == 1 表明 $v_iv_j$ 边存在,这是根据邻接图矩阵定义得出的
结果。

第1行,visited[j] 不能理解这句。

第2行的循环里面的递归,什么时候终止?

邻接表代码(不懂)
void DFS(GraphAdjList GL, int i)
{
    EdgeNode *p;
    visited[i] = TRUE;
    printf("%c", GL->adjList[i].data);
    p = GL->adjList[i].firstEdge;
    while(p){
        if(!visited[p->adjvex]){
            DFS(GL, p->adjvex);
        }
        p = p->next;
    }
}

void DFSTraverse(GraphAdjList GL)
{
    int i;
    for(i = 0; i < GL->numVertexes; i++){
        visited[i] = FALSE;
    }
    for(i = 0; i < GL->numVertexes; i++){
        if(!visited[i]){
            DFS(GL, i);
        }
    }
}

广度优先遍历(Breadth_First_Search)

摘抄

数据结构_图_广度优先遍历_摘抄_1
数据结构_图_广度优先遍历_摘抄_2

不明白如何得到这张图。

代码

邻接矩阵代码(不懂)
void BFSTraverse(MGraph G)
{
    int i, j;
    Queue Q;
    for(i = 0; i < G.numVertexes; i++){
        visited[i] = FALSE;
    }
    InitQueue(&Q);  // 初始化一个辅助用的队列
    for(i = 0; i < G.numVertexes; i++){     // 对每一个顶点做循环
        if(!visited[i]){
            visited[i] = TRUE;
            printf("%c", G.vexs[i]);
            EnQueue(&Q, i);     // 将此顶点入队列
            while(!QueueEmpty(Q)){
                DeQueue(&Q, &i);    // 将队列中元素出队列,赋值给i
                for(j = 0; j < G.numVertexes; j++){
                    if(G.arc[i][j] == 1 && !visited[j]){
                        visited[j] = TRUE;
                        printf("%c", G.vexs[j]);
                        EnQueue(&Q, j);
                    }
                }
            }
        }
    }
}
邻接表代码(不懂)
void BFSTraverse(GraphAdjList GL)
{
    int i;
    EdgeNode *p;
    Queue Q;
    for(i = 0; i < GL->numVertexes; i++){
        visited[i] = FALSE;
    }
    InitQueue(&Q);
    for(i = 0; i < GL->numVertexes; i++){
        if(!visited[i]){
            visited[i] = TRUE;
            printf("%c", GL->adjList[i].data);
            EnQueue(&Q, i);
            while(!QueueEmpty(Q)){
                DeQueue(&Q, &i);
                p = GL->adjList[i].firstEdge;   // 找到当前顶点边表链表头指针
                while(p){
                    if(!visited[p->adjvex]){
                        visited[p->adjvex] = TRUE;
                        printf("%c", GL->adjList[p->adjvex].data);
                        EnQueue(&Q, p->adjvex);
                    }
                }
                p = p->next;    // 指针指向下一个邻接点
            }
        }
    }
}

最小生成树

定义(不懂)

把构造连通网的最小代价生成树称为最小生成树(Minimum Cost Spanning Tree)。

普里姆(Prim)算法(不懂)

代码

void MiniSpanTree_Prim(MGraph G)
{
    int min, i, j, k;
    int adjvex[MAXVEX];     // 保存相关顶点下标
    int lowcost[MAXVEX];    // 保存相关顶点间边的权值
    // 初始化第一个权值为0,即v0加入生成树
    // lowcost的值为0,在这里就是此下标的顶点已经加入生成树
    lowcost[0] = 0;     
    adjvex[0] = 0;  // 初始化第一个顶点下标为0
    for(i = 1; i < G.numVertexes; i++){
        lowcost[i] = G.arc[0][i];   // 将v0顶点与之有边的权值存入数组
        adjvex[i] = 0;  // 初始化都为v0的下标
    }
    for(i = 1; i < G.numVertexes; i++){
        min = INFINITY; //  初始化最小权值为 INFINITY
        j = 1; k = 0;
        while(j < G.numVertexes){
            if(lowcost[j] != 0 && lowcost[j] < min){
                min = lowcost[j];   // 让当前权值成为最小值
                k = j;      // 将当前最小值的下标存入k
            }
            j++;
        }
        printf("(%d, %d)", adjvex[k], k);   //打印当前顶点边中权值最小的边
        lowcost[k] = 0;     // 将当前顶点的权值设置为0,表示此顶点已经完成任务
        for(j = 1; j < G.numVertexes; j++){
            if(lowcost[j] != 0 && G.arc[k][j] < lowcost[j]){
                lowcost[j] = G.arc[k][j];   // 将较小权值存入lowcost
                adjvex[j] = k;      // 将下标为k的顶点存入adjvex
            }
        }
    }
}

代码解释

数据结构_图_普里姆算法_代码解释_8

数据结构_图_普里姆算法_代码解释_1

数据结构_图_普里姆算法_代码解释_2

数据结构_图_普里姆算法_代码解释_3

数据结构_图_普里姆算法_代码解释_4

数据结构_图_普里姆算法_代码解释_5

数据结构_图_普里姆算法_代码解释_6

数据结构_图_普里姆算法_代码解释_7

克鲁斯卡尔(Kruskal)算法

代码

typedef struct 
{
    int begin;
    int end;
    int weight;
}Edge;

void MiniSpanTree_Kruskal(MGraph G)
{
    int i, n, m;
    Edge edges[MAXEDGE];    // 定义边集数组
    int parent[MAXVEX];     // 定义一数组用来判断边与边是否形成环路
    for(i = 0; i < G.numVertexes; i++){
        parent[i] = 0;      // 初始化数组值为0
    }
    for(i = 0; i < G.numVertexes; i++){
        n = Find(parent, edges[i].begin);
        m = Find(parent, edges[i].end);
        if(n != m){     // 假如n与m不等,说明此边没有与现有生成树形成环路
            // 将此边的结尾顶点放入下标为起点的parent中
            // 表示此顶点已经在生成树集合中
            parent[n] = m;      
            printf("(%d, %d) %d", edges[i].begin, edges[i].end, edges[i].weight);
        }
    }
}

int Find(int *parent, int f)    // 查找连线顶点的尾部下标
{
    while(parent[f] > 0){
        f = parent[f];
    }
    
    return f;
}

代码解释

数据结构_图_克鲁斯卡尔算法_代码解释_1

数据结构_图_克鲁斯卡尔算法_代码解释_2

数据结构_图_克鲁斯卡尔算法_代码解释_3

数据结构_图_克鲁斯卡尔算法_代码解释_4

数据结构_图_克鲁斯卡尔算法_代码解释_5

数据结构_图_克鲁斯卡尔算法_代码解释_6

数据结构_图_克鲁斯卡尔算法_代码解释_7

数据结构_图_克鲁斯卡尔算法_代码解释_8

数据结构_图_克鲁斯卡尔算法_代码解释_9

数据结构_图_克鲁斯卡尔算法_代码解释_10

数据结构_图_克鲁斯卡尔算法_代码解释_11

数据结构_图_克鲁斯卡尔算法_代码解释_12

最短路径(看不懂)

暂时放弃

定义

非网图的最短路径,是指两顶点之间经过的边数最少的路径。

网图的最短路径,是指两顶点之间经过的边上的权值之和最小的路径。
路径上的第一个源点,最后一个顶点是终点。

拓扑排序

关键路径

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

推荐阅读更多精彩内容

  • 第一章 绪论 什么是数据结构? 数据结构的定义:数据结构是相互之间存在一种或多种特定关系的数据元素的集合。 第二章...
    SeanCheney阅读 5,754评论 0 19
  • 图是一种比线性表和树更复杂的数据结构,在图中,结点之间的关系是任意的,任意两个数据元素之间都可能相关。图是一种多对...
    Alent阅读 2,297评论 1 22
  • 内容整理于鱼c工作室教程 1. 图的基本概念 1.1 图的概念 图(Graph)是由顶点的有穷非空集合和顶点之间边...
    阿阿阿阿毛阅读 3,168评论 0 2
  • https://zh.visualgo.net/graphds 浅谈图形结构https://zh.visualgo...
    狼之独步阅读 4,137评论 0 0
  • 图是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合...
    开心糖果的夏天阅读 816评论 0 9