Tại sao việc lặp lại qua Danh sách sẽ nhanh hơn việc lập chỉ mục qua nó?


125

Đọc tài liệu Java cho Danh sách ADT, nó nói:

Giao diện Danh sách cung cấp bốn phương thức để truy cập theo vị trí (được lập chỉ mục) vào các phần tử danh sách. Danh sách (như mảng Java) là không dựa trên. Lưu ý rằng các hoạt động này có thể thực hiện theo thời gian tỷ lệ thuận với giá trị chỉ mục cho một số triển khai (ví dụ: lớp LinkedList). Do đó, việc lặp lại các thành phần trong danh sách thường được ưu tiên hơn là lập chỉ mục thông qua nó nếu người gọi không biết việc thực hiện.

Chính xác điều này có nghĩa là gì? Tôi không hiểu kết luận được rút ra.


12
Một ví dụ khác có thể giúp bạn hiểu trường hợp chung của vấn đề này là bài viết "Back to Basics" của Joel Spolsky - tìm kiếm "Thuật toán của họa sĩ Shlemiel".
một CVn

Câu trả lời:


211

Trong danh sách được liên kết, mỗi phần tử có một con trỏ tới phần tử tiếp theo:

head -> item1 -> item2 -> item3 -> etc.

Để truy cập item3, bạn có thể thấy rõ rằng bạn cần đi bộ từ đầu qua mọi nút cho đến khi bạn đến mục3, vì bạn không thể nhảy trực tiếp.

Vì vậy, nếu tôi muốn in giá trị của từng thành phần, nếu tôi viết điều này:

for(int i = 0; i < 4; i++) {
    System.out.println(list.get(i));
}

chuyện gì xảy ra thế này:

head -> print head
head -> item1 -> print item1
head -> item1 -> item2 -> print item2
head -> item1 -> item2 -> item3 print item3

Điều này là không hiệu quả khủng khiếp vì mỗi khi bạn lập chỉ mục, nó sẽ khởi động lại từ đầu danh sách và đi qua mọi mục. Điều này có nghĩa là sự phức tạp của bạn có hiệu quả O(N^2)chỉ để vượt qua danh sách!

Nếu thay vào đó tôi đã làm điều này:

for(String s: list) {
    System.out.println(s);
}

Sau đó, những gì xảy ra là:

head -> print head -> item1 -> print item1 -> item2 -> print item2 etc.

tất cả trong một giao dịch duy nhất, đó là O(N).

Bây giờ, đi đến việc thực hiện khác Listđược ArrayList, một trong đó là được hỗ trợ bởi một mảng đơn giản. Trong trường hợp đó, cả hai đường ngang trên đều tương đương nhau, vì một mảng nằm liền kề nhau nên nó cho phép nhảy ngẫu nhiên đến các vị trí tùy ý.


29
Thông báo nhỏ: LinkedList sẽ tìm kiếm từ cuối danh sách nếu chỉ mục nằm ở nửa sau của danh sách, nhưng điều đó không thực sự thay đổi sự kém hiệu quả cơ bản. Nó làm cho nó chỉ hơi ít vấn đề.
Joachim Sauer

8
Điều này là không hiệu quả khủng khiếp . Đối với LinkedList -yes lớn hơn, đối với nhỏ hơn, nó có thể hoạt động nhanh hơn REVERSE_THRESHOLDđược đặt 18 in java.util.Collections, thật kỳ lạ khi thấy câu trả lời được nâng cao mà không có nhận xét.
bestsss

1
@DanDiplo, nếu cấu trúc được Liên kết, có, nó đúng. Tuy nhiên, sử dụng cấu trúc LinkedS là một bí ẩn nhỏ. Họ hầu như luôn thực hiện tồi tệ hơn nhiều so với những người được hỗ trợ mảng (dấu chân bộ nhớ thêm, địa phương gc không thân thiện và khủng khiếp). Danh sách tiêu chuẩn trong C # là mảng được hỗ trợ.
bestsss

3
Thông báo nhỏ: Nếu bạn muốn kiểm tra loại lặp nào sẽ được sử dụng (được lập chỉ mục so với Iterator / foreach), bạn luôn có thể kiểm tra xem Danh sách có thực hiện RandomAccess (giao diện đánh dấu):List l = unknownList(); if (l instanceof RandomAccess) /* do indexed loop */ else /* use iterator/foreach */
afk5min

1
@ KK_07k11A0585: Trên thực tế, vòng lặp for được tăng cường trong ví dụ đầu tiên của bạn được biên dịch thành một trình vòng lặp như trong ví dụ thứ hai, vì vậy chúng tương đương nhau.
Tudor

35

Câu trả lời được ngụ ý ở đây:

Lưu ý rằng các hoạt động này có thể thực hiện theo thời gian tỷ lệ thuận với giá trị chỉ mục cho một số triển khai (ví dụ: lớp LinkedList)

Một danh sách liên kết không có một chỉ mục vốn có; việc gọi .get(x)sẽ yêu cầu thực hiện danh sách để tìm mục nhập đầu tiên và gọi .next()x-1 lần (đối với truy cập O (n) hoặc thời gian tuyến tính), trong đó danh sách được hỗ trợ mảng có thể chỉ mục vào backingarray[x]O (1) hoặc thời gian không đổi.

Nếu bạn nhìn vào JavaDocLinkedList , bạn sẽ thấy bình luận

Tất cả các hoạt động thực hiện như có thể được dự kiến ​​cho một danh sách liên kết đôi. Các hoạt động lập chỉ mục vào danh sách sẽ duyệt qua danh sách từ đầu hoặc cuối, tùy theo chỉ số nào gần với chỉ mục được chỉ định.

trong khi JavaDoc choArrayList có tương ứng

Thực hiện thay đổi kích thước mảng của giao diện Danh sách. Triển khai tất cả các hoạt động danh sách tùy chọn và cho phép tất cả các yếu tố, bao gồm null. Ngoài việc thực hiện giao diện Danh sách, lớp này cung cấp các phương thức để thao tác kích thước của mảng được sử dụng bên trong để lưu trữ danh sách. (Lớp này gần tương đương với Vector, ngoại trừ việc nó không được đồng bộ hóa.)

Các size, isEmpty, get, set, iterator, và listIteratorcác hoạt động chạy trong thời gian không đổi. Hoạt động thêm chạy trong thời gian không đổi được khấu hao, nghĩa là thêm n phần tử yêu cầu thời gian O (n). Tất cả các hoạt động khác chạy trong thời gian tuyến tính (nói đại khái). Hệ số không đổi thấp so với LinkedListthực hiện.

Một câu hỏi có liên quan có tiêu đề "Tóm tắt Big-O cho Khung sưu tập Java" có câu trả lời chỉ ra tài nguyên này, "Bộ sưu tập Java JDK6" mà bạn có thể thấy hữu ích.


7

Trong khi câu trả lời được chấp nhận là chắc chắn chính xác, tôi có thể chỉ ra một lỗ hổng nhỏ. Trích dẫn Tudor:

Bây giờ, đi đến phần triển khai khác của List là ArrayList, cái đó được hỗ trợ bởi một mảng đơn giản. Trong trường hợp đó, cả hai đường ngang trên đều tương đương nhau , vì một mảng nằm liền kề nhau nên nó cho phép nhảy ngẫu nhiên đến các vị trí tùy ý.

Điều này không hoàn toàn đúng. Đó là sự thực

Với một ArrayList, một vòng lặp được viết bằng tay nhanh hơn khoảng 3 lần

nguồn: Thiết kế cho hiệu suất, tài liệu Android của Google

Lưu ý rằng vòng lặp viết tay đề cập đến lần lặp được lập chỉ mục. Tôi nghi ngờ nó là do iterator được sử dụng với tăng cường cho các vòng lặp. Nó tạo ra một hiệu suất nhỏ trong hình phạt trong một cấu trúc được hỗ trợ bởi một mảng liền kề. Tôi cũng nghi ngờ điều này có thể đúng với lớp Vector.

Quy tắc của tôi là, sử dụng vòng lặp nâng cao bất cứ khi nào có thể và nếu bạn thực sự quan tâm đến hiệu suất, chỉ sử dụng phép lặp được lập chỉ mục cho ArrayLists hoặc vectơ. Trong hầu hết các trường hợp, bạn thậm chí có thể bỏ qua điều này - trình biên dịch có thể tối ưu hóa điều này trong nền.

Tôi chỉ muốn chỉ ra rằng trong bối cảnh phát triển của Android, cả hai điểm vượt trội của ArrayLists không nhất thiết phải tương đương . Chỉ là thức ăn cho suy nghĩ.


Nguồn của bạn chỉ là Anndroid. Điều này có đúng với các JVM khác không?
Matsemann

Không hoàn toàn chắc chắn tbh, nhưng một lần nữa, sử dụng tăng cường cho các vòng lặp nên được mặc định trong hầu hết các trường hợp.
Dhruv Gairola

Nó có ý nghĩa với tôi, loại bỏ tất cả logic lặp khi truy cập vào cấu trúc dữ liệu sử dụng một mảng hoạt động nhanh hơn. Tôi không biết nếu nhanh hơn 3x, nhưng chắc chắn nhanh hơn.
Setzer22

7

Lặp lại một danh sách với phần bù cho việc tra cứu, chẳng hạn như i, tương tự như thuật toán của họa sĩ Shlemiel .

Shlemiel có được một công việc như một họa sĩ đường phố, vẽ những đường chấm chấm xuống giữa đường. Vào ngày đầu tiên, anh ta lấy một lon sơn ra đường và hoàn thành 300 yard đường. "Đó là khá tốt!" ông chủ của mình nói, "bạn là một công nhân nhanh chóng!" và trả cho anh ta một cái kopeck.

Ngày hôm sau Shlemiel chỉ được thực hiện 150 yard. "Chà, điều đó gần như không tốt như ngày hôm qua, nhưng bạn vẫn là một công nhân nhanh nhẹn. 150 yard là đáng kính trọng", và trả cho anh ta một cái kopeck.

Ngày hôm sau Shlemiel vẽ 30 ​​thước đường. "Chỉ 30!" ông chủ hét lên. "Điều đó không thể chấp nhận được! Vào ngày đầu tiên bạn đã làm việc đó gấp mười lần! Chuyện gì đang xảy ra vậy?"

"Tôi không thể giúp nó," Shlemiel nói. "Mỗi ngày tôi càng ngày càng xa cái hộp sơn!"

Nguồn .

Câu chuyện nhỏ này có thể giúp bạn dễ hiểu hơn những gì đang diễn ra trong nội bộ và tại sao nó lại kém hiệu quả như vậy.


4

Để tìm phần tử thứ i của một LinkedListtriển khai phải đi qua tất cả các phần tử cho đến phần thứ i.

Vì thế

for(int i = 0; i < list.length ; i++ ) {
    Object something = list.get(i); //Slow for LinkedList
}
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.