我希望找到一个算法,用于检查具有n个元素的链表的一致性。该链表使用虚拟头(也称为哨兵节点)。该算法需要在O(n)时间内运行,并且可以除了迭代列表所需的空间外,使用O(1)额外空间。列表的大小未知。此外,禁止修改列表。
如果存在指向先前列表项的列表项,则该列表将被视为不一致。
首先我考虑存储第一个元素,然后在比较当前元素与第一个元素时遍历列表。
如果存在指向先前列表项的列表项,则该列表将被视为不一致。
首先我考虑存储第一个元素,然后在比较当前元素与第一个元素时遍历列表。
这个列表提供了一个 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;
}
Floyd's "Tortoise and Hare" algorithm可以解决问题,只需要对其进行微小的修改即可适用于您的虚拟头/尾(哨兵)节点。
以下是我编写的伪代码:
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;
}
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(不需要额外的存储空间):
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;
}
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
else
子句,但是保留它也可以。我可能会将函数重命名为FindCyclePosition
之类的名称,并将返回类型更改为N。 - GrahamS一开始我没有考虑使用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
slow
是否为空?这种情况会在什么时候发生?使用哨兵节点应该避免出现空值,所以 slow
应该直接从 Sentinel.next
开始。而且为什么要在循环中检查其他条件?你只需这样做:while (slow != list.Sentinel && fast != list.Sentinel)
。 - GrahamSslow
和fast
都将初始化为list.Sentinel
,因此while
循环不会执行,函数将返回true。 - GrahamS
n
吗? - joop