对于一种特殊但是常见的有向图:每个点都有一条出边(出度为0)。我们想要掌握它的结构,怎么办呢?很容易发现,这样的图一定是这样的:
而我们单看某一个起点出发能到达的子图时,我们会发现这是一个‘ρ’形的图;即“一条链”接上“一个环”的形状。
当我们想要描述这个‘ρ’形图的结构时,自然便会引入三个参数:“环长”,“链长”,“链末端/环起点”。
如果空间足够我们糟蹋,那开个数组存储每个点是否被访问过,或者深度之类的信息,自然很好处理出这三个参数的值。但是能这样做的前提有二:一、点的数目不会大到我们的空间承受不了的程度(因为经过的点都必须记录下它的信息);二、点的编号映射到数组的成本能够承受(具体一点比如说点的编号不大)。
当我们在只有“比较两点是否是同一点”和“询问一个点的出边”这两种操作被允许的情景下,如何找到刚刚我们想求的参数呢?
设入环前,连的长度为\(m\),环长为\(n\)
只用一个指针(指用来指示点编号的变量),能记录的信息很有限。
那么考虑两个指针,以不同的速度从起点出发:一个指针(记为tortoise,慢指针)每次经过一个出边指向下一个点;而另一个指针(记为hare,快指针)每次经过两个出边指向下下个点。
这样一来,当慢指针从起点出发,经过\(m\)条边,刚刚进入环时;快指针已经经过了\(2m\)条边;
记环起点为\(0\)点,\(0\)点的出边到达的点为\(1\)点,接下来\(2\)点,\(3\)点,一直到\(n-1\)点,以此类推……
那么慢指针这时到达了0点,而快指针则到达了\((m \mod n)\)点,而快指针到慢指针的距离为\((-m \mod n)\);因为接下来慢指针每走一步,两个指针之间都会缩短距离\(1\),所以当快慢指针相遇时指针的位置正好会在\((-m\mod n)\)。
那么必然,慢指针再走m步,它便会回到0点;因为\((m+(-m)) \mod n \equiv 0\)
因此,我们只需要在两者相遇时把快指针放回起点,并把它的速度调整到和慢指针一致(都是每次经过一条出边)。那么m次之后,他们必然会相遇于环起点。(即\(0\)点)
记录期间指针的移动次数,便可以计算出m
这时候,一个指针不动,另一个指针绕环一周,通过记录相遇步数,便可以得出环的长度。