จะตรวจจับลูปในรายการที่เชื่อมโยงได้อย่างไร?


434

สมมติว่าคุณมีโครงสร้างรายการเชื่อมโยงใน Java มันประกอบไปด้วยโหนด:

class Node {
    Node next;
    // some user data
}

และแต่ละโหนดชี้ไปที่โหนดถัดไปยกเว้นโหนดสุดท้ายซึ่งมีค่า null สำหรับถัดไป สมมติว่ามีความเป็นไปได้ที่รายการสามารถมีลูป - เช่นโหนดสุดท้ายแทนการมีค่า null มีการอ้างอิงถึงหนึ่งในโหนดในรายการที่มาก่อนมัน

เป็นวิธีที่ดีที่สุดในการเขียนอะไร

boolean hasLoop(Node first)

ซึ่งจะกลับมาtrueถ้าโหนดที่กำหนดเป็นคนแรกของรายการที่มีวงและfalseอื่น ๆ ? คุณจะเขียนอย่างไรเพื่อให้มีเนื้อที่คงที่และใช้เวลาพอสมควร

นี่คือภาพของรายการที่มีลักษณะเป็นวง:

ข้อความแสดงแทน


50
ว้าว .. ฉันชอบที่จะทำงานให้กับนายจ้างคนนี้finite amount of space and a reasonable amount of time?:)
codaddict

10
@SLaks - การวนซ้ำไม่จำเป็นต้องวนซ้ำกลับไปที่โหนดแรก มันสามารถวนกลับไปได้ครึ่งทาง
jjujuma

109
คำตอบด้านล่างนี้มีค่าสำหรับการอ่าน แต่คำถามสัมภาษณ์แบบนี้มันแย่มาก คุณอาจรู้คำตอบ (เช่นคุณเห็นตัวแปรในอัลกอริทึมของ Floyd) หรือไม่และมันก็ไม่ได้ทำอะไรเลยเพื่อทดสอบการใช้เหตุผลหรือความสามารถในการออกแบบของคุณ
GaryF

3
เพื่อความเป็นธรรม "การรู้อัลกอริธึม" ส่วนใหญ่จะเป็นแบบนี้ - เว้นแต่คุณกำลังทำสิ่งต่าง ๆ ในระดับการวิจัย!
Larry

12
@GaryF แต่ถึงกระนั้นก็จะมีการเปิดเผยให้รู้ว่าพวกเขาจะทำอย่างไรเมื่อพวกเขาไม่รู้คำตอบ เช่นพวกเขาจะทำตามขั้นตอนพวกเขาจะทำงานกับใครพวกเขาจะทำอย่างไรเพื่อเอาชนะความรู้ด้านอัลกอริทึมที่ขาดไป
Chris Knight

คำตอบ:


538

คุณสามารถทำให้การใช้งานของอัลกอริทึมวงจรการค้นพบของฟลอยด์ยังเป็นที่รู้จักเต่าและอัลกอริทึมกระต่าย

ความคิดที่จะมีการอ้างอิงถึงสองรายการและย้ายไปที่ความเร็วที่แตกต่างกัน ย้ายไปข้างหน้าหนึ่ง1โหนดและอีก2โหนดหนึ่ง

  • หากรายการที่เชื่อมโยงมีการวนซ้ำพวกเขาจะได้พบกันอย่างแน่นอน
  • อื่นทั้งสองอ้างอิง (หรือของพวกเขาnext) nullจะกลายเป็น

ฟังก์ชั่น Java ใช้อัลกอริทึม:

boolean hasLoop(Node first) {

    if(first == null) // list does not exist..so no loop either
        return false;

    Node slow, fast; // create two references.

    slow = fast = first; // make both refer to the start of the list

    while(true) {

        slow = slow.next;          // 1 hop

        if(fast.next != null)
            fast = fast.next.next; // 2 hops
        else
            return false;          // next node null => no loop

        if(slow == null || fast == null) // if either hits null..no loop
            return false;

        if(slow == fast) // if the two ever meet...we must have a loop
            return true;
    }
}

29
ยังต้องทำการตรวจสอบโมฆะfast.nextก่อนโทรnextอีกครั้ง:if(fast.next!=null)fast=fast.next.next;
cmptrgeekken

12
คุณควรตรวจสอบไม่เพียง (ช้า == เร็ว) แต่: (ช้า == เร็ว || ช้า.next == เร็ว) เพื่อป้องกันไม่ให้กระโดดเร็วกว่าช้า
Oleg Razgulyaev

13
ผมผิด: ได้อย่างรวดเร็วไม่สามารถกระโดดข้ามช้าเพราะกระโดดข้ามช้าอย่างรวดเร็วขั้นตอนต่อไปควรมี POS เช่นเดียวกับช้า :)
Oleg Razgulyaev

4
ตรวจสอบช้า == null ซ้ำซ้อนเว้นแต่รายการมีเพียงหนึ่งโหนด คุณสามารถกำจัดการโทรหนึ่งครั้งไปยัง Node.next นี่คือลูปเวอร์ชันที่ง่ายกว่าและเร็วกว่า: pastie.org/927591
Kay Sarraute

22
คุณควรอ้างอิงการอ้างอิงของคุณจริงๆ อัลกอรึทึมนี้คิดค้นโดยโรเบิร์ตฟลอยด์ในช่วงทศวรรษที่ 60 รู้จักกันในชื่ออัลกอริธึมการค้นหาวัฏจักรของฟลอยด์ อัลกอริทึมเต่าและกระต่าย
joshperry

127

นี่คือการปรับแต่งโซลูชัน Fast / Slow ซึ่งจัดการรายการความยาวคี่ได้อย่างถูกต้องและปรับปรุงความชัดเจน

boolean hasLoop(Node first) {
    Node slow = first;
    Node fast = first;

    while(fast != null && fast.next != null) {
        slow = slow.next;          // 1 hop
        fast = fast.next.next;     // 2 hops 

        if(slow == fast)  // fast caught up to slow, so there is a loop
            return true;
    }
    return false;  // fast reached null, so the list terminates
}

2
สวยและกระชับ รหัสนี้สามารถปรับให้เหมาะสมโดยการตรวจสอบว่าช้า == เร็ว || (fast.next! = null && slow = fast.next); :)
arachnode.net

11
@ arachnode.net นั่นไม่ใช่การเพิ่มประสิทธิภาพ ถ้าเช่นslow == fast.nextนั้นslowจะเท่ากับfastการทำซ้ำครั้งถัดไปมาก มันช่วยประหยัดการทำซ้ำได้เพียงครั้งเดียวโดยใช้ค่าใช้จ่ายในการทดสอบเพิ่มเติมสำหรับการทำซ้ำทุกครั้ง
Jason C

@ ana01 slowไม่สามารถกลายเป็นโมฆะก่อนได้fastเนื่องจากเป็นไปตามเส้นทางการอ้างอิงเดียวกัน (เว้นแต่คุณจะมีการแก้ไขรายการพร้อมกันซึ่งการเดิมพันทั้งหมดจะปิด)
Dave L.

ด้วยความอยากรู้วิธีนี้ใช้กับเลขคี่ได้อย่างไร ไม่สามารถผ่านเต่าไปยังรายการที่มีการเชื่อมโยงความยาวคี่ได้หรือไม่
theGreenCabbage

1
@TheGreenCabbage วนซ้ำของกระต่ายที่ได้รับ 1 ขั้นต่อไปข้างหน้าของเต่า ดังนั้นหากกระต่ายอยู่ข้างหลัง 3 ขั้นดังนั้นการทำซ้ำครั้งต่อไปจะใช้สองกระโดดและเต่าจะกระโดดหนึ่งครั้งและตอนนี้กระต่ายอยู่ข้างหลัง 2 ก้าว หลังจากการทำซ้ำครั้งต่อไปกระต่ายอยู่ข้างหลัง 1 ฮอปและจากนั้นมันก็ถูกจับได้อย่างแน่นอน หากกระต่ายใช้เวลา 3 กระโดดในขณะที่เต่าจับตัวหนึ่งมันก็สามารถข้ามได้เพราะมันจะได้รับ 2 ครั้งในแต่ละครั้ง แต่เนื่องจากมันได้กำไรเพียง 1 ครั้งต่อการทำซ้ำจึงไม่สามารถข้ามได้
Dave L.

52

ดีกว่าอัลกอริธึมของฟลอยด์

Richard Brent อธิบายอัลกอริธึมการตรวจหาวัฏจักรทางเลือกซึ่งค่อนข้างเหมือนกระต่ายและเต่า [วงจรของ Floyd] ยกเว้นว่าโหนดช้าที่นี่ไม่เคลื่อนที่ แต่ต่อมา "หายตัว" ไปยังตำแหน่งของโหนดรวดเร็วที่คงที่ ช่วงเวลา

คำอธิบายมีอยู่ที่นี่: http://www.siafoo.net/algorithm/11 เบรนต์อ้างว่าอัลกอริธึมของเขาเร็วกว่าอัลกอริธึมรอบของฟลอยด์ 24 ถึง 36% ความซับซ้อนของเวลา O (n) ความซับซ้อนของพื้นที่ O (1)

public static boolean hasLoop(Node root){
    if(root == null) return false;

    Node slow = root, fast = root;
    int taken = 0, limit = 2;

    while (fast.next != null) {
        fast = fast.next;
        taken++;
        if(slow == fast) return true;

        if(taken == limit){
            taken = 0;
            limit <<= 1;    // equivalent to limit *= 2;
            slow = fast;    // teleporting the turtle (to the hare's position) 
        }
    }
    return false;
}

คำตอบนี้ยอดเยี่ยมมาก!
valin077

1
จริงๆชอบคำตอบของคุณรวมไว้ในบล็อกของฉัน - k2code.blogspot.in/2010/04/...
kinshuk4

ทำไมคุณต้องตรวจสอบslow.next != null? เท่าที่ฉันเห็นslowอยู่ข้างหลังหรือเท่ากับfastเสมอ
TWiStErRob

ฉันทำสิ่งนี้มานานแล้วเมื่อฉันเริ่มเรียนรู้อัลกอริทึม แก้ไขรหัส ขอบคุณ :)
Ashok Bijoy Debnath

50

ทางเลือกอื่นสำหรับ Turtle and Rabbit ซึ่งไม่ค่อยดีเท่าที่ฉันเปลี่ยนรายการชั่วคราว:

ความคิดคือการเดินรายการและย้อนกลับในขณะที่คุณไป จากนั้นเมื่อคุณไปถึงโหนดแรกที่มีการเยี่ยมชมแล้วตัวชี้ถัดไปของมันจะชี้ไปที่ "ย้อนกลับ" ทำให้การวนซ้ำดำเนินต่อไปfirstอีกครั้งโดยที่จุดสิ้นสุดนั้น

Node prev = null;
Node cur = first;
while (cur != null) {
    Node next = cur.next;
    cur.next = prev;
    prev = cur;
    cur = next;
}
boolean hasCycle = prev == first && first != null && first.next != null;

// reconstruct the list
cur = prev;
prev = null;
while (cur != null) {
    Node next = cur.next;
    cur.next = prev;
    prev = cur;
    cur = next;
}

return hasCycle;

รหัสทดสอบ:

static void assertSameOrder(Node[] nodes) {
    for (int i = 0; i < nodes.length - 1; i++) {
        assert nodes[i].next == nodes[i + 1];
    }
}

public static void main(String[] args) {
    Node[] nodes = new Node[100];
    for (int i = 0; i < nodes.length; i++) {
        nodes[i] = new Node();
    }
    for (int i = 0; i < nodes.length - 1; i++) {
        nodes[i].next = nodes[i + 1];
    }
    Node first = nodes[0];
    Node max = nodes[nodes.length - 1];

    max.next = null;
    assert !hasCycle(first);
    assertSameOrder(nodes);
    max.next = first;
    assert hasCycle(first);
    assertSameOrder(nodes);
    max.next = max;
    assert hasCycle(first);
    assertSameOrder(nodes);
    max.next = nodes[50];
    assert hasCycle(first);
    assertSameOrder(nodes);
}

การย้อนกลับทำงานอย่างถูกต้องหรือไม่เมื่อลูปชี้ไปที่โหนดอื่นที่ไม่ใช่ลำดับแรก หากรายการที่เชื่อมโยงเริ่มต้นเป็นเช่นนี้ 1-> 2-> 3-> 4-> 5-> 2 (มีวงรอบตั้งแต่ 5 ถึง 2) รายการที่กลับรายการจะมีลักษณะดังนี้ 1> 2 <-3 <-4 <-5? และถ้าสิ่งที่ตรงกันข้ามคือรายการที่สร้างขึ้นใหม่จะถูกทำให้แน่นขึ้น?
Zenil

1
@ Zenil: นั่นเป็นเหตุผลที่ฉันเขียนว่า testcase ล่าสุดที่ซึ่งโหนดสุดท้ายชี้ไปที่ตรงกลางของรายการ หากการสร้างใหม่ไม่ทำงานการทดสอบนั้นจะล้มเหลว เกี่ยวกับตัวอย่างของคุณ: การกลับรายการของ 1-> 2-> 3-> 5-> 2 จะเป็น 1-> 2-> 5-> 4-> 3-> 2, เนื่องจากการวนซ้ำจะหยุดเพียงครั้งเดียวเมื่อสิ้นสุดรายการ ถึงไม่ใช่เมื่อถึงจุดสิ้นสุดของลูป (ซึ่งเราไม่สามารถตรวจจับได้ง่าย) ถึงแล้ว
meriton

28

เต่าและกระต่าย

ดูอัลกอริทึม Rho ของ Pollardอัลกอริทึมของโรพอลลาร์ไม่ใช่ปัญหาเดียวกัน แต่บางทีคุณอาจเข้าใจตรรกะจากมันและนำไปใช้กับรายการที่ลิงก์

(หากคุณขี้เกียจคุณสามารถตรวจสอบวงจรการตรวจสอบ - ตรวจสอบส่วนที่เกี่ยวกับเต่าและกระต่าย)

ใช้เวลาเชิงเส้นและตัวชี้พิเศษ 2 ตัวเท่านั้น

ใน Java:

boolean hasLoop( Node first ) {
    if ( first == null ) return false;

    Node turtle = first;
    Node hare = first;

    while ( hare.next != null && hare.next.next != null ) {
         turtle = turtle.next;
         hare = hare.next.next;

         if ( turtle == hare ) return true;
    }

    return false;
}

(วิธีการแก้ปัญหาส่วนใหญ่ไม่ได้ตรวจสอบทั้งสำหรับnextและnext.nextโมฆะนอกจากนี้เนื่องจากเต่าอยู่ด้านหลังเสมอคุณไม่ต้องตรวจสอบเพื่อหา null - กระต่ายทำอย่างนั้นแล้ว)


13

ผู้ใช้unicornaddictมีอัลกอริทึมที่ดีข้างต้น แต่น่าเสียดายที่มันมีข้อผิดพลาดสำหรับรายการที่ไม่มีการวนซ้ำของความยาวคี่> = 3 ปัญหาคือfastสามารถติด "อยู่" ก่อนสิ้นสุดรายการslowจับได้และ ตรวจพบลูป (ผิด)

นี่คืออัลกอริทึมที่ถูกต้อง

static boolean hasLoop(Node first) {

    if(first == null) // list does not exist..so no loop either.
        return false;

    Node slow, fast; // create two references.

    slow = fast = first; // make both refer to the start of the list.

    while(true) {
        slow = slow.next;          // 1 hop.
        if(fast.next == null)
            fast = null;
        else
            fast = fast.next.next; // 2 hops.

        if(fast == null) // if fast hits null..no loop.
            return false;

        if(slow == fast) // if the two ever meet...we must have a loop.
            return true;
    }
}

10

ในบริบทนี้มีการโหลดไปยังเนื้อหาต้นฉบับทุกที่ ฉันแค่ต้องการโพสต์การแสดงแผนภาพที่ช่วยให้ฉันเข้าใจแนวคิด

เมื่อพบกันอย่างรวดเร็วและช้า ณ จุด p

ระยะทางเดินทางโดยเร็ว = a + b + c + b = a + 2b + c

ระยะทางเดินทางโดยช้า = a + b

เนื่องจากความเร็วนั้นเร็วกว่าช้ากว่าถึง 2 เท่า ดังนั้นA + 2b + c = 2 (A + B)แล้วเราได้รับA = C

ดังนั้นเมื่อตัวชี้แบบช้าอื่นวิ่งอีกครั้งจากหัวถึง qในเวลาเดียวกันตัวชี้แบบเร็วจะทำงานจากp ถึง qดังนั้นพวกเขาจึงพบกันที่จุดqด้วยกัน

ป้อนคำอธิบายรูปภาพที่นี่

public ListNode detectCycle(ListNode head) {
    if(head == null || head.next==null)
        return null;

    ListNode slow = head;
    ListNode fast = head;

    while (fast!=null && fast.next!=null){
        fast = fast.next.next;
        slow = slow.next;

        /*
        if the 2 pointers meet, then the 
        dist from the meeting pt to start of loop 
        equals
        dist from head to start of loop
        */
        if (fast == slow){ //loop found
            slow = head;
            while(slow != fast){
                slow = slow.next;
                fast = fast.next;
            }
            return slow;
        }            
    }
    return null;
}

2
รูปภาพมีมูลค่ามากกว่าพันคำ ขอบคุณสำหรับคำอธิบายที่เรียบร้อยและเรียบง่าย!
Calios

1
คำอธิบายที่ดีที่สุดบนอินเทอร์เน็ต จะเพิ่มว่านี่เป็นการพิสูจน์ว่าตัวชี้ที่เร็วและช้ามาบรรจบกันหลังจากเวลาเชิงเส้น
VarunPandey

ถ้าaมีขนาดใหญ่กว่าความยาวของลูปการทำแบบเร็วจะทำให้หลายลูปและสูตรdistance (fast) = a + b + b + cจะเปลี่ยนเป็นการa + (b+c) * k + bแนะนำพารามิเตอร์เพิ่มเติมkที่นับจำนวนของลอปป์ที่ทำโดยแบบเร็ว
Ben

9

ขั้นตอนวิธี

public static boolean hasCycle (LinkedList<Node> list)
{
    HashSet<Node> visited = new HashSet<Node>();

    for (Node n : list)
    {
        visited.add(n);

        if (visited.contains(n.next))
        {
            return true;
        }
    }

    return false;
}

ความซับซ้อน

Time ~ O(n)
Space ~ O(n)

ความซับซ้อนของอวกาศเป็นอย่างไร O (2n)
Programmer345

@ user3543449 คุณถูกต้องมันควรจะเป็นเพียงแค่nแก้ไข
Khaled.K

1
นี่เป็นเวลาจริง ~ O (n ^ 2) เนื่องจากแต่ละรายการมีการตรวจสอบ ArrayList ที่ใช้ O (n) และมี O (n) ของพวกเขา ใช้ HashSet แทนสำหรับเวลาเชิงเส้น
Dave L.

3
นี้ไม่ได้ทดสอบรอบ แต่สำหรับค่าที่ซ้ำกันโดยใช้องค์ประกอบและequals hashCodeมันไม่เหมือนกัน และมันnullเป็นองค์ประกอบสุดท้าย LinkedListและคำถามที่ไม่ได้พูดอะไรเกี่ยวกับการเก็บโหนดใน
Lii

2
@Lii เป็นรหัสเทียมไม่ใช่รหัส Java นั่นคือสาเหตุที่ฉันตั้งชื่อด้วยAlgorithm
Khaled.K

8

ต่อไปนี้อาจไม่ใช่วิธีที่ดีที่สุด - เป็น O (n ^ 2) อย่างไรก็ตามควรให้บริการเพื่อให้งานเสร็จ (ในที่สุด)

count_of_elements_so_far = 0;
for (each element in linked list)
{
    search for current element in first <count_of_elements_so_far>
    if found, then you have a loop
    else,count_of_elements_so_far++;
}

คุณจะรู้ได้อย่างไรว่ามีองค์ประกอบกี่รายการในรายการที่จะทำเพื่อ ()
Jethro Larson

@JethroLarson: โหนดสุดท้ายในรายการที่เชื่อมโยงชี้ไปยังที่อยู่ที่รู้จัก (ในการใช้งานหลาย ๆ อันนี่คือ NULL) ยุติการ for-loop เมื่อถึงที่อยู่ที่รู้จัก
ปาร์กกี้

3
public boolean hasLoop(Node start){   
   TreeSet<Node> set = new TreeSet<Node>();
   Node lookingAt = start;

   while (lookingAt.peek() != null){
       lookingAt = lookingAt.next;

       if (set.contains(lookingAt){
           return false;
        } else {
        set.put(lookingAt);
        }

        return true;
}   
// Inside our Node class:        
public Node peek(){
   return this.next;
}

ให้อภัยฉันไม่รู้ (ฉันยังค่อนข้างใหม่กับ Java และการเขียนโปรแกรม) แต่ทำไมจะไม่ทำงานข้างต้น

ฉันเดาว่านี่จะไม่ช่วยแก้ปัญหาเรื่องพื้นที่คงที่ ... แต่อย่างน้อยในเวลาที่เหมาะสม มันจะใช้พื้นที่ของรายการที่เชื่อมโยงรวมถึงพื้นที่ของชุดที่มีองค์ประกอบ n (โดยที่ n คือจำนวนองค์ประกอบในรายการที่เชื่อมโยงหรือจำนวนองค์ประกอบจนกว่าจะถึงวง) และสำหรับเวลาการวิเคราะห์กรณีที่เลวร้ายที่สุดฉันคิดว่าจะแนะนำ O (nlog (n)) SortedSet การค้นหาสำหรับ contain () เป็น log (n) (ตรวจสอบ javadoc แต่ฉันค่อนข้างแน่ใจว่าโครงสร้างพื้นฐานของ TreeSet คือ TreeMap ซึ่งเป็นต้นไม้สีแดงดำ) และในกรณีที่แย่ที่สุด (ไม่มีลูป หรือวนที่ปลายสุด) จะต้องค้นหา


2
ใช่โซลูชันที่มีชุดการทำงานบางประเภททำงานได้ดี แต่ต้องใช้พื้นที่ตามสัดส่วนกับขนาดของรายการ
jjujuma

3

หากเราได้รับอนุญาตให้ฝังชั้นเรียนNodeฉันจะแก้ปัญหาตามที่ฉันใช้งานด้านล่าง hasLoop()วิ่งใน O (n) counterเวลาและใช้เวลาเพียงพื้นที่ของ สิ่งนี้ดูเหมือนจะเป็นทางออกที่เหมาะสมหรือไม่? หรือมีวิธีที่จะทำโดยไม่ฝังNode? (เห็นได้ชัดว่าในการใช้งานจริงจะมีวิธีการมากขึ้นเช่นRemoveNode(Node n)ฯลฯ )

public class LinkedNodeList {
    Node first;
    Int count;

    LinkedNodeList(){
        first = null;
        count = 0;
    }

    LinkedNodeList(Node n){
        if (n.next != null){
            throw new error("must start with single node!");
        } else {
            first = n;
            count = 1;
        }
    }

    public void addNode(Node n){
        Node lookingAt = first;

        while(lookingAt.next != null){
            lookingAt = lookingAt.next;
        }

        lookingAt.next = n;
        count++;
    }

    public boolean hasLoop(){

        int counter = 0;
        Node lookingAt = first;

        while(lookingAt.next != null){
            counter++;
            if (count < counter){
                return false;
            } else {
               lookingAt = lookingAt.next;
            }
        }

        return true;

    }



    private class Node{
        Node next;
        ....
    }

}

1

คุณสามารถทำได้แม้ในเวลา O (1) คงที่ (แม้ว่าจะไม่เร็วมากหรือมีประสิทธิภาพ): มีจำนวน จำกัด ของโหนดที่หน่วยความจำของคอมพิวเตอร์ของคุณสามารถเก็บได้กล่าว N บันทึก หากคุณสำรวจมากกว่า N บันทึกคุณจะมีลูป


นี่ไม่ใช่ O (1) อัลกอริทึมนี้ไม่มีความซับซ้อนของเวลาที่มีความหมายในสัญกรณ์ใหญ่ สัญกรณ์รูปตัวใหญ่จะบอกคุณเกี่ยวกับประสิทธิภาพในขีด จำกัดเมื่อขนาดอินพุตเข้าสู่อินฟินิตี้ ดังนั้นหากขั้นตอนวิธีการของคุณสร้างขึ้นบนสมมติฐานที่ว่ามีเป็นรายการไม่มีมีมากกว่าองค์ประกอบ N สำหรับบางขนาดใหญ่ N, ขีด จำกัด ของการรันไทม์ขนาดรายการแนวทางอินฟินิตี้จะไม่ได้กำหนด ดังนั้นความซับซ้อนไม่ใช่ "O (อะไร)"
fgp

1
 // To detect whether a circular loop exists in a linked list
public boolean findCircularLoop() {
    Node slower, faster;
    slower = head;
    faster = head.next; // start faster one node ahead
    while (true) {

        // if the faster pointer encounters a NULL element
        if (faster == null || faster.next == null)
            return false;
        // if faster pointer ever equals slower or faster's next
        // pointer is ever equal to slower then it's a circular list
        else if (slower == faster || slower == faster.next)
            return true;
        else {
            // advance the pointers
            slower = slower.next;
            faster = faster.next.next;
        }
    }
}

1
boolean hasCycle(Node head) {

    boolean dec = false;
    Node first = head;
    Node sec = head;
    while(first != null && sec != null)
    {
        first = first.next;
        sec = sec.next.next;
        if(first == sec )
        {
            dec = true;
            break;
        }

    }
        return dec;
}

ใช้ฟังก์ชั่นด้านบนเพื่อตรวจสอบการวนรอบในรายการที่เชื่อมโยงใน java


2
เกือบเหมือนคำตอบของฉันด้านบน แต่มีปัญหา มันจะโยน NullPointerException สำหรับรายการที่มีรายการความยาวคี่ (ไม่มีลูป) ตัวอย่างเช่นถ้า head.next เป็นโมฆะแล้ว sec.next.next จะส่ง NPE
Dave L.

1

การตรวจจับลูปในรายการที่ลิงก์สามารถทำได้ด้วยวิธีใดวิธีหนึ่งที่ง่ายที่สุดซึ่งส่งผลให้เกิดความซับซ้อน O (N) โดยใช้ hashmap หรือ O (NlogN) โดยใช้วิธีการเรียงลำดับ

เมื่อคุณสำรวจรายการที่เริ่มต้นจากส่วนหัวให้สร้างรายการที่อยู่ที่เรียงลำดับแล้ว เมื่อคุณแทรกที่อยู่ใหม่ให้ตรวจสอบว่าที่อยู่นั้นมีอยู่แล้วในรายการที่เรียงลำดับซึ่งใช้ความซับซ้อน O (logN)


ความซับซ้อนของ apporach นี้คือ O (N log N)
fgp

0

ฉันไม่สามารถมองเห็นวิธีการนี้ใช้เวลาหรือพื้นที่คงที่ทั้งสองจะเพิ่มขึ้นตามขนาดของรายการ

ฉันจะใช้ IdentityHashMap (ระบุว่ายังไม่มี IdentityHashSet) และเก็บแต่ละโหนดลงในแผนที่ ก่อนที่จะจัดเก็บโหนดคุณจะต้องเรียกว่ามีคีย์อยู่ ถ้า Node มีอยู่แล้วคุณจะมีรอบ

ItentityHashMap ใช้ == แทน. equals เพื่อให้คุณตรวจสอบว่าวัตถุอยู่ในหน่วยความจำแทนที่จะเป็นเนื้อหาเดียวกันหรือไม่


3
แน่นอนว่ามันเป็นไปไม่ได้ที่จะใช้เวลาจำนวนหนึ่งคงที่เนื่องจากอาจมีการวนซ้ำในตอนท้ายสุดของรายการดังนั้นจึงต้องเข้าชมทั้งรายการ อย่างไรก็ตามอัลกอริทึมเร็ว / ช้าแสดงวิธีแก้ปัญหาโดยใช้หน่วยความจำจำนวนคงที่
Dave L.

มันไม่ได้อ้างถึงว่ามันเป็นพฤติกรรมแบบซีมโทติคกล่าวคือมันเป็นเส้นตรง O (n) โดยที่ n คือความยาวของรายการ แก้ไขจะเป็น O (1)
Mark Robson

0

ฉันอาจจะช้าและใหม่มากที่จะจัดการกับหัวข้อนี้ แต่ยังคง..

เหตุใดจึงไม่ชี้ที่อยู่ของโหนดและชี้ "ถัดไป" ที่ชี้ไปที่เก็บไว้ในตาราง

หากเราจัดระเบียบด้วยวิธีนี้

node present: (present node addr) (next node address)

node 1: addr1: 0x100 addr2: 0x200 ( no present node address till this point had 0x200)
node 2: addr2: 0x200 addr3: 0x300 ( no present node address till this point had 0x300)
node 3: addr3: 0x300 addr4: 0x400 ( no present node address till this point had 0x400)
node 4: addr4: 0x400 addr5: 0x500 ( no present node address till this point had 0x500)
node 5: addr5: 0x500 addr6: 0x600 ( no present node address till this point had 0x600)
node 6: addr6: 0x600 addr4: 0x400 ( ONE present node address till this point had 0x400)

ดังนั้นจึงมีวงจรที่เกิดขึ้น


โซลูชันของคุณไม่ผ่านข้อกำหนด "จำนวนพื้นที่คงที่"
Arnaud

0

นี่คือรหัสที่รันได้ของฉัน

สิ่งที่ฉันทำคือการเคารพรายการเชื่อมโยงโดยใช้สามโหนดชั่วคราว (ความซับซ้อนของพื้นที่O(1)) ที่ติดตามการเชื่อมโยง

ข้อเท็จจริงที่น่าสนใจเกี่ยวกับการทำคือช่วยตรวจสอบวงจรในรายการที่เชื่อมโยงเพราะเมื่อคุณไปข้างหน้าคุณไม่คาดหวังว่าจะกลับไปที่จุดเริ่มต้น (โหนดรูท) และหนึ่งในโหนดชั่วคราวควรเป็นโมฆะเว้นแต่คุณ มีวงจรซึ่งหมายความว่ามันชี้ไปที่โหนดรูต

ความซับซ้อนเวลาของขั้นตอนวิธีนี้เป็นและความซับซ้อนของพื้นที่O(n)O(1)

นี่คือโหนดคลาสสำหรับรายการที่ลิงก์:

public class LinkedNode{
    public LinkedNode next;
}

นี่คือรหัสหลักที่มีกรณีทดสอบอย่างง่าย ๆ ของสามโหนดที่โหนดสุดท้ายชี้ไปที่โหนดที่สอง:

    public static boolean checkLoopInLinkedList(LinkedNode root){

        if (root == null || root.next == null) return false;

        LinkedNode current1 = root, current2 = root.next, current3 = root.next.next;
        root.next = null;
        current2.next = current1;

        while(current3 != null){
            if(current3 == root) return true;

            current1 = current2;
            current2 = current3;
            current3 = current3.next;

            current2.next = current1;
        }
        return false;
    }

นี่คือกรณีทดสอบอย่างง่าย ๆ ของสามโหนดที่โหนดสุดท้ายชี้ไปที่โหนดที่สอง:

public class questions{
    public static void main(String [] args){

        LinkedNode n1 = new LinkedNode();
        LinkedNode n2 = new LinkedNode();
        LinkedNode n3 = new LinkedNode();
        n1.next = n2;
        n2.next = n3;
        n3.next = n2;

        System.out.print(checkLoopInLinkedList(n1));
    }
}

0

รหัสนี้ได้รับการปรับให้เหมาะสมและจะให้ผลลัพธ์ที่รวดเร็วกว่าเมื่อเลือกไว้เป็นคำตอบที่ดีที่สุดรหัสนี้จะช่วยในการไล่ล่าตัวชี้ไปข้างหน้าและข้างหลังซึ่งจะเกิดขึ้นในกรณีต่อไปนี้หากเราทำตาม 'ดีที่สุด คำตอบ 'วิธีการดูผ่านระยะแห้งของสิ่งต่อไปนี้และคุณจะรู้ว่าสิ่งที่ฉันพยายามจะพูดจากนั้นดูปัญหาผ่านวิธีการที่กำหนดด้านล่างและวัดหมายเลข ขั้นตอนเพื่อค้นหาคำตอบ

1-> 2-> 9-> 3 ^ -------- ^

นี่คือรหัส:

boolean loop(node *head)
{
 node *back=head;
 node *front=head;

 while(front && front->next)
 {
  front=front->next->next;
  if(back==front)
  return true;
  else
  back=back->next;
 }
return false
}

คุณแน่ใจหรือว่าสิ่งนี้ให้ผลลัพธ์ที่ถูกต้องในทุกสถานการณ์? หากคุณเรียกใช้อัลกอริทึมนี้ในรายการ 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 3 -> ... ฉันเชื่อว่ามันจะคืนค่า 4 เป็นหัวในขณะที่คุณต้องการ 3.
Sunreef

คำถามคือการค้นหาว่ามีลูปอยู่หรือไม่ในกรณีนี้ใช่คำถามจะทำงานได้ดีและได้ผลลัพธ์บูลีนที่ต้องการสำหรับเคสหากคุณต้องการโหนดที่แน่นอนจากการที่ลูปเริ่มต้นเราจะ จำเป็นต้องเพิ่มบางสิ่งเพิ่มเติมลงในรหัส แต่เท่าที่การสร้างผลลัพธ์เป็นสิ่งที่เกี่ยวข้องจะทำให้ข้อสรุปเร็วขึ้น
Sarthak Mehra

คุณไม่ได้อ่านคำถามอย่างถูกต้อง: วิธีที่ดีที่สุดในการเขียนboolean hasLoop(Node first)ซึ่งจะกลับมาจริงถ้าโหนดที่กำหนดเป็นครั้งแรกของรายการที่มีวงและเท็จอย่างอื่น?
Sunreef

นี่คือการรันแบบแห้งสำหรับรายการของคุณค่าแรกหมายถึงตัวชี้หลังและส่วนที่สองหมายถึงตัวชี้ไปข้างหน้า (1,1) - (1,3) - (2,3) - (2,5) - (2,5) - (3,5) - (3,7) - (4,7) - (4,4)
Sarthak Mehra

อันที่จริงฉันรู้แล้วว่ามีสองวิธีในการเข้าใจคำถาม (หรืออย่างน้อยฉันก็เห็นการตีความสองแบบที่แตกต่างกัน) อัลกอริทึมของคุณถูกต้องหากคุณเพิ่งค้นหาว่ามีลูปหรือไม่ แต่ฉันคิดว่าคำถามกำลังถามว่าลูปเริ่มต้นจากที่ใด
Sunreef

0

นี่คือทางออกของฉันใน java

boolean detectLoop(Node head){
    Node fastRunner = head;
    Node slowRunner = head;
    while(fastRunner != null && slowRunner !=null && fastRunner.next != null){
        fastRunner = fastRunner.next.next;
        slowRunner = slowRunner.next;
        if(fastRunner == slowRunner){
            return true;
        }
    }
    return false;
}

0

คุณอาจใช้อัลกอริทึมเต่าของฟลอยด์ตามที่แนะนำในคำตอบข้างต้นเช่นกัน

อัลกอริทึมนี้สามารถตรวจสอบว่ารายการที่เชื่อมโยงเดี่ยวมีรอบปิด สิ่งนี้สามารถทำได้โดยการวนซ้ำรายการด้วยสองพอยน์เตอร์ที่จะเคลื่อนที่ด้วยความเร็วที่แตกต่างกัน ด้วยวิธีนี้หากมีรอบสองตัวชี้จะพบในบางจุดในอนาคต

โปรดอย่าลังเลที่จะตรวจสอบการโพสต์บล็อกของฉันในโครงสร้างข้อมูลรายการที่เชื่อมโยงซึ่งฉันได้รวมข้อมูลโค้ดด้วยการใช้อัลกอริทึมดังกล่าวข้างต้นในภาษาจาวา

ความนับถือ,

Andreas (@xnorcode)


0

นี่คือทางออกสำหรับการตรวจจับรอบ

public boolean hasCycle(ListNode head) {
            ListNode slow =head;
            ListNode fast =head;

            while(fast!=null && fast.next!=null){
                slow = slow.next; // slow pointer only one hop
                fast = fast.next.next; // fast pointer two hops 

                if(slow == fast)    return true; // retrun true if fast meet slow pointer
            }

            return false; // return false if fast pointer stop at end 
        }

0

// ฟังก์ชั่นลิสต์ค้นหารายชื่อที่เชื่อมโยง

int findLoop(struct Node* head)
{
    struct Node* slow = head, *fast = head;
    while(slow && fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
            return 1;
    }
 return 0;
}

-1

วิธีนี้มีค่าใช้จ่ายด้านพื้นที่ แต่มีการนำไปใช้ง่ายกว่า:

สามารถระบุ Loop ได้โดยการเก็บโหนดใน Map และก่อนที่จะวางโหนด; ตรวจสอบว่ามีโหนดอยู่แล้ว หากมีโหนดอยู่แล้วในแผนที่นั่นหมายความว่ารายการที่เชื่อมโยงมีการวนซ้ำ

public boolean loopDetector(Node<E> first) {  
       Node<E> t = first;  
       Map<Node<E>, Node<E>> map = new IdentityHashMap<Node<E>, Node<E>>();  
       while (t != null) {  
            if (map.containsKey(t)) {  
                 System.out.println(" duplicate Node is --" + t  
                           + " having value :" + t.data);  

                 return true;  
            } else {  
                 map.put(t, t);  
            }  
            t = t.next;  
       }  
       return false;  
  }  

สิ่งนี้ไม่เป็นไปตามจำนวนพื้นที่จำกัด ที่กำหนดในคำถาม!
dedek

ยอมรับว่ามันมีพื้นที่เหนือศีรษะ เป็นอีกแนวทางหนึ่งในการแก้ปัญหานี้ วิธีที่ชัดเจนคือเต่าและอัลกอริธึมฮาร์ซ
rai.skumar

@downvoter มันจะมีประโยชน์ถ้าคุณสามารถอธิบายเหตุผลได้เช่นกัน
rai.skumar

-2
public boolean isCircular() {

    if (head == null)
        return false;

    Node temp1 = head;
    Node temp2 = head;

    try {
        while (temp2.next != null) {

            temp2 = temp2.next.next.next;
            temp1 = temp1.next;

            if (temp1 == temp2 || temp1 == temp2.next) 
                return true;    

        }
    } catch (NullPointerException ex) {
        return false;

    }

    return false;

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.