欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > IT业 > 数据结构——图论基础

数据结构——图论基础

2025/5/10 14:11:28 来源:https://blog.csdn.net/2303_82176667/article/details/144472386  浏览:    关键词:数据结构——图论基础

文章目录

  • 1.图的基本概念
    • 1.1图的定义
    • 1.2 图的基本术语
  • 2.图的存储结构与基本运算算法
    • 2.1邻接矩阵
    • 2.2邻接表
    • 2.3十字链表
  • 3.图的遍历
    • 3.1深度优先遍历(DFS)
    • 3.2广度优先遍历(DFS)
  • 4.生成树与最小生成树
    • 4.1生成树的概念
    • 4.2非连通图与生成树
  • 5.最短路径
    • 5.1Dijkstra算法
    • 5.2Floyd-Warshall算法
  • 6.拓扑排序

1.图的基本概念

1.1图的定义

在数学和计算机科学中,图(Graph)是一种数据结构,用于表示对象与对象之间的关系。图由两部分组成:

  1. 顶点(Vertex或Node): 图中的基本单位,表示对象或元素。
  2. 边(Edge):连接顶点的线段,表示顶点之间的关系或连接。
    图可以用符号G=(V,E)表示,其中:
  • V是一个顶点集合。
  • E是一个边集合,每条边连接一个或多个顶点。

1.2 图的基本术语

  1. 无向图(undirected graph)
    如果一个图结构中,所有的边都没有方向性,那么这种图便称为无向图。

  2. 有向图(Digraph)
    在有向图中,图的边是有方向的。每条边都有一个方向,从一个顶点指向另一个顶点,表示一种单向的关系。

  3. 端点与邻接点

    • 无向图中,若存在一条边(i,j),则顶点i和顶点j为端点,它们互为邻接点。
    • 有向图中,若存在一条边<i, j> → 顶点i为起始端点(简称为起点),顶点j为终止端点(简称为终点),它们互为邻接点。
  4. 顶点的度、入度与出度

    • 无向图中,以顶点i为端点的边数称为该顶点的度。
    • 有向图中,以顶点i为终点的入边的数目,称为该顶点的入度;以顶点i为始点的出边的数目,称为该顶点的出度。一个顶点的入度与出度的和为该顶点的度。
  5. 完全图

    • 无向图中,每两个顶点之间都存在着一条边,称为完全无向图,包含有 n(n-1)/2 条边。
    • 有向图中,每两个顶点之间都存在着方向相反的两条边,称为完全有向图,包含有 n(n-1) 条边。
  6. 子图
    设有两个图 G = (V, E)G' = (V', E'),若 V'V 的子集,且 E'E 的子集,则称 G'G 的子图。

  7. 路径和路径长度

    • 在一个图 G = (V, E) 中,从顶点 i 到顶点 j 的一条路径为 (i, i2, i3, ..., im, j),其中所有的 (ix, iy) 属于 E(G)<ix, im> 属于 E(G)
    • 路径长度是指一条路径上经过的边的数目。
    • 若一条路径上除开始点和结束点可以相同外,其余顶点均不相同,则称此路径为简单路径。
  8. 回路或环

    • 若一条路径上的开始点与结束点为同一个顶点,则此路径被称为回路或环。
    • 开始点与结束点相同的简单路径被称为简单回路或简单环。
  9. 连通、连通图和连通分量(无向图中):

    • 若从顶点 i 到顶点 j 有路径,则称顶点 ij连通 的。
    • 若图中任意两个顶点都连通,则称为 连通图,否则称为 非连通图
    • 无向图 G 中的极大连通子图称为 G连通分量。显然,任何连通图的连通分量只有一个,即本身,而非连通图有多个连通分量。
  10. 强连通图和强连通分量

    • 若从顶点 i 到顶点 i 有路径,则称从顶点 ij 是连通的。
    • 若图 G 中的任意两个顶点 ij 都连通,即从顶点 ij 和从顶点 ji 都存在路径,则称图 G强连通图
    • 有向图G中的极大强连通子图称为G的强连通分量。
    • 强连通图只有一个强连通分量,即本身。非强连通图有多个强连通分量。
  11. 权和网

    • 图中每一条边都可以附带有一个对应的数值,这种与边相关的数值称为权。权可以表示从一个顶点到另一个顶点的距离或花费的代价。
    • 边上带有权的图称为带权图,也称作网。

2.图的存储结构与基本运算算法

2.1邻接矩阵

  1. 若G是无向图:
adjMat[i][j] = adjMat[j][i] = 1//i和j之间存在边
adjMMat[i][j] = 0//i和j之间不存在边
  1. 若G是有向图
adjMat[i][j] = 1//<i,j>
adjMMat[i][j] = 0//i和j之间不存在边
  1. 若G是带权无向图
adjMat[i][j] = adjMat[j][i] = 权重//带权边
adjMMat[i][j] = 无穷大//i和j之间不存在边
  1. 若G是带权有向图
adjMat[i][j] == 权重//带权边<i,j>
adjMMat[i][j] = 无穷大//i和j之间不存在边

邻接矩阵结构体表示

// This is the implementation of a graph data structure in C.  
#define Max_Size 255  
typedef struct{  int vertices[Max_Size];  int adjMat[Max_Size][Max_Size];  int size;  
}GraphAdjMat;

2.2邻接表

使用邻接表表示图是一种常见的图数据结构,它使用数组和链表来存储图中的顶点和边。每个顶点都有一个链表,链表中存储着与该顶点相邻的所有顶点的信息。对于带权无向图,链表中的每个节点还会存储边的权重。

// 定义邻接表中的节点
struct EdgeNode {int adjvex;         // 邻接点的索引int weight;         // 边的权重struct EdgeNode *next; // 指向下一个邻接点的指针
};// 定义图的结构
struct Graph {int numVertices;    // 图的顶点数struct EdgeNode **adjList; // 邻接表数组
};

2.3十字链表

在图的表示方法中,十字链表(也称为边链表)是一种用于表示图的边和顶点信息的数据结构。十字链表由边表和顶点表组成,其中边表中的每个边记录了与该边相连的两个顶点的信息,而顶点表中的每个顶点记录了与该顶点相连的所有边的信息。

#define MAXV 20 // 最大顶点数// 定义边表节点
typedef struct ArcNode {int adjvex; // 该边所指向的顶点的位置struct VNode *v; // 指向顶点表中相应顶点的指针struct ArcNode *next; // 指向下一条边的指针
} ArcNode;// 定义顶点表节点
typedef struct VNode {int data; // 顶点信息ArcNode *first; // 指向第一条依附该顶点的边
} VNode, AdjList[MAXV]; // 顶点表// 定义图的结构
typedef struct {AdjList vertices; // 指向顶点表的指针int vexnum, arcnum; // 图的当前顶点数和弧数
} CrossGraph;

3.图的遍历

3.1深度优先遍历(DFS)

深度优先遍历过程:

  1. 从图中某个初始顶点v出发,首先访问初始顶点v。
  2. 选择一个与顶点v相邻且没被访问过的顶点w,再从w出发进行深度优先搜索,直到图中与当前顶点v邻接的所有顶点都被访问过为止。

无向图 ( G = (V, E) ),其中
V = {a, b, c, d, e, f}
E = {(a, b), (a, e), (a, c), (b, e), (c, f), (f, d), (e, d)}
对该图进行深度优先排序,得到的顶点序列正确的是( )。
A. a, b, e, c, d, f
B. a, c, f, e, b, d
C. a, e, b, c, f, d
D. a, e, d, f, c, b

在这里插入图片描述

深度优先搜索得到:a,b,e,d,f,c

// 定义图的节点
typedef struct Node {char data;struct Node* next;
} Node;// 定义栈的节点
typedef struct StackNode {Node* ptr;struct StackNode* next;
} StackNode;// 定义栈
typedef struct Stack {StackNode* top;
} Stack;// 初始化栈
void initStack(Stack* s) {s->top = NULL;
}// 检查栈是否为空
int isEmpty(Stack* s) {return s->top == NULL;
}// 入栈
void push(Stack* s, Node* n) {StackNode* newNode = (StackNode*)malloc(sizeof(StackNode));newNode->ptr = n;newNode->next = s->top;s->top = newNode;
}// 出栈
Node* pop(Stack* s) {if (isEmpty(s)) return NULL;StackNode* temp = s->top;Node* node = temp->ptr;s->top = s->top->next;free(temp);return node;
}// 创建图的节点
Node* createNode(char data) {Node* newNode = (Node*)malloc(sizeof(Node));newNode->data = data;newNode->next = NULL;return newNode;
}// 添加边
void addEdge(Node** head, char src, char dest) {Node* newNode = createNode(dest);newNode->next = *head;*head = newNode;
}// 深度优先遍历
void dfs(Node* head) {Stack s;initStack(&s);Node* current = head;while (current != NULL || !isEmpty(&s)) {while (current != NULL) {printf("%c ", current->data);push(&s, current);current = current->next;}if (!isEmpty(&s)) {current = pop(&s);current = current->next;}}
}

3.2广度优先遍历(DFS)

广度优先遍历的过程:

  1. 访问初始点v,接着访问v的所有未被访问过的邻接点V,V’…,v.
  2. 按照V,V’…,v,的次序,访问每一个顶点的所有未被访问过的邻接点,
  3. 依次类推,直到图中所有和初始点v有路径相通的顶点都被访问过为止。
    在这里插入图片描述

广度优先搜索得到:a,b,e,c,d,f

// 定义图的节点
typedef struct Node {char data;struct Node* next;
} Node;// 定义队列的节点
typedef struct QueueNode {char data;struct QueueNode* next;
} QueueNode;// 定义队列
typedef struct Queue {QueueNode* front;QueueNode* rear;
} Queue;// 初始化队列
void initQueue(Queue* q) {q->front = q->rear = NULL;
}// 检查队列是否为空
int isEmpty(Queue* q) {return q->front == NULL;
}// 入队
void enqueue(Queue* q, char data) {QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));newNode->data = data;newNode->next = NULL;if (q->rear == NULL) {q->front = q->rear = newNode;} else {q->rear->next = newNode;q->rear = newNode;}
}// 出队
char dequeue(Queue* q) {if (isEmpty(q)) return '\0';QueueNode* temp = q->front;char data = temp->data;q->front = q->front->next;if (q->front == NULL) {q->rear = NULL;}free(temp);return data;
}// 创建图的节点
Node* createNode(char data) {Node* newNode = (Node*)malloc(sizeof(Node));newNode->data = data;newNode->next = NULL;return newNode;
}// 添加边(这里我们使用邻接表表示图)
void addEdge(Node** adjList[], char src, char dest) {Node* newNode = createNode(dest);newNode->next = adjList[src];adjList[src] = newNode;
}// 广度优先遍历
void bfs(Node* adjList[], char startVertex) {Queue q;initQueue(&q);enqueue(&q, startVertex);int visited[256] = {0}; // 假设图中的顶点数据不会超过256个不同的字符visited[startVertex] = 1;while (!isEmpty(&q)) {char currentVertex = dequeue(&q);printf("%c ", currentVertex);Node* temp = adjList[currentVertex];while (temp) {char adjVertex = temp->data;if (!visited[adjVertex]) {visited[adjVertex] = 1;enqueue(&q, adjVertex);}temp = temp->next;}}
}

4.生成树与最小生成树

4.1生成树的概念

生成树是图的一个子图,它满足以下条件:

  1. 连接性:生成树必须包含图中的所有顶点,且这些顶点之间是连通的。
  2. 无环性:生成树中不能包含任何环(cycle),即它是一个树结构。
  3. 最小边数:生成树的边数恰好是顶点数减一,即n−1n−1 条边,其中nn 是顶点数。

生成树的一个重要性质是,对于一个有nn 个顶点的图,它的生成树有n−1n−1 条边,且这些边连接了图中的所有顶点。

下面是关于DFS(深度优先搜索)生成树和BFS(广度优先搜索)生成树的对比

特征DFS生成树BFS生成树
搜索策略深度优先广度优先
起始点任意顶点任意顶点
遍历顺序尽可能深地搜索树的分支一层一层地搜索树的分支
栈的使用使用栈(递归实现)使用队列
路径选择总是选择未访问的邻接顶点中深度最大的顶点总是选择未访问的邻接顶点中深度最小的顶点
生成树结构可能包含后继节点可能包含前驱节点
时间复杂度(O(V+E))(O(V+E))
空间复杂度(O(V))(O(V))
适用场景适合解决需要深入探索的问题,如寻找路径适合解决需要逐层遍历的问题,如最短路径
实现方式递归或栈队列

4.2非连通图与生成树

  1. 对于连通图:仅需调用遍历过程(DFS或BFS)一次,从图中任一顶点出发,便可以遍历图中的各个顶点,产生相应的生成树。

  2. 对于非连通图:需多次调用遍历过程。每个连通分量中的顶点集和遍历时走过的边一起构成一棵生成树。所有连通分量的生成树组成非连通图的生成森林。
    重点:求带权连通图的最小生成树
    在带权连通图中求最小生成树(Minimum Spanning Tree, MST)是一个经典的问题,可以通过几种不同的算法来解决。最常用的算法包括:

  3. Kruskal 算法:通过选择权重最小的边来构建树,同时确保不会形成环。

  4. Prim 算法:从一个顶点开始,逐步添加权重最小的边,直到所有顶点都被包含在树中。

Kruskal 算法的基本步骤如下:

  1. 将图中的所有边按照权重从小到大排序。
  2. 初始化一个空的森林(每个顶点都是一个单独的树)。
  3. 遍历排序后的边列表,对于每条边:
    • 如果这条边连接的两个顶点属于不同的树,则将这条边加入到最小生成树中,并将这两个顶点所在的树合并。
    • 如果这条边连接的两个顶点属于同一棵树,则忽略这条边,以避免形成环。
  4. 重复步骤3,直到最小生成树包含所有顶点。

Prim 算法的基本步骤如下:

  1. 从任意一个顶点开始,将其作为最小生成树的起始点。
  2. 选择一个权重最小的边,该边的一个顶点在当前的最小生成树中,另一个顶点不在。
  3. 将这个顶点加入到最小生成树中。
  4. 重复步骤2和3,直到所有顶点都被包含在最小生成树中。

5.最短路径

图的最短路径是指在图中从一个顶点(起点)到另一个顶点(终点)之间的一条路径,使得这条路径上的边的权重之和最小。最短路径问题在许多实际应用中非常重要,例如在交通网络、通信网络等领域。

5.1Dijkstra算法

Dijkstra算法是一种用于寻找图中单源最短路径的算法,由荷兰计算机科学家艾兹赫尔·戴克斯特拉(Edsger W. Dijkstra)于1956年提出。该算法适用于带有非负权重的图。

算法步骤:

  1. 初始化:将所有顶点的距离设为无穷大(除了起始顶点,设为0),并创建一个未访问顶点集合。
  2. 选择:从未访问顶点集合中选择距离最小的顶点,标记为已访问。
  3. 更新:对于该顶点的所有邻接点,计算通过当前顶点到邻接点的距离,如果这个距离小于邻接点当前记录的距离,则更新邻接点的距离。
  4. 重复:重复步骤2和3,直到所有顶点都被访问或者目标顶点被访问。

算法特点:

  • 效率:使用优先队列可以提高效率,尤其是在顶点数较多的图中。
  • 适用性:适用于边权重非负的图。
  • 局限性:不适用于包含负权重边的图。

5.2Floyd-Warshall算法

Floyd-Warshall算法是一种用于寻找图中所有顶点对之间最短路径的算法。该算法由Robert Floyd于1962年提出。

算法步骤:

  1. 初始化:创建一个距离矩阵,其中dist[i][j]表示顶点i到顶点j的直接距离,如果ij之间没有直接的边,则dist[i][j]为无穷大。
  2. 迭代:对于每个顶点k,检查是否可以通过k来找到ij的更短路径。即更新dist[i][j]min(dist[i][j], dist[i][k] + dist[k][j])
  3. 重复:对所有顶点k重复步骤2,然后对所有顶点ij更新dist[i][j]

算法特点:

  • 效率:时间复杂度为O(n^3),其中n是顶点的数量。
  • 适用性:适用于密集图,尤其是当需要计算所有顶点对之间的最短路径时。
  • 局限性:对于稀疏图,效率较低,因为需要存储和更新完整的距离矩阵。

6.拓扑排序

拓扑排序是针对有向无环图(Directed Acyclic Graph,简称DAG)的一种排序算法,它将图中的所有顶点排成一个线性序列,使得对于任何一条有向边U→VU→V,顶点UU都在顶点VV的前面。换句话说,就是对图中的顶点进行排序,使得每个顶点都出现在它指向的任何顶点之前。

拓扑排序不是唯一的,因为图中可能存在多个顶点序列满足拓扑排序的条件。拓扑排序常用于任务调度、课程规划、工程问题解决等领域,其中任务之间存在依赖关系,需要按照特定的顺序执行。

拓扑排序的算法步骤:

  1. 计算入度:计算图中每个顶点的入度(即指向该顶点的边的数量)。
  2. 初始化队列:将所有入度为0的顶点加入到一个队列(或栈)中。
  3. 处理队列:当队列不为空时,执行以下操作:
    • 从队列中移除一个顶点,并将其加入到拓扑排序的结果序列中。
    • 遍历该顶点的所有邻接点,将每个邻接点的入度减1。
    • 如果某个邻接点的入度变为0,则将其加入队列中。
  4. 检查环:如果图中存在环,则无法进行拓扑排序,因为环中的顶点无法被排序。

拓扑排序的算法特点:

  • 适用性:仅适用于有向无环图。
  • 结果:结果可能不唯一,但任何有效的拓扑排序都满足定义的要求。
  • 应用:在任务调度、课程规划等场景中非常有用。
void topologicalSort(int graph[MAX_NODES][MAX_NODES], int n) {  int in_degree[MAX_NODES] = {0};  // 初始化入度数组  int queue[MAX_NODES], front = 0, rear = 0;  int result[MAX_NODES];  int count = 0;  // 计算每个节点的入度  for (int i = 0; i < n; i++) {  for (int j = 0; j < n; j++) {  if (graph[i][j] == 1) {  in_degree[j]++;  }  }  }  // 将所有入度为0的节点加入队列  for (int i = 0; i < n; i++) {  if (in_degree[i] == 0) {  queue[rear++] = i;  }  }  // 拓扑排序  while (front != rear) {  int node = queue[front++];  result[count++] = node;  // 将当前节点加入拓扑排序结果  // 遍历与当前节点相连的所有节点  for (int i = 0; i < n; i++) {  if (graph[node][i] == 1) {  in_degree[i]--;  if (in_degree[i] == 0) {  queue[rear++] = i;  }  }  }  }  // 检查是否有环  if (count != n) {  printf("图中有环,无法进行拓扑排序\n");  } else {  printf("拓扑排序结果: ");  for (int i = 0; i < n; i++) {  printf("%d ", result[i]);  }  printf("\n");  }  
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词