中国IT动力,最新最全的IT技术教程
最新100篇 | 推荐100篇 | 专题100篇 | 排行榜 | 搜索 | 在线API文档 | 网通镜像
首 页 | 程序开发 | 操作系统 | 软件应用 | 图形图象 | 网络应用 | 精文荟萃 | 教育认证 | 硬件维护 | 未整理篇 | 站长教程
ASP JS PHP工程 ASP.NET 网站建设 UML J2EESUN .NET VC VB VFP 网络维护 数据库 DB2 SQL2000 Oracle Mysql
服务器 Win2000 Office C DreamWeaver FireWorks Flash PhotoShop 上网宝典 CorelDraw 协议大全 网络安全 微软认证
硬件维护  CPU  主板  硬盘  内存  显卡  显示器  键盘鼠标  声卡音箱  打印机  机箱电源  BIOS  网卡  C#  Java  Delphi  vs.net2005
  当前位置:> 程序开发 > 编程语言 > C/C++
算法求解!如何判断一个单向链表是否有环路?
作者:未知 时间:2005-09-13 19:30 出处:ChinaUnix.net 责编:chinaitpower
              摘要:算法求解!如何判断一个单向链表是否有环路?

要求: 算法中使用的内存数量是一个常数, 即不能因为链表长度的增减使用的内存也增减. 
下面是本人的一个实现: 
struct list{ 
int data; 
struct list *next; 
}; 
int has_circle(struct list *head) 

struct list *cur1 = head; 
int pos1 = 0; 
while(cur1){ 
struct list *cur2 = head; 
int pos2 = 0; 
pos1 ++; 
while(cur2){ 
pos2 ++; 
if(cur2 == cur1){ 
if(pos1 == pos2) 
break; 
else 
return 1; //has circle 

cur2 = cur2->next; 

cur1 = cur1->next; 

return 0; 


问: 以上算法是否还可优化或者是否还有其他更好的算法? 
请各位大侠赐教!

 flw 回复于:2004-09-20 13:18:10
用两个指针,一个的步长为 1,另外一个的为 2,从表头开始一起往前走,如果相遇,表明有环路,否则就是没有了。

 FH 回复于:2004-09-20 13:48:38
楼上算法不错!

 cu_pang 回复于:2004-09-20 13:49:43
感谢楼上. 这是一个非常精彩的算法.
我想你的第二重境界快要达到了.

 flw 回复于:2004-09-20 13:55:59
多谢两位捧场!
这个算法不是我自创的,
而是我在 2001 年的时候,在这个版面看到过。
当时的无双、我都曾经受益。

BTW:这个算法,似乎是许多年前一个前辈高人所创,并非出自 ChinaUnix。

 FH 回复于:2004-09-20 14:10:47
呵呵,此人数学功力很深啊!

 飞灰橙 回复于:2004-09-20 15:12:06
不错!居然感受到一点乐趣。

 yutian 回复于:2004-09-20 15:20:07
高,服了

 FH 回复于:2004-09-20 15:41:16
[quote:7a8c2baf80="flw"]这个算法,似乎是许多年前一个前辈高人所创,并非出自 ChinaUnix。[/quote:7a8c2baf80]
这个算法中,选取两个互素的数作为步长即可。
大一些的步长的效率如何呢?我觉得好像稍微快一些,虽然差别微乎其微。
[code:1:7a8c2baf80]{
   int i;
   T *p = Head, *q = Head;

   while ( p != NULL ) {
      for ( i = 0; i < STEP1; i ++ ) {
         if ( ( p = p->next ) == NULL )
            return FALSE;
      }
      for ( i = 0; i < STEP2; i ++ ) {
         q = q->next;
      }
      if ( p == q ) {
         return TRUE;
      }
   }
   return FALSE;
}[/code:1:7a8c2baf80]

 飞灰橙 回复于:2004-09-20 15:48:44
[quote:1e7004e5ca="FH"]这个算法中...[/quote:1e7004e5ca]

老大,不大对吧!
如何能保证
1. while一定能退出。
2. q->next一定不会变成NULL ?

 yangtou 回复于:2004-09-20 16:15:04
不需要两个步长互素,只要步长不同就可以。

 yutian 回复于:2004-09-20 16:20:45
1,2的话,比较的次数平均是1.5n(假设是环)

互素的话,平均数是多少呢

 flw 回复于:2004-09-20 16:27:30
[quote:9b3148618f="FH"]这个算法中,选取两个互素的数作为步长即可。[/quote:9b3148618f]
这个反了吧?
互素的时候,应该是效率最低的时候吧?
因为对于两个互素的数来讲,单位范围内的公倍数太少了。

另外,大步长也不可取,
因为相遇的机会不如小步长。
尽管(少计算一次循环变量+少比较一次指针)。
但是如果“超过”了,
损失可就大了。

我凭感觉,1 和 2 应该是最好的。
因为 1 和 2 的时候,步长为 1 的指针只需要跑一圈,
步长为 2 的指针只需要跑 2 圈。

但是如果是别的步长,
可能需要好几圈才能相遇。
互素的时候,情形应该会更差。

 yangtou 回复于:2004-09-20 16:52:37
设步长分别为a和b,链表回环结点数为n,非环回环为m
设经过x次跨步,则只要ax和bx对n同余并且ax和bx都大于m就可以相遇(假设a>b)

ax-bx=pn
bx>m

得到:
x=pn/(a-b) > m/b(只需pn可整除(a-b))

指针移动次数为(a+b)x=(a+b)/(a-b)*pn

x的值只于(a-b)还有(b/m)有关,所以ab取值大反不好


不知道是不是想错了

 FH 回复于:2004-09-20 16:56:13
补充:
1.我的例子代码中忘了说明STEP1>STEP2。
2.我们没有假定所有的链都是有环的,步长大的话对于开放链能够更快地到达终点。
3.影响效率的因素不仅仅是指针的移动次数,还应该考虑指针比较的次数。
4.互素的条件好像是多余的,我还没仔细列式计算。楼上的方法不错,可惜不太全面。

 yangtou 回复于:2004-09-20 17:03:29
如果我上面的想法正确的化,应该和互素与否没有关系
不过ab的大小确实需要考虑,即使是回路的,也要考虑mn的大小,如果m
很大而n很小,那么ab取值有可能大一些好。

 yutian 回复于:2004-09-20 17:03:53
虽然更快到达终点,但只是对于开发的

如果是环的话,加大步长会增加移动的次数

 飞灰橙 回复于:2004-09-20 17:07:53
如果STEP1>STEP2,确实不会陷入死循环。

窃以为还是步长小点好。

 思一克 回复于:2004-09-20 17:16:09
讨论这问题了---
flw等讨论的方法是好方法。
可能还有不太好的,但也实用的方法-----仅仅用一个指针可否?

 FH 回复于:2004-09-20 17:28:01
假设步长分别为a、b,a>b,环外m个节点,环内n个节点。
经过x步重合,表明ax-bx=pn,bx>m。
从而x=pn/(a-b)>(m/b)
指针移动权重为u,次数为(a+b)x,指针判断权重为v,次数为ax,指针比较的权重为w,次数为x,
所以总耗时T=pn(au+bu+av+w)/(a-b)。
这里p,a,b都是变量,(ka+b+l)/(a-b)可以充分接近于k,就是a和b的差充分大时。
另外,由bx>m可知,b较大时x的最小值可能会较小。

 flw 回复于:2004-09-20 17:28:20
一个指针的话,则必须留下脚印才行,但是这样就把数据弄脏了。
不可取。

 FH 回复于:2004-09-20 17:36:37
[quote:d30e5841d8="flw"]一个指针的话,则必须留下脚印才行,但是这样就把数据弄脏了。
不可取。[/quote:d30e5841d8]
同意!

 win_hate 回复于:2004-09-20 18:50:29
这个问题应该有很长历史了,《C 专家编程》的附录,程序员工作面试的秘密就提到过这个问题。在这

里我试图对这个问题做一个数学的分析。

如果单链表里存在重复的点,则该链表中包含一个环,事实上,可以用下面的图来表示

这使我想起 Pollard 的 "rho" 算法,事实上本问题与 "rho" 算法有一个共同点,寻找一个碰撞。

从这个图我们可以看到,如果一个单链表里出现了重复的点,则从表头开始走,无论以什么步调,必定

会落到环中。所以我们可以肯定,如果以某个步调走,碰到了NULL,则该链表无重合点。

尝试用两个指针,以不同的步调前进,如果他们能相遇,必定是在环中。假定指针 p 以步调 f 前进,q

以步调 g 前进,g>f。则 q 先进入链表的环。有一种情况很特殊,就是:在 p 刚进入环的时候就与 q 

相遇。这是一个小概率事件,我们排除它,不考虑这种情况。可以认为:

p 进入环的时候,偏移为 a, 而同时 q 的偏移为 b, 环的长度为 n。(参考下面的图)


往后, p, q 就在圈内打转,它们在x 步后重合的条件为:

fx + a = gx + b (mod n)
(f-g)x = b-a (mod n)

上式有解等价于  (f-g, n) | (b-a)。 

但是,我们在事前不知道 n, 不知道 b-a, 所以唯一能确保 (f-g, n) | (b-a) 成立的是 f-g =1。

只要 f-g = 1, 我们就能一定能检测出重合的情况,这是一个充分条件。

而一旦 p 刚进入环时与 q 不等,(f-g, n) | (b-a) 就成为检测重合的必要条件。前面一些朋友说 f,g 

互素或 f, g 不同即可的观点是错误的。从 (f-g, n) | (b-a) 这个条件应该能找到反例。但这个我就

留给有兴趣的朋友了。

f = 1, g = 2 未必是最好的,因为如果 "rho" 的尾巴很长,p 要花费很多工夫才能进入环。此外,虽

然步调大的时候,可能要跑好几个圈才能覆盖整个环,甚至在很多情况下不能覆盖整个环,但它跑一圈

的时间也相应减少,足以抵消。可惜的是,分析最优的选择,超出了我的能力范围。











 win_hate 回复于:2004-09-20 18:55:42
贴图可把我搞晕了。

 yangtou 回复于:2004-09-20 19:28:07
不需要考虑细节情况,就可以判断可能性
x=pn/(a-b) > m/b
只要改变p的值就可以找到合适的x,x>m/b所以只要ab不等就必定相遇

另外如果m较小比如0,由x=pn/(a-b)(不考虑m/b)
则x只决定于a-b(只要改变p就可以得到x的合适的最小值,而p是决定于a-b的)
T=(au+bu+av+w)x,在(a-b)一定的情况下,ab取值越小T越小

 win_hate 回复于:2004-09-20 20:31:45
yangtou      说得对。

我上面的推理中,有一处条件没有用尽,显然:

a = (xf - m)  mod n
b = (xg-m ) mod n

a - b = x (f-g)  mod n 

从而可知道当  f - g != 0 时, (f-g, n) | (a - b) 总是成立的。所以只要 f,g 不等,即可检测出重合点。此外, yangtou 的推理也比我的简单,我应该在发贴前仔细看看各位的帖子的。 :em16: 


以上是按我的记号,以下按 yangtou 的:
T=(au+bu+av+w)x 是什么东西?

 yangtou 回复于:2004-09-20 20:44:02
T是FH老大帖子里面的

 思一克 回复于:2004-09-21 09:07:26
TO:flw as well as FH.
可以不碰数据。用指针中的无用区域做标记。

这是可以运行的和完成任务的。
但不符合数据结构,还用了C技巧。总之不是一个“好”程序。

 FH 回复于:2004-09-21 10:08:15
楼上老兄,我觉得从理论上就不存在什么“指针中的无用区域”,涉及到具体实现则有可能存在,但会影响原应用。
我觉得我们所探讨的方法是在应用之外以旁观者的角度去考察,应当不干涉或影响原有应用。

 flw 回复于:2004-09-21 20:21:20
[quote:93d23a2087="思一克"]TO:flw as well as FH.
可以不碰数据。用指针中的无用区域[color=red:93d23a2087]做标记[/color:93d23a2087][/quote:93d23a2087]
“做标记”本身就是对原始数据的一种修改。
如果你修改的是别人的数据,我不管;
如果你修改的是我的数据,
那我就告诉你:不许动![color=red:93d23a2087]在我这里,每一个字节、每一个比特,都不允许你为了节省一个指针变量而给我乱动![/color:93d23a2087]

 思一克 回复于:2004-09-22 08:48:26
to flw:
这里是在探讨计算和C的方法和技巧。
问题就是那个问题,目的是做一个函数达到所有要求。

我是在说,用一个指针完全可以达到问题要求。当函数返回时,数据,链表当然都完好无损,保持原来不动。

我没有说我说的是一个好方法,而且说你给出的方法好。
即使是不好的方法,只要是可行的,我们就可以讨论它。本论坛不就是讨论C程序的各种可能的方法吗?
对吧

 FH 回复于:2004-09-22 08:58:49
to 思一克:
给你个结构链表,能给我们说说一个指针是怎么做的么?
[code:1:71d9ff85f8]struct {
   void *next;
   int number;
}[/code:1:71d9ff85f8]

 思一克 回复于:2004-09-22 09:03:51
在linux i386下,指针里面就相当与一个整数,你看成是unsigned整数,而且它没有用完所有的位。
如果在考虑指针对齐,可以空出来的位就更多了。
原来一个DOS下的著名软件(好象叫sidekick吧)就充分利用了我说的技巧--利用指针的空闲位。

当然,利用后要恢复,不能破坏连表的任何内容。

 FH 回复于:2004-09-22 09:22:57
算法应当不涉及具体的系统,因此,不能说指针中有空余的位。
至于对齐,谁又能保证对齐是以字为单位呢?如果是字节对齐呢?

 思一克 回复于:2004-09-22 09:31:23
to: FH, 你说的对。但这是C/C++论坛,不是专门研究算法的,所以我才会说出。如果在算法论坛上,我不会说。

还有,我是从实用角度来说的。如果为研究算法,全当这方法无效。
如果研究C技巧,可以研究。

 FH 回复于:2004-09-22 09:58:41
实际上,一个指针是可以解决问题的,自己管理一个堆栈就是了,可是复杂度要比用两个指针高得多,得不偿失。

 aero 回复于:2004-09-22 09:58:47
用的哪个指针中的空于位呢?这个如果是链表中的指针,那么这个“位”的所有权不还是链表的所有者吗?你怎么知道他没有利用这个“位”呢?而如果是另外用来移动的指针,那么……想不出怎么用这个位。

 思一克 回复于:2004-09-22 10:35:06
如何发code帖子使格式不乱? 我忘了

 aero 回复于:2004-09-22 11:24:48

“[co*de]”和“[/co*de]”把code括起来。

 yutian 回复于:2004-09-22 15:56:31
这个问题越讨论越明朗了

 star55 回复于:2004-09-23 19:22:26
用拓扑排序就行了

即去枝叶方法。一般的数据结构都会有介绍,看看就明白了,在这边我也将不清楚,还是书比较容易说明问题,呵呵

 mfmain 回复于:2004-09-23 20:00:02
大家把问题复杂化了,一般说来应该步长分别为1和2是最优算法,问题可以简化为时针和分针的相遇问题,假设时针和分针每步都走一格的整数倍,显然如果步长分别为1和2,则能保证在一圈之内肯定相遇,而其他情况除非瞎猫正好蹦到死耗子,否则...

 mfmain 回复于:2004-09-24 14:38:12
sorry, 刚才分析的不对,假设步长为a,b,进入圈之前长度为m,圈长n,实际上,不论步长为多少(只要不相同),圈内相遇的步数都不会超过n步,我们重新分析效率问题:
双方都进入圈需要:m/min{a,b}步
最差情况下圈内相遇的步数:n/最大公约数{a,b,n}
事实上,因为n是随机的,因此圈内相遇的步数也是随机的,不可控制,但上限是n步,所以应该尽量减小双方进入圈需要的步数,所以a,b越大越好,但又产生了另外一个问题,就是步长越长,走一步所花的时间就越长,所以是两难!

 YeLLoW 回复于:2004-09-24 15:16:13
学习中,非常感谢

 jiangchun_xn 回复于:2004-09-24 15:32:18
普通算法:时间复杂度O(n^2)
变步长算法时间复杂度接近O(n)
的确算法非常漂亮,

但是无论哪种数据结构本身就应该满足这种数据结构的逻辑意义,因此,对于出现中间环的链表本身就是发生了错误,说明链表的操作中发生了错误.(单向链表不应该提供尾指针随意指向的方法)

如果在实际中会发生这种情况,这就说明用单向链表这种数据结构就是不合适的.这种结构应该是多入单出,可以证明这样的环只会存在一个.(可以用数学归纳法),这样维护这个结构可以在链表之外维护一个bool变量circled,并且提供一个方法,就是设置尾指针制向,同时修改circled,这样,查找是否有环的时间复杂度为o(1).

其实我并不是说这个算法不好,相反,这个算法非常精妙,但是,我个人认为数据结构更重要的是如何选择合适的数据结构和严格的应用数据结构.

 aero 回复于:2004-09-24 15:42:50
你的意思是要用另一个变量来对应链表尾指针的NULL与否?当circled是false的时候,要记得去把尾指针置NULL,然后把circled置true?

如果这个程序员能记得去置circled为true,那他自然就会记得将尾指针置成NULL了。而他记得将尾指着置成NULL,却未必能记得将circled置成false。

觉得加的这个变量是蛇足,没有任何意义!链表自然是不应该出错的。但是一旦他里面有了环,怎么能保证和链表一同维护的circled是可信的呢?

 jiangchun_xn 回复于:2004-09-24 15:51:10
[quote:d30d38340f="aero"]你的意思是要用另一个变量来对应链表尾指针的NULL与否?当circled是false的时候,要记得去把尾指针置NULL,然后把circled置true?

如果这个程序员能记得去置circled为true,那他自然就会记得将尾指针置成NULL了。..........[/quote:d30d38340f]


的确circled有点多余( :cry: )

我的意思是链表不应该出现环(除了头尾相连的为了方便的链表),链表提供的所有方法均不应该产生环.产生环就说明发生了 意外 的错误.

关闭本页
 
首页 | 投资与合作 | 服务条款 | 隐私政策 | 收藏本站 | 设为首页 | 新用户注册 | 免责声明 | 使用帮助
Copyright ©2005-2008 chinaitpower.com All rights reserved. www.chinaitpower.com 版权所有