这里写自定义目录标题
十字链表(Orthogonal List)总结
概述
十字链表是一种专门用于存储有向图的数据结构,它巧妙地结合了邻接表的优点,通过双向链表的设计同时维护每个顶点的出弧和入弧信息。这种结构特别适合于需要频繁查询顶点入度和出度的应用场景。
数据结构定义
1. 弧结点结构 (ArcBox)
typedef struct ArcBox {
int tailvex; // 弧尾顶点的位置
int headvex; // 弧头顶点的位置
struct ArcBox* hlink; // 弧头相同的下一条弧
struct ArcBox* tlink; // 弧尾相同的下一条弧
InfoType* info; // 该弧相关信息的指针
} ArcBox;
2. 顶点结构 (VexNode)
typedef struct VexNode {
VertexType data; // 顶点信息
ArcBox* firstin; // 指向第一个入弧
ArcBox* firstout; // 指向第一个出弧
} VexNode;
3. 图结构 (OLGraph)
typedef struct {
VexNode xlist[MAX_VERTEX_NUM]; // 顶点数组
int vexnum; // 当前顶点数
int arcnum; // 当前弧数
} OLGraph;
结构示意图
基本结构图解
假设有一个简单的有向图:A→B,A→C,B→C,下面展示十字链表的存储结构:
有向图:
A ——→ B
| |
↓ ↓
C ←———┘
顶点数组 xlist:
┌─────┬─────┬─────┐
│ A │ B │ C │
├─────┼─────┼─────┤
│ in │ in │ in │ ← firstin指针
│ out │ out │ out │ ← firstout指针
└─────┴─────┴─────┘
0 1 2
详细链表结构
A的出弧链表(firstout): A→B, A→C
┌───┬───┬──────┬──────┬────┐ ┌───┬───┬──────┬──────┬────┐
│ 0 │ 1 │hlink │tlink │info│───→│ 0 │ 2 │hlink │tlink │info│─→ NULL
└───┴───┴──────┴──────┴────┘ └───┴───┴──────┴──────┴────┘
tail head tail head
A B A C
B的出弧链表(firstout): B→C
┌───┬───┬──────┬──────┬────┐
│ 1 │ 2 │hlink │tlink │info│───→ NULL
└───┴───┴──────┴──────┴────┘
tail head
B C
C的入弧链表(firstin): A→C, B→C
↗ hlink指向下一个以C为弧头的弧
┌───┬───┬──────┬──────┬────┐ ┌───┬───┬──────┬──────┬────┐
│ 0 │ 2 │hlink │tlink │info│───→│ 1 │ 2 │hlink │tlink │info│─→ NULL
└───┴───┴──────┴──────┴────┘ └───┴───┴──────┴──────┴────┘
tail head tail head
A C B C
完整的十字链表示意图
顶点表:
xlist[0] A: firstin=NULL, firstout=→弧(A→B)
xlist[1] B: firstin=→弧(A→B), firstout=→弧(B→C)
xlist[2] C: firstin=→弧(A→C), firstout=NULL
弧结点连接关系:
弧(A→B): tailvex=0, headvex=1, tlink=→弧(A→C), hlink=NULL
弧(A→C): tailvex=0, headvex=2, tlink=NULL, hlink=→弧(B→C)
弧(B→C): tailvex=1, headvex=2, tlink=NULL, hlink=NULL
可视化表示:
firstout tlink firstout tlink
A ┌─────────→ [A→B] ────→ [A→C] ────→ NULL
│ │ │
│ hlink hlink
│ ↓ ↓
firstin NULL ┌─[B→C]
↓ │ │
NULL │ hlink
│ ↓
B ┌─────────────────────┘ NULL
│ firstout
│ ↓
firstin ┌─[B→C]────→ NULL
↓ │ tlink
[A→B] │
│ │
hlink │
↓ │
NULL │
│
C │
│ │
firstin ←──┘
↓
[A→C]
│
hlink
↓
[B→C]
│
hlink
↓
NULL
核心概念解释
术语说明
| 中文术语 | 英文术语 | 含义 |
|---|---|---|
| 弧头 | Head | 有向边的终点,箭头指向的顶点 |
| 弧尾 | Tail | 有向边的起点,箭头发出的顶点 |
| 入弧 | In-arc | 指向某顶点的有向边 |
| 出弧 | Out-arc | 从某顶点发出的有向边 |
链表组织方式
十字链表的精妙之处在于每条弧同时存在于两个链表中:
- 出弧链表: 通过
tlink连接,将同一起点的所有弧串联 - 入弧链表: 通过
hlink连接,将同一终点的所有弧串联
主要操作详解
1. 图的创建 (CreateGraph)
Status CreateGraph(OLGraph* G) {
// 输入顶点数和弧数
scanf("%d %d", &G->vexnum, &G->arcnum);
// 初始化顶点信息
for (i = 0; i < G->vexnum; i++) {
scanf("%c", &G->xlist[i].data);
G->xlist[i].firstin = G->xlist[i].firstout = NULL;
}
// 构建十字链表
for (k = 0; k < G->arcnum; k++) {
scanf("%c %c", &v1, &v2);
i = LocateVex(*G, v1); // 弧尾
j = LocateVex(*G, v2); // 弧头
p = (ArcBox*)malloc(sizeof(ArcBox));
p->tailvex = i; p->headvex = j;
// 插入出弧链表
p->tlink = G->xlist[i].firstout;
G->xlist[i].firstout = p;
// 插入入弧链表
p->hlink = G->xlist[j].firstin;
G->xlist[j].firstin = p;
}
}
2. 弧的删除 (DeleteArc)
这是十字链表中最复杂的操作,需要同时从两个链表中删除结点:
Status DeleteArc(OLGraph* G, VertexType v, VertexType w) {
int i = LocateVex(*G, v); // 弧尾位置
int j = LocateVex(*G, w); // 弧头位置
// 第一步:从出弧链表中删除
ArcBox* p = G->xlist[i].firstout;
if (p->headvex == j) {
// 要删除的是第一个结点
G->xlist[i].firstout = p->tlink;
} else {
// 找到前驱结点
while (p->tlink && p->tlink->headvex != j) {
p = p->tlink;
}
if (p->tlink) {
ArcBox* q = p->tlink;
p->tlink = q->tlink;
p = q; // p指向要删除的结点
}
}
// 第二步:从入弧链表中删除同一结点
ArcBox* q = G->xlist[j].firstin;
if (q == p) {
// 要删除的是第一个结点
G->xlist[j].firstin = q->hlink;
} else {
// 找到前驱结点
while (q->hlink != p) {
q = q->hlink;
}
q->hlink = p->hlink;
}
// 第三步:释放内存
free(p);
G->arcnum--;
return OK;
}
3. 弧的插入 (InsertArc)
Status InsertArc(OLGraph* G, VertexType v, VertexType w) {
int i = LocateVex(*G, v); // 弧尾
int j = LocateVex(*G, w); // 弧头
// 创建新弧结点
ArcBox* p = (ArcBox*)malloc(sizeof(ArcBox));
p->tailvex = i;
p->headvex = j;
p->info = NULL;
// 插入出弧链表(头插法)
p->tlink = G->xlist[i].firstout;
G->xlist[i].firstout = p;
// 插入入弧链表(头插法)
p->hlink = G->xlist[j].firstin;
G->xlist[j].firstin = p;
G->arcnum++;
return OK;
}
优缺点分析
优点
- 高效的入度查询: 可以快速遍历某顶点的所有入弧
- 高效的出度查询: 可以快速遍历某顶点的所有出弧
- 空间效率: 相比邻接矩阵,对于稀疏图节省大量空间
- 操作灵活: 支持动态插入和删除弧
缺点
- 存储开销: 每条弧需要额外的指针域
- 实现复杂: 删除操作需要同时维护两个链表
- 内存碎片: 动态分配可能导致内存碎片化
应用场景
- 网络流算法: 需要同时考虑节点的入度和出度
- 拓扑排序: 需要快速查找入度为0的顶点
- 关键路径: 在AOE网中快速查找前驱和后继
- 图的遍历: 特别是需要逆向遍历的场景
时间复杂度分析
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 插入弧 | O(1) | 头插法,常数时间 |
| 删除弧 | O(度数) | 需要遍历链表查找 |
| 查找顶点的出弧 | O(出度) | 遍历出弧链表 |
| 查找顶点的入弧 | O(入度) | 遍历入弧链表 |
| 判断弧是否存在 | O(min(入度,出度)) | 遍历较短的链表 |
与其他图存储方式比较
| 特性 | 邻接矩阵 | 邻接表 | 十字链表 |
|---|---|---|---|
| 空间复杂度 | O(V²) | O(V+E) | O(V+E) |
| 查找弧 | O(1) | O(度数) | O(min(入度,出度)) |
| 插入弧 | O(1) | O(1) | O(1) |
| 删除弧 | O(1) | O(度数) | O(度数) |
| 求入度 | O(V) | 困难 | O(入度) |
| 求出度 | O(V) | O(出度) | O(出度) |
| 适用图类型 | 稠密图 | 稀疏图 | 有向稀疏图 |
编程注意事项
- 内存管理: 删除操作时要正确释放内存,避免内存泄漏
- 指针维护: 删除弧时必须同时更新两个链表的指针
- 边界检查: 操作前要验证顶点是否存在
- 初始化: 创建图时要正确初始化所有指针为NULL
总结
十字链表是一种专为有向图设计的精巧数据结构,它通过双向链表的巧妙组织,在空间效率和操作便利性之间找到了很好的平衡点。虽然实现相对复杂,但在需要频繁查询顶点入度和出度的应用场景中,十字链表展现出了显著的优势。
理解十字链表的关键在于把握其"一弧双链"的核心思想:每条弧既属于起点的出弧链表,也属于终点的入弧链表,这种设计使得有向图的许多操作都能高效完成。
511

被折叠的 条评论
为什么被折叠?



