有环单链表的结点个数的统计方法

对于无环单链表,计算其结点个数是相当简单的。C代码如下:

int get_length(list_t *head)
{
        int len = 0;
        for (list_t *p = head; p != NULL; p = p->next)
                len++;
        return len;
}

那么对于有环的单链表,统计其总的结点个数,就变得复杂一些。

  • 首先,统计在环上的总的结点个数,不妨记为N1;
  • 其次,找到环的入口结点,不妨记为Joint;
  • 然后,从单链表的头结点开始到Joint结束,统计这一线性链表上的总的结点个数(不包括Joint),不妨记为N2;
  • 最后,N1+N2就是有环单链表的总的结点个数。

对应的C代码如下:

 1 int get_total_length(list_t *head)
 2 {
 3         int len = get_loop_length(head);
 4 
 5         list_t *joint = (len != 0) ? get_loop_joint(head) : NULL;
 6 
 7         for (list_t *p = head; p != joint; p = p->next)
 8                 len++;
 9 
10         return len;
11 }

其中,

  • L3的get_loop_length()是获取环上的总的结点个数。
  • L5的get_loop_joint()是获取环的入口结点。

在给出get_loop_length()和get_loop_joint()的实现之前,我们不妨先看看如何判断一个单链表有环。

1. 判断一个单链表有环

通常的做法就是使用快慢两个指针,把探索单链表是否有环的过程演绎成一个追击问题。快指针和慢指针都从链表头开始移动,慢指针每次移动一步,而快指针每次移动两步,如果快指针能够追上慢指针,则说明有环。C代码如下:

 1 /**
 2  * Detect a singly linked list has a loop
 3  */
 4 bool is_loop(list_t *head)
 5 {
 6         list_t *fast = head;
 7         list_t *slow = head;
 8 
 9         /*
10          * If loop does not exist, fast should firstly reach the end
11          * a) if the length of list is even, fast       will be NULL
12          * b) if the length of list is odd,  fast->next will be NULL
13          */
14         while (fast != NULL && fast->next != NULL) {
15                 fast = fast->next->next;
16                 slow = slow->next;
17 
18                 /*
19                  * Well, loop is found as the fast catches up with the slow
20                  */
21                 if (fast == slow)
22                         return true;
23         }
24 
25         return false;
26 }

2. 统计有环单链表的环上结点个数

方法还是使用快慢指针。首先使用快慢指针定位一个环上的结点,然后从此结点的下一个结点开始,单步移动指针完成统计。C代码如下:

 1 /**
 2  * Get the length of the loop if a singly linked list has a loop
 3  */
 4 int get_loop_length(list_t *head)
 5 {
 6         list_t *fast = head;
 7         list_t *slow = head;
 8         list_t *node = NULL;
 9 
10         /* get a node in the loop */
11         while (fast != NULL && fast->next != NULL) {
12                 fast = fast->next->next;
13                 slow = slow->next;
14 
15                 if (fast == slow) {
16                         node = slow;
17                         break;
18                 }
19         }
20 
21         /* no loop found hence the length should be zero */
22         if (node == NULL)
23                 return 0;
24 
25         /* now walk again to get the length of the loop */
26         int len = 1;
27         for (list_t *p = node->next; p != node; p = p->next)
28                 len++;
29         return len;
30 }

3. 获取有环单链表的入口结点

这个算法稍微复杂一点,当然还是使用快慢指针。首先使用快慢指针定位环上的一个结点,不妨记为Node; 然后让快指针从链表头Head开始单步移动,同时慢指针从Node开始单步移动。一旦快慢指针相遇,我们就找到了入口结点Joint。为帮助理解,首先假设:

  • Head到Joint的长度记为X
  • Joint到Node的长度记为Y1
  • Node到Joint的长度记为Y2

然后用一个图给出X=Y2的证明。(图是本人用LibreOffice画的,转载请注明出处

C代码如下: (注释中也给出了严格的数学证明)

 1 /**
 2  * Get the joint if a singly linked list has a loop
 3  */
 4 list_t *
 5 get_loop_joint(list_t *head)
 6 {
 7         list_t *fast = head;
 8         list_t *slow = head;
 9         list_t *node = NULL;
10 
11         /* get a node in the loop */
12         while (fast != NULL && fast->next != NULL) {
13                 fast = fast->next->next;
14                 slow = slow->next;
15 
16                 if (fast == slow) {
17                         node = slow;
18                         break;
19                 }
20         }
21 
22         /* no loop found hence the joint should be NULL */
23         if (node == NULL)
24                 return NULL;
25 
26         /*
27          * The slow walks the loop from the node, and let the fast walk the
28          * list from its head. They should meet at the joint.
29          *
30          *     Head        Joint
31          *     |           |
32          *     O-->O-->O-->O<--O<--O
33          *                 |       ^
34          *                 V       |
35          *                 O-->O-->O
36          *                          \
37          *                           Node
38          *
39          *     x : The length from Head  to Joint
40          *     y1: The length from Joint to Node
41          *     y2: The length from Node  to Joint
42          *
43          *     Total steps of the slow walked: x + y1
44          *     Total steps of the fast walked: x + y1 + y2 + y1
45          *     Note the fast and the slow have the same times to move,
46          *     So                              x + y1 == (x + y1 + y2 + y1) / 2
47          *                                 ==> 2x + 2y1 == x + 2y1 + y2
48          *                                 ==> x == y2
49          */
50         fast = head;
51         slow = node;
52         while (fast != slow) {
53                 fast = fast->next;
54                 slow = slow->next;
55         }
56 
57         return slow;
58 }

到此为止,我们就用Top-down的方法把有环单链表的结点个数的统计方法解释清楚了,完整代码请参见这里。也许你会好奇为什么我要研究诸如此类的单链表问题,答案之一就是那些装13的公司(比如BAT)特别喜欢考算法,吼吼:-)

posted @ 2017-11-21 17:11  veli  阅读(1219)  评论(0编辑  收藏  举报