检查链表循环的算法

Algorithm to check a linked-list for loops

我想找到一个 checks a linked-list with n elements for consistency 的算法。链表使用 dummy head(也称为哨兵节点)。除了迭代列表所需的 space 之外,算法需要 run in O(n) time 并且允许 use O(1) extra space。列表 is unknownsize。另外是 forbidden to modify the list.

如果有一个列表项指向前一个列表项,则该列表被视为不一致。 首先,我考虑存储第一个元素,然后在将当前元素与第一个元素进行比较的同时遍历列表。

基本上,指向上一个项目意味着在列表中有一个循环。在这种情况下,循环检查似乎是合适的。

如果链接列表中的每个项目都没有访问 属性,则需要一些 RAM。 如果您有一个 Visited 属性,您首先需要在 运行 算法之前将其清除。这可能不符合您的大 O 要求。

不清楚 "points at previous list item" 是什么意思。等于引用(对象)或 value/set 的 属性 值(结构)?我假设参考。下面的代码也可以轻松修改以处理结构。

static void Main(string[] args)
{
    var list = BuildALinkedListFromSomeData();
    var isConsitent = IsConsistent(list);
}

static bool IsConsistent(LinkedList<Item> list)
{ 
    var visited = new List<LinkedListNode<Item>>()
    var runner = list.First;
    while(runner != null)
    {
        if (visited.Contains(runner))
            return false;
        visited.Add(runner);
        runner = runner.Next;
    }
    return true;
}

O(n) 解决方案,它使用已经使用存储空间的现有数字 VisitCounter space(不需要额外的存储空间):

static bool IsConsistent(LinkedList<Item> list)
{
    var runner = list.First;
    if (runner == null)
        return false;  // Assume consistent if empty

    var consistent = true; 
    var runId = runner.Value.VisitCount;
    while (runner != null)
    {
        // Does the traversed item match the current run id?
        if(runner.Value.VisitCount > runId)
        {
            // No, Flag list as inconsistent. It must have been visited previously during this run
            consistent = false;
            // Reset the visit count (so that list is ok for next run)
            runner.Value.VisitCount = runId; 
        }
        // Increase visit count
        runner.Value.VisitCount++;
        // Visit next item in list
        runner = runner.Next;
    }
    return consistent;
}

这会更改列表中某个项目的内容,但不会更改列表本身。如果您不允许更改列表中某个项目的内容,那么这当然也不是解决方案。好吧,转念一想,这根本不是一个可能的解决方案。当不一致时,你的列表是循环的,最后的算法永远不会完成:)

然后您将不得不从列表中的每个访问项目向后遍历列表,这将打破您的 O(n+1) 要求。

结论:如果 Count 可用,则并非不可能完成任务。参见 GrahamS 的回答

列表是否提供了一个 Size 属性 告诉你它包含多少个元素 (n) 而无需遍历它?

如果是这样,那么满足您所有大 O 要求的简单解决方案是尝试遍历列表,边走边计算元素。如果计数超过列表中元素的预期数量,则它有一个循环。

伪代码看起来像这样:

bool isConsistent (List list)
{
    bool consistent = true;
    Node node = list.Sentinel.Next;
    int count = 0;

    while (node != list.Sentinel && consistent)
    {
         count++;

         if (count > list.Size)
             consistent = false;

         node = node.Next;
    }

    return consistent;
}

在 O(n) 中完成并使用 O(1) 存储。

一开始没想过用list.Sentinel。现在我有了一个新想法。

IsConsistent(LinkedList<Item> list) :boolean
    slow = List.Sentinel.next :Element
    fast = slow.next :Element
    while(slow != list.Sentinel && fast != list.Sentinel) do
        if(slow == fast) return false
        else 
              slow:= slow.next
              fast:= fast.next.next 
    od
    return true

Floyd's "Tortoise and Hare" algorithm 做你需要的,只需要一个小的修改就可以与你的虚拟 head/tail(哨兵)节点一起工作。

这是我编写伪代码的方式:

bool IsConsistent (List list)
{
    Node tortoise = list.Sentinel.Next;
    Node hare = tortoise.Next;

    while (tortoise != list.Sentinel && hare != list.Sentinel)
    {
        if (tortoise == hare)
            return false;

        tortoise = tortoise.Next;
        hare = hare.Next.Next;
    }

    return true;
}

这是我对第二个问题的解答。

IsConsistent(LinkedList<Item> list) :N
    slow = List.Sentinel.next :Element
    fast = slow.next :Element
    isConsistent = true :boolean
    while(fast != list.Sentinel && fast.next != list.Sentinel && isConsistent) do
        if(slow == fast)
            isConsistent = false
        else 
            slow:= slow.next
            fast:= fast.next.next 
    od
    if(isConsistent)
        return 0
    else
        position = 0 : N
        slow:= list.Sentinel
        while(slow != fast) do
            slow:= slow.next
            fast:= fast.next
            position:= position + 1
        od
        return position