Làm thế nào để phát hiện một vòng lặp trong danh sách liên kết?


434

Giả sử bạn có cấu trúc danh sách được liên kết trong Java. Nó được tạo thành từ các nút:

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

và mỗi nút chỉ đến nút tiếp theo, ngoại trừ nút cuối cùng, có giá trị null cho tiếp theo. Giả sử có khả năng danh sách có thể chứa một vòng lặp - tức là Nút cuối cùng, thay vì có null, có một tham chiếu đến một trong các nút trong danh sách đi trước nó.

Cách viết tốt nhất

boolean hasLoop(Node first)

cái nào sẽ trả về truenếu Node đã cho là đầu tiên của danh sách có vòng lặp, và falsengược lại? Làm thế nào bạn có thể viết để nó cần một không gian cố định và một khoảng thời gian hợp lý?

Đây là hình ảnh của một danh sách với một vòng lặp trông như thế nào:

văn bản thay thế


50
Wow..Tôi rất thích làm việc cho nhà tuyển dụng này finite amount of space and a reasonable amount of time?:)
codaddict

10
@SLaks - vòng lặp không cần thiết lặp lại cho nút đầu tiên. Nó có thể lặp lại nửa chừng.
jjujuma

109
Các câu trả lời dưới đây rất đáng đọc, nhưng các câu hỏi phỏng vấn như thế này rất tệ. Bạn có thể biết câu trả lời (tức là bạn đã thấy một biến thể trên thuật toán của Floyd) hoặc bạn không biết và nó không làm gì để kiểm tra khả năng suy luận hoặc khả năng thiết kế của bạn.
GaryF

3
Công bằng mà nói, hầu hết "các thuật toán biết" đều như thế này - trừ khi bạn đang làm những việc ở cấp độ nghiên cứu!
Larry

12
@GaryF Và nó sẽ được tiết lộ để biết những gì họ sẽ làm khi họ không biết câu trả lời. Ví dụ, họ sẽ thực hiện những bước nào, họ sẽ làm việc với ai, họ sẽ làm gì để khắc phục sự thiếu kiến ​​thức về thuật toán?
Chris Knight

Câu trả lời:


538

Bạn có thể sử dụng thuật toán tìm chu kỳ của Floyd , còn được gọi là thuật toán rùa và thỏ .

Ý tưởng là có hai tham chiếu đến danh sách và di chuyển chúng ở các tốc độ khác nhau . Di chuyển một chuyển tiếp bởi 1nút và khác bởi 2các nút.

  • Nếu danh sách liên kết có một vòng lặp chắc chắn họ sẽ gặp nhau.
  • Khác một trong hai tài liệu tham khảo (hoặc của họ next) sẽ trở thành null.

Hàm Java thực hiện thuật toán:

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
Cũng cần thực hiện kiểm tra null fast.nexttrước khi gọi nextlại:if(fast.next!=null)fast=fast.next.next;
cmptrgeekken

12
bạn nên kiểm tra không chỉ (chậm == nhanh) mà: (chậm == nhanh || chậm.next == nhanh) để tránh nhảy nhanh qua chậm
Oleg Razgulyaev

13
tôi đã sai: nhanh không thể nhảy chậm, vì nhảy qua chậm ở bước tiếp theo nhanh nên có cùng tư thế là chậm :)
Oleg Razgulyaev

4
Việc kiểm tra Slow == null là không cần thiết trừ khi danh sách chỉ có một nút. Bạn cũng có thể thoát khỏi một cuộc gọi đến Node.next. Đây là phiên bản đơn giản và nhanh hơn của vòng lặp: pastie.org/927591
Kay Sarraute

22
Bạn nên thực sự trích dẫn tài liệu tham khảo của bạn. Thuật toán này được phát minh bởi Robert Floyd trong thập niên 60, được gọi là thuật toán tìm chu kỳ của Floyd, hay còn gọi là thuật toán. Thuật toán Rùa và thỏ.
joshperry

127

Đây là một sàng lọc của giải pháp Nhanh / Chậm, xử lý chính xác các danh sách độ dài lẻ và cải thiện độ rõ ràng.

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
Đẹp và cô đọng. Mã này có thể được tối ưu hóa bằng cách kiểm tra nếu chậm == nhanh | | (fast.next! = null && Slow = fast.next); :)
arachnode.net

11
@ arachnode.net Đó không phải là một sự tối ưu hóa. Nếu slow == fast.nextsau đó slowsẽ bằng fastvới lần lặp tiếp theo; nó chỉ tiết kiệm được một lần lặp tối đa bằng chi phí của một bài kiểm tra bổ sung cho mỗi lần lặp.
Jason C

@ ana01 slowkhông thể trở thành null trước khi fastnó đi theo cùng một đường dẫn tham chiếu (trừ khi bạn có sửa đổi đồng thời danh sách trong trường hợp tất cả các cược bị tắt).
Dave L.

Vì tò mò làm thế nào để làm việc này cho số lẻ? Không thể thỏ vẫn vượt qua rùa trong danh sách liên kết chiều dài kỳ lạ?
theGreenCabbage

1
@theGreenCabbage Mỗi lần lặp của vòng lặp, thỏ rừng tiến thêm 1 bước so với rùa. Vì vậy, nếu thỏ rừng đứng sau 3 bước, thì lần lặp tiếp theo sẽ mất hai bước và con rùa mất một bước nhảy, và bây giờ thỏ rừng đứng sau 2 bước. Sau lần lặp lại tiếp theo, thỏ rừng đứng sau 1 hop, và sau đó nó bắt kịp chính xác. Nếu thỏ rừng mất 3 bước trong khi rùa lấy một con, thì nó có thể bỏ qua bởi vì nó sẽ tăng thêm 2 lần mỗi lần, nhưng vì nó chỉ tăng 1 lần mỗi lần lặp nên nó không thể bỏ qua.
Dave L.

52

Tốt hơn thuật toán của Floyd

Richard Brent đã mô tả một thuật toán phát hiện chu kỳ thay thế , khá giống với thỏ và rùa [chu kỳ của Floyd] ngoại trừ, nút chậm ở đây không di chuyển, nhưng sau đó được "dịch chuyển" đến vị trí của nút nhanh khi cố định khoảng thời gian.

Mô tả có sẵn ở đây: http://www.siafoo.net/alacticm/11 Brent tuyên bố rằng thuật toán của anh ta nhanh hơn 24 đến 36% so với thuật toán chu kỳ của Floyd. Độ phức tạp thời gian O (n), độ phức tạp không gian 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;
}

Câu trả lời này thật tuyệt vời!
valin077

1
Thực sự thích câu trả lời của bạn, bao gồm nó trên blog của tôi - k2code.blogspot.in/2010/04/ .
kinshuk4

Tại sao bạn cần kiểm tra slow.next != null? Theo như tôi có thể thấy slowlà luôn ở phía sau hoặc bằng fast.
TWiStErRob

Tôi đã làm điều này từ lâu, khi tôi bắt đầu học các thuật toán. Chỉnh sửa mã. Cảm ơn :)
Ashok Bijoy Debnath

50

Một giải pháp thay thế cho Rùa và Thỏ, không hoàn toàn tốt đẹp, vì tôi tạm thời thay đổi danh sách:

Ý tưởng là đi bộ danh sách, và đảo ngược nó khi bạn đi. Sau đó, khi bạn lần đầu tiên tiếp cận một nút đã được truy cập, con trỏ tiếp theo của nó sẽ trỏ "ngược", khiến việc lặp lại tiếp tục tiến về phía trước first, nơi nó kết thúc.

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;

Mã kiểm tra:

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

Liệu đảo ngược hoạt động chính xác khi vòng lặp đang trỏ đến bất kỳ nút nào khác trước? Nếu danh sách được liên kết ban đầu giống như thế này 1-> 2-> 3-> 4-> 5-> 2 (với chu kỳ từ 5 đến 2), thì danh sách đảo ngược trông giống như 1-> 2 <-3 <-4 <-5? Và nếu ngược lại, danh sách được xây dựng lại cuối cùng sẽ bị sai lệch?
Zenil

1
@Zenil: Đó là lý do tại sao tôi đã viết bài kiểm tra cuối cùng đó, nơi nút cuối cùng chỉ vào giữa danh sách. Nếu tái thiết sẽ không hoạt động, thử nghiệm đó sẽ thất bại. Về ví dụ của bạn: sự đảo ngược của 1-> 2-> 3-> 5-> 2 sẽ là 1-> 2-> 5-> 4-> 3-> 2, vì vòng lặp chỉ dừng lại khi kết thúc danh sách đã đạt được, không phải khi kết thúc vòng lặp (mà chúng ta không thể dễ dàng phát hiện) đã đạt được.
meriton

28

Rùa và thỏ

Hãy xem thuật toán rho của Pollard . Đây không phải là vấn đề hoàn toàn giống nhau, nhưng có lẽ bạn sẽ hiểu logic từ nó và áp dụng nó cho các danh sách được liên kết.

(nếu bạn lười biếng, bạn có thể kiểm tra phát hiện chu kỳ - kiểm tra phần về rùa và thỏ.)

Điều này chỉ yêu cầu thời gian tuyến tính, và 2 con trỏ thêm.

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

(Hầu hết các giải pháp không kiểm tra cả hai nextnext.nextcho null. Ngoài ra, vì con rùa luôn ở phía sau, bạn không phải kiểm tra nó cho null - thỏ đã làm điều đó rồi.)


13

Người dùng unicornaddict có một thuật toán đẹp ở trên, nhưng thật không may, nó có một lỗi cho các danh sách không có bản đồ có độ dài lẻ> = 3. Vấn đề là fastcó thể bị "kẹt" ngay trước khi kết thúc danh sách,slow bắt kịp nó và một vòng lặp (sai) được phát hiện.

Đây là thuật toán đã sửa.

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

Trong bối cảnh này, có rất nhiều tài liệu văn bản ở khắp mọi nơi. Tôi chỉ muốn đăng một đại diện sơ đồ thực sự giúp tôi nắm bắt được khái niệm.

Khi nhanh và chậm gặp nhau tại điểm p,

Quãng đường đi được nhanh = a + b + c + b = a + 2b + c

Quãng đường đi chậm = a + b

Vì nhanh là nhanh gấp 2 lần so với chậm. Vậy a + 2b + c = 2 (a + b) , sau đó ta được a = c .

Vì vậy, khi một con trỏ chậm khác chạy lại từ đầu đến q , đồng thời, con trỏ nhanh sẽ chạy từ p đến q , vì vậy chúng gặp nhau tại điểm q cùng nhau.

nhập mô tả hình ảnh ở đây

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
Một bức tranh đáng giá hơn hàng ngàn từ. Cảm ơn lời giải thích gọn gàng và đơn giản!
Calios

1
Giải thích tốt nhất trên internet. Sẽ chỉ thêm rằng điều này chứng tỏ rằng con trỏ nhanh và chậm hội tụ sau thời gian tuyến tính
VarunPandey

nếu alớn hơn độ dài của vòng lặp thì nhanh sẽ tạo ra nhiều vòng lặp và công thức distance (fast) = a + b + b + csẽ thay đổi thành a + (b+c) * k + bgiới thiệu thêm tham số kđếm số lopps được tạo bởi một vòng nhanh.
Bến

9

Thuật toán

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

Phức tạp

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

Làm thế nào phức tạp không gian O (2n)?
Lập trình

@ user3543449 bạn nói đúng, nó phải được chỉ n, cố định
Khaled.K

1
Đây thực sự là thời gian ~ O (n ^ 2) vì mỗi lần kiểm tra một ArrayList sẽ lấy O (n) và có O (n) trong số chúng. Sử dụng Hashset thay cho thời gian tuyến tính.
Dave L.

3
Điều này không kiểm tra các chu kỳ mà cho các giá trị trùng lặp bằng cách sử dụng các phần tử equalshashCode. Đó không phải là điều tương tự. Và nó hội nghị nullvề yếu tố cuối cùng. Và câu hỏi không nói gì về việc lưu trữ các nút trong a LinkedList.
Lii

2
@Lii đó là mã giả, đó không phải là mã Java, đó là lý do tại sao tôi đặt tiêu đề cho nó bằng Thuật toán
Khaled.K

8

Sau đây có thể không phải là phương pháp tốt nhất - đó là O (n ^ 2). Tuy nhiên, nó sẽ phục vụ để hoàn thành công việc (cuối cùng).

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

Làm thế nào bạn biết có bao nhiêu phần tử trong danh sách để thực hiện for ()?
Jethro Larson

@JethroLarson: Nút cuối cùng trong danh sách được liên kết trỏ đến một địa chỉ đã biết (trong nhiều triển khai, đây là NULL). Chấm dứt vòng lặp for khi đạt đến địa chỉ đã biết.
Sparky

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

Hãy tha thứ cho tôi về sự thiếu hiểu biết của tôi (tôi vẫn còn khá mới đối với Java và lập trình), nhưng tại sao các công việc trên không hoạt động?

Tôi đoán điều này không giải quyết được vấn đề không gian liên tục ... nhưng ít nhất nó sẽ đến đó trong một thời gian hợp lý, đúng không? Nó sẽ chỉ lấy không gian của danh sách được liên kết cộng với không gian của một tập hợp có n phần tử (trong đó n là số phần tử trong danh sách được liên kết hoặc số phần tử cho đến khi đạt đến một vòng lặp). Và theo thời gian, phân tích trường hợp xấu nhất, tôi nghĩ, sẽ đề xuất O (nlog (n)). Các tra cứu được sắp xếp để chứa () là log (n) (kiểm tra javadoc, nhưng tôi khá chắc chắn cấu trúc cơ bản của TreeSet là TreeMap, trong đó lần lượt là một cây màu đỏ đen) và trong trường hợp xấu nhất (không có vòng lặp, hoặc lặp ở cuối), nó sẽ phải thực hiện n tra cứu.


2
Có một giải pháp với một số loại Set hoạt động tốt, nhưng yêu cầu không gian tỷ lệ với kích thước của danh sách.
jjujuma

3

Nếu chúng tôi được phép nhúng lớp Node, tôi sẽ giải quyết vấn đề khi tôi triển khai nó bên dưới. hasLoop()chạy trong thời gian O (n) và chỉ chiếm không gian của counter. Điều này có vẻ như là một giải pháp thích hợp? Hoặc có một cách để làm điều đó mà không nhúng Node? (Rõ ràng, trong một triển khai thực tế, sẽ có nhiều phương thức hơn, như RemoveNode(Node n), v.v.)

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

Bạn thậm chí có thể làm điều đó trong thời gian O (1) không đổi (mặc dù nó sẽ không nhanh hoặc hiệu quả): Có một số lượng hạn chế các nút mà bộ nhớ máy tính của bạn có thể giữ, nói N bản ghi. Nếu bạn duyệt nhiều hơn N bản ghi, thì bạn có một vòng lặp.


Đây không phải là O (1), thuật toán này không có độ phức tạp thời gian có ý nghĩa trong ký hiệu big-O. Ký hiệu chữ O lớn chỉ cho bạn biết về hiệu suất trong giới hạn khi kích thước đầu vào chuyển sang vô cùng. Vì vậy, nếu thuật toán của bạn được xây dựng trên giả định rằng có không có danh sách với hơn yếu tố N cho một số lượng lớn N, giới hạn của thời gian chạy như kích thước danh sách tiến tới vô cùng được xác định. Do đó, độ phức tạp không phải là "O (bất cứ thứ gì)".
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;
}

Sử dụng chức năng trên để phát hiện một vòng lặp trong danh sách liên kết trong java.


2
Gần giống như câu trả lời của tôi ở trên, nhưng có một vấn đề. Nó sẽ đưa ra một NullPulumException cho các danh sách có danh sách độ dài lẻ (không có vòng lặp). Ví dụ: nếu head.next là null, thì sec.next.next sẽ ném NPE.
Dave L.

1

Việc phát hiện một vòng lặp trong danh sách được liên kết có thể được thực hiện theo một trong những cách đơn giản nhất, dẫn đến độ phức tạp O (N) bằng cách sử dụng hàm băm hoặc O (NlogN) bằng cách sử dụng phương pháp sắp xếp.

Khi bạn duyệt qua danh sách bắt đầu từ đầu, hãy tạo một danh sách địa chỉ được sắp xếp. Khi bạn chèn một địa chỉ mới, hãy kiểm tra xem địa chỉ đó đã có trong danh sách đã sắp xếp chưa, có độ phức tạp O (logN) không.


Độ phức tạp của apporach này là O (N log N)
fgp

0

Tôi không thể thấy bất kỳ cách nào để thực hiện việc này mất một khoảng thời gian hoặc không gian cố định, cả hai sẽ tăng theo kích thước của danh sách.

Tôi sẽ sử dụng một IdentityHashMap (với điều kiện là chưa có IdentityHashset) và lưu trữ mỗi Node vào bản đồ. Trước khi một nút được lưu trữ, bạn sẽ gọi chứaKeyKey trên đó. Nếu Node đã tồn tại, bạn có một chu kỳ.

ItentityHashMap sử dụng == thay vì .equals để bạn kiểm tra vị trí của đối tượng trong bộ nhớ thay vì nếu nó có cùng nội dung.


3
Chắc chắn nó không thể mất một khoảng thời gian cố định, vì có thể có một vòng lặp ở cuối danh sách, vì vậy toàn bộ danh sách phải được truy cập. Tuy nhiên, thuật toán Fast / Slow cho thấy một giải pháp sử dụng một lượng bộ nhớ cố định.
Dave L.

Có phải nó không đề cập đến hành vi tiệm cận của nó, tức là nó là tuyến tính O (n) trong đó n là độ dài của danh sách. Đã sửa sẽ là O (1)
Mark Robson

0

Tôi có thể rất muộn và mới để xử lý chủ đề này. Nhưng vẫn..

Tại sao không thể lưu địa chỉ của nút và nút "tiếp theo" được lưu trữ trong bảng

Nếu chúng ta có thể lập bảng theo cách này

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)

Do đó có một chu kỳ hình thành.


Giải pháp của bạn không vượt qua yêu cầu "lượng không gian cố định".
Arnaud

0

Đây là mã runnable của tôi.

Những gì tôi đã làm là hoàn nguyên danh sách được liên kết bằng cách sử dụng ba nút tạm thời (độ phức tạp không gian O(1)) để theo dõi các liên kết.

Thực tế thú vị khi thực hiện là giúp phát hiện chu trình trong danh sách được liên kết bởi vì khi bạn tiếp tục, bạn không mong đợi quay lại điểm bắt đầu (nút gốc) và một trong các nút tạm thời sẽ chuyển sang null trừ khi bạn có một chu kỳ có nghĩa là nó trỏ đến nút gốc.

Độ phức tạp thời gian của thuật toán này là O(n)và độ phức tạp không gian là O(1).

Đây là nút lớp cho danh sách được liên kết:

public class LinkedNode{
    public LinkedNode next;
}

Đây là mã chính với trường hợp kiểm tra đơn giản gồm ba nút mà nút cuối cùng trỏ đến nút thứ hai:

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

Đây là trường hợp thử nghiệm đơn giản gồm ba nút mà nút cuối cùng trỏ đến nút thứ hai:

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

Mã này được tối ưu hóa và sẽ tạo ra kết quả nhanh hơn so với câu được chọn là câu trả lời đúng nhất. Mã này tiết kiệm từ việc đi vào một quá trình rất dài để theo đuổi con trỏ nút tiến và lùi sẽ xảy ra trong trường hợp sau nếu chúng ta làm theo 'tốt nhất Trả lời 'phương pháp. Xem qua các bước sau đây và bạn sẽ nhận ra điều tôi đang cố gắng nói. Sau đó, hãy xem xét vấn đề thông qua phương pháp đã cho bên dưới và đo lường không. các bước thực hiện để tìm câu trả lời.

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

Đây là mã:

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
}

Bạn có chắc chắn rằng điều này tạo ra kết quả đúng trong mọi tình huống? Nếu bạn chạy thuật toán này trong danh sách 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 3 -> ..., tôi tin rằng nó sẽ trả về 4 như đầu, trong khi bạn muốn 3.
Sunreef

Câu hỏi chỉ là tìm xem có tồn tại một vòng lặp hay không. Trong trường hợp này, vâng, câu hỏi sẽ hoàn toàn hoạt động tốt và nhận được kết quả boolean mong muốn cho trường hợp. Nếu bạn muốn nút chính xác từ nơi vòng lặp bắt đầu, thì chúng tôi sẽ cần thêm đôi khi vào mã. Nhưng khi có liên quan đến việc tạo ra kết quả, điều này sẽ tạo ra kết luận nhanh hơn.
Sarthak Mehra

Bạn đã không đọc đúng câu hỏi: Cách viết tốt nhất boolean hasLoop(Node first)sẽ trở thành đúng nếu Node đã cho là đầu tiên của danh sách có vòng lặp và ngược lại là sai?
Sunreef

Đây là bước chạy khô cho danh sách của bạn. Giá trị đầu tiên có nghĩa là con trỏ trở lại và phần thứ hai có nghĩa là con trỏ tiến. (1,1) - (1,3) - (2,3) - (2,5) - (3,5) - (3,7) - (4,7) - (4,4).
Sarthak Mehra

Trên thực tế, tôi nhận ra rằng có hai cách để hiểu câu hỏi (hoặc ít nhất là tôi thấy hai cách hiểu khác nhau). Thuật toán của bạn là chính xác nếu bạn chỉ tìm kiếm nếu có một vòng lặp. Nhưng tôi nghĩ rằng câu hỏi đang hỏi nơi vòng lặp bắt đầu.
Sunreef

0

Đây là giải pháp của tôi trong 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

Bạn cũng có thể sử dụng thuật toán rùa của Floyd như được đề xuất trong các câu trả lời ở trên.

Thuật toán này có thể kiểm tra xem một danh sách liên kết đơn có chu trình kín không. Điều này có thể đạt được bằng cách lặp lại một danh sách với hai con trỏ sẽ di chuyển ở tốc độ khác nhau. Theo cách này, nếu có một chu kỳ, hai con trỏ sẽ gặp nhau tại một thời điểm nào đó trong tương lai.

Xin vui lòng kiểm tra bài đăng trên blog của tôi trên cấu trúc dữ liệu danh sách được liên kết, trong đó tôi cũng bao gồm một đoạn mã với việc thực hiện thuật toán được đề cập ở trên trong ngôn ngữ java.

Trân trọng,

Andreas (@xnorcode)


0

Đây là giải pháp để phát hiện chu kỳ.

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

// danh sách liên kết tìm chức năng lặp

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

Cách tiếp cận này có không gian trên không, nhưng thực hiện đơn giản hơn:

Vòng lặp có thể được xác định bằng cách lưu trữ các nút trong Bản đồ. Và trước khi đặt nút; kiểm tra xem nút đã tồn tại chưa. Nếu nút đã tồn tại trong bản đồ thì có nghĩa là Danh sách liên kết có vòng lặp.

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

Điều này không đáp ứng số lượng hạn chế không gian liên tục được đưa ra trong câu hỏi!
khấu trừ

đồng ý nó có không gian trên cao; đó là một cách tiếp cận khác để giải quyết vấn đề này. Cách tiếp cận rõ ràng là thuật toán rùa và harse.
rai.skumar

@downvoter, sẽ rất hữu ích nếu bạn có thể giải thích lý do.
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;

}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.