Độ phức tạp thời gian của enqueue và dequeue của một hàng đợi được thực hiện với một danh sách liên kết đơn là gì?


7

Tôi đang cố gắng hiểu độ phức tạp thời gian của hàng đợi được triển khai với cấu trúc dữ liệu danh sách được liên kết. Cuốn sách của tôi nói rằng chúng ta có thể thực hiện một hàng đợi trong thời gian O (1) bằng cách:

  • enqueue ở phía sau
  • dequeueing ở đầu

và nó cũng nói

Lưu ý rằng mặc dù việc thêm một phần tử vào đuôi là thời gian không đổi, việc xóa một phần tử khỏi phần đuôi là O (n) vì chúng ta phải tìm phần đuôi mới

Tôi biết rằng việc thêm một nút mới vào hàng đợi của tôi ở đầu (enqueue) sẽ mất O (1) vì tôi có con trỏ đầu. Tương tự, loại bỏ một nút ở đầu (dequeue) cũng sẽ mất O (1). Nhưng tôi không hiểu tại sao việc thêm một phần tử ở phía sau (enqueue) lại lấy O (1), trong khi loại bỏ nó (dequeue) sẽ mất O (n). Nó sẽ có ý nghĩa hơn đối với tôi nếu enqueue ở phía sau lấy O (n) thay vì O (1), vì trong cả hai trường hợp (thêm / xóa ở phía sau) sẽ cần tìm con trỏ đuôi và do đó nó cần phải đi qua toàn bộ danh sách.


7
Vui lòng không xóa thông tin quan trọng khỏi câu hỏi của bạn sau khi bạn đã nhận được câu trả lời. Chúng tôi muốn cặp câu hỏi và câu trả lời sẽ hữu ích cho những người khác trong tương lai, vì vậy hãy cố gắng giữ mọi thứ hoàn chỉnh.
Thằn lằn rời rạc

Câu trả lời:


14

Enqueueing

Bạn không cần phải duyệt qua toàn bộ danh sách để tìm đuôi mới, bạn chỉ cần thêm một nút mới nơi đuôi hiện tại trỏ đến và đặt nút đó làm đuôi mới.

Mã giả (giả sử rằng head != nulltail != null):

function enqueue(value) {
    node = new Node(value)     // O(1)
    tail.next = node           // O(1)
    tail = node                // O(1)
    size++                     // O(1)
}

Từ đó chúng ta có thể kết luận rằng độ phức tạp của thời gian là Ôi(1).


Dequeueing

Để khử nhiễu, chúng ta chỉ cần đặt nút tiếp theo của đầu hiện tại làm đầu mới và trả về giá trị của đầu cũ.

Lưu ý : Đừng quên rằng nếu đầu mới được đặt thành null, đuôi cũng sẽ được đặt thành null:

Mã giả (giả sử rằng head != nulltail != null):

function dequeue() {
    value = head.value         // O(1)
    head = head.next           // O(1)
    size--                     // O(1)

    if (head == null) {        // O(1)
        tail = null            // O(1)
    }

    return value               // O(1)
}

Tất cả các hoạt động này có Ôi(1) độ phức tạp thời gian, làm cho độ phức tạp thời gian của hàm dequeue Ôi(1) cũng.


Đang tìm kiếm

Tìm kiếm một giá trị được thực hiện bằng cách duyệt qua tất cả các mục, bắt đầu từ đầu. Trong trường hợp xấu nhất, bạn sẽ cần phải đi qua toàn bộ hàng đợi, điều này làm cho độ phức tạp của thời gian trong trường hợp xấu nhấtÔi(n).

Ví dụ: nếu bạn muốn loại bỏ đuôi, độ phức tạp thời gian sẽ là Ôi(n). Điều này là do bạn sẽ cần tìm đuôi mới cho hàng đợi và vì đuôi không có quyền truy cập vào phần tử trước đó trong danh sách liên kết đơn, nên bạn sẽ cần tìm kiếm toàn bộ hàng đợi cho đuôi mới.

Mã giả (giả sử rằng head != nulltail != null):

function removeLast() {

    // Edge case when there is only 1 element in the queue.
    if (head == tail) {                   // O(1)
        value = head.value                // O(1)
        head = null                       // O(1)
        tail = null                       // O(1)

        return value                      // O(1)
    }

    // Searching for the new tail.
    newTail = head                        // O(1)
    while (newTail.next != tail) {        // O(n)
        newTail = newTail.next            // O(1)
    }

    value = tail.value                    // O(1)
    newTail.next = null                   // O(1)
    tail = newTail                        // O(1)

    return tail                           // O(1)    
}

Từ đó có thể thấy rằng sự phức tạp thời gian thực sự là Ôi(n).


Enqueue không xử lý một danh sách trống. Trong khi dequeue xử lý khi danh sách trở nên trống rỗng.
ratchet freak

1

Nhận xét trong cuốn sách dường như giả định rằng việc thực hiện danh sách được liên kết của bạn duy trì hai con trỏ, trỏ headđến nút đầu tiên trong danh sách và lasttrỏ đến nút cuối cùng.

Với thiết kế này, việc thêm vào danh sách chỉ đơn giản là:

list.last.next = newNode;
list.last = newNode;

Nhưng việc loại bỏ nút cuối cùng đòi hỏi phải tìm nút thứ 2 đến nút cuối cùng, vì vậy bạn có thể xóa nextliên kết của nó và thay đổi lastcon trỏ để trỏ đến nút đó. Điều này yêu cầu quét danh sách để tìm nút ở đâu node.next == list.last.

Bạn cũng có thể có một list.secondToLastcon trỏ, nhưng điều đó không giải quyết được vấn đề, bởi vì khi bạn loại bỏ nút cuối cùng, bạn cần thay đổi nút này để trỏ đến nút thứ ba trước đó đến nút cuối cùng và vấn đề này sẽ tái diễn trong toàn bộ danh sách.

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.