数据结构 图(Graph)

数据结构 图(Graph)

1. 图介绍

  • 在数据结构中, Graph是一种非常重要的数据结构,用于表示不同的对象(主要是节点)之间的关系。

  • 图由节点V(顶点)(Vertex)和边EEdge)组成,即有Graph={∑V+∑E},节点表示图中的元素,而边表示节点之间的关系。图可以用于建模各种实际问题,如社交网络中的用户关系、计算机网络中的路由、地图中的道路网络等。

  • 图可以分为有向图DAG和无向图G两种类型。在有向图中,边有方向,表示从一个节点到另一个节点的单向关系;而在无向图中,边没有方向,表示节点之间的双向关系。

  • 图还可以根据边的权重分为带权图和无权图。带权图中,每条边都有一个相关联的权重或成本WWeight),此时Graph={∑V+∑E+∑W},而在无权图中,所有边的权重都是相同的(1或者常数),或者没有权重。

  • 图的常见操作包括添加节点、添加边、删除节点、删除边、查找节点、查找边等。图的遍历算法包括深度优先搜索(DFS)和广度优先搜索(BFS),用于遍历图中的所有节点。

  • 入度和出度,入度表示有多少条边指向特定的节点,而出度表示从特定的节点出发一共有多少边指向其他节点。

2. 图分类

2.1 按有无方向划分

2.1.1 有向图
  • 有向图无环(DAG)的性质:
  • DAG是一个有向图,其中边有方向,表示从一个节点到另一个节点的单向关系。
  • DAG 不包含任何环路,即不存在从某个节点出发经过若干条边回到该节点的路径。因此,DAG 的结构是一个有向无环的图。
  • DAG 可以用来表示一些具有前后关系的任务或事件,如任务调度、依赖关系等。例如,编译过程中的源文件依赖关系就可以用 DAG 来表示。
  • 由于没有环路,DAG 中不存在循环依赖的问题,因此可以进行拓扑排序等操作。
  • 数据结构 图(Graph)_第1张图片
2.1.2 无向图
  • 无向图(G)的性质:
  • 无向图中的边没有方向,表示节点之间的双向关系,即任意两个节点之间都可以互相到达。
  • 无向图可以有环路,即存在一条路径可以从某个节点出发经过若干条边回到该节点。
  • 无向图通常用来表示不考虑方向的关系,如社交网络中的好友关系、交通网络中的道路连接等。
  • 无向图中的边是对称的,即如果节点 A 与节点 B 之间有一条边,那么节点 B 与节点 A 之间也有一条边。
  • 数据结构 图(Graph)_第2张图片
2.1.3 图的存储方式

图主要以邻接表邻接矩阵作为存储媒介进行存储。

2.1.3.1 邻接表
  • 邻接表是一种基于链表的数据结构,用于表示图的结构。
    对于每个节点,邻接表存储其相邻节点的列表。通常是通过数组链表的组合来实现,其中数组的索引表示节点,而每个数组元素是一个链表,存储与该节点相邻的节点。
    邻接表适用于稀疏图,即节点数相对边数较少的图,因为它只存储实际存在的边,节省了空间。

  • 数据结构 图(Graph)_第3张图片

  • 数据结构 图(Graph)_第4张图片

2.1.3.2 邻接矩阵
  • 邻接矩阵是一个二维数组,用于表示图的结构。
    对于具有 n 个节点的图,邻接矩阵是一个 n × n 的矩阵,其中行和列分别表示节点,矩阵的元素表示节点之间是否有边相连。
    如果图是有向图,邻接矩阵中的元素可以是 01,表示没有边或有边;如果图是带权图,邻接矩阵中的元素可以是实际的权重值W
    邻接矩阵适用于稠密图,即节点数和边数相对较多的图,因为它提供了 O(1) 时间复杂度的边查找操作,但是占用了更多的空间。

2.2 按权重划分

2.2.1 带权图
  • 带权图是指图中的边携带有权重或者相关的数值信息。

  • 在带权图中,每条边都与一个权重值相关联,表示边的强度、长度、成本等。

  • 带权图常用于建模一些实际问题,如最短路径问题、列车调度问题、哥尼斯堡的“七桥问题”等。

  • 带权图也可以是无向图或有向图,无向带权图表示节点之间的双向关系并携带权重,有向带权图表示单向关系并携带权重。

    数据结构 图(Graph)_第5张图片

2.2.2 无权图
  • 不带权图是指图中的边没有权重或者没有相关的数值信息。

  • 在不带权图中,边只表示节点之间的连接关系,而不包含额外的信息。

  • 无向图有向图都可以是不带权图,其中无向图的边表示节点之间的双向关系,而有向图的边表示单向关系。

3. 图常见操作

3.1 添加节点(顶点)

用于向图中添加新的节点。

#include 
#include 

#define MAX_VERTICES 100 // 假设最大节点数量为100

// 图的结构体定义
typedef struct {
   
    int numVertices; // 节点数量
    int adjMatrix[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵表示图的连接关系
} Graph;

// 初始化图
void initializeGraph(Graph *graph) {
   
    int i, j;
    graph->numVertices = 0;
    for (i = 0; i < MAX_VERTICES; i++) {
   
        for (j = 0; j < MAX_VERTICES; j++) {
   
            graph->adjMatrix[i][j] = 0; // 初始化邻接矩阵,表示节点间没有边相连
        }
    }
}

// 向图中添加新节点
void addVertex(Graph *graph) {
   
    if (graph->numVertices < MAX_VERTICES) {
   
        // 新节点的索引即为当前节点数量
        graph->numVertices++;
    } else {
   
        printf("图中节点数量已达到最大值,无法添加新节点。\n");
    }
}

int main() {
   
    Graph graph;
    initializeGraph(&graph);

    // 添加三个节点
    addVertex(&graph);
    addVertex(&graph);
    addVertex(&graph);

    printf("图中当前节点数量:%d\n", graph.numVertices);

    return 0;
}

3.2 删除节点

从图中删除指定的节点,以及与该节点相关联的边。

#include 
#include 

#define MAX_VERTICES 100 // 假设最大节点数量为100

// 图的结构体定义
typedef struct {
   
    int numVertices; // 节点数量
    int adjMatrix[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵表示图的连接关系
} Graph;

// 初始化图
void initializeGraph(Graph *graph) {
   
    int i, j;
    graph->numVertices = 0;
    for (i = 0; i < MAX_VERTICES; i++) {
   
        for (j = 0; j < MAX_VERTICES; j++) {
   
            graph->adjMatrix[i][j] = 0; // 初始化邻接矩阵,表示节点间没有边相连
        }
    }
}

// 向图中添加新节点
void addVertex(Graph *graph) {
   
    if (graph->numVertices < MAX_VERTICES) {
   
        // 新节点的索引即为当前节点数量
        graph->numVertices++;
    } else {
   
        printf("图中节点数量已达到最大值,无法添加新节点。\n");
    }
}

// 从图中删除节点及其相关联的边
void removeVertex(Graph *graph, int vertex) {
   
    if (vertex >= 0 && vertex < graph->numVertices) {
   
        int i, j;

        // 从邻接矩阵中删除相关联的边
        for (i = 0; i < graph->numVertices; i++) {
   
            graph->adjMatrix[vertex][i] = 0; // 删除与该节点相关联的出边
            graph->adjMatrix[i][vertex] = 0; // 删除与该节点相关联的入边
        }

        // 将该节点从图中删除
        for (i = vertex; i < graph->numVertices - 1; i++) {
   
            for (j = 0; j < graph->numVertices; j++) {
   
                graph->adjMatrix[j][i] = graph->adjMatrix[j][i + 1]; // 将后面的节点向前移动
            }
        }
        for (i = vertex; i < graph->numVertices - 1; i++) {
   
            for (j = 0; j < graph->numVertices; j++) {
   
                graph->adjMatrix[i][j] = graph->adjMatrix[i + 1][j]; // 将后面的节点向前移动
            }
        }

        graph->numVertices--; // 更新节点数量
    } else {
   
        printf("要删除的节点不存在。\n");
    }
}

int main() {
   
    Graph graph;
    initializeGraph(&graph);

    // 添加四个节点
    addVertex(&graph);
    addVertex(&graph);
    addVertex(&graph);
    addVertex(&graph);

    // 添加一些边
    graph.adjMatrix[0][1] = 1; // 添加从节点0到节点1的边
    graph.adjMatrix[1][2] = 1; // 添加从节点1到节点2的边
    graph.adjMatrix[2][3] = 1; // 添加从节点2到节点3的边

    printf("删除前,图中当前节点数量:%d\n", graph.numVertices);

    // 删除节点2及其相关联的边
    removeVertex(&graph, 2);

    printf("删除后,图中当前节点数量:%d\n", graph.numVertices);

    return 0;
}

3.3 添加边

在图中添加一条边,连接两个节点。

#include 
#include 

#define MAX_VERTICES 100 // 假设最大节点数量为100

// 图的结构体定义
typedef struct {
   
    int numVertices; // 节点数量
    int numEdges;    // 边的数量
    int adjMatrix[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵表示图的连接关系
} Graph;

// 初始化图
void initializeGraph(Graph *graph) {
   
    int i, j;
    graph->numVertices = 0;
    graph->numEdges = 0;
    for (i = 0; i < MAX_VERTICES; i++) {
   
        for (j = 0; j < MAX_VERTICES; j++) {
   
            graph->adjMatrix[i][j] = 0; // 初始化邻接矩阵,表示节点间没有边相连
        }
    }
}

// 向图中添加新节点
void addVertex(Graph *graph) {
   
    if (graph->numVertices < MAX_VERTICES) {
   
        // 新节点的索引即为当前节点数量
        graph->numVertices++;
    } else {
   
        printf("图中节点数量已达到最大值,无法添加新节点。\n");
    }
}

// 在图中添加一条边,连接两个节点
void addEdge(Graph *graph, int source, int destination) {
   
    if (source >= 0 && source < graph->numVertices && destination >= 0 && destination < graph->numVertices) {
   
        // 添加边将邻接矩阵中对应位置设为1
        graph->adjMatrix[source][destination] = 1;
        // 如果是无向图,还需添加反向边
        graph->adjMatrix[destination]

你可能感兴趣的:(数据结构,c语言,图论)