Tại sao việc thêm vào Danh sách trong Scala có độ phức tạp thời gian O (n)?


13

Tôi chỉ đọc rằng thời gian thực hiện của hoạt động chắp thêm cho một List(: +) tăng tuyến tính với kích thước của List.

Áp dụng cho một Listhoạt động có vẻ như khá phổ biến. Tại sao cách thành ngữ để làm điều này là chuẩn bị trước các thành phần và sau đó đảo ngược danh sách? Nó cũng không thể là một lỗi thiết kế vì việc thực hiện có thể được thay đổi tại bất kỳ thời điểm nào.

Theo quan điểm của tôi, cả việc chuẩn bị và bổ sung phải là O (1).

Có bất kỳ lý do chính đáng cho việc này?


2
Phụ thuộc vào định nghĩa của bạn về "hợp pháp". Scala tập trung nhiều vào cấu trúc dữ liệu bất biến, danh sách ẩn danh có mặt khắp nơi, thành phần chức năng, v.v ... Việc triển khai danh sách mặc định (không có con trỏ có thể thay đổi thêm vào đuôi danh sách) hoạt động tốt cho kiểu đó. Nếu bạn cần một danh sách mạnh mẽ hơn, ít nhất thì rất dễ dàng để viết thùng chứa của riêng bạn gần như không thể phân biệt được với các tiêu chuẩn.
Kilian Foth

1
Liên kết trang web chéo - Áp dụng một yếu tố vào cuối danh sách trong Scala - có một chút trong đó về bản chất của nó. Dường như một danh sách trong scala là bất biến, do đó bạn cần sao chép nó, đó là O (N).

Bạn có thể sử dụng một trong nhiều cấu trúc dữ liệu có thể thay đổi có sẵn hoặc cấu trúc dữ liệu không thay đổi với thời gian nối thêm O (1) mà Scala cung cấp. List[T]giả sử bạn đang sử dụng nó theo cách bạn sẽ làm trong một ngôn ngữ chức năng thuần túy - thường làm việc từ đầu với giải cấu trúc và chuẩn bị.
KChaloux

3
Chuẩn bị sẽ đưa con trỏ nút tiếp theo của đầu mới vào danh sách bất biến hiện có - không thể thay đổi. Đó là O (1).

1
Đối với công việc và phân tích tinh dịch về chủ đề chung về các phép đo độ phức tạp của cấu trúc dữ liệu trong FP thuần túy, hãy đọc luận án của Okasaki sau đó được xuất bản thành một cuốn sách. Nó được đánh giá cao và đọc rất tốt cho bất cứ ai học một số FP để hiểu cách suy nghĩ về việc tổ chức dữ liệu trong FP. Nó cũng được viết tốt và dễ đọc và làm theo để hoàn toàn là một văn bản chất lượng.
Jimmy Hoffa

Câu trả lời:


24

Tôi sẽ mở rộng nhận xét của mình một chút. Các List[T]cấu trúc dữ liệu, từ scala.collection.immutableđược tối ưu hóa để làm việc theo cách một danh sách bất biến trong một hoàn toàn chức năng hơn công trình ngôn ngữ lập trình. Nó đã rất nhanh thêm vào trước lần, và người ta cho rằng bạn sẽ làm việc trên đầu cho hầu hết các truy cập của bạn.

Danh sách không thay đổi có được thời gian trả trước rất nhanh do thực tế là họ mô hình hóa danh sách được liên kết của họ dưới dạng một loạt các "tế bào khuyết điểm". Ô xác định một giá trị đơn và một con trỏ tới ô tiếp theo (kiểu danh sách liên kết đơn lẻ cổ điển):

Cell [Value| -> Nil]

Khi bạn thêm vào một danh sách, bạn thực sự chỉ tạo một ô mới, phần còn lại của danh sách hiện tại được trỏ đến:

Cell [NewValue| -> [Cell[Value| -> Nil]]

Bởi vì danh sách này là bất biến, bạn an toàn để làm điều này mà không cần sao chép thực tế . Không có nguy cơ danh sách cũ thay đổi và khiến tất cả các giá trị trong danh sách mới của bạn trở nên không hợp lệ. Tuy nhiên, bạn mất khả năng có một con trỏ có thể thay đổi đến cuối danh sách của bạn như một sự thỏa hiệp.

Điều này cho vay rất tốt để làm việc đệ quy trên danh sách. Giả sử bạn đã xác định phiên bản của riêng mình filter:

def deleteIf[T](list : List[T])(f : T => Boolean): List[T] = list match {
  case Nil => Nil
  case (x::xs) => f(x) match {
    case true => deleteIf(xs)(f)
    case false => x :: deleteIf(xs)(f)
  }
}

Đó là một hàm đệ quy chỉ hoạt động từ phần đầu của danh sách và tận dụng lợi thế của khớp mẫu thông qua trình trích xuất ::. Đây là thứ bạn thấy rất nhiều trong các ngôn ngữ như Haskell.

Nếu bạn thực sự muốn nối nhanh, Scala cung cấp rất nhiều cấu trúc dữ liệu có thể thay đổi và bất biến để lựa chọn. Về phía đột biến, bạn có thể nhìn vào ListBuffer. Ngoài ra, Vectortừ scala.collection.immutablecó một thời gian nối nhanh.


bây giờ tôi hiểu rồi! Nó có ý nghĩa hoàn chỉnh.
DPM

Tôi không biết bất kỳ Scala nào, nhưng đó không phải là elsemột vòng lặp vô hạn? Tôi nghĩ nó nên là một cái gì đó như x::deleteIf(xs)(f).
Svick

@svick Uh ... vâng. Vâng, đúng vậy. Tôi đã viết nó một cách nhanh chóng và không xác minh mã của mình, vì tôi đã có một cuộc họp để đến: p (Nên sửa ngay bây giờ!)
KChaloux

@Jubbat Bởi vì headtailtruy cập với loại danh sách này rất nhanh - nhanh hơn so với sử dụng bất kỳ bản đồ hoặc mảng dựa trên hàm băm nào - đó là một loại tuyệt vời cho các hàm đệ quy. Đây là một lý do tại sao danh sách là một loại cốt lõi trong hầu hết các ngôn ngữ chức năng (ví dụ: Haskell hoặc Scheme)
từ

Câu trả lời tuyệt vời. Tôi có lẽ sẽ thêm một TL; DR chỉ đơn giản nói rằng "bởi vì bạn nên trả trước, không nối thêm" (có thể giúp xóa các giả định cơ bản mà hầu hết các nhà phát triển có về Lists và nối thêm / trả trước).
Daniel B
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.