链表判断是否有环的方法分析及论证

一个问题:判断单项链表中是否有环,若有环,找出环的汇点

方法1:


每次访问一个节点,记录下地址信息,存放于set中,每次访问下一个节点,查看该节点是否已经存在,若存在,则该节点是构成环的关键点 
输入: list 
输出:若有环,返回构成环的关键点,若没有,返回NULL 

pCurNode = list.head
set<listNode*> visistedSet

while (pCurNode->next)
    pCurNode = pCurNode->next
    bool ret = checkVisitedSet(pCurNode, visitedSet)    //检查该节点是否已经访问过
    if ret = true then 
        return pCurNode;
    end if
    visitedSet.add(pCurNode)
end while
return NULL;

空间:O(N) 记录访问过的每个节点地址 
时间:外层循环N次,内部检查visitedSet所花时间如下:   
1   0
2   lg(1)
3   lg(2)
.
.
.
N   lg(N-1)

时间复杂度为:lg(1) + lg(2) + lg(3) + ...... lg(N-1) = lg((N-1)!)
若采用HASH表,则在visitedSet中查找时间为1,那么时间复杂度为O(N)

方法2


2.1 判断是否有环

设置两个指针,一个指针每次移动一个步长,另外一个指针每次移动2个步长,若没有环,则两个节点在访问的过程中都会变成NULL; 若有环,则两个指针会在环中不停的移动,由于两者步长不一样,一定会相遇。 

 

我们认为相遇也只是感性的认识,在一个圈内,一个速度快,一个速度慢,肯定能碰头。
下面我们分析一下,为什么一定为相遇。只要保证,一定会相遇,我们才能用这种方法,进行判断链表是否有环。

先做如下参数定义:
1.链表节点数量为N  链表设定顺序[1......N] (头节点不算)
2.环汇点的下表为c 
3.圈长:N - c + 1
4.设此处slow走了s步,则fast走了2s步


相遇时slow下标为:(分两种情况,走的步长未超过链表长度/超过链表长度)

s (s <= N)
c + (s - c)%(N - c + 1) (s > N)
fast下标为 
2s  (2s <= N)
c + (2s - c)%(N - c + 1) (2s > N)

当两者相遇时,下标肯定是相等的。 
第一种组合: s = 2s  s = 0 
第二种情况: s = c +  (2s - c)%(N - c + 1)  (N/2 <= s <= N)
第三种情况: c + (s - c)%(N - c + 1)  = 2s  (s > N && 2s <= N) 无意义
第四种情况: c + (s - c)%(N - c + 1)  =  c + (2s - c)%(N - c + 1)   (s > N)



针对上面四种情况,我们只需要证明第二种情况和第四种情况,若方程肯定有解,说明两个指针肯定能够相遇, 
若无解,则两个指针不可能相遇,该方法无效。 

对于第二种情况:即 (s - c) = (2s - c)%(N - c + 1)   (  N/2 <= s <= N)  
即 2s - c = k * (N - c + 1) + s - c (k为1.2.3.......)  s = k * (N - c + 1)方程有解。 

对于第四种情况: 去c,得到 (s - c)%(N - c + 1)  =  (2s - c)%(N - c + 1)     (s > N)  
设余数为 f 
s  -  c = (N - c + 1) * m + f 
2s - c = (N - c + 1) * n + f 
则s = (N - c + 1)*(n - m)  (m,n 为正整数 && n >m) 

综上所述:一定存在这样的步长s = (N - c + 1)*k (s >= N/2 & s >= c)使得快慢指针相遇。 
有了上述说明后,我们就可以放心大胆的使用这个方法,去证明链表中有没有环,如果有环,那么我们一定可以找到。 
checkCircle操作:  
listNode* checkCircle(list* pList)
{
	assert(pList);
	assert(pList->head);

        //起点相同,开始起跑
	listNode* pOneStep = pList->head;
	listNode* pTwoStep = pList->head;

	while (pOneStep && pTwoStep)
	{
		pOneStep = pOneStep->next;

		pTwoStep = pTwoStep->next;
		if (pTwoStep)  pTwoStep = pTwoStep->next;

		if (pOneStep == pTwoStep)
		    return pOneStep;
	}
	return NULL;
}

2.2 找汇点位置

在网络上看别人的博客,常常会提到这么一句话:“找到环的入口点 当fast若与slow相遇时,slow肯定没有走遍历完链表,而fast已经在环内循环了n圈(1<=n)”,就是说在slow指针走完一圈内肯定能相遇,但是都没有说为什么. 

此处用反证法:即slow指针不可能在一圈范围内相遇,即不存在正整数k,满足 (s>= c && s <= N) 
c <= (N - c + 1) * k <= N
c - 1 < (N - c + 1) * k < N + 1
(c - 1) / (N - c + 1) < k < (N + 1)/ (N - c + 1)
再看区间范围:   (N + 1)/ (N - c + 1)  -  (c - 1) / (N - c + 1)  = (N - c + 2) / (N - c + 1) > 1 !!! 
区间范围是大于1的,一定可以取一个整数k,满足上述不等式,所以假设不成立。 
那么slow指针一定可以在一圈范围内就和fast指针相遇。 

还利用之前证明得到的关系求汇点位置: 
s = (N - c + 1) * k;
s = (k - 1)*(N - c + 1) + N - c + 1
c = (k - 1)*(N - c + 1)  + N - s + 1
c为汇点位置, N - c + 1为环的长度, N - s + 1为相遇点到环汇点的距离 
那就是说用一个指针指向头节点,用一个指针指向相遇节点,依次往前走,一定会在汇点出相遇。寻找汇点方法: 
listNode* findPoint(list* pList)
{
        assert(pList);
        assert(pList->head);
	listNode* meetNode =  checkCircle(pList);
	if (!meetNode)	return NULL;

	listNode* p = pList->head;
	listNode* q = meetNode;
	
	while (p != q)
	{
    	    p = p->next;
	    q = q->next;
	}
	return p;
}


测试程序

链表结构定义: 
typedef struct listNode 
{
	struct listNode *next;
	int		key;
}listNode;

typedef struct list
{
	listNode* head;
	listNode* tail;
	int	len;		
}list;

API说明

list*  createEmptyList();
void listRelease(list* pList);
void addNodeInHead(list* pList, int key);
void addNodeInTail(list* pList, int key);
listNode* searchNode(list* pList, int key);
listNode* checkCircle(list* pList);
listNode* findPoint(list* pList);
void showList(list* pList);


我们主要关注: 

测试代码: 
int main(int argc, char *argv[])
{
	//生成链表
	list* pList = createEmptyList();
	for (int i = 0; i < 100; i++)
	    addNodeInTail(pList, i);

	//在节点40处圈个环
	listNode* pCircleNode = searchNode(pList, 40);
	pList->tail->next = pCircleNode;
		
       //判断是否有环
	listNode* pNode = checkCircle(pList);
	if (!pNode)	
		printf("No Circle\n");
	else
		printf("Node Value: %d\n", pNode->key);

        //找汇点
        listNode* pointNode = findPoint(pList);
	if (!pointNode)	
	    printf("No Point\n");
	else
	    printf("\nPointNode Value: %d\n", pointNode->key);
	
        listRelease(pList);
	getchar();
	return 0;
}

测试结果:
如图所示,在测试程序中设置了再节点40处构成环,查找确实找到了节点40为汇点

 

在节点59处,两个指针相遇,并找到环的汇点40。

原创出处:http://my.oschina.net/myspaceNUAA/blog/74505

另外,这篇blog讲得更清楚http://www.cppblog.com/humanchao/archive/2012/11/12/47357.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值