检查循环链表的算法

时间:2016-05-12 08:05:31

标签: algorithm performance linked-list singly-linked-list sentinel

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

如果列表项指向前一个列表项,则列表计为不一致。 首先,我考虑存储第一个元素,然后在将当前元素与第一个元素进行比较时迭代列表。

6 个答案:

答案 0 :(得分:3)

列表是否提供了一个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)存储。

答案 1 :(得分:2)

Floyd's "Tortoise and Hare" algorithm执行您需要的操作,只需要进行一些小修改就可以使用虚拟头/尾(sentinel)节点。

以下是我编写伪代码的方法:

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;
}

答案 2 :(得分:1)

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

目前尚不清楚“前一个列表项目中的点数”是什么意思。是通过引用(对象)还是相同的值/属性值集(struct)?我假设参考。可以很容易地修改下面的代码来处理结构。

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;
}

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

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)要求。

结论:如果可以获得计数,那么就不可能执行任务。见格雷厄姆的回答

答案 3 :(得分:1)

以下是我对第二个问题的解决方案。

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

起初我没有想过使用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