文章目录
一、图的介绍
图是一种比较复杂的数据结构,在线性表中数据元素之间仅有线性关系,每个元素只有一个直接前驱和直接后继(元素之间只存在一对一关系),在树形结构中元素之间有着明显的层次关系,每一层的元素只能和下层的多个元素有关系(元素之间存在一对多关系),而在图形结构中,任意两个结点之间都可能有关系(元素之间存在多对多关系)。
二、图的定义和术语
顶点:图中的数据元素被称为顶点,一般使用V表示图的顶点的有穷非空集合。
弧: 两个顶点之间的关系记作<v,w>,表示能从顶点v到达顶点w,也就是v能到w,但w不一定能到v,我们称v为弧尾或初始点,称w为弧头或终端点。
有向图: 由弧+顶点构成的图叫有向图,也就是顶点之间是单行道。
边: 两个顶点之间的关系记录(v,w),表示既能从v到w,也能从w到v,我们称v和w之间的关系是一条边。
无向图: 由边+顶构成的图有无向图,顶点之间的双行道。
注意: 一般使用G代表图,V顶点的集合,VR代表弧或边的集合,n代表顶点的数目,e代表边或弧的数目,我们不讨论顶点到自己的边或弧。
完全图: 在无向图中,e的取值范围是0 ~ n/2(n-1),如果无向图的边的数量达到最大值这种无向图称称为完全图。
有向完全图: 在有向图中,e的取值范围是0 ~ n(n-1),如果有向图的弧的数量达到最大值这种有向图称称为有向完全图。
稀疏图和稠密图: 如果图中的边和弧很少,e<nlogn 这种图被称为稀疏图,反之称为稠密图。
权和网: 如果图中的顶点到另一个顶点需要代价(距离或耗费),那么在表示边或弧的时候需要附加数据,附加的数据就叫做权,带权的图通常被称为网,这也是互联网的由来。
子图: 假定有两个图G1和G2,如果G1的顶点集合是G2的顶点集合的子集,且G1的边或弧集合是G2的边或弧集合的子集,则称G1是G2的子图。
邻接点: 在无向图中如果有一条边(v,w),则v,w两个顶点互为邻接点,即v和w相邻接,边(v,w)依附于顶点v,w,或者说(v,w)和顶点v、w相关联。
顶点v的度: 在无向图中与顶点v相关联的边的数量。
顶点v的入度和出度: 在有向图中,以顶点v作为弧头的弧的数量称为顶点的入度,以顶点v作为弧尾的弧的数量称为顶点的出度。
路径: 从顶点v到达顶点w所经历的顶点序叫做路径,路径的长度就是边或弧的数目。
回路或环: 起始点和终点相同的路径称为回路或环。
简单路径: 路径中顶点不重复出现的路径称为简单路径。
简单回路或简单环: 起始点和终点相同且其余顶点不重复出现,被称为简单回路或简单环。
连通图: 在无向图中,从顶点v到顶点w有路径,则称v和w是连通的,如果图中任意两个顶点都是连通的,则称图为连通图。
连通分量: G1和G2都是连通图,且G1是G2的子图,则称G1是G2的连通分量或极大连通子图。
强连通图: 在有向图中,如果任意一对顶点都双向连通,则称图为强连通图。
强连通分量: G1和G2都是强连通图,且G1是G2的子图,则称G1是G2的强连通分量或极大强连通子图。
三、图的存储结构:
邻接矩阵:
使用两个数组来存储数据元素(顶点)和数据元素之间的关系(边或弧)的信息。
一维数组:用于存储顶点。
二维数组:用于存储弧或边。
邻接矩阵的优点:
访问速度快,方便计算结点的度、入度、出度。
邻接矩阵的缺点:
顶点的容量有限,扩容非常麻烦,只适合存储稠密图,存储稀疏图时会有大量的内存浪费。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 无向图
typedef struct Grahh
{
size_t cal; // 顶点容量
size_t cnt; // 顶点数量
char *vertex; // 存储顶点的一维数组
char **edge; // 存储边的二维数组
} Graph;
Graph *create_graph(size_t cal)
{
Graph *graph = malloc(sizeof(Graph));
graph->vertex = malloc(sizeof(char) * cal);
graph->cal = cal;
graph->cnt = 0;
graph->edge = malloc(sizeof(char *) * cal);
for (int i = 0; i < cal; i++)
{
graph->edge[i] = calloc(sizeof(char), cal);
}
return graph;
}
bool add_vertex_graph(Graph *graph, char vertex)
{
if (graph->cnt >= graph->cal)
return false;
for (int i = 0; i < graph->cnt; i++)
{
if (graph->vertex[i] == vertex)
return false;
}
graph->vertex[graph->cnt++] = vertex;
return true;
}
bool add_edge_graph(Graph *graph, char v1, char v2)
{
int v1_index = -1, v2_index = -1;
for (int i = 0; i < graph->cnt; i++)
{
if (graph->vertex[i] == v1)
v1_index = i;
if (graph->vertex[i] == v2)
v2_index = i;
}
if (-1 == v1_index || -1 == v2_index || v1_index == v2_index)
return false;
graph->edge[v1_index][v2_index] = 1;
graph->edge[v2_index][v1_index] = 1;
return true;
}
// 查询顶点在一维数组中的位置,如果不存在则返回-1
int query_vertex_graph(Graph *graph, char vertex)
{
for (int i = 0; i < graph->cnt; i++)
{
if (vertex == graph->vertex[i])
return i;
}
return -1;
}
// 查询顶点第一个邻接点,如果不存在则返回'\0'
char first_vertex_graph(Graph *graph, char vertex)
{
int index = query_vertex_graph(graph, vertex);
if (-1 == index)
return '\0';
for (int i = 0; i < graph->cnt; i++)
{
if (1 == graph->edge[index][i])
return graph->vertex[i];
}
return '\0';
}
// 删除顶点
bool del_vertex_graph(Graph *graph, char vertex)
{
int index = query_vertex_graph(graph, vertex);
if (-1 == index)
return false;
for (int i = 0; i < graph->cnt; i++)
{
graph->edge[index][i] = 0;
graph->edge[i][index] = 0;
}
return true;
}
void _dfs_graph(Graph *graph, int index, bool *flags)
{
if (!flags[index])
return;
printf("%c\n", graph->vertex[index]);
flags[index] = false;
for (int i = 0; i < graph->cnt; i++)
{
if (1 == graph->edge[index][i])
_dfs_graph(graph, i, flags);
}
}
// 深度优先遍历,与树的前序遍历类似
void dfs_graph(Graph *graph)
{
bool flags[graph->cnt];
for (int i = 0; i < graph->cnt; i++)
flags[i] = true;
for (int i = 0; i < graph->cnt; i++)
_dfs_graph(graph, i, flags);
}
// 广度优先遍历,与树的层序遍历一样,需要队列结构配合
void bfs_graph(Graph *graph)
{
bool flags[graph->cnt];
for (int i = 0; i < graph->cnt; i++)
flags[i] = true;
int queue[graph->cnt