判断单链表是否存在环,判断两个链表是否相交问题详解

http://www.cppblog.com/humanchao/archive/2008/04/17/47357.html

主要, 在涉及结构体的情况下,一定要加上struct 关键字!!!!!!

有一个单链表,其中可能有一个环,也就是某个节点的next指向的是链表中在它之前的节点,这样在链表的尾部形成一环。

问题:

1、如何判断一个链表是不是这类链表?
2、如果链表为存在环,如何找到环的入口点?

解答:

一、判断链表是否存在环,办法为:

设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。(当然,fast先行头到尾部为NULL,则为无环链表)程序如下:

bool IsExitsLoop(slist *head)
{
    slist
*slow= head*fast = head;

    while ( fast && fast->next ) 
    {
        slow 
= slow->next;
        fast 
= fast->next->next;
       
if ( slow == fast ) break;
    }

    return !(fast == NULL || fast->next == NULL);
}

二、找到环的入口点

当fast若与slow相遇时,slow肯定没有走遍历完链表,而fast已经在环内循环了n圈(1<=n)。假设slow走了s步,则fast走了2s步(fast步数还等于s 加上在环上多转的n圈),设环长为r,则:

2s = s + nr
s= nr

设整个链表长L,入口环与相遇点距离为x,起点到环入口点的距离为a。
a + x = nr
a + x = (n – 1)r +r = (n-1)r + L - a
a = (n-1)r + (L – a – x)

(L – a – x)为相遇点到环入口点的距离,由此可知,从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点,于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。程序描述如下:

slist* FindLoopPort(slist *head)
{
    slist
*slow = head, *fast = head;

    while ( fast && fast->next ) 
    {
        slow 
= slow->next;
        fast 
= fast->next->next;
       
if ( slow == fast ) break;
    }

    if (fast == NULL || fast->next == NULL)
   
    return NULL;

    slow 
= head;
    while (slow != fast)
    {
         slow 
= slow->next;
         fast 
= fast->next;
    }

    return slow;
}


扩展问题:

判断两个单链表是否相交,如果相交,给出相交的第一个点(两个链表都不存在环)。

比较好的方法有两个:

一、将其中一个链表首尾相连,检测另外一个链表是否存在环,如果存在,则两个链表相交,而检测出来的依赖环入口即为相交的第一个点。

二、如果两个链表相交,那个两个链表从相交点到链表结束都是相同的节点,我们可以先遍历一个链表,直到尾部,再遍历另外一个链表,如果也可以走到同样的结尾点,则两个链表相交。

这时我们记下两个链表length,再遍历一次,长链表节点先出发前进(lengthMax-lengthMin)步,之后两个链表同时前进,每次一步,相遇的第一点即为两个链表相交的第一个点。

========================================================

这道题被做为微软的经典笔试题已经被传烂了,当然,一些人也把这道题做为面试准备充分与否的标准,如果在面试的时候这种口水题都答不上来,事先无疑就没有做好准备工作,而这也被认为是在浪费彼此的时间,最后的评价自然不会高到哪里去吧。 所以,面试前对于一些经典的题目还是不能吊以轻心。

单链表

单链表是一种比较常见的数据结构,就是一种拥有一个能够指向自己数据类型的指针做为成员(next指针)的抽象类型,一个单链表的结点中的next指针,可以指向另一个相同类型的结点或者NULL,多个结点首尾相接便形成一个链状的结构。不知道的同学需要好好再翻翻书或者查查资料

一些情况下,如果处于末尾的结点并没有指向NULL而是指向自己或者自己上游的某个结点,这时该链表中就会出现一个环状的结构,比如,对结点1,2,3,4,5来说,如果1->2->3->4->5->2,链表中就存在着2->3->4->5->2的一个环。

如何才能判断一个环的存在性算法思路比较简单,很多人也应该知道,就是两个指针h1,h2都从头开始遍历链表,h1每次向前走1步,h2每次向前走2步,如果h2碰到了NULL,说明环不存在;如果h2碰到本应在身后的h1说明环存在(也就是发生了套圈)。

这里,有几点需要认证的地方:

  1. 如果环不存在,一定是h2先碰到NULL:这点应该也不用说
  2. 如果环存在,h2与h1一定会相遇,而且相遇的点在环内:h2比h1遍历的速度快,一定不会在开始的那段非环的链表部分相遇,所以当h1,h2都进入环后,h2每次移动都会使h2与h1之间在前进方向上的差距缩小1,最后,总归会使得差距减少为0,完成相遇。

程序比较简单,C代码如下

Code
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <stdlib.h>
  
#define true 1
#define false 0
  
struct list_node{
     int data;
     struct list_node* next;
};
typedef struct list_node ListNode;
typedef ListNode* List;
  
int isCircleExist(List list)
{
     List h1,h2;
     h1 = h2 = list;
     while ( true )
     {
         if (h2 == NULL || h2->next == NULL)
         {
             return false ;
         }
         h1 = h1->next;
         h2 = h2->next->next;
         if (h1 == h2)
         {
             return true ;
         }
     }
     return false ;
}
  
int main()
{
     ListNode n1,n2,n3,n4,n5;
     n1.next = &n2;
     n2.next = &n3;
     n3.next = &n4;
     n4.next = &n5;
     n5.next = &n2;
     //n5.next = NULL;
     List list = &n1;
     if (isCircleExist(list))
     {
         printf ( "exist\n" );
     }
     else
     {
         printf ( "not exist\n" );
     }
}
#include <stdio.h>
#define true 1
#define false  0
struct list_node
{
 int data;
 struct list_node *next;
};
typedef struct list_node ListNode;
typedef struct list_node *List;
int is_circle_exist( List list )
{
 List h1, h2;
 h1 = h2 = list;
 while( true )
 {
 if( h2  ||h2->next )
 {
  
  h1 = h1->next;
  h2 = h2->next->next;
  if( h1 == h2)
   return 1;
 }
 return 0;
 }
}
main()
{
 ListNode n1 ,n2, n3 ,n4, n5;
 n1.next = &n2;
 n2.next = &n3;
 n3.next = &n4;
 n4.next = &n5;
 //n5.next = &n2;
 n5.next = NULL;
 List list = &n1;
 if(is_circle_exist( list ))
 {
  printf( "exist\n" ); 
 }
 else
 {
  printf( "not exist\n" ); 
 }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值