图(Graph)是一种复杂的数据结构,用于表示对象之间的关系。图在计算机科学和许多实际应用中具有广泛的应用,如社交网络分析、路径规划、网络路由、图像处理等。本文将详细介绍如何使用C语言实现图,包括基本概念、图的表示方法、基本操作、常用算法(如深度优先搜索和广度优先搜索)、示例代码、优缺点及其应用场景。
图的基本概念
定义
图由顶点(Vertex)和边(Edge)组成,用于表示对象及其相互关系。图可以分为多种类型,主要包括:
-
有向图(Directed Graph):边具有方向,从一个顶点指向另一个顶点。
-
无向图(Undirected Graph):边没有方向,表示两个顶点之间的双向关系。
-
加权图(Weighted Graph):边具有权重,表示连接顶点之间的成本、距离或其他度量。
-
无权图(Unweighted Graph):边没有权重,或者所有边具有相同的权重。
应用场景
-
社交网络:表示用户(顶点)之间的好友关系(边)。
-
地图和路径规划:表示地点(顶点)和道路(边),用于计算最短路径。
-
网络路由:表示网络设备(顶点)和连接(边),用于数据包传输。
-
任务调度和依赖关系:表示任务(顶点)及其依赖关系(边)。
-
编译器设计:表示语法规则(顶点)之间的关系(边)。
图的表示方法
在C语言中,图主要有两种常见的表示方法:
-
邻接矩阵(Adjacency Matrix)
-
邻接表(Adjacency List)
1. 邻接矩阵
邻接矩阵是一种二维数组,用于表示图中顶点之间的连接关系。对于一个包含V
个顶点的图,邻接矩阵是一个V x V
的矩阵,其中matrix[i][j]
表示顶点i
和顶点j
之间是否存在边(及其权重)。
优点
-
查询边是否存在:时间复杂度为O(1)。
-
实现简单。
缺点
-
空间浪费:对于稀疏图(边数较少的图),邻接矩阵会浪费大量空间。
-
插入和删除边的时间复杂度高。
示例代码
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTICES 100
typedef struct Graph {
int numVertices;
int isDirected;
int matrix[MAX_VERTICES][MAX_VERTICES];
} Graph;
// 初始化图
void initializeGraph(Graph* g, int numVertices, int isDirected) {
g->numVertices = numVertices;
g->isDirected = isDirected;
for (int i = 0; i < numVertices; i++)
for (int j = 0; j < numVertices; j++)
g->matrix[i][j] = 0;
}
// 添加边
void addEdgeMatrix(Graph* g, int src, int dest) {
if (src >= g->numVertices || dest >= g->numVertices) {
printf("顶点编号超出范围\n");
return;
}
g->matrix[src][dest] = 1;
if (!g->isDirected)
g->matrix[dest][src] = 1;
}
// 删除边
void removeEdgeMatrix(Graph* g, int src, int dest) {
if (src >= g->numVertices || dest >= g->numVertices) {
printf("顶点编号超出范围\n");
return;
}
g->matrix[src][dest] = 0;
if (!g->isDirected)
g->matrix[dest][src] = 0;
}
// 打印邻接矩阵
void printMatrix(Graph* g) {
printf("邻接矩阵:\n");
for (int i = 0; i < g->numVertices; i++) {
for (int j = 0; j < g->numVertices; j++) {
printf("%d ", g->matrix[i][j]);
}
printf("\n");
}
}
int main() {
Graph g;
initializeGraph(&g, 5, 0); // 创建一个无向图,5个顶点
addEdgeMatrix(&g, 0, 1);
addEdgeMatrix(&g, 0, 4);
addEdgeMatrix(&g, 1, 2);
addEdgeMatrix(&g, 1, 3);
addEdgeMatrix(&g, 1, 4);
addEdgeMatrix(&g, 2, 3);
addEdgeMatrix(&g, 3, 4);
printMatrix(&g);
removeEdgeMatrix(&g, 1, 4);
printf("删除边(1, 4)后:\n");
printMatrix(&g);
return 0;
}
输出结果
邻接矩阵:
0 1 0 0 1
1 0 1 1 1
0 1 0 1 0
0 1 1 0 1
1 1 0 1 0
删除边(1, 4)后:
0 1 0 0 1
1 0 1 1 0
0 1 0 1 0
0 1 1 0 1
1 0 0 1 0
2. 邻接表
邻接表是一种更加节省空间的图表示方法,特别适用于稀疏图。它由一个数组和若干链表组成,数组的每个元素对应一个顶点,链表中存储与该顶点相邻接的所有顶点。
优点
-
节省空间:仅存储存在的边,适合稀疏图。
-
遍历邻接顶点更高效。
缺点
-
查询边是否存在的时间复杂度较高(O(V))。
-
实现相对复杂。
示例代码
#include <stdio.h>
#include <stdlib.h>
// 定义邻接表的链表节点
typedef struct AdjListNode {
int dest;
struct AdjListNode* next;
} AdjListNode;
// 定义邻接表
typedef struct AdjList {
AdjListNode* head; // 指向链表头部
} AdjList;
// 定义图
typedef struct Graph {
int numVertices;
int isDirected;
AdjList* array;
} Graph;
// 创建一个新的邻接表节点
AdjListNode* createAdjListNode(int dest) {
AdjListNode* newNode = (AdjListNode*)malloc(sizeof(AdjListNode));
if (!newNode) {
printf("内存分配失败\n");
exit(1);
}
newNode->dest = dest;
newNode->next = NULL;
return newNode;
}
// 初始化图
Graph* initializeGraph(int numVertices, int isDirected) {
Graph* graph = (Graph*)malloc(sizeof(Graph));
if (!graph) {
printf("内存分配失败\n");
exit(1);
}
graph->numVertices = numVertices;
graph->isDirected = isDirected;
// 创建一个邻接表数组
graph->array = (AdjList*)malloc(numVertices * sizeof(AdjList));
if (!graph->array) {
printf("内存分配失败\n");
exit(1);
}
// 初始化每个邻接表为NULL
for (int i = 0; i < numVertices; i++)
graph->array[i].head = NULL;
return graph;
}
// 添加边
void addEdgeList(Graph* graph, int src, int dest) {
if (src >= graph->numVertices || dest >= graph->numVertices) {
printf("顶点编号超出范围\n");
return;
}
// 将dest添加到src的链表
AdjListNode* newNode = createAdjListNode(dest);
newNode->next = graph->array[src].head;
graph->array[src].head = newNode;
// 如果是无向图,则将src添加到dest的链表
if (!graph->isDirected) {
newNode = createAdjListNode(src);
newNode->next = graph->array[dest].head;
graph->array[dest].head = newNode;
}
}
// 删除边
void removeEdgeList(Graph* graph, int src, int dest) {
if (src >= graph->numVertices || dest >= graph->numVertices) {
printf("顶点编号超出范围\n");
return;
}
// 从src的链表中删除dest
AdjListNode* temp = graph->array[src].head;
AdjListNode* prev = NULL;
while (temp != NULL && temp->dest != dest) {
prev = temp;
temp = temp->next;
}
if (temp != NULL) {
if (prev == NULL)
graph->array[src].head = temp->next;
else
prev->next = temp->next;
free(temp);
}
// 如果是无向图,则从dest的链表中删除src
if (!graph->isDirected) {
temp = graph->array[dest].head;
prev = NULL;
while (temp != NULL && temp->dest != src) {
prev = temp;
temp = temp->next;
}
if (temp != NULL) {
if (prev == NULL)
graph->array[dest].head = temp->next;
else
prev->next = temp->next;
free(temp);
}
}
}
// 打印邻接表
void printAdjList(Graph* graph) {
for (int v = 0; v < graph->numVertices; v++) {
AdjListNode* temp = graph->array[v].head;
printf("顶点 %d:", v);
while (temp) {
printf(" -> %d", temp->dest);
temp = temp->next;
}
printf("\n");
}
}
int main() {
int numVertices = 5;
Graph* graph = initializeGraph(numVertices, 0); // 创建一个无向图
addEdgeList(graph, 0, 1);
addEdgeList(graph, 0, 4);
addEdgeList(graph, 1, 2);
addEdgeList(graph, 1, 3);
addEdgeList(graph, 1, 4);
addEdgeList(graph, 2, 3);
addEdgeList(graph, 3, 4);
printAdjList(graph);
removeEdgeList(graph, 1, 4);
printf("删除边 (1,4) 后:\n");
printAdjList(graph);
return 0;
}
输出结果
顶点 0: -> 4 -> 1
顶点 1: -> 4 -> 3 -> 2 -> 0
顶点 2: -> 3 -> 1
顶点 3: -> 4 -> 2 -> 1
顶点 4: -> 3 -> 1 -> 0
删除边 (1,4) 后:
顶点 0: -> 4 -> 1
顶点 1: -> 3 -> 2 -> 0
顶点 2: -> 3 -> 1
顶点 3: -> 4 -> 2 -> 1
顶点 4: -> 3 -> 0
图的选择:邻接矩阵 vs 邻接表
特性 | 邻接矩阵 | 邻接表 |
---|---|---|
空间复杂度 | O(V²) | O(V + E) |
边查询速度 | O(1) | O(V) |
边的插入删除 | O(1) - O(V)(对于稀疏图,O(1)较好) | O(1) - O(V)(需要遍历链表) |
适用场景 | 稠密图(边数接近最大边数) | 稀疏图(边数远小于最大边数) |
实现复杂度 | 简单 | 相对复杂 |
图的基本操作
1. 添加顶点
在邻接矩阵表示中,添加顶点通常意味着扩展矩阵的大小。然而,由于C语言中数组大小固定,通常需要提前确定最大顶点数。使用邻接表则更灵活,可以动态添加顶点。
2. 添加边
已在上述表示方法中详细示例。
3. 删除边
已在上述表示方法中详细示例。
4. 遍历图
图的遍历主要有两种常用方式:
-
深度优先搜索(Depth-First Search, DFS)
-
广度优先搜索(Breadth-First Search, BFS)
4.1 深度优先搜索(DFS)
DFS是一种递归或使用栈的遍历方法,优先访问深度更大的分支。
递归实现示例(邻接表)
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTICES 100
// 定义邻接表节点
typedef struct AdjListNode {
int dest;
struct AdjListNode* next;
} AdjListNode;
// 定义邻接表
typedef struct AdjList {
AdjListNode* head;
} AdjList;
// 定义图
typedef struct Graph {
int numVertices;
AdjList* array;
} Graph;
// 创建新节点
AdjListNode* createAdjListNode(int dest) {
AdjListNode* newNode = (AdjListNode*)malloc(sizeof(AdjListNode));
newNode->dest = dest;
newNode->next = NULL;
return newNode;
}
// 初始化图
Graph* initializeGraph(int numVertices) {
Graph* graph = (Graph*)malloc(sizeof(Graph));
graph->numVertices = numVertices;
graph->array = (AdjList*)malloc(numVertices * sizeof(AdjList));
for (int i = 0; i < numVertices; i++)
graph->array[i].head = NULL;
return graph;
}
// 添加边
void addEdgeList(Graph* graph, int src, int dest) {
AdjListNode* newNode = createAdjListNode(dest);
newNode->next = graph->array[src].head;
graph->array[src].head = newNode;
}
// 打印图
void printGraph(Graph* graph) {
for (int v = 0; v < graph->numVertices; v++) {
AdjListNode* temp = graph->array[v].head;
printf("顶点 %d:", v);
while (temp) {
printf(" -> %d", temp->dest);
temp = temp->next;
}
printf("\n");
}
}
// DFS递归实现
void DFSUtil(Graph* graph, int v, int visited[]) {
visited[v] = 1;
printf("%d ", v);
AdjListNode* temp = graph->array[v].head;
while (temp) {
if (!visited[temp->dest])
DFSUtil(graph, temp->dest, visited);
temp = temp->next;
}
}
// DFS主函数
void DFS(Graph* graph, int startVertex) {
int* visited = (int*)malloc(graph->numVertices * sizeof(int));
for (int i = 0; i < graph->numVertices; i++)
visited[i] = 0;
printf("DFS遍历(从顶点 %d 开始): ", startVertex);
DFSUtil(graph, startVertex, visited);
printf("\n");
free(visited);
}
int main() {
int numVertices = 5;
Graph* graph = initializeGraph(numVertices);
addEdgeList(graph, 0, 1);
addEdgeList(graph, 0, 4);
addEdgeList(graph, 1, 2);
addEdgeList(graph, 1, 3);
addEdgeList(graph, 1, 4);
addEdgeList(graph, 2, 3);
addEdgeList(graph, 3, 4);
printGraph(graph);
DFS(graph, 0); // 从顶点0开始DFS
return 0;
}
输出结果
顶点 0: -> 4 -> 1
顶点 1: -> 4 -> 3 -> 2
顶点 2: -> 3
顶点 3: -> 4
顶点 4:
DFS遍历(从顶点 0 开始): 0 1 2 3 4
4.2 广度优先搜索(BFS)
BFS是一种使用队列的遍历方法,先访问离起始顶点较近的节点。
实现示例(邻接表)
#include <stdio.h>
#include <stdlib.h>
// 定义邻接表节点
typedef struct AdjListNode {
int dest;
struct AdjListNode* next;
} AdjListNode;
// 定义邻接表
typedef struct AdjList {
AdjListNode* head;
} AdjList;
// 定义图
typedef struct Graph {
int numVertices;
AdjList* array;
} Graph;
// 队列节点
typedef struct QueueNode {
int data;
struct QueueNode* next;
} QueueNode;
// 队列
typedef struct Queue {
QueueNode* front;
QueueNode* rear;
} Queue;
// 创建新邻接表节点
AdjListNode* createAdjListNode(int dest) {
AdjListNode* newNode = (AdjListNode*)malloc(sizeof(AdjListNode));
newNode->dest = dest;
newNode->next = NULL;
return newNode;
}
// 初始化图
Graph* initializeGraph(int numVertices) {
Graph* graph = (Graph*)malloc(sizeof(Graph));
graph->numVertices = numVertices;
graph->array = (AdjList*)malloc(numVertices * sizeof(AdjList));
for (int i = 0; i < numVertices; i++)
graph->array[i].head = NULL;
return graph;
}
// 添加边
void addEdgeList(Graph* graph, int src, int dest) {
AdjListNode* newNode = createAdjListNode(dest);
newNode->next = graph->array[src].head;
graph->array[src].head = newNode;
}
// 打印图
void printGraph(Graph* graph) {
for (int v = 0; v < graph->numVertices; v++) {
AdjListNode* temp = graph->array[v].head;
printf("顶点 %d:", v);
while (temp) {
printf(" -> %d", temp->dest);
temp = temp->next;
}
printf("\n");
}
}
// 创建队列节点
QueueNode* createQueueNode(int data) {
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 初始化队列
void initializeQueueStruct(Queue* q) {
q->front = q->rear = NULL;
}
// 检查队列是否为空
int isQueueEmptyStruct(Queue* q) {
return q->front == NULL;
}
// 入队
void enqueueStruct(Queue* q, int data) {
QueueNode* temp = createQueueNode(data);
if (q->rear == NULL) {
q->front = q->rear = temp;
return;
}
q->rear->next = temp;
q->rear = temp;
}
// 出队
int dequeueStruct(Queue* q) {
if (isQueueEmptyStruct(q)) {
printf("队列为空,无法出队\n");
exit(1);
}
QueueNode* temp = q->front;
int data = temp->data;
q->front = q->front->next;
if (q->front == NULL)
q->rear = NULL;
free(temp);
return data;
}
// BFS实现
void BFS(Graph* graph, int startVertex) {
int* visited = (int*)malloc(graph->numVertices * sizeof(int));
for (int i = 0; i < graph->numVertices; i++)
visited[i] = 0;
Queue q;
initializeQueueStruct(&q);
visited[startVertex] = 1;
enqueueStruct(&q, startVertex);
printf("BFS遍历(从顶点 %d 开始): ", startVertex);
while (!isQueueEmptyStruct(&q)) {
int currentVertex = dequeueStruct(&q);
printf("%d ", currentVertex);
AdjListNode* temp = graph->array[currentVertex].head;
while (temp) {
int adjVertex = temp->dest;
if (!visited[adjVertex]) {
visited[adjVertex] = 1;
enqueueStruct(&q, adjVertex);
}
temp = temp->next;
}
}
printf("\n");
free(visited);
}
int main() {
int numVertices = 5;
Graph* graph = initializeGraph(numVertices);
addEdgeList(graph, 0, 1);
addEdgeList(graph, 0, 4);
addEdgeList(graph, 1, 2);
addEdgeList(graph, 1, 3);
addEdgeList(graph, 1, 4);
addEdgeList(graph, 2, 3);
addEdgeList(graph, 3, 4);
printGraph(graph);
BFS(graph, 0); // 从顶点0开始BFS
return 0;
}
输出结果
顶点 0: -> 4 -> 1
顶点 1: -> 4 -> 3 -> 2
顶点 2: -> 3
顶点 3: -> 4
顶点 4:
BFS遍历(从顶点 0 开始): 0 1 4 2 3
图的常用算法
1. 深度优先搜索(DFS)
DFS用于遍历或搜索图中的节点,从一个起始节点出发,尽可能深地探索每个分支。
实现步骤
-
从起始节点开始,访问节点并标记为已访问。
-
对每个相邻未访问的节点,递归进行DFS。
应用
-
查找连通分量
-
拓扑排序
-
检测环
2. 广度优先搜索(BFS)
BFS用于遍历或搜索图中的节点,从一个起始节点开始,先访问邻近节点,再逐层向外扩展。
实现步骤
-
从起始节点入队并标记为已访问。
-
反复从队列中取出节点,访问所有未访问的邻接节点,并将其入队。
应用
-
查找最短路径(在无权图中)
-
层次遍历
-
网络广播
3. 最短路径算法
用于计算图中两个节点之间的最短路径。常见算法包括Dijkstra算法、Bellman-Ford算法、Floyd-Warshall算法等。
示例:Dijkstra算法(未在此详述,可根据需要进一步扩展)
4. 最小生成树(MST)
用于找到图中的子集,使得包含所有顶点且没有环的边的总权重最小。常用算法包括Prim算法和Kruskal算法。
示例:Kruskal算法(未在此详述,可根据需要进一步扩展)
完整示例程序
下面是一个完整的示例程序,展示了如何使用邻接表表示无向图,并执行DFS和BFS遍历操作。
#include <stdio.h>
#include <stdlib.h>
// 定义邻接表节点
typedef struct AdjListNode {
int dest;
struct AdjListNode* next;
} AdjListNode;
// 定义邻接表
typedef struct AdjList {
AdjListNode* head;
} AdjList;
// 定义图
typedef struct Graph {
int numVertices;
int isDirected;
AdjList* array;
} Graph;
// 队列节点
typedef struct QueueNode {
int data;
struct QueueNode* next;
} QueueNode;
// 队列
typedef struct Queue {
QueueNode* front;
QueueNode* rear;
} Queue;
// 创建新邻接表节点
AdjListNode* createAdjListNode(int dest) {
AdjListNode* newNode = (AdjListNode*)malloc(sizeof(AdjListNode));
newNode->dest = dest;
newNode->next = NULL;
return newNode;
}
// 初始化图
Graph* initializeGraph(int numVertices, int isDirected) {
Graph* graph = (Graph*)malloc(sizeof(Graph));
graph->numVertices = numVertices;
graph->isDirected = isDirected;
graph->array = (AdjList*)malloc(numVertices * sizeof(AdjList));
for (int i = 0; i < numVertices; i++)
graph->array[i].head = NULL;
return graph;
}
// 添加边
void addEdgeList(Graph* graph, int src, int dest) {
if (src >= graph->numVertices || dest >= graph->numVertices) {
printf("顶点编号超出范围\n");
return;
}
// 添加从src到dest的边
AdjListNode* newNode = createAdjListNode(dest);
newNode->next = graph->array[src].head;
graph->array[src].head = newNode;
// 如果是无向图,则添加从dest到src的边
if (!graph->isDirected) {
newNode = createAdjListNode(src);
newNode->next = graph->array[dest].head;
graph->array[dest].head = newNode;
}
}
// 打印图
void printGraph(Graph* graph) {
for (int v = 0; v < graph->numVertices; v++) {
AdjListNode* temp = graph->array[v].head;
printf("顶点 %d:", v);
while (temp) {
printf(" -> %d", temp->dest);
temp = temp->next;
}
printf("\n");
}
}
// 创建队列节点
QueueNode* createQueueNode(int data) {
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 初始化队列
void initializeQueueStruct(Queue* q) {
q->front = q->rear = NULL;
}
// 检查队列是否为空
int isQueueEmptyStruct(Queue* q) {
return q->front == NULL;
}
// 入队
void enqueueStruct(Queue* q, int data) {
QueueNode* temp = createQueueNode(data);
if (q->rear == NULL) {
q->front = q->rear = temp;
return;
}
q->rear->next = temp;
q->rear = temp;
}
// 出队
int dequeueStruct(Queue* q) {
if (isQueueEmptyStruct(q)) {
printf("队列为空,无法出队\n");
exit(1);
}
QueueNode* temp = q->front;
int data = temp->data;
q->front = q->front->next;
if (q->front == NULL)
q->rear = NULL;
free(temp);
return data;
}
// DFS递归实现
void DFSUtil(Graph* graph, int v, int visited[]) {
visited[v] = 1;
printf("%d ", v);
AdjListNode* temp = graph->array[v].head;
while (temp) {
if (!visited[temp->dest])
DFSUtil(graph, temp->dest, visited);
temp = temp->next;
}
}
// DFS主函数
void DFS(Graph* graph, int startVertex) {
int* visited = (int*)malloc(graph->numVertices * sizeof(int));
for (int i = 0; i < graph->numVertices; i++)
visited[i] = 0;
printf("DFS遍历(从顶点 %d 开始): ", startVertex);
DFSUtil(graph, startVertex, visited);
printf("\n");
free(visited);
}
// BFS实现
void BFS(Graph* graph, int startVertex) {
int* visited = (int*)malloc(graph->numVertices * sizeof(int));
for (int i = 0; i < graph->numVertices; i++)
visited[i] = 0;
Queue q;
initializeQueueStruct(&q);
visited[startVertex] = 1;
enqueueStruct(&q, startVertex);
printf("BFS遍历(从顶点 %d 开始): ", startVertex);
while (!isQueueEmptyStruct(&q)) {
int currentVertex = dequeueStruct(&q);
printf("%d ", currentVertex);
AdjListNode* temp = graph->array[currentVertex].head;
while (temp) {
int adjVertex = temp->dest;
if (!visited[adjVertex]) {
visited[adjVertex] = 1;
enqueueStruct(&q, adjVertex);
}
temp = temp->next;
}
}
printf("\n");
free(visited);
}
int main() {
int numVertices = 5;
Graph* graph = initializeGraph(numVertices, 0); // 创建一个无向图
addEdgeList(graph, 0, 1);
addEdgeList(graph, 0, 4);
addEdgeList(graph, 1, 2);
addEdgeList(graph, 1, 3);
addEdgeList(graph, 1, 4);
addEdgeList(graph, 2, 3);
addEdgeList(graph, 3, 4);
printGraph(graph);
DFS(graph, 0); // 从顶点0开始DFS
BFS(graph, 0); // 从顶点0开始BFS
return 0;
}
输出结果
顶点 0: -> 4 -> 1
顶点 1: -> 4 -> 3 -> 2 -> 0
顶点 2: -> 3 -> 1
顶点 3: -> 4 -> 1 -> 2
顶点 4: -> 1 -> 0 -> 3
DFS遍历(从顶点 0 开始): 0 1 2 3 4
BFS遍历(从顶点 0 开始): 0 1 4 2 3
图的优缺点
优点
-
灵活性高:可以表示多种关系和结构,如网络、社交关系、依赖关系等。
-
强大的表达能力:适用于复杂的关系和多种应用场景。
-
多种算法支持:丰富的图算法支持各种计算需求,如最短路径、连通性、匹配等。
缺点
-
空间复杂度高:尤其是邻接矩阵表示,对于稀疏图会浪费大量空间。
-
实现复杂:特别是对于复杂图算法,代码实现较为复杂。
-
难以可视化:大型图的可视化和理解较为困难。
图的变种与扩展
1. 有向无环图(DAG)
DAG是一种有向图,没有环路。广泛应用于任务调度、项目管理、表达式解析等。
2. 权重图
边具有权重,表示连接顶点之间的成本、距离等。常用于路径规划、网络优化等。
3. 多重图
允许多个边连接同一对顶点。常用于表示多种不同类型的关系。
4. 稀疏图与稠密图
-
稀疏图(Sparse Graph):边数远小于最大边数,通常使用邻接表表示。
-
稠密图(Dense Graph):边数接近最大边数,通常使用邻接矩阵表示。
图的应用场景
-
社交网络分析:表示用户和好友关系,分析社交圈、影响力等。
-
地图与导航:表示地理位置和道路网络,计算最短路径。
-
网络路由:表示网络节点和连接,优化数据传输路径。
-
任务调度与依赖管理:表示任务及其依赖关系,优化执行顺序。
-
编译器设计:表示语法规则和依赖关系,进行语法分析。
-
推荐系统:表示用户、物品和交互关系,进行个性化推荐。