判断单链表中是否有环,计算出环的首地址 C语言实现

  判断单链表中是否有环,如果有,得出进入环时首个节点的地址.

  有环的定义是,链表的尾节点指向了链表中的某个节点。

如:           ____________

                 ↓                      ↑

 ①->②->③->④->⑤->⑥


那么要判断单链表中是否有环,主要有以下两种方法:

方法一:使用p1、p2两个指针指向头节点,p1总是向前走,但p2每次都从头开始走,对于每个节点,看p1走的步数是否和p2一样。如图,当p1从6走到3时,用了6步,此时若p2从head出发,则只需两步就到3,因而步数不等,出现矛盾,存在环。而且这个点就是进入环时首个节点。(参考函数Plink check_link_1(Plink head))

方法二:使用p1、p2两个指针指向头节点,p1每次向前走一步,p2每次向前走两步,若在某个时候p1 == p2,则存在环。然后p2指向头结点,使p1,p2每次向前走一步,进行比较p1,p2如果p1==p2那么这个节点就是入环时首个节点。

  算法如下:如上图所示,如果①节点到③节点的距离为x,假设第一次移动时,p1 == p2时的节点是⑤的话,③到⑤的距离为y, ③到的距离为z。  所以我们可以得到第一次p1==p2时,那么p1和p2走的步数的差就是p1走过的步数,而且是z的倍数。x+y+mz=nz,所以x+y=(m-n)z。所以当x+y向前移动x时肯定会到达入环时首个节点。 x+y+x=(m-n)z+x。这样我们就可以求得入环首节点的的地址了。(参考函数Plink check_link_1(Plink head))


代码如下所示:

/*
 * 判断单链表中是否有环,如果有,得出进入环节点的首地址.
 */

#include <stdio.h>
#include <stdlib.h>

typedef struct link{
	int data;
	struct link *next;
}*Plink;

#define LINK_MAX 10

/* 创建一个单链表 */
void create_link(Plink *head, int num)
{
	Plink node=NULL;
	Plink next=NULL;

	if(*head == NULL){
		*head = (Plink)malloc(sizeof(struct link));
		if(*head == NULL){
			exit(0);
		}
		(*head)->data = num;
		(*head)->next = NULL;
		return;
	}

	node = *head;

	while(node->next != NULL){
		node = node->next;
	}

	next = (Plink)malloc(sizeof(struct link));
	if(next == NULL){
		exit(0);
	}
	next->data = num;
	next->next = NULL;
	node->next = next;

	return;
}


/* 在单链表中设定一个环,让尾节点指向链表中的第n个节点 */
int create_loop(Plink *head, int n)
{
	int count=1;
	Plink node = *head;
	Plink n_node = NULL;

	if(head == NULL){
		return -1;
	}

	while(node->next != NULL){
		if(count == n){
			n_node = node;
		}
		node = node->next;
		count++;
	}

	/* 尾节点和自身成一个环时 */
	if(count == n){
		n_node = node;
	}

	if(n_node == NULL){
		return -1;
	}

	node->next = n_node;

	return 0;
}

/* 打印链表的前n个data的值 */
void print_link(Plink head, int n)
{
	int count=0;
	Plink node = head;

	while((node != NULL) && (count < n)){
		printf("%d->", node->data);
		node = node->next;
		count++;
	}
	printf("\n");

	return;
}

/*
 * 使用p1、p2两个指针,p1总是向前走,但p2每次都从头开始走,
 * 对于每个节点,看p1走的步数是否和p2一样。数不等,存在环。
 * 而且该点就是入环时首个节点的地址。
 */
Plink check_link_1(Plink head)
{
	int count1=0,count2=0;
	Plink p1=head, p2=head;

	while(p1){

		/* p2每次都从头开始走 */
		count2 = 0;
		p2=head;
		while(p2 && (p1 != p2)){
			p2 = p2->next;
			count2++;
		}

		if((p1 == p2) && (count1 != count2)){
			return p2;			
		}

		p1=p1->next;
		count1++;
	}

	return NULL;
}

/*
 * 使用p1、p2两个指针,p1每次向前走一步,p2每次向前走两步,
 * 若在某个时候p1 == p2,则存在环.
 * 然后p1继续向前走,p2重头开始一步一步向前走,当p1 == p2时,
 * 那么该点就是入环时首个节点的地址
 */

Plink check_link_2(Plink head)
{
	int flag=0;

	Plink p1=head, p2=head;
	while(p2){
		p1=p1->next;
		if(p2->next){
			p2=p2->next->next;
		}

		if(p1 == p2){
			flag=1;
			break;
		}
	}

	if(flag){
		p2=head;
		while( p1!=p2 ){
			p1=p1->next;
			p2=p2->next;
		}
		return p1;
	}

	return NULL;
}


/* 删除链表 */
void delete_link(Plink *head)
{
	return;
}

int main()
{
	int i=0,n=0;
	int ret=0;
	Plink root=NULL;
	Plink node=NULL;


	/* 创建一个单链表 */
	for(i=1; i<=LINK_MAX; i++){
		create_link(&root, i);
	}

	/* 在单链表中设定一个环,让尾节点指向链表中的第n个节点 */
	n = 5;
	ret = create_loop(&root,n);
	if(ret){
		printf("no\n");
	}else{
		printf("yes\n");
	}

	/* 打印链表 */
	n = 20;
	print_link(root,n);

	/* 检查该环 */
	node = check_link_1(root);
	if(node == NULL){
		printf("单链表中不存在环\n");
	}else{
		printf("单链表中存在环,进入环节点的首地址:%x, data:%d\n",node, node->data);
	}

	/* 删除链表 */
	delete_link(&root);

	return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值