当前位置: 首页 > news >正文

C语言--实现图的基本操作

使用C语言实现图的创建以及遍历等基本操作

文章目录

  • 图基本概念
  • 邻接矩阵和邻接表对比
  • 定义图
  • 创建图
  • 添加边
  • 深度优先遍历DFS
    • 运行
  • 广度优先遍历BFS
    • 运行
  • 迪杰斯特拉算法
    • 运行
  • 弗洛伊德算法
    • 运行

图基本概念

图由顶点和边组成。根据边之间的方向性可以分为有向图和无向图,无向图表示双向关系,有向图表示单向关系。

邻接矩阵和邻接表对比

在C语言中,图的创建可以通过邻接矩阵和邻接表两种方式来实现。

  • 邻接表使用链表数组来表示图中所有顶点之间关系。每个顶点都有一个链表,链表中的每个节点表示从该顶点出发的边。优点是对稀疏图来说节省大量空间,适用于稀疏图。
  • 邻接矩阵使用二维数组来表示节点之间的权值。优点是直观便于理解,缺点是对于稀疏图来说,会浪费大量空间,适用于稠密图。

本文中使用邻接矩阵来表示无向图。假设顶点从编号0开始。

定义图

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

#define MAX_VERTICES 10

typedef struct Graph {
    int vertices; // 图节点个数
    int adjMatrix[MAX_VERTICES][MAX_VERTICES]; // 图中边的关系
} Graph;

创建图

不带权重版:0表示不连通,1表示连通

void createGraph(Graph *graph, int vertices) {
    graph->vertices = vertices;
    for (int i = 0; i < vertices; i++) {
        for (int j = 0; j < vertices; j++) {
            graph->adjMatrix[i][j] = 0;
        }
    }
}

带权值版:INT_MAX表示不连通

void createGraphWithWeight(Graph *graph, int vertices) {
    graph->vertices = vertices;
    for (int i = 0; i < vertices; i++) {
        for (int j = 0; j < vertices; j++) {
            if (i==j) {
                graph->adjMatrix[i][j] = 0;
            }else {
                graph->adjMatrix[i][j] = INT_MAX;
            }
        }
    }
}

添加边

void addEdge(Graph *graph, int u, int v) {
    graph->adjMatrix[u][v] = 1;
    graph->adjMatrix[v][u] = 1;
}
void addEdgeWithWeight(Graph *graph, int u, int v, int weight) {
    graph->adjMatrix[u][v] = weight;
    graph->adjMatrix[v][u] = weight;
}

深度优先遍历DFS

沿着一条路径深入探索,直到尽头,再回溯到上一个节点尝试其他路径。
递归通过栈来管理函数调用的层级,和DFS纵深优先的特性非常契合。

void DFS(Graph *graph, int vertex, bool visited[]) {
    // 最先访问起点
    visited[vertex] = true;
    printf("%d ", vertex);

    for (int i = 0; i < graph->vertices; i++) {
        // 递归访问连通且未访问的所有节点
        if (graph->adjMatrix[vertex][i] && !visited[i]) {
             DFS(graph, i, visited);
        }
    }
}

void DFSTraverse(Graph *graph, int startVertex) {
     bool visited[MAX_VERTICES] = {false};
     DFS(graph, startVertex, visited);
}

运行

如何建立图:
在这里插入图片描述

int main() {
    Graph graph;
    createGraph(&graph, 4);
    addEdge(&graph, 0, 1);
    addEdge(&graph, 0, 2);
    addEdge(&graph, 1, 3);

    DFSTraverse(&graph, 0);
}

先遍历0–1–3
再回溯到1–2
在这里插入图片描述

广度优先遍历BFS

从一个未访问的顶点开始,先遍历这个顶点的所有相邻节点,再依次遍历每个相邻节点的相邻节点。
利用队列先进先出的特性,将待访问顶点的相邻节点依次放入队列中,然后依次出队列,实现广度优先遍历

void BFSTraverse(Graph *graph, int startVertex) {
    bool visited[MAX_VERTICES] = {false};

    int queue[MAX_VERTICES] = {0};
    int front=-1, rear=-1;

    //起始顶点加入队列
    queue[++rear] = startVertex;
    visited[startVertex] = true;

    while(rear != front) {
      int currentVertex = queue[++front];
      printf("%d ", currentVertex);

      for (int i = 0; i < graph->vertices; i++) {
        if (graph->adjMatrix[currentVertex][i] && !visited[i]) {
              // 将未访问且连通的加入队列
              queue[++rear] = i;
              visited[i] = true;
        }
      }
    }
}

运行

先遍历0的邻居节点0–1–2
再遍历1和2的邻居节点3
在这里插入图片描述

迪杰斯特拉算法

用于寻找单源最短路径

  • 采用一个一维数组标志节点是否已访问(也就是是否加入节点集中)
  • 采用另外一个数组记录未包含在节点中的节点到该节点集的最短距离,初始到其他结点距离都是INT_MAX。
  • 采用贪心算法,每次都选取距离节点集最近且未被访问的节点加入到节点集中,然后再根据这个节点更新最短距离数组。
int findMinIndex(Graph *graph, bool visited[], int distance[]){
    int nodeNum = graph->vertices;
    int min = INT_MAX,minIndex = 0;

    for(int i=0;i<nodeNum;i++){
        if(!visited[i] && distance[i]<min){
            min = distance[i];
            minIndex = i;
        }
    }
    return minIndex;
}

void dijkstra(Graph *graph, bool visited[], int distance[]){
    int nodeNum = graph->vertices;


    printf("加入节点集顺序:\n");
    // 解释下这里为什么是nodeNum-1,如果最后只剩一个节点不在节点集的时候,那么节点与节点集直接只有一条路径,就没有优化的必要了。
    for(int count=0;count<nodeNum-1;count++){
        int current = findMinIndex(graph, visited, distance);
        visited[current] = true;
        printf("%d\t", current);
        for(int i=0;i<nodeNum;i++) {
            if (!visited[i]
            && distance[current] != INT_MAX
            && graph->adjMatrix[current][i] != INT_MAX
            && distance[current] + graph->adjMatrix[current][i]< distance[i]) {
                distance[i] = distance[current] + graph->adjMatrix[current][i];
            }
        }
    }
    printf("\n");
}

void dijkstraFun(Graph *graph, int start) {
    int nodeNum = graph->vertices;
    bool visited[nodeNum];
    int distance[nodeNum];
    for(int i=0;i<nodeNum;i++){
        distance[i] = INT_MAX;
        visited[i] = false;
    }
    distance[start] = 0;
    dijkstra(graph, visited, distance);

    for(int i=0;i<nodeNum;i++){
        if (i != start) {
            printf("到节点 %d 的最短距离是:%d\n",i, distance[i]);
        }
    }
}

运行

我们从网上随便找一个迪杰斯特拉的图,然后建立节点和边,进行测试
这里uvwxyz分别对应012345
在这里插入图片描述

int main() {
    Graph graph;
    createGraphWithWeight(&graph, 6);
    addEdgeWithWeight(&graph, 0, 1,2);
    addEdgeWithWeight(&graph, 0, 2,5);
    addEdgeWithWeight(&graph, 0, 3,1);

    addEdgeWithWeight(&graph, 1, 2,3);
    addEdgeWithWeight(&graph, 1, 3,2);

    addEdgeWithWeight(&graph, 2, 3,3);
    addEdgeWithWeight(&graph, 2, 4,1);
    addEdgeWithWeight(&graph, 2, 5,5);

    addEdgeWithWeight(&graph, 3, 4,1);

    addEdgeWithWeight(&graph, 4, 5,2);

    dijkstraFun(&graph, 0);
}

可以非常容易验证下面结果的正确性。
在这里插入图片描述

弗洛伊德算法

用于寻找多源最短路径

采用动态规划的思想,通过不断更新节点之间的最短路径长度来逐步求解任意两个节点之间的最短路径。
原理可以参考这个: 弗洛伊德(Floyd’s)算法—解决最短路径经典算法

void Floyd(Graph *graph){
    int len = graph->vertices;

    int i,j,k;
    printf("初始化矩阵:\n");
    for(i=0;i<len;i++) {
        for(j=0;j<len;j++) {
            if(graph->adjMatrix[i][j] != INT_MAX) {
                printf("%d\t",graph->adjMatrix[i][j]);
            }else {
                printf("∞\t");
            }
        }
        printf("\n");
    }
    for(k=0;k<len;k++){
        for (i = 0; i < len; i++) {
            for (j = 0; j < len; j++) {
                if(graph->adjMatrix[i][k] != INT_MAX
                    && graph->adjMatrix[k][j] != INT_MAX
                    && graph->adjMatrix[i][j] > graph->adjMatrix[i][k] + graph->adjMatrix[k][j]){
                    graph->adjMatrix[i][j] = graph->adjMatrix[i][k] + graph->adjMatrix[k][j];
                }
            }
        }
    }
    printf("调整后的矩阵:\n");
    for(i=0;i<len;i++) {
        for(j=0;j<len;j++) {
            if(graph->adjMatrix[i][j] != INT_MAX) {
                printf("%d\t",graph->adjMatrix[i][j]);
            }else {
                printf("∞\t");
            }
        }
        printf("\n");
    }
}

运行

int main() {
    Graph graph;
    createGraphWithWeight(&graph, 6);
    addEdgeWithWeight(&graph, 0, 1,2);
    addEdgeWithWeight(&graph, 0, 2,5);
    addEdgeWithWeight(&graph, 0, 3,1);

    addEdgeWithWeight(&graph, 1, 2,3);
    addEdgeWithWeight(&graph, 1, 3,2);

    addEdgeWithWeight(&graph, 2, 3,3);
    addEdgeWithWeight(&graph, 2, 4,1);
    addEdgeWithWeight(&graph, 2, 5,5);

    addEdgeWithWeight(&graph, 3, 4,1);

    addEdgeWithWeight(&graph, 4, 5,2);


    Floyd(&graph);
}

在这里插入图片描述

相关文章:

  • 探秘 LangChain 函数定义
  • Java 性能优化:从原理到实践的全面指南
  • #systemverilog# 关于基于systemveriog验证平台的RTL+TB文件编译顺序问题的讨论
  • c++11--std::forwaord--完美转发
  • zk源码-7.ZAB协议和数据存储二
  • arm_math.h、arm_const_structs.h 和 arm_common_tables.h
  • 游戏引擎学习第217天
  • Day14:关于MySQL的索引——创、查、删
  • 【腾讯云智】20250329笔试算法题
  • QT聊天项目开发DAY02
  • NIPS2024论文 End-to-End Ontology Learning with Large Language Models
  • SpringBoot-Web开发
  • 网络空间安全(56)Laravel框架讲解
  • NoSQL入门指南:Redis与MongoDB的Java实战
  • gdb调试之.gdbinit 文件的用法
  • 最大子序和问题——动态规划/贪心算法解决
  • 2025年AI语音克隆工具全面评估与选型指南
  • Redis-基本概念
  • shield.io网站|markdown中适用的“徽标”
  • 使用ADB工具分析Android应用崩溃原因:以闪动校园为例
  • 精品消费“精”在哪?多在体验上下功夫
  • 白玉兰奖征片综述丨海外剧创作趋势观察:跨界·融变·共生
  • 美国将与阿联酋合作建立海外最大的人工智能数据中心
  • 一图读懂丨创新创业人才最高补贴500万元!临港新片区发布创客新政“十二条”
  • 因港而兴,“长江黄金水道”上的宜宾故事
  • 知名猎头公司创始人兼首席执行官庄华因突发疾病逝世,享年62岁