参考:Robert Sedgewick,《算法:C语言实现》第1章
问题描述
假设给定整数对的一个序列,其中每个整数表示某类型的一个对象,我们想要说明整数对p-q表示“p连接到q”。假设“连通”关系是可传递的:也就是说如果p和q之间连通,q和r之间连通,那么p和r也是连通的。我们的目标是写一个过滤集合中的无关对的程序。程序的输入为整数对p-q,如果已经看到的数对中并不隐含这p连通到q,那么就输出该对。如果前面输入大数对中隐含着p连通到q,那么程序应该忽略p-q,并应该继续输入下一对。如图1-1给出来一个输入输出示例。
我们的问题是设计能够记录足够多它所看见大数对信息的程序,并能够判断一个新的数对是否是连通的。非形式地,我们称设计这样一个算法的任务叫做连通性问题。
这里,我们首先假设有N个对象,每个对象都与0~N-1之间大一个整数名对应。
由图论的基本结果可以得出结论:所有N个对象是连通的,当且仅当连通算法输出的数对的个数恰好为N-1个。
努力确定算法的基本操作很重要,这使我们为连通性问题设计的算法可以用于许多类似大问题。确切的说,当我们得到一个新对时,我们必须首先确定它是否表示一个新的连接,然后把已经看到的连接信息合并到已得到的对象的连通关系中,使得它能够检查将要看到的连接。我们把这两个任务封装成“查找”和“合并”两个抽象操作,根据这两个操作很容易求解连通性问题。
在从输入中读取一个新的整数对p-q后,对于数对中的每一个数执行查找操作,如果对的成员在同一个集合中,说明它们是连通的,那么考虑下一对;如果它们不在同一个集合中,则执行合并操作,并输出这个对。
1
合并-查找算法
开发求解给定问题高效算法大过程中,第一步是实现这个问题的一个简单算法。如果我们需要解决几个容易的特定问题的实例,那么,简单实现就能完成这项工作。如果要用更复杂的算法,简单实现可以用于检查小规模例子的正确性,并成为评估算法性能的一个基准。我们总是关注算法的效率,但我们在开发解决问题的第一个程序时,更关注的是确保程序的正确性。
首先考虑如何存储所有输入对,然后写一个遍历这些输入的函数,然后检查下一个数对是否是连通的。我们会选用另外一种方法。首先,实际应用中数对的个数可能很大,不能把它们全部放在内存中。其次,更重要的是,即使我们能够把它们放在内存中,也没有一种简单大方法能够由连接关系集合很快地确定两个对象是否连通。在这里我们首先考虑一种简单大方法,因为它们可以求解难度更小大问题,且这些方法不要求村村所有对,因而是更高效的方法。这些方法利用整数数组,每个整数对应一个对象,用于保存实现合并和查找操作时所需要大必要信息。
程序1.1是求解连通性问题大“快速-查找算法”(quick-find algorithm)的一种简单实现。算法的基础是一个整数数组,当且仅当第p个元素值与第q个元素的值相等时,说明p和q是连通的。初始化时,先设置第i个元素的值为i,其中0<=i<=N。为了实现p与q的合并操作,我们遍历数组,把所有取值与p元素值相同的元素值改为q,即表示把新对象q加入到之前p对象所在的组(q作为这个组所有对象的新根节点)。同样,我们也可以选择另一种方式,把所有取值为q的元素值改为p,道理是一样的。
这个程序从标准输入读取小于N的非负整数对序列(数对p-q表示“把p所在连通集合中的所有对象连接到对象q”),并且输出还未连通的输入对。程序中使用数组id,每个元素表示一个对象,且具有如下性质,当且仅当p和q是连通的,id[p]=id[q]。为简化起见,定义N为编译时的常数。另一方面,也可以从输入得到它,并动态地分配id数组。