引言
图是离散数学中一个重要的数据结构,广泛应用于计算机科学和其他领域。在实际应用中,经常需要处理有向图和有向带权图,这就需要一种高效的图的存储方法。十字链表存储法是一种常用的表示有向图和有向带权图的数据结构,它结合了邻接表和逆邻接表,使得查找入边和出边的操作更加高效。
十字链表存储法的概念和原理
在十字链表中,每个顶点对应一个顶点结点,下图为顶点结点的结构图
data:是顶点存储的数据
firstIn:指向此顶点的第一条入边
firstOut:指向此顶点的第一条出边
所以,顶点结点只存储它的顶点数据,和它的一条入边和一条出边
在十字链表中,每条边对应一个边结点,下图为边结点的结构图
tailVex:这条边的边尾(没有箭头)顶点的数组下标,相当于标识它的边尾顶点
headVex:这条边的边头(有箭头)顶点的数组下标,相当于标识它的边头顶点
这样就确定了这条边的头和尾了
headLink:指向边头与此边的边头相同的一条边
tailLink:指向边尾与此边的边尾相同的一条边
weight:边的权
这样就包含了所有边的信息,顶点和边的信息都能够表示,所以十字链表能够表示一个图
理解了十字链表的数据结构和它的概念及原理,那么对图的操作就很简单了,其实就是对链表和指针的操作
十字链表存储法的优点
高效查找:十字链表存储法使得查找某个顶点的出边和入边的操作变得高效,时间复杂度为O(d),其中d为该顶点的度数。
节省空间:相比邻接矩阵法,十字链表存储法在空间上更加节省,尤其在稀疏图的情况下,可以大大减少存储空间。
支持有向带权图:十字链表存储法不仅适用于有向图,还可以方便地表示有向带权图,为实际应用提供了更多的可能性。
十字链表数据结构的定义
下面用C语言实现十字链表的数据结构
#define MAX_VERTICES 100
typedef int VertexDataType;
// 定义边结构
typedef struct Edge {
int tailVex; // 边的起点,顶点的数组下标
int headVex; // 边的终点,顶点的数组下标
int weight; // 边的权重(如果是有权图)
Edge* headLink; // 指向下一条具有相同起点的边的指针
Edge* tailLink; // 指向下一条具有相同终点的边的指针
} Edge;
// 定义顶点结构
typedef struct Vertex {
VertexDataType data; // 顶点数据
Edge* firstOut; // 指向第一个出边的指针
Edge* firstIn; // 指向第一个入边的指针
} Vertex;
// 定义图的结构
typedef struct Graph {
int num_vertices; // 图中顶点的数量
int num_edges; // 图中边的数量
Vertex vertices[MAX_VERTICES]; // 存储顶点的数组
} Graph;
基本操作
// 初始化图
void initGraph(Graph* graph, int num_vertices) {
graph->num_vertices = num_vertices;
graph->num_edges = 0;
for (int i = 0; i < num_vertices; ++i) {
graph->vertices[i].data = i; // 以顶点索引作为顶点数据
graph->vertices[i].firstOut = NULL; // 初始化顶点的出边链表为空
graph->vertices[i].firstIn = NULL; // 初始化顶点的入边链表为空
}
}
// 添加一条边
void addEdge(Graph* graph, int tail, int head, int weight) {
// 创建新的边节点
Edge* newEdge = (Edge*)malloc(sizeof(Edge));
newEdge->tailVex = tail;
newEdge->headVex = head;
newEdge->weight = weight;
newEdge->headLink = graph->vertices[head].firstIn; // 新边的headLink指向终点为head的第一个入边
newEdge->tailLink = graph->vertices[tail].firstOut; // 新边的tailLink指向起点为tail的第一个出边
// 更新顶点的出边链表和入边链表
graph->vertices[head].firstIn = newEdge; // 将新边添加到终点为head的入边链表的头部
graph->vertices[tail].firstOut = newEdge; // 将新边添加到起点为tail的出边链表的头部
// 边数量加1
graph->num_edges++;
}
// 获取顶点的入度
int getInDegree(Graph* graph, int vertex) {
int in_degree = 0;
Edge* edge = graph->vertices[vertex].firstIn;
while (edge != NULL) {
in_degree++;
edge = edge->headLink;
}
return in_degree;
}
// 获取顶点的出度
int getOutDegree(Graph* graph, int vertex) {
int out_degree = 0;
Edge* edge = graph->vertices[vertex].firstOut;
while (edge != NULL) {
out_degree++;
edge = edge->tailLink;
}
return out_degree;
}
总结
十字链表存储法是一种高效表示有向图和有向带权图的数据结构。通过它,我们可以方便地查找某个顶点的出边和入边,同时节省存储空间。在实际应用中,十字链表存储法在处理有向图和有向带权图时表现出色,是一个值得掌握和应用的数据结构。
希望本篇博文能够帮助读者理解十字链表存储法,并在实际应用中发挥其优势,提高图相关问题的处理效率。