概要
介绍图的表示和图的搜索。图的搜索指的是系统化地跟随图中的边来访问图中的每个节点。图搜索算法可以用来发现图的结构。许多的图算法在一开始都会先通过搜索来获得图的结构,其他一些图算法则是对基本的搜索加以优化。可以说,图的搜素技巧是整个图算法领域的核心。
22.1图的表示
对于图G=(V,E),可以用两种标准表示方法表示。一种表示法将图作为邻接链表的组合,另一种是将图作为邻接矩阵来看待。两种表示方法都可以表示无向图,也可以表示有向图。
此处略过了矩阵的实现代码,先放邻接链表的基本构造代码:
static void Main(string[] args)
{
var graph = new Graph();
graph.ShowGraph();
}
public class Graph
{
public Node[] Adj;
public int Edges;
public Graph()
{
Console.WriteLine("结点个数:");
var count = Convert.ToInt32(Console.ReadLine());
Adj = new Node[count];
for (var i = 0; i < count; i++)
{
Adj[i] = new Node(i + 1);
}
Console.WriteLine("边数:");
Edges = Convert.ToInt32(Console.ReadLine());
for (var i = 0; i < Edges; i++)
{
int u, v;
Console.WriteLine("边" + (i + 1) + "的头结点为:" + (u = Convert.ToInt32(Console.ReadLine())));
Console.WriteLine("边" + (i + 1) + "的尾结点为:" + (v = Convert.ToInt32(Console.ReadLine())));
Insert(Adj, u - 1, v);
}
}
public void ShowGraph()
{
var count = Adj.Count();
Console.WriteLine("图共有" + count + "个结点");
Console.WriteLine("共有" + Edges + "条边:");
for (var i = 0; i < count; i++)
{
var x = Adj[i].next;
var u = Adj[i].value;
while (x != null)
{
Console.WriteLine(u + "->" + x.value);
x = x.next;
}
}
}
}
public class Node
{
public Node next;
public int value;
public Node(int value)
{
this.value = value;
next = null;
}
}
public static void Insert(Node[] adj, int index, int v)
{
if (adj != null)
{
var u = adj[index];
var vNode = new Node(v);
vNode.next = u.next;
u.next = vNode;
}
}
如果G是一个有向图,则对于边(u,v)来说,结点v将出现在链表Adj[u]里,因此所有链表长度的和等于|E|;如果G是一个无向图,长度之和为2|E|。但无论是有向还是无向,存储空间均为Θ(V+E)。
对邻接链表稍加修改,即可用来表示权重图。权重通常由一个函数ω:E→R的函数给出,我们可以将边(u,v)的权重存储在链表中。
邻接链表的一个潜在缺陷就是无法快速判断一条边(u,v)是否是图中的一条边,唯一的办法是在邻接链表Adj[u]里面搜索结点v。(当然矩阵表示就没有这个问题,但是却付出了消耗更大空间的代价。)
22.1练习
22.1-1 给定有向图的邻接链表,需要多长时间才能计算出每个结点的出度?
多长时间才能计算出每个结点的入度?
O(E)的时间能计算出出度。
O(V+E)的时间能计算出入度。
22.1-2 给定一棵有7个结点的完全二叉树的邻接链表,请给出等价的邻接矩阵表示。这里假设结点的编号为从1~7。
7个结点的完全二叉树有6条边,循环非叶子结点即可找到所有邻接矩阵的表示结点。
因此在77的矩阵中边为a[1,2]、[1,3]、[2,4]、[2,5]、[3,6]、[3,7]。
22.1-3 有向图GT=(V,E)的转置是图G = (V,ET),这里ET = {(u,v)} ∈VV:(u,v)∈E}。对于邻接链表和邻接矩阵两种表示,请给出从图G计算出GT的有效算法,并分析算法的运行时间。
邻接链表
/// <summary>
/// 翻转图G的所有边
/// </summary>
public void Revert()
{
var count = Adj.Length;
var adj_ = new Node[count];
for (var i = 0; i < count; i++)
{
adj_[i] = new Node(i + 1);
}
for (var i = 0; i < count; i++)
{
var x = Adj[i].next;
while (x != null)
{
Console.WriteLine(x.value + "->" + (i + 1));
Insert(adj_, x.value - 1, i + 1);
x = x.next;
}
}
}
对遍历V个结点的所有边进行翻转,也就是说每条边都进行了翻转,运行时间为O(V+E)。
邻接矩阵
这里就不放出代码了,主要是循环矩阵,对Adj[u,v]进行翻转得到Adj[v,u] = 1即可。
运行时间为O(V^2)。
22.1-4 给定多图G=(V,E)的邻接链表(多图是允许重复边和自循环边的图),请给出一个时间为O(V+E)的算法,用来计算改图的“等价”无向图G’ = (V,E’)的邻接链表表示。
public void UnDirected()
{
var count = Adj.Length;
var adj_ = new Node[count];
// d充当计数器
var d = new int[count + 1];
for (var i = 0; i < count; i++)
{
adj_[i] = new Node(i + 1);
d[i] = 0;
}
Console.WriteLine("等价无向图:");
for (var i = 0; i < count; i++)
{
var x = Adj[i].next;
while (x != null)
{
if (x.value != (i + 1)
&& d[x.value - 1] != (i + 1))
{
Console.WriteLine(x.value + "->" + (i + 1));
Insert(adj_, x.value - 1, i + 1);
d[x.value - 1] = i + 1;
}
x = x.next;
}
}
}