判断单链表中是否有环

参考资料:判断单链表里面有没有环

一、问题描述:

对无头结点的单链表link,判断该单链表中是否含有环,如有返回环的入口结点。

二、问题分析:

算法的思想是设定两个指针p1、p2,其中p1每次向前移动一步,p2每次向前移动两步。那么如果单链表存在环,则p1和p2一定会相遇;否则p2将首先遇到NULL。为什么当单链表存在环时,p1和p2一定会相遇呢?

A) 假设单链表是一个环形单链表,并设该环上有n个结点,编号一次为0, 1, 2, ..., n-1,其中结点s-1指向的下一个结点为结点s,结点n-1指向的下一个结点为结点0,1<=s<n。用i表示p1移动的步数:

1. 若i=0时,p1、p2都指向结点0,则i>0时,p1指向结点(i mod n),p2指向结点((2i) mod n)。p1、p2相遇,即(i mod n)=((2i) mod n),也即i≡2i(mod n) => (2i-i) mod n=0 => i mod n=0。故,当i=N*n时,p1、p2相遇,且相遇在结点0。

2. 若i=0时,p1指向结点0,p2指向结点t,0<=t<n,则i>0时,p1指向结点(i mod n),p2指向结点((2i+t) mod n)。p1、p2相遇,即(i mod n)=((2i+t) mod n),也即i≡(2i+t)(mod n) => (2i+t-i) mod n=0 => (i+t) mod n=0。故,当i=N*n-t时,p1、p2相遇,且相遇在结点((n-t) mod n)。

B) 假设单链表link含有环,其中开始前(k-1)个结点(a1, a2, ..., ak-1)位于环外,第k个结点(ak)为环的入口结点(也记为c0),环上结点(c0, c1, ..., cn-1)总数为n。设k=m*n+t,0<=t<n-1。

若i=1时,p1指向首结点a1,p2指向结点a1的下一个结点,则i=k时,p1指向结点ak(也即结点c0),p2指向结点(ci mod n),即结点(ck mod n),也即结点(ct mod n)。此时令i为0,然后移动p1、p2,则当i=N*n-t时,p1、p2相遇,且相遇在结点(c(n-t) mod n)。此时若p1继续向前移动k步,则p1指向结点(c(n-t+k) mod n),即结点(c(n-t+m*n+t) mod n),也即入口结点(c0)。

故判断单链表里是否有环,及求环的入口结点方法如下,时间复杂度O(k+n):

第一步:首先判断单链表是否为空,若为空则不存在环,入口结点不存在,返回NULL,算法结束;否则,执行第二步;

第二步:首先令p1指向单链表首结点,p2指向首结点的下一结点。p1每次向前移动一步,p2每次向前移动两步。若p2为NULL,则不存在环,返回NULL,算法结束;否则,若p1、p2相遇,存在环,执行第三步;

第三步:将p1向前移动一步,将p2指向单链表首结点。之后p1、p2每次均向前移动一步,直到p1、p2首次相遇(需再移动k-1步),相遇处即为环入口结点(结点c0,也即结点ak),返回p1,算法结束。

三、代码实现:

#include<cstdio>
#include<cstdlib>

typedef struct node{
	int data;
	struct node *next;
}NODE;

NODE *has_circle(NODE *link);
void destroy_link(NODE *&link);

int main(int argc,char argv[]){
	NODE *t,*h,*p,*g;
	p=(NODE *)malloc(sizeof(NODE));
	p->data=1;
	p->next=NULL;
	h=t=p;
	for(int i=2;i<=11;++i){
		p=(NODE *)malloc(sizeof(NODE));
		p->data=i;
		p->next=NULL;
		t->next=p;
		t=p;
		if(i==7){
			g=p;
		}
	}
	if(p=has_circle(h)){
		printf("%d\n",p->data);
	}else{
		printf("NULL\n");
	}

	t->next=g;
	if(p=has_circle(h)){
		printf("%d\n",p->data);
	}else{
		printf("NULL\n");
	}
	t->next=NULL;
	destroy_link(h);

	return 0;
}

NODE *has_circle(NODE *link){
	NODE *p1,*p2;
	if(link==NULL){
		return NULL;
	}
	p1=link;
	p2=link->next;
	while(p2&&p1!=p2){
		printf("::%d::%d\n",p1->data,p2->data);
		p1=p1->next;
		if(p2=p2->next){
			p2=p2->next;
		}
	}
	if(p2==NULL){
		return NULL;
	}else{
		printf("%d::%d\n",p1->data,p2->data);
		p2=link;
		p1=p1->next;
		while(p1!=p2){
			printf("##%d::%d\n",p1->data,p2->data);
			p1=p1->next;
			p2=p2->next;
		}
		return p1;
	}	
}

void destroy_link(NODE *&link){
	NODE *p;
	p=link;
	while(p){
		link=link->next;
		free(p);
		p=link;
	}	
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值