重学图结构

news2024/11/16 18:28:58

图的描述

  • G = (V, E),一个偶对
  • V:顶点(数据元素)组成的有穷非空集合
  • {E}:边的有穷集合、
  • 图的逻辑结构:多对多

相关术语

  • 无向图:每条边都是无方向的,如下图1
    在这里插入图片描述

  • 有向图:每条边都是有方向的,如下图2
    在这里插入图片描述

  • 完全图:任意两个顶点都有边相连,分为无向完全图、有向完全图,如下图3、4
    在这里插入图片描述
    在这里插入图片描述

  • 稀疏图:有很少边或者弧的图(e < n * log(2, n))

  • 弧:带有方向的边也叫弧

  • 稠密图:有较多的边或者弧的图

  • 网:边或者弧带有权值的图

  • 邻接:两个顶点被边或者弧相连称为邻接,无向图中使用(vi, vj)表示vi与vj互为邻接点;有向图中使用<vi, vj>,表示vi邻接到vj,vj邻接于vi

  • 关联(依附):边或者弧与顶点之间的关系,说白了就是这条边或者弧就是某两个顶点连一起得到的,这个关系就是关联

  • 顶点的度:与顶点关联的边的数目;在有向图中,顶点的度等于顶点的出度和入度之和

  • 入度:以当前顶点为终点的有向边的条数,图二中A的入度为2

  • 出度:以当前顶点为起点的有向边的条数,图二中A的出度为1

  • 有向树:仅有一个顶点入度为0,其余顶点的入度为1的有向图被称为有向树

  • 路径:接续的边(不间断的边边相连)构成的顶点序列

  • 路径长度:网的某路径的权值之和

  • 回路(环):第一个顶点和最后一个顶点具有相同的路径

  • 简单路径:除了起点和终点是一样的,其他顶点均不同的路径

  • 简单回路(简单环):除了路径的起点和终点一样外,其余的顶点均不相同的路径

  • 连通图(强连通图):在无(有)向图G = (V, {E})中,若对任何两个顶点v、都存在从v到u的路径,则称G是连通图,图2就不是强连通图,因为A到D有路径,但是不存在D到A的路径

  • 权:就是网的边上的权值

  • 子图:G = (V, {E}),G1 = (V1, {E1}),若V1∈V,E1∈E,则称G1是G的子图

  • 连通分量(强连通分量):无向图G的极大连通子图称为G的连通分量

  • 极大连通子图:该子图是G的连通子图,将G的任何不在该子图中的顶点加入,子图不在连通

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

  • 极大强连通子图:该子图是G的强连通子图,将D的任何不在该子图中的顶点加入,子图不在是强连通

  • 极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边就不再连通

  • 生成树:包含无向图G所有顶点的极小连通子图

  • 生成森林:对非连通图,由各个连通分量的生成树的集合

图的重要操作

创建图

CreateGraph(&G, V, VR)
    初始条件:V是图的顶点集,VR是图中弧的集合
    操作结果:按V和VR的定义构造图G

DFS(深度优先遍历)

DFSTraverse(G)
    初始条件:图存在
    操作结果:对图进行深度优先遍历

BFS(广度优先遍历)

BFSTraverse(G)
    初始条件:图G存在
    操作结果:对图进行广度优先遍历

图的存储结构

  • 借助二维数组表示,称为数组表示法(邻接矩阵)
  • 链式存储结构:使用多重链表(如邻接表、连接多重表、十字链表)

无向图的邻接矩阵表示法

  • 建立一个顶点表(记录顶点信息)和一个邻接矩阵(顶点之间的关系)
  • 用一维数组表示顶点表
  • 邻接矩阵用二维数组,用1表示两点之间有边或者弧;用0表示两点之间不存在有边或者弧;默认顶点与自身为0
    在这里插入图片描述

上图的邻接矩阵为

ABCDE
A01010
B10101
C01001
D10001
E01110
  • 无向图的邻接矩阵是一个对称矩阵
  • 顶点i的度是第i行或者列的1的个数

有向图的邻接矩阵表示法

  • vi到vj的弧记录为1,不存在的话记录0
    在这里插入图片描述

上图的邻接矩阵为

ABCD
A0101
B0000
C1000
D0010

网的邻接矩阵表示法

  • 如果两点之间存在边或者弧(因为网也分为有向网和无向网),则记录值,否则记录一个特殊数,比如说0,-1
    在这里插入图片描述

    上图的邻接矩阵为

V1V2V3V4V5V6
V1050700
V2004000
V3800009
V4005006
V5000500
V6300010

代码实现邻接矩阵表示法

// 邻接矩阵存储法的思想是基于两个数组分别存储顶点(一维)和邻接矩阵(二维)
typedef char VertexType; // 顶点类型
typedef int ArcType;    // 边的权值的类型

#define MAXSIZE 10 // 最大顶点数目

typedef struct {
    VertexType vertex[MAXSIZE];
    ArcType arcs[MAXSIZE][MAXSIZE];
    // 记录当前顶点数、边数
    int vernum, arcnum;
} AMGraph;

实现无向网的表示

  • 输入顶点数和边数
  • 把顶点添加到顶点表
  • 初始化邻接矩阵,默认权值全是0值(就是你设定的不存在的值)
  • 构造邻接矩阵

使用邻接矩阵的优缺点

优点

  • 直观、简单、好理解
  • 方便检查任意对顶点是否存在关联
  • 便于计算顶点的度

缺点

  • 不便于删除和增加新顶点
  • 边太少时,仍是一个是一个nxn(n是顶点个数)稀疏矩阵,浪费空间
  • 统计稀疏图边数浪费时间

相关代码

// 用于表示存在关联的两个顶点以权值
typedef struct {
    VertexType vertex1;
    VertexType vertex2;
    ArcType weight;
} Linked;

// 辅助函数,用于获取顶点在顶点集的索引
int getIndex(AMGraph *graph, VertexType name);

// 建立无向网
// 参数依次是边的顶点个数、边的个数、网、顶点集、关联数组集
void create(int _vernum, int _arcnum, AMGraph *graph, VertexType vertexes[_vernum], Linked linked[_arcnum]);

// 直观的打印出邻接矩阵
void printMatrix(AMGraph *graph);
//
// Created by 20281 on 2023/6/11.
//

#include "AdjacencyMatrix.h"
#include "stdio.h"

int getIndex(AMGraph *graph, VertexType name){
    for (int i=0;i<graph->vernum;i++){
        if (graph->vertex[i] == name) return i;
        else continue;
    }
    return -1;
}

void create(int _vernum, int _arcnum, AMGraph *graph, VertexType vertexes[_vernum], Linked linked[_arcnum]){
    graph->vernum = _vernum;
    graph->arcnum = _arcnum;
    for (int i=0;i<_vernum;i++){
        graph->vertex[i] = vertexes[i];
    }
    for (int i=0;i<_vernum;i++){
        for (int j=0;j<_vernum;j++){
            graph->arcs[i][j] = 0;
        }
    }
    for (int i=0;i<_arcnum;i++){
        int ver1 = getIndex(graph, linked[i].vertex1);
        int ver2 = getIndex(graph, linked[i].vertex2);
        ArcType weight = linked[i].weight;
        graph->arcs[ver1][ver2] = weight;
        graph->arcs[ver2][ver1] = weight;
    }
}

void printMatrix(AMGraph *graph){
    printf("\t");
    for (int i=0;i<graph->vernum;i++){
        printf("%c\t", graph->vertex[i]);
    }
    printf("\n");
    for (int i=0;i<graph->vernum;i++){
        printf("%c\t", graph->vertex[i]);
        for (int j=0;j<graph->vernum;j++){
            printf("%d\t", graph->arcs[i][j]);
        }
        printf("\n");
    }
}

在这里插入图片描述

对上面网的测试

//
// Created by 20281 on 2023/6/11.
//

#include "AdjacencyMatrix.h"


int main(){
    // 创建img8中的网
    Linked linkeds[6] = {
            {'A', 'B', 4},
            {'A', 'D', 6},
            {'B', 'C', 3},
            {'B', 'F', 10},
            {'C', 'E', 7},
            {'D', 'E', 11},
    };
    VertexType vertexes[6] = {'A', 'B', 'C', 'D', 'E', 'F'};
    AMGraph amGraph;
    create(6, 6, &amGraph, vertexes, linkeds);

    // 打印
    printMatrix(&amGraph);
    return 0;
}

输出结果

        A       B       C       D       E       F
A       0       4       0       6       0       0
B       4       0       3       0       0       10
C       0       3       0       0       7       0
D       6       0       0       0       11      0
E       0       0       7       11      0       0
F       0       10      0       0       0       0

邻接表存储法

  • 由顶点构成的结点组成的一维数组
  • 用线性链表存储关联同一顶点的边(或者以顶点为尾的弧)

直观展示无向图的邻接表存储法,空间复杂度O(n+2e),其中n是顶点数,e是边数,下同
在这里插入图片描述

直观展示有向图的出度邻接表存储法,空间复杂度O(n+e)
在这里插入图片描述

相关代码

typedef char VertexType; // 顶点类型
typedef int ArcType;    // 边的权值的类型

#define MAXSIZE 10 // 最大顶点数目

// 被链接的顶点结构
typedef struct ArcNode{
    // 当前顶点的索引
    int index;
    // 被链接的顶点的所在的顶点结构
    struct ArcNode *next;
    // 权值
    ArcType weight;
}ArcNode ;

// 顶点集内的每个结点
typedef struct{
    // 顶点数据
    VertexType vert;
    // 指向的第一个与之链接的顶点
    ArcNode *firstArc;
} VNode, AdjList[MAXSIZE];

// 网的表示
typedef struct {
    // 顶点集组成的一维数组 AdjList vertexes 相当于 VNode vnode[MAXSIZE]
    AdjList vertexes;
    int vernum, arcnum;
}ALGraph;

实现邻接表的表示

  • 确定顶点数和边数
  • 建立顶点表并初始化指针域为NULL
  • 创建邻接表

邻接表表示法的优缺点

优点

  • 对稀疏图来说节约空间
  • 对于无向图来说,每个顶点对应的链表结点数,就是该顶点的就、度

缺点

  • 对于有向图需要构造出度表和入度表来计算顶点的度
  • 不方便检查任意一对点是否关联

经过邻接矩阵与邻接表的对比,稀疏图(网)一般使用邻接链表,稠密图(网)一般使用邻接矩阵

代码实现邻接链表的操作

// 用于表示存在关联的两个顶点以权值
typedef struct {
    VertexType vertex1;
    VertexType vertex2;
    ArcType weight;
} Linked2;

// 辅助函数,用于获取顶点在顶点集的索引
int getIndex2(ALGraph *graph, VertexType name);

// 初始化
// 传入参数依次是网本身、顶点个数、边数、顶点集、关联集
// 方法一是改变指针位置来更新链接更新
void createALG1(ALGraph *graph, int _vernum, int _arcnum, VertexType vertexes[_vernum], Linked2 linked[_arcnum]);
// 方法二是在链表尾部不断追加来更新链接更新
// 简单来说,createALG1 是将新节点直接插入链表的开头,而 createALG2 是将新节点插入链表的末尾。
void createALG2(ALGraph *graph, int _vernum, int _arcnum, VertexType vertexes[_vernum], Linked2 linked[_arcnum]);

// 直观的打印出邻接链表
void printLinkedList(ALGraph *graph);

// 释放链表
void freeLinkedList(ALGraph * graph);

//
// Created by 20281 on 2023/6/12.
//

#include "AdjacentLinkedList.h"
#include "stdio.h"
#include "stdlib.h"

int getIndex2(ALGraph *graph, VertexType name){
    for (int i=0;i<graph->vernum;i++){
        if (graph->vertexes[i].vert == name){
            return i;
        } else continue;
    }
    return -1;
}

void createALG1(ALGraph *graph, int _vernum, int _arcnum, VertexType vertexes[_vernum], Linked2 linked[_arcnum]){
    graph->arcnum = _arcnum;
    graph->vernum = _vernum;
    for (int i=0;i<_vernum;i++){
        graph->vertexes[i].vert = vertexes[i];
        graph->vertexes[i].firstArc = NULL;
    }
    for (int i=0;i<_arcnum;i++){
        int index1 = getIndex2(graph, linked[i].vertex1);
        int index2 = getIndex2(graph, linked[i].vertex2);
        int weight = linked[i].weight;
        ArcNode * newNode = (ArcNode* ) malloc(sizeof(ArcNode));
        newNode->index = index2;
        newNode->weight = weight;
        newNode->next = graph->vertexes[index1].firstArc;
        graph->vertexes[index1].firstArc = newNode;
        ArcNode * newNode2 = (ArcNode* ) malloc(sizeof(ArcNode));
        newNode2->index = index1;
        newNode2->weight = weight;
        newNode2->next = graph->vertexes[index2].firstArc;
        graph->vertexes[index2].firstArc = newNode2;
    }
}

void createALG2(ALGraph* graph, int _vernum, int _arcnum, VertexType vertexes[_vernum], Linked2 linked[_arcnum]) {
    graph->arcnum = _arcnum;
    graph->vernum = _vernum;

    // 初始化顶点集
    for (int i = 0; i < _vernum; i++) {
        graph->vertexes[i].vert = vertexes[i];
        graph->vertexes[i].firstArc = NULL;
    }

    // 构建邻接链表
    for (int i = 0; i < _arcnum; i++) {
        int index1 = getIndex2(graph, linked[i].vertex1);
        int index2 = getIndex2(graph, linked[i].vertex2);
        int weight = linked[i].weight;

        // 创建新节点1
        ArcNode* newNode = (ArcNode*)malloc(sizeof(ArcNode));
        newNode->index = index2;
        newNode->weight = weight;
        newNode->next = NULL;

        // 将新节点1插入链表1的末尾
        if (graph->vertexes[index1].firstArc == NULL) {
            graph->vertexes[index1].firstArc = newNode;
        } else {
            ArcNode* currentFirstNode = graph->vertexes[index1].firstArc;
            while (currentFirstNode->next != NULL) {
                currentFirstNode = currentFirstNode->next;
            }
            currentFirstNode->next = newNode;
        }

        // 创建新节点2
        ArcNode* newNode2 = (ArcNode*)malloc(sizeof(ArcNode));
        newNode2->index = index1;
        newNode2->weight = weight;
        newNode2->next = NULL;

        // 将新节点2插入链表2的末尾
        if (graph->vertexes[index2].firstArc == NULL) {
            graph->vertexes[index2].firstArc = newNode2;
        } else {
            ArcNode* currentFirstNode2 = graph->vertexes[index2].firstArc;
            while (currentFirstNode2->next != NULL) {
                currentFirstNode2 = currentFirstNode2->next;
            }
            currentFirstNode2->next = newNode2;
        }
    }
}


void printLinkedList(ALGraph *graph){
    for (int i=0;i<graph->vernum;i++){
        printf("%c-->", graph->vertexes[i].vert);
        ArcNode * firstLinkedNode = graph->vertexes[i].firstArc;
        while (firstLinkedNode != NULL){
            printf("(%d, %d)-->", firstLinkedNode->weight, firstLinkedNode->index);
            firstLinkedNode = firstLinkedNode->next;
        }
        printf("\n");
    }
}

void freeLinkedList(ALGraph * graph){
    for (int i=0;i<graph->vernum;i++){
        ArcNode *currentNode = graph->vertexes[i].firstArc;
        while (currentNode !=NULL){
            ArcNode * temp = currentNode;
            currentNode = currentNode->next;
            free(temp);
        }
    }
}

在这里插入图片描述

对上面网的测试

// 邻接表
    ALGraph alGraph;
    Linked2 linkeds_2[6] = {
            {'A', 'B', 4},
            {'A', 'D', 6},
            {'B', 'C', 3},
            {'B', 'F', 10},
            {'C', 'E', 7},
            {'D', 'E', 11},
    };
    ALGraph alGraph2;
    Linked2 linkeds_3[6] = {
            {'A', 'B', 4},
            {'A', 'D', 6},
            {'B', 'C', 3},
            {'B', 'F', 10},
            {'C', 'E', 7},
            {'D', 'E', 11},
    };
    createALG1( &alGraph, 6, 6, vertexes, linkeds_2);
    createALG2( &alGraph2, 6, 6, vertexes, linkeds_3);

    // 直观打印
    printLinkedList(&alGraph);
    printLinkedList(&alGraph2);

    // 释放链表内存
    freeLinkedList(&alGraph);
    freeLinkedList(&alGraph2);

输出结果

A-->(6, 3)-->(4, 1)-->
B-->(10, 5)-->(3, 2)-->(4, 0)-->
C-->(7, 4)-->(3, 1)-->
D-->(11, 4)-->(6, 0)-->
E-->(11, 3)-->(7, 2)-->
F-->(10, 1)-->

A-->(4, 1)-->(6, 3)-->
B-->(4, 0)-->(3, 2)-->(10, 5)-->
C-->(3, 1)-->(7, 4)-->
D-->(6, 0)-->(11, 4)-->
E-->(7, 2)-->(11, 3)-->
F-->(10, 1)-->

邻接表改进

  • 对于有向图,缺点是求取顶点的度困难,需要由出度表和入度表结合才行,于是有了十字链表
  • 对于无向图,缺点是每条边都需要存储两边,于是有了邻接多重表,邻接多重表的出现是为了解决一条边存储一次且能够表示两个点的问题

图(网)的遍历

  • 把连通图的每个顶点遍历一遍,且每个顶点仅被遍历一次
  • 设置辅助数组记录每个顶点是否被访问过,对应顶点初始状态为0,访问则记录为1
  • 核心是根据当前顶点找到并遍历与之相邻的顶点

深度优先搜索遍历(DFS)(来自大佬的总结)

最直观的例子就是“走迷宫”。假设你站在迷宫的某个岔路口,然后想找到出口。你随意选择一个岔路口来走,走着走着发现走不通的时候,你就回退到上一个岔路口,重新选择一条路继续走,直到最终找到出口(这个出口可以理解为最后一个被遍历的顶点)。这种走法就是一种深度优先搜索策略。

广度优先搜索遍历(BFS)(来自大佬的总结)

  • 从图中某顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点
  • 然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。
  • 如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。

基于无向图的邻接矩阵表示法实现对图的DFS

// DFS算法的实现
// index是开始起点的索引、visited是辅助数组、_c是计数器,我特地设定的,用于统计递归了多少次
void _DFS(AMGraph *graph, int index, int visited[], int *_c);
void DFS(AMGraph *graph, int index);
void _DFS(AMGraph *graph, int index, int visited[], int *_c){
    *_c += 1;
    printf("%c", graph->vertex[index]);
    visited[index] = 1;
    for (int i=0;i<graph->vernum;i++){
        // 当前的顶点与邻近的顶点之间有关联并且邻近顶点未被访问情况下
        if (graph->arcs[index][i] != 0 && visited[i] == 0){
            _DFS(graph, i, visited, _c);
        }
    }
}

// _DFS才是核心,DFS只是辅助作用和直观打印过程
void DFS(AMGraph *graph, int index){
    // 辅助数组,用于记录每个点是否被访问
    int visitedArray[graph->vernum];
    for (int i = 0;i<graph->vernum;i++){
        visitedArray[i] = 0;
    }
    int _c = 0;
    printf("开始遍历……\n");
    _DFS(graph, index, visitedArray, &_c);
    printf("\n");
    printf("函数触发了%d次……\n", _c);
}
// 对img11进行DFS
// 用1表示边边关联,而不是权重
Linked linked_img11[6] = {
  {'1', '2', 1},
  {'1', '3', 1},
  {'1', '4', 1},
  {'2', '5', 1},
  {'3', '5', 1},
  {'4', '6', 1}
};
VertexType vertexes_img11[6] = {'1', '2', '3', '4', '5', '6'};
AMGraph amGraph_img11;
create(6, 6, &amGraph_img11, vertexes_img11, linked_img11);
printMatrix(&amGraph_img11);

// DFS
DFS(&amGraph_img11, 1);

// 结果
// 开始遍历……   
// 213546
// 函数触发了6次……

基于邻接链表的无向图的代码实现BFS

// BFS算法的实现
// index 表示从哪个顶点作为开始访问
void _BFS(ALGraph *graph, int index, int vertexArray[], Queue *queue);
void BFS(ALGraph *graph, int index);
void _BFS(ALGraph *graph, int index, int vertexArray[], Queue *queue){
    push(queue, index);
    printf("%c ", graph->vertexes[index].vert);
    vertexArray[index] = 1;
    while (isEmpty(queue) != 1){
        int outIndex = pop(queue);
        // 找到与出队顶点相关的且没被访问的顶点
        ArcNode * currentNode = graph->vertexes[outIndex].firstArc;
        // 找到相关点
        while (currentNode != NULL){
            // 找到没被访问的点
            if (vertexArray[currentNode->index] == 0){
                push(queue, currentNode->index);
                printf("%c ", graph->vertexes[currentNode->index].vert);
                vertexArray[currentNode->index] = 1;
            } else {}
            currentNode = currentNode->next;
        }
    }
}

void BFS(ALGraph *graph, int index){
    int vertexArray[graph->vernum];
    for (int i=0;i<graph->vernum;i++){
        vertexArray[i] = 0;
    }
    printf("Begin Traversal ......\n");
    Queue queue;
    createQueue(&queue);
    _BFS(graph, index, vertexArray, &queue);
    printf("\n");
}
// 对img12实现邻接链表的BFS演示
    Linked2 linked_img12[9] = {
            {'1', '2', 1},
            {'1', '3', 1},
            {'2', '4', 1},
            {'2', '5', 1},
            {'3', '6', 1},
            {'3', '6', 1},
            {'4', '8', 1},
            {'5', '8', 1},
            {'6', '7', 1},
    };
    VertexType img12_vec[8] = {'1', '2', '3', '4', '5', '6', '7', '8'};
    ALGraph alGraph_img12;
    createALG2(&alGraph_img12, 8, 9, img12_vec, linked_img12);
    printLinkedList(&alGraph_img12);
    BFS(&alGraph_img12, 2);
    // 释放内存
    freeLinkedList(&alGraph_img12);
1-->(1, 1)-->(1, 2)-->
2-->(1, 0)-->(1, 3)-->(1, 4)-->
3-->(1, 0)-->(1, 5)-->(1, 5)-->
4-->(1, 1)-->(1, 7)-->
5-->(1, 1)-->(1, 7)-->
6-->(1, 2)-->(1, 2)-->(1, 6)-->
7-->(1, 5)-->
8-->(1, 3)-->(1, 4)-->
Begin Traversal ......
3 1 6 2 7 4 5 8

图的应用

  • 最小生成树
  • 最短路径
  • 拓扑排序
  • 关键路径

最小生成树

  • 生成树:所有顶点连在一起,但是不存在回路的图。它顶点个数与图顶点个数一样,是图的极小连通子图,去掉任意一条边就不再连通。n个顶点的生成树有n-1条边
  • 最小生成树:一个无向网可以有多个不同的生成树,各边权值和最小的无向网被称为最小生成树(最小代价生成树)
  • MST性质:设N = (V, E)是一个连通网,U是顶点集V的一个非空子集。若边(u, v)是一条具有最小权值的边,其中u∈U,v∈V-U(差集的意思),则必存在一颗包含边(u, v)的最小生成树
  • 根据MST性质,n个顶点分为两个集合——已落在生成树上的顶点集:U,尚未落在生成树上面的顶点集:V-U,然后在连通的U中的顶点和V-U顶点组成的边选取权值最小的边
  • 相关算法:Prim算法、Kruskal算法

Prim算法(普利姆算法)

  • 设N = (V, E)是连通网,TE是N上最小生成树中边的集合
  • 初始令U = {u。}(u。∈V),TE = {}
  • 在所有u∈U,v∈V-U的边(u, v)∈E中,找一条权值最小的边
  • 将(u。, v,)并入集合TE中,同时v。加入U中
  • 重复第三第四步,直到U = V为止,最终得到T = (V, TE)是N的最小生成树
  • 下面大致写一份代码实现,基于邻接链表
ALGraph Prim(AlGraph *graph){
    ALGraph mst;
    // 初始
    U = {mst.vertexes[0].vert};
    V_U = {除了mst.vertexes[0].vert外的所有的点};
    // 这里面TE就是Linked2类型的
    Linked2 links[graph->vernum - 1];
    // links当前添加元素的计数
    _counter = 0;
    // 直到U = V为止,也就是说U长度为V为止
    while(getLength(U) != graph->vernum){
        // 在所有u∈U,v∈V-U的边(u, v)∈E中,找一条权值最小的边
        // 将(u。, v,)并入集合TE中,同时v。加入U中
        link = {};
        link.weight = 0;
        for (u in U){
            for (v in V_U){
                // 如果两点成关联
                if (islinked(u, v)){
                    // 每次都比较,获取最小权值的关联
                    if (getWeight(u, v) <= link.weight){
                        link.vertex1 = u;
                        link.vertex2 = v;
                    }
                }
            }
        }
        // 此时拿到了最小权值边
        append(&U, link.vertex1);
        remove(&V_U, link.vertex2);
        links[_counter] = link;
        _couter++;
    }
    // 最后获取这棵树
    createALG2(&mst, _counter+1, _counter, U, links);
    return mst;
}

Kruskal算法(克鲁斯卡尔算法)

  • 基于连通网N = (V, E),一开始直接生成n个顶点和无边的的非连通图T = (V, {})
  • 从E中选取权值最小的边,若该边依附的顶点落在T中不同的连通分量上(即不能形成闭环),则将此边加入到T中,否则舍去此边,选取下一条权值最小的边
  • 以此类推,直到T中的所有顶点都在一个连通分量上
  • 最小生成树可能不唯一
  • 下面大致写一份代码实现,基于邻接链表
ALGraph Kruskal(AlGraph *graph){
    ALGraph mst;
    // 初始化只有独立的顶点,没有关联的网
    createALG2(&mst, graph->vernum, 0, graph->vertexes, NULL);
    // 获取graph的有关联的边点,得到Linked2 links
    getLinks(graph);
    // 先从links找到权值最小边mlink添加到mst,并从links删除此组合
    getMinCompose(links);
    remove(&links, mlink);
    append(&mst, mlink);
    // 计数,成功了graph->vernum-2次停止
    _counter = 1;
    while (_counter < graph->vernum-2){
        // 从links找到权值最小且不形成的link添加到mst,并删除这样的link
        // 那如果这个边权值最小,但形成闭环,是不是要从考虑的边中删除
        getMinCompose(links); // 设为temp_link;
        // 形成闭环
        if (isLoop(mst, temp_link)){}
        else{
            append(&mst, temp_link);
            _counter++;
        }
        remove(&links, temp_link);
    }
    return mst;
}

对比

算法名字Prim算法Kruskal算法
算法思想选择点选择边
时间复杂度O(n^2)O(ElogE)
适用范围稠密网稀疏网

最短路径

  • 在有线网顶点A到顶点B的路径中找到各边权值和最小的
  • 两类问题:两点之间的最短路径(单源最短路径)、从某起点出发到其他各点最短路径(所有顶点间的最短路径)
  • 针对上面两个问题的不同经典的算法:Dijkstra算法(迪杰斯特拉算法)、Floyd算法(弗洛伊德算法)

Dijkstra算法

  • 先找到从起点v。到各终点vk的直达路径(就是两个点之间有关联的),然后从中找到最短的
  • 然后对其余各路径进行适当调整

在这里插入图片描述

对上图进行算法分析

备注

  • 使用0表示此处无路径(很多教程或者视频使用无穷大)
  • 使用‘-’符号表示此处此点已经找到最短路径
点名从v0到各点的最短路径
v1131313---
v28-----
v3013----
v430301919--
v5000222121
v63232322020-
当前最短点v2v3v1v4v6v5
路径长度81313192021

Floyd算法

  • 解决顶点间的最短路径
  • 逐个增加中间顶点试探
  • 从vi到vj的所有可能存在的路径中,选出一条长度最短的路径

有向无环图(DGA图)

  • 顾名思义,有向图中不存在环(没有回路存在)

在这里插入图片描述

该图中左图就是一个有向无环图,右侧不是


  • 分为AOV网和AOE网
  • AOV网:用一个有向图表示一个工程中的各个子工程及其相互制约的关系,其中以顶点表示活动,弧表示活动之间的优先制约关系,这种有向图称为顶点表示活动的网,简称AOV网。用于解决拓扑排序问题
  • AOE网:用一个有向图表示一个工程的各子工程及其相互制约的关系,以弧表示活动,以顶点表示活动的开始或者结束事件,称这种有向图为边表示活动的网,简称AOE网。用于解决关键路径问题

AOV网

  • 若从顶点i到顶点j有一条有向路径,则i是j的前驱,j是i的后继
  • 若<i, j>是网中有向边,则i是j的直接前驱,j是i的直接后继
  • AOV网中不允许有回路

拓扑排序

  • 在AOV网中没有回路前提下,把全部活动排成线性序列,如果存在弧<i, j>,则在这个序列中,i一定排在j前面,具有这样性质的线性序列被称为拓扑有序序列,相应的排序算法被称为拓扑排序。是用来解决任务之间的前后依赖关系的,比如说,大学课程中,必须学完高等数学,才是开设基于高等数学知识的大学物理这门课程
  • 拓扑排序把一个网变成了一个线性序列
  • 拓扑序列不唯一
  • 拓扑排序可以用来检测有向网中存在环——如果输出的有序序列包含全部顶点,则说明没有环,否则存在

具体实现

  • 在有向图中选择一个没有前驱的顶点并且输出
  • 从图中删除该顶点以及所有以它为尾的弧
  • 重复上面的两步,直到全部的顶点都被输出;或者是图中不存在无前驱的顶点为止

AOE网

  • 顶点表示事件、弧表示活动、弧的权表示活动持续时间
  • 出度为0的顶点为汇点、入度为0的顶点为源点

关键路径

  • 从源点到汇点路径长度最长的路径

具体实现

  • ve(vj):表示事件vj最早的发生时间
  • vl(vj):表示事件vj最晚的发生时间
  • e(i):表示活动ai的最早开始时间
  • l(i):表示活动ai的最晚开始时间
  • l(i) - e(i):表示完成活动ai的时间余量
  • 关键活动:关键路径上的活动,即l(i) == e(i)的活动
  • 设活动ai用弧<j, k>表示,其持续时间记为w(j, k)
  • ve(j) = Max(ve(i) + w(i, j))
  • vl(i) = Min(vl(j) - w(i, j))

在这里插入图片描述

对上面AOE网进行关键路径查找

顶点vevl
v100
v266
v346
v458
v577
v6710
v71616
v81414
v91818
活动ell-e
a1000
a2022
a3033
a4660
a5462
a6583
a7770
a8770
a97103
a1016160
a1114140

得到关键活动是:a1 a4 a7 a8 a10 a11


补充

  • Warshall算法
  • 利用Floyd算法求有向图的中心点

Warshall算法

Warshall算法是一种用于解决传递闭包(transitive closure)问题的算法。传递闭包是指在有向图中,如果存在一条路径从节点i到节点j,那么节点i可以达到节点j。Warshall算法的目标是计算出图中所有节点对之间的可达性。

具体而言,Warshall算法通过一个二维矩阵(通常称为邻接矩阵)来表示有向图的连接关系。矩阵中的元素A[i][j]表示从节点i到节点j是否存在一条边。初始时,如果有一条边直接连接节点i和节点j,则A[i][j]为1,否则为0。

Warshall算法的主要思想是通过迭代更新邻接矩阵,以逐步计算出所有节点对之间的可达性。算法的步骤如下:

初始化邻接矩阵,将直接连接的边设置为1,没有直接连接的边设置为0。
对于每个节点k,遍历所有节点对(i, j),如果存在一条路径从节点i经过节点k到节点j,即A[i][k]为1且A[k][j]为1,则将A[i][j]设置为1。
重复步骤2,直到对每个节点都进行了一次遍历。最终的邻接矩阵将表示图中所有节点对之间的可达性。
通过Warshall算法,最终得到的邻接矩阵能够表明在图中从一个节点到另一个节点是否存在一条路径。如果A[i][j]为1,则表示节点i可以达到节点j;如果A[i][j]为0,则表示节点i无法达到节点j。

总结来说,Warshall算法的实质是通过迭代更新邻接矩阵,来计算出图中所有节点对之间的可达性,从而解决传递闭包问题。

利用Floyd算法求有向图的中心点

  • 图的中心点:图中一个点到其他的顶点的最短路径求和最小
  • 思路:在一个带权图G中,求出一个顶点v,使得v到其余顶点的最短路径长度之和最小。首先用弗罗伊德算法求出图中各个顶点之间的最短路径长度,然后再求出每个顶点到其余各顶点的最短路径长度之和,从中选取一个最短路径长度之和最小的顶点即为要求的顶点。

声明:
部分图片源于互联网、部分更通俗易懂的概念源于搜索
教学看的是青岛大学王卓老师的
源代码下载:https://gitee.com/PythonnotJava/Data-structure-and-algorithm-collection/tree/master/ReGraph

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/655373.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

信息技术专业标准体系框架设计研究与思考

为了支撑企业数字化转型智能化发展工作&#xff0c;近期对信息技术专业标准体系进行了简单的梳理&#xff0c;借助ChatGPT进行分类设计分析&#xff0c;并对国际、行业及其他公司的信息技术专业标准体系进行了调研学习&#xff0c;最终形成了信息技术专业标准体系框架的思考建议…

【备战秋招】每日一题:4月23日美团春招第三题:题面+题目思路 + C++/python/js/Go/java带注释

为了更好的阅读体检&#xff0c;为了更好的阅读体检&#xff0c;&#xff0c;可以查看我的算法学习博客第三题-农村大亨 在线评测链接:P1247 题目内容 塔子哥是一个喜欢种田类的游戏的人&#xff0c;他觉得这样的游戏可以让他体验到农民的乐趣&#xff0c;同时也可以锻炼他的…

MYSQL中 find_in_set() 函数用法详解

MYSQL中 find_in_set() 函数用法详解 官方涵义&#xff08;MySQL手册中语法说明&#xff09; FIND_IN_SET(str,strlist) &#xff1a; str 要查询的字符串&#xff0c;strlist 需查询的字段&#xff0c;参数以”,”分隔&#xff0c;形式如 (1,2,6,8,10,22)&#xff1b;该函数的…

分布式存储与并行处理环境配置:Hadoop、HBase和Spark等

本文介绍Linux系统中配置Hadoop、HBase和Spark环境&#xff0c;包括安装Java运行环境、下载安装包、进行配置和测试。通过这种方式&#xff0c;可以搭建一个强大的分布式计算环境&#xff0c;用于处理大规模数据集。为了成功配置Hadoop、HBase和Spark环境&#xff0c;需要理解它…

chatgpt赋能python:Python实现输出在同一行的技巧

Python实现输出在同一行的技巧 介绍 在Python中&#xff0c;我们经常需要将一些文本或者变量输出到控制台上&#xff0c;以便查看或者进行交互式调试。但是默认情况下&#xff0c;每次输出都会换行展示&#xff0c;这在某些情况下可能会让输出显得比较混乱&#xff0c;尤其是…

Ubuntu安装及使用教程

Ubuntu安装及使用教程 一、基本介绍二、vmware下安装ubuntu系统三、ubuntu系统使用 回到目录   回到末尾 一、基本介绍 对于ubuntu而言&#xff0c;就是linux操作系统的具体&#xff0c;而linux对于ubuntu来说就是他的抽象&#xff1b;在linux操作系统中&#xff0c;因为应用…

SSD202D-挂载根文件系统

参考链接: 启明云端分享|SSD20X 如何使用SD卡根文件系统_sudo./make_sd.sh_启明智显的博客-CSDN博客 启明云端分享|SSD202D核心板使用NFS_lx_mem=0x3f00000 mma_heap=mma_heap_name0,miu=0,sz=_启明智显的博客-CSDN博客 然后思路: 1.要在kernel打开文件系统格式支持 CONFIG_…

chatgpt赋能python:Python计算圆面积方法教程

Python计算圆面积方法教程 你是否想过如何用Python计算圆面积&#xff1f;圆形是一个基本的几何形状&#xff0c;计算圆的面积是一个必要的技能。Python作为一种流行的编程语言&#xff0c;可以帮助我们更轻松地计算圆的面积。本教程将介绍如何使用Python计算圆的面积。 什么…

【Red Hat7.9安装Oracle11g--调用图形化界面的几种方式】

【Red Hat7.9安装Oracle11g--调用图形化界面的几种方式】 &#x1f53b; 一、续上一篇[【Red Hat 7.9---详细安装Oracle 11g---图形化界面方式】](https://blog.csdn.net/qq_41840843/article/details/131198718?spm1001.2014.3001.5501)⛳ 1.1 前言⛳ 1.2 方式一、使用Xmanag…

Mock和Vite-plugin-Mock的区别是什么?

简介 我不知道大家和我是否有一样的疑问&#xff0c;之前Mock.js用的挺好&#xff0c;为啥又出现了一个vite-plugin-mock,而且这个插件还依赖于Mock.js.那么他的优势到底是什么呢&#xff1f;如果你也有这样的疑问&#xff0c;本文最后会给出答案解开这个谜底 前言 我之前已经…

6月更新——企业互联,搭建起企业与企业之间的业务桥梁

企业互联 企业互联的作用是促进企业之间的合作和交流&#xff0c;提高企业的运营效率和竞争力。具体来说&#xff0c;企业互联可以实现以下几个方面的作用&#xff1a; 信息共享&#xff1a; 通过企业互联&#xff0c;企业可以方便地实现内部和外部信息的共享和传递。这样可…

chatgpt赋能python:Python装pipinstall的方法

Python装pip install的方法 如果你是一个Python开发者&#xff0c;那么你一定需要一些第三方库来辅助你完成一些任务。而pip就是Python的包管理器&#xff0c;它可以让你很方便地安装和管理第三方库。但是&#xff0c;在使用pip之前&#xff0c;我们需要先安装它。 什么是pip…

1.OpenStack介绍及环境部署(PS笔记)

当面对KVM集群的时候&#xff0c;我们对KVM的管理以及宿主机的管理就会遇到很大的难度&#xff0c;例如&#xff1a; 查看每一个宿主机有多少台KVM虚拟机? 查看每一个宿主机资源信息&#xff0c;每一个KVM虚拟机资源信息?查看每一台宿主机配置信息&#xff0c;每一个KVM虚拟机…

C#异步详解

异步编程是指在程序执行过程中&#xff0c;不需要等待某个操作完成&#xff0c;就可以继续执行后续的代码。比如我们开发了一个web页面中有一个上传文件功能&#xff0c;我们上传文件时使用异步操作&#xff0c;就不用等待文件的上传时间&#xff0c;可以先在网页上进行其他操作…

Linux 命令(9)—— top

文章目录 1、命令简介2、使用方法3、命令行参数4、显示摘要5、进程列表字段6、交互命令a、全局命令b、摘要区域命令c、任务区域命令d、颜色映射e、可选显示窗口 7、配置文件a、系统配置文件b、个人配置文件 1、命令简介 top提供运行系统的动态实时视图。它可以显示系统摘要信息…

RK3399平台开发系列讲解(Pinctrl子系统)pinctrl的使用

平台内核版本安卓版本RK3399Linux4.4Android7.1🚀返回专栏总目录 文章目录 一、配置 pinctrl二、调用 pinctrl2.1、Iomux 配置2.2、驱动强度配置2.3、上下拉配置沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍pinctrl设备树的使用方法。 一、配置 pinctr…

牛客网专项练习——C语言错题集(6)

文章目录 数组地址的移位C 源程序和函数的关系二维数组值的表示方式&#xff08;n & (n-1)) 0容易出错的 位运算fseek 可以实现的操作 数组地址的移位 个人理解&#xff1a; 这题的 A C 选项的差别应该在优先级上&#xff08;下文有 C 语言运算符优先级表&#xff09;&am…

移动端测试-Adb工具

移动端测试_Adb工具 Adb的基本使用简要介绍通用操作步骤一、连接设备二、断开设备三、安装四、卸载五、抓取日志六、文件传输七、截屏八、清除apk缓存九、查看终端各apk占用内存 Adb的基本使用 简要介绍 adb其实就是移动端的调试工具&#xff0c; 全称为Android Debug Bridge…

chatgpt赋能python:Python设置画笔粗细:从入门到精通

Python设置画笔粗细&#xff1a;从入门到精通 在Python中&#xff0c;画图是非常常见的操作。设置画笔的粗细是其中的一个重要部分。本文将从最基础的设置粗细开始讲起&#xff0c;逐步深入&#xff0c;带您了解Python设置画笔粗细的各种方法。 一、使用pensize()函数 pensi…

【备战秋招】每日一题:5月13日美团春招第三题:题面+题目思路 + C++/python/js/Go/java带注释

为了更好的阅读体检&#xff0c;为了更好的阅读体检&#xff0c;&#xff0c;可以查看我的算法学习博客第三题-火车调度 在线评测链接:P1288 题目描述 塔子哥是一位火车车厢调度员。 这一天&#xff0c;一列带有 n 个编号车厢的列车进站了&#xff0c;编号为 1\rightarrow …