数据结构习题:判断单链表是否有环状结构,并找到环的入口

643 阅读3分钟

1 解题思路

在单链表中存在环状结构大致可以画成以下样子:

单链表的环状结构.jpg

可以看到node5结点的指针本应指向null,却指到了node2,使得链表中出现了一个环,有点类似循环单链表的结构。此时去遍历整个链表,会死循环。那如何找出这个环的入口呢?

  • 分析 先在头结点设置两个快慢指针,快指针每次移动两个结点,而慢指针每次移动一个结点,那么根据操场跑圈的经验,跑的慢的指针必定会被跑得快的指针“套圈”,我们把环入口到他们两个相遇点的距离设为a,假设在相遇前,快指针已经跑了n圈,环的周长为C,头结点到环入口的距离为L,如图所示:

单链表的环状结构 (2).jpg

我们计算快指针经过的路程(根据快指针走的路程是慢指针的2倍),可以得到以下等式:

2(L+a)=L+a+nC2(L+a)=L+a+nC

化简后得到:

L=nCaL=nC-a

这个化简后的等式可以得出以下结论:

  1. 从头结点到环入口的距离等于从环入口跑了n圈的长度减去环入口到相遇点的距离。
  2. 换句话说,如果让一个结点从头开始跑,另一个结点从相遇处开始跑,那么他们两个必定会在环入口相遇。

到这里,问题已经理清,接下来就要做以下两个步骤:

  1. 利用快慢指针找出两者相遇的结点
  2. 让一个指针从头结点开始跑,另一个从相遇处开始跑,返回两者相遇的结点

2 Python代码实现

2.1 构建单链表

# 结点
class Node(object):
    def __init__(self, element):
        self.element = element
        self.next = None

# 带头节点的单链表
class linklist(object):
    def __init__(self):
        self.next = None

    # 从尾部添加结点
    def append(self, element):
        node = Node(element)
        if self.next == None:
            self.next = node
        else:
            cur = self.next
            while cur.next != None:
                cur = cur.next
            cur.next = node

    # 打印链表
    def traverse(self):
        alist = []
        cur = self.next
        while cur != None:
            alist.append(cur.element)
            cur = cur.next
        return alist

2.2 找到相遇结点

求解相对比较简单,保证慢结点一次移动一个单位,快结点一次移动两个单位:

def meet(node):
    slow = node.next           # 慢指针
    fast = node.next.next      # 快指针
    while slow != fast:        # 当指针指向的结点相同时停止循环
        slow = slow.next
        fast = fast.next.next
    return slow                #返回任意一个指针的结点皆可

2.3 找到环入口

设置两个指针,一个从头跑,一个从相遇的结点开始跑(调用上述函数):

def is_ring(linklist):
    cur1 = linklist.next           # 从头结点开始跑的指针
    cur2 = meet(linklist.next)     # 相遇的结点
    while cur1 != cur2:            # 两者相遇的结点即为环入口
        cur1 = cur1.next
        cur2 = cur2.next
    return cur1

2.4 测试

创建链表,插入一些数据,并将尾结点指针指向第三个结点(10),以此构造出环结构:

# 创建列表
a = linklist()

# 插入数据
for i in [2, 5, 10, 98, 1, 3, 56]:
    a.append(i)

# 构造环
cur = a.next
while cur.next != None:
    cur = cur.next
cur.next = a.next.next.next
# 测试
b = is_ring(a)
b.element
> 10

可以看到最终返回了第三个结点为(10),问题顺利解决~