您的位置:首页 > 其它

Introduction to graphs and their data structures Section I[翻译]

2007-04-03 17:57 579 查看
Introduction to graphs and their data structures
Section 1
【原文见: http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=graphsDataStrucs1
作者: By gladius
Topcoder Member
翻译: 农夫三拳@seu(drizzlecrj@gmail.com)

Introduction
Recognizing a graph problem
Representing a graph and key concepts
Singly linked lists
Trees
Graphs
Array representation


Introduction
图是程序世界中一个基础数据结构,当然在TopCoder中也少不了。它通常作为Division2中的难题或者Division1中的中等或者难题出现,且有解决它的许多不同方法。它们的难度可以从一个2D的网格中从开始点到结束点查找一条路径到一些较难的问题如你可以安排一些有最大容量的管子来找到最大流量的水(它们就是我们稍后要谈到的最大流-最小割问题)。在图论问题中选择正确的数据结构是至关重要的。一个看起来很棘手的问题在使用适当的数据结构表示时可以使用几行搞定。幸运的是,TopCoder中使用的语言中的标准库给了我们很大帮助!

Recognizing a graph problem
解决图论问题的第一个关键点就是认识到它是一个图论问题。问题听起来可能很复杂,因为问题的作者并不总是直接告诉你。几乎所有的题目都会在问题中使用一个网格或者网络,但有时候它们会伪装起来。其次,如果你需要查找某类路径时,这通常也是个图论问题。一些与图论相关的关键字是:顶点,节点,边,连接,连通性,路径,环和方向。具有这些特征的一个简单问题描述如下:

“Bob在他家附近迷路了。他需要从当前位置返回到家中。Bob家的附近是一个二维的网格,以(0,0)开始,(width - 1, height -1)结束。bob可以毫不费力气的走过一些空地,但不能经过房子。Bob只能水平或者竖直的幂次移动一个方格。 Bob的初始位置以'B'表示,房子的未知用'H'表示。网格中的空地用'.'表示,房屋以'X'表示,找到Bob能够回家需要走的最少步数,如果Bob不能到家,返回-1。

一个宽度为7并且长度为5网格的样例如下:

X..B
.X.X.XX
.H..
X
..X.


Representing a graph and key concepts
图可以表示许多不同类型的系统,从一个二维的网格(像上面问题描述的一样)到一个internet上的用来显示从计算机A到计算机B数据需要传输的时间的映射。我们首先需要定义一个图包含哪些部分。事实上,只有两个,结点和边。一个结点(或者顶点)是图当中的一个离散位置。一个边(或者连接)是一个可以要么有向或者无向,并且包含一个耗费值的连接两个顶点的链接。一条无向边意味着你在这条边上访问的方向没有限制。因此比方说,如果从A到B有一条无向边,你可以从A移动到B或者从B移动到A。一个有向边只运行从一个方向进行访问,因此如果从A到B有一个有向边的话,你可以从A访问到B,但是不可以从B访问到A。一个思考边和顶点的简单方法是把边看作给定两个顶点返回耗费的函数。我们稍后将看到这个方法的一个例子。

图的数学描述中,图G={V,E}被定义中一系列的顶点V和一些边的集合(不用必须是一个集合)E。一个边可以被定义成(u,v),这里的u和v都是V的元素。在这点上有一些有用的技术词汇:

Order=图当中的顶点数量 Size-图当中的边的数量

Singly linked lists
最简单的图的一个例子就是一个单链表!现在我们可以开始看到图的数据结构的力量了,因为它不但可以表示非常复杂的关系,还可以表示简单的链表。

一个单链表有一个“头”结点,并且每一个结点都有一个指向后继结点的链接。因此结构看起来像这样:

structure node
[node] link;
[data]
end

node head;

一个简单的例子像这样:

node B, C;
head.next = B;
B.next = C;
C.next = null;
可以图形化的表示成head->B->C->null。这里我使用null代表链表的结束。

回到费用函数的概念上,我们的费用函数看起来像下面一样:

cost(X, Y) := if (X.link = Y) return 1;
else if (X = Y) return 0;
else "Not possible"
这个费用函数中我们只可以从我们的当前结点到达直接连接的结点。要习惯费用函数,因为在任何时候当你碰到一个图论问题时,你会和它们的各种形式打交道!现在你可能会问一个问题“等等,从A到C的费用会返回不可能,而我们可以通过B从C到A啊!”这时一个非常合理的看法,但是费用函数只是简单的对从一个结点 *直接* 到达另一个的费用进行处理。我们稍后将涉及在一个一般的图上找出距离。

现在我们已经看到了一个简单类型的图的例子了,下面我们将会转向一个更加复杂的例子。

Trees
这节讲的都是树。我们将很简要的概括它们,作为完全了解图的一个铺垫。在我们上面所列举的例子中,我们稍微限制了可以表示的数据的类型。例如,如果你想构建一个家族树(从每个孩子开始,孩子和父母间有着层次关系),你不可能为每一个孩子存储多于一对的父母。因此我们很显然的需要一种新的数据类型。我们新的结点结构看起来像下面一样:

structure node
[node] mother, father;
[string] name
end

node originalChild;

费用函数:

cost(X, Y) := if ((X.mother = Y) or (X.father = Y)) return 1;
else if (X = Y) return 0;
else "Not possible"
这里我们可以看到每一个结点都有一个母亲和父亲。因此由于结点是一个递归定义的结构,每一个母亲有母亲和父亲,并且每一个父亲有母亲和父亲,如此下去。这里的一个问题是如果你在计算机中表示这个数据结构,也许会形成一个环路。而一棵树显然不能有环路。稍微动下脑筋问题就使得问题简单了:一个孩子的父亲同样是那个孩子的儿子吗? 我开始头痛了。因此我们在构建一棵树的时候要确保它确实是一个树结构,而不是一个一般的图。树的一个标准定义是,连通的无环图。这意味着图中没有环路并且每一个图至少和图中的其他结点连接。

另外一个需要注意的事情是我们可以想象这样一个情形,一棵树需要引用多于两个的结点,例如在一家层次管理机构,你可以用一个管理很多人的经理和一个管理经理的CEO。我们上面的例子【译者注:指的是孩子,父母亲的例子】其实是二叉树,因为它仅有两个节点引用。下面我们将讨论可以表示一个一般图的数据结构!

Graphs
树当中只允许结点有子结点,并且不能有环路,而在一个更加一般的图中我们可以表示许多不同情形。一个常见的例子就是城市间的航班路径。如果城市A和城市B有一个航班,那么两个城市间有一条边。边的耗费可以是航班所花费的时间或者所需要使用的燃料。

我们表示这个的方法是使用一个结点(定点),它包含指向其他结点的链接和结点相关的数据。因此以我们的航班路径作为例子,我们可以把机场的名字作为结点数据域,并且对于一个离开城市的航班我们有一个指向目的地的邻接元素。

structure node
[list of nodes] neighbors
[data]
end

cost(X, Y) := if (X.neighbors contains Y) return X.neighbors[Y];
else "Not possible"

list nodes;

这时表示图的一个一般方法。它允许我们具有从一个结点到另外一个结点的多重边并且它是一个图表示的精简表示。然后它通常比其他表示(如我们下面讨论的数组方法)难以使用。

Array representation
把图用一系列的结点表示是一个很好的方法。但是通常在TopCoder中我们在一些看起来很简单问题上有限制。通常我们的图都相对很小,只有少量的结点和边。这种情况下我们可以使用不同类型的更容易工作的数据结构。

基本的方法是构造一个2维整型数组,第i行,第j列的元素代表从结点i到结点j的费用。如果从i到j的连接不存在,我们可以使用一些类型的标记值(通常是一个非常大或者非常小的数,如-1或者整数的最大值)。这种类型的数据结构的另外一个好处是我们可以非常简单的表示有向和无向图。

因此,例如,下面的邻接矩阵

A B C
A 0 1 5
B -1 0 1
C -1 -1 0

将意味着和自身的连接权重为0,与B的连接权重为1而和C的连接权重为5。另一方面,结点B与结点A不连接,和自身的连接权重为0,并且和C的连接权重为1。结点C和其他结点都不连接。如果我们画出这个图的话,将像下面一样:



这种表示不仅对于结点间不拥有多重边的图非常方便,还能允许我们简化图上面的操作。

...继续第二节
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐