如何检测单个链表是否有循环? 如果它有循环,那么如何找到循环的起始点,即循环开始的节点。
答案 0 :(得分:123)
你可以通过在列表中运行两个指针来检测它,这个过程被称为同名寓言之后的乌龟和野兔算法。
首先,检查列表是否为空(head
是null
)。如果是这样,就不可能循环,所以现在就停止。
否则,在第一个节点tortoise
上启动第一个指针head
,在第二个节点hare
上启动第二个指针head.next
。
然后继续循环,直到hare
为null
(在单元素列表中可能已经为真),将tortoise
推进1,将hare
推进为每个3
迭代。野兔保证首先到达终点(如果 结束),因为它开始前进并且运行得更快。
如果没有结束(即,如果存在循环),它们最终将指向同一节点,您可以停止,因为知道您已在循环中找到某处节点。 / p>
考虑以下从head -> 1 -> 2 -> 3 -> 4 -> 5
^ |
| V
8 <- 7 <- 6
开始的循环:
tortoise
从1开始hare
,在{2}开始(tortoise,hare) = (1,2) (2,4) (3,6) (4,8) (5,4) (6,6)
,它们采用以下值:
(6,6)
因为它们在hare
变得相等,并且因为tortoise
总是在非循环列表中超出def hasLoop (head):
return false if head = null # Empty list has no loop.
tortoise = head # tortoise initially first element.
hare = tortoise.next # Set hare to second element.
while hare != null: # Go until hare reaches end.
return false if hare.next null # Check enough left for hare move.
hare = hare.next.next # Move hare forward two.
tortoise = tortoise.next # Move tortoise forward one.
return true if hare = tortoise # Same means loop found.
endwhile
return false # Loop exit means no loop.
enddef
,这意味着您已经发现一个循环。
伪代码将是这样的:
O(n)
此算法的时间复杂度为O(n)
,因为访问的节点数(通过乌龟和野兔)与节点数成正比。
在循环中知道节点后,还有一个head -> 1 -> 2 -> 3 -> 4 -> 5
^ |
| V
8 <- 7 <- 6
\
x (where hare and tortoise met).
保证方法可以找到循环的 start 。
在循环中的某处找到一个元素之后让我们返回到原始位置,但是你不确定循环的开始位置。
hare
这是要遵循的过程:
size
并将1
设为hare
。tortoise
和hare
不同,就会继续前进size
,每次增加size
。这最终给出了循环的大小,在这种情况下为6。1
为you must *already* be at the start of the loop (in a loop of size one, there is only one possible node that can *be* in the loop so it *must* be the first in that loop). In this case, you simply return
,则表示hare
hare`为开头,并跳过下面的其余步骤。tortoise
和hare
同时设置为列表的第一个元素,并将size
提前7
次提前{到{ {1}}在这种情况下)。这给出了两个不同的指针正好循环的大小。hare
和tortoise
不同,就把它们放在一起(野兔以更稳重的速度跑,与乌龟的速度相同 - 我猜它已经累了第一次运行)。由于它们在任何时候都会保持size
个完全相同的元素,tortoise
当hare
将返回到循环的开头。您可以通过以下演练了解到这一点:
size tortoise hare comment
---- -------- ---- -------
6 1 1 initial state
7 advance hare by six
2 8 1/7 different, so advance both together
3 3 2/8 different, so advance both together
3/3 same, so exit loop
因此3
是循环的起点,因为这两个操作(循环检测和循环开始发现)都是O(n)
并且是顺序执行的,所以整个过程也是{{ 1}}。
如果您想要一个更正式的证据,可以检查以下资源:
如果您只是支持该方法(非正式证明),您可以运行以下Python 3程序,该程序评估其大量大小(循环中有多少元素)和引入程序的可行性(循环开始前的元素。)
你会发现它总能找到两个指针相遇的点:
O(n)
答案 1 :(得分:34)
所选答案给出O(n * n)解决方案以找到循环的起始节点。这是一个O(n)解决方案:
一旦我们发现慢速A和快速B在循环中相遇,使其中一个静止而另一个继续每次步进一步,以确定周期的周长,比如P。
然后我们将一个节点放在头部并让它走P步,并将另一个节点放在头部。我们每次都将这两个节点前进一步,当它们第一次相遇时,它就是循环的起点。
答案 2 :(得分:6)
您也可以使用哈希映射来查找链接列表是否有循环,函数使用哈希映射来查找链接列表是否有循环
static bool isListHaveALoopUsingHashMap(Link *headLink) {
map<Link*, int> tempMap;
Link * temp;
temp = headLink;
while (temp->next != NULL) {
if (tempMap.find(temp) == tempMap.end()) {
tempMap[temp] = 1;
} else {
return 0;
}
temp = temp->next;
}
return 1;
}
两指针方法是最好的方法,因为时间复杂度是O(n)Hash Map所需的额外O(n)空间复杂度。
答案 3 :(得分:2)
我在Narasimha Karamanchi的数据结构书中读到了这个答案。
我们可以使用 Floyd循环查找算法,也称为龟和野兔算法。在这里,使用了两个指针;一个(比如slowPtr
)由一个节点提前,另一个(比如fastPtr
)由两个节点提前。如果单个链表中存在任何循环,它们肯定会在某个时刻相遇。
struct Node{
int data;
struct Node *next;
}
// program to find the begin of the loop
int detectLoopandFindBegin(struct Node *head){
struct Node *slowPtr = head, *fastPtr = head;
int loopExists = 0;
// this while loop will find if there exists a loop or not.
while(slowPtr && fastPtr && fastPtr->next){
slowPtr = slowPtr->next;
fastPtr = fastPtr->next->next;
if(slowPtr == fastPtr)
loopExists = 1;
break;
}
如果存在任何循环,那么我们将其中一个指针指向头部,现在将它们推进到单个节点。他们将会遇到的节点将是单个链表中循环的 start 节点。
if(loopExists){
slowPtr = head;
while(slowPtr != fastPtr){
fastPtr = fastPtr->next;
slowPtr = slowPtr->next;
}
return slowPtr;
}
return NULL;
}
答案 4 :(得分:1)
以下代码将查找SLL中是否存在循环,如果存在循环,则返回起始节点。
int find_loop(Node *head){
Node * slow = head;
Node * fast = head;
Node * ptr1;
Node * ptr2;
int k =1, loop_found =0, i;
if(!head) return -1;
while(slow && fast && fast->next){
slow = slow->next;
/*Moving fast pointer two steps at a time */
fast = fast->next->next;
if(slow == fast){
loop_found = 1;
break;
}
}
if(loop_found){
/* We have detected a loop */
/*Let's count the number of nodes in this loop node */
ptr1 = fast;
while(ptr1 && ptr1->next != slow){
ptr1 = ptr1->next;
k++;
}
/* Now move the other pointer by K nodes */
ptr2 = head;
ptr1 = head;
for(i=0; i<k; i++){
ptr2 = ptr2->next;
}
/* Now if we move ptr1 and ptr2 with same speed they will meet at start of loop */
while(ptr1 != ptr2){
ptr1 = ptr1->next;
ptr2 = ptr2->next;
}
return ptr1->data;
}
答案 5 :(得分:1)
在大多数情况下,所有先前的答案都是正确的,但这是具有可视化和代码的逻辑的简化版本(适用于Python 3.7)
逻辑很简单,正如其他人解释的那样。我要创建乌龟/慢速和野兔/快速。如果我们以不同的速度移动两个指针,那么最终快将遇到慢速!您也可以将其视为大头钉圆形区域中的两个跑步者。如果快速奔跑的人不断绕行,那么它将遇到/超过慢奔跑的人。
因此,对于每次迭代,我们将以1速度移动龟/慢速指针,同时继续以2的速度递增或移动Hare / fast指针。一旦它们满足,我们便知道存在一个循环。这也称为Floyd's cycle-finding algorithm
这是执行此操作的Python代码(注意has_cycle方法是主要部分):
#!/usr/bin/env python3
class Node:
def __init__(self, data = None):
self.data = data
self.next = None
def strnode (self):
print(self.data)
class LinkedList:
def __init__(self):
self.numnodes = 0
self.head = None
def insertLast(self, data):
newnode = Node(data)
newnode.next = None
if self.head == None:
self.head = newnode
return
lnode = self.head
while lnode.next != None :
lnode = lnode.next
lnode.next = newnode # new node is now the last node
self.numnodes += 1
def has_cycle(self):
slow, fast = self.head ,self.head
while fast != None:
if fast.next != None:
fast = fast.next.next
else:
return False
slow = slow.next
if slow == fast:
print("--slow",slow.data, "fast",fast.data)
return True
return False
linkedList = LinkedList()
linkedList.insertLast("1")
linkedList.insertLast("2")
linkedList.insertLast("3")
# Create a loop for testing
linkedList.head.next.next.next = linkedList.head;
#let's check and see !
print(linkedList.has_cycle())
答案 6 :(得分:0)
boolean hasLoop(Node *head)
{
Node *current = head;
Node *check = null;
int firstPtr = 0;
int secondPtr = 2;
do {
if (check == current) return true;
if (firstPtr >= secondPtr){
check = current;
firstPtr = 0;
secondPtr= 2*secondPtr;
}
firstPtr ++;
} while (current = current->next());
return false;
}
另一个O(n)解决方案。
答案 7 :(得分:0)
当我查看所选答案时,我尝试了几个例子,发现:
如果(A1,B1),(A2,B2)......(AN,BN)是指针A和B的遍历
其中A步骤1元素和B步骤2元素,Ai和Bj是A和B遍历的节点,AN = BN。
然后,循环开始的节点是Ak,其中k = floor(N / 2)。
答案 8 :(得分:0)
答案 9 :(得分:0)
另一种解决方案
检测循环:
删除循环:
在步骤3中检测到循环后,将上一个节点的下一个值设置为NULL
#code
def detect_remove_loop(头)
cur_node = head
node_list = []
while cur_node.next is not None:
prev_node = cur_node
cur_node = cur_node.next
if cur_node not in node_list:
node_list.append(cur_node)
else:
print('Loop Detected')
prev_node.next = None
return
print('No Loop detected')
答案 10 :(得分:0)
首先,创建一个节点
struct Node {
int data;
struct Node* next;
};
全局初始化头指针
Struct Node* head = NULL;
在链接列表中插入一些数据
void insert(int newdata){
Node* newNode = new Node();
newNode->data = newdata;
newNode->next = head;
head = newNode;
}
创建一个函数detectLoop()
void detectLoop(){
if (head == NULL || head->next == NULL){
cout<< "\nNo Lopp Found in Linked List";
}
else{
Node* slow = head;
Node* fast = head->next;
while((fast && fast->next) && fast != NULL){
if(fast == slow){
cout<<"Loop Found";
break;
}
fast = fast->next->next;
slow = slow->next;
}
if(fast->next == NULL){
cout<<"Not Found";
}
}
}
从main()调用函数
int main()
{
insert(4);
insert(3);
insert(2);
insert(1);
//Created a Loop for Testing, Comment the next line to check the unloop linkedlist
head->next->next->next->next = head->next;
detectLoop();
//If you uncomment the display function and make a loop in linked list and then run the code you will find infinite loop
//display();
}
答案 11 :(得分:0)
bool FindLoop(struct node *head)
{
struct node *current1,*current2;
current1=head;
current2=head;
while(current1!=NULL && current2!= NULL && current2->next!= NULL)
{
current1=current1->next;
current2=current2->next->next;
if(current1==current2)
{
return true;
}
}
return false;
}
答案 12 :(得分:-1)
一种完全不同的方法: - 反转链表。 如果你再次到达头部时倒车,那么列表中有一个循环, 如果你得到NULL,那么没有循环。 总时间复杂度为O(n)