Tại sao tôi nên sử dụng Deque trên Stack?


157

Tôi cần một Stackcấu trúc dữ liệu cho trường hợp sử dụng của tôi. Tôi có thể đẩy các mục vào cấu trúc dữ liệu và tôi chỉ muốn lấy mục cuối cùng từ Stack. Các javadoc cho Stack nói:

Một tập hợp các hoạt động ngăn xếp LIFO đầy đủ và nhất quán hơn được cung cấp bởi giao diện Deque và các cài đặt của nó, nên được sử dụng theo sở thích của lớp này. Ví dụ:

Deque<Integer> stack = new ArrayDeque<>();

Tôi chắc chắn không muốn hành vi được đồng bộ hóa ở đây vì tôi sẽ sử dụng cơ sở hạ tầng cục bộ này cho một phương thức. Ngoài ra tại sao tôi nên thích Dequehơn Stackở đây?

PS: javadoc từ Deque nói:

Deques cũng có thể được sử dụng như các ngăn xếp LIFO (Lần xuất trước cuối cùng). Giao diện này nên được sử dụng theo sở thích đối với lớp Stack kế thừa.


1
Nó cung cấp nhiều phương thức nướng hơn, hay đúng hơn là "một bộ hoàn chỉnh và nhất quán hơn", chúng sẽ làm giảm số lượng mã bạn phải viết nếu bạn tận dụng chúng?

Câu trả lời:


190

Đối với một điều, nó hợp lý hơn về mặt thừa kế. Theo tôi, thực tế Stackmở rộng Vectorlà rất lạ. Thời kỳ đầu ở Java, sự kế thừa đã bị lạm dụng IMO - Propertieslà một ví dụ khác.

Đối với tôi, từ quan trọng trong các tài liệu bạn trích dẫn là nhất quán . Dequehiển thị một tập hợp các thao tác hoàn toàn có thể tìm nạp / thêm / xóa các mục từ đầu hoặc cuối của bộ sưu tập, lặp đi lặp lại, v.v. - và đó là nó. Có chủ ý không có cách nào để truy cập một yếu tố theo vị trí, điều này Stacklộ ra đó là một lớp con của Vector.

Ồ, và cũng Stackkhông có giao diện, vì vậy nếu bạn biết bạn cần các Stackhoạt động, cuối cùng bạn sẽ cam kết với một lớp cụ thể cụ thể, thường không phải là một ý tưởng tốt.

Cũng như được chỉ ra trong các ý kiến, StackDequecó các lệnh lặp ngược:

Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println(new ArrayList<>(stack)); // prints 1, 2, 3


Deque<Integer> deque = new ArrayDeque<>();
deque.push(1);
deque.push(2);
deque.push(3);
System.out.println(new ArrayList<>(deque)); // prints 3, 2, 1

cũng được giải thích trong JavaDocs cho Deque.iterator () :

Trả về một trình vòng lặp qua các phần tử trong deque này theo trình tự thích hợp. Các yếu tố sẽ được trả về theo thứ tự từ đầu (đầu) đến cuối (đuôi).


10
javadoc của ArrayDeque nói rằng "Lớp này có khả năng nhanh hơn Stack khi được sử dụng như một ngăn xếp và nhanh hơn LinkedList khi được sử dụng như một hàng đợi." .. Làm thế nào để tôi xác định liệu tôi có ý định sử dụng này như một ngăn xếp hoặc như một hàng đợi?
Geek

23
@Geek: Bạn không. Vấn đề là nếu bạn muốn hành vi xếp hàng, bạn có thể sử dụng LinkedList, nhưng ArrayDequeuesẽ (thường) sẽ nhanh hơn. Nếu bạn muốn hành vi ngăn xếp, bạn có thể sử dụng Stacknhưng ArrayDequesẽ (thường) sẽ nhanh hơn.
Jon Skeet

7
Có phải nó ít nhạy cảm hơn về mặt trừu tượng không? Ý tôi là, không có giải pháp nào thực sự tốt về mặt trừu tượng, bởi vì Stackcó vấn đề phơi nhiễm đại diện, nhưng nếu tôi muốn cấu trúc dữ liệu ngăn xếp, tôi muốn có thể gọi các phương thức như đẩy, bật và nhìn trộm, và không phải là những thứ phải làm với đầu kia của ngăn xếp.
PeteyPabPro

4
@JonSkeet cũng lặp iterator của Stack là sai, vì nó lặp từ dưới lên trên thay vì từ trên xuống dưới. stackoverflow.com/questions/16992758/ Mạnh
Pavel

1
@PeteyPabPro: Bạn nói đúng. Sử dụng Dequeue làm stack vẫn cho phép sử dụng không phải LIFO như các phương thức Vector được kế thừa của Stack. Có thể tìm thấy giải thích về vấn đề và giải pháp (có đóng gói) tại đây: baddotrobot.com/blog/2013/01/10/stack-vs-deque
rics

4

Dưới đây là cách giải thích của tôi về sự không nhất quán được đề cập trong phần mô tả về lớp Stack.

Nếu bạn xem Triển khai mục đích chung ở đây - bạn sẽ thấy có một cách tiếp cận nhất quán để thực hiện bộ, bản đồ và danh sách.

  • Đối với thiết lập và bản đồ, chúng tôi có 2 triển khai tiêu chuẩn với bản đồ băm và cây. Cái đầu tiên được sử dụng nhiều nhất và cái thứ hai được sử dụng khi chúng ta cần một cấu trúc có trật tự (và nó cũng thực hiện giao diện riêng của nó - Sortedset hoặc SortedMap).

  • Chúng tôi có thể sử dụng phong cách khai báo ưa thích như Set<String> set = new HashSet<String>();xem lý do ở đây .

Nhưng lớp Stack: 1) không có giao diện riêng; 2) là một lớp con của lớp Vector - dựa trên mảng có thể thay đổi kích thước; Vì vậy, danh sách liên kết thực hiện ngăn xếp ở đâu?

Trong giao diện Deque, chúng tôi không gặp vấn đề như vậy bao gồm hai triển khai (mảng có thể thay đổi kích thước - ArrayDeque; danh sách được liên kết - LinkedList).


2

Thêm một lý do để sử dụng Dequeue trên Stack là Dequeue có khả năng sử dụng các luồng chuyển đổi thành danh sách với việc giữ khái niệm LIFO được áp dụng trong khi Stack thì không.

Stack<Integer> stack = new Stack<>();
Deque<Integer> deque = new ArrayDeque<>();

stack.push(1);//1 is the top
deque.push(1)//1 is the top
stack.push(2);//2 is the top
deque.push(2);//2 is the top

List<Integer> list1 = stack.stream().collect(Collectors.toList());//[1,2]

List<Integer> list2 = deque.stream().collect(Collectors.toList());//[2,1]

2

Dưới đây là một vài lý do tại sao Deque tốt hơn Stack:

Thiết kế hướng đối tượng - Kế thừa, trừu tượng hóa, các lớp và giao diện: Stack là một lớp, Deque là một giao diện. Chỉ một lớp có thể được mở rộng, trong khi bất kỳ số lượng giao diện nào cũng có thể được thực hiện bởi một lớp duy nhất trong Java (nhiều kiểu kế thừa). Sử dụng giao diện Deque sẽ loại bỏ sự phụ thuộc vào lớp Stack cụ thể và tổ tiên của nó và giúp bạn linh hoạt hơn, ví dụ: tự do mở rộng một lớp khác hoặc trao đổi các triển khai khác nhau của Deque (như LinkedList, ArrayDeque).

Không nhất quán: Stack mở rộng lớp Vector, cho phép bạn truy cập phần tử theo chỉ mục. Điều này không phù hợp với những gì Stack thực sự nên làm, đó là lý do tại sao giao diện Deque được ưa thích (nó không cho phép các hoạt động đó) - các hoạt động được phép của nó phù hợp với những gì cấu trúc dữ liệu FIFO hoặc LIFO nên cho phép.

Hiệu năng: Lớp Vector mà Stack mở rộng về cơ bản là phiên bản "an toàn luồng" của ArrayList. Việc đồng bộ hóa có khả năng có thể gây ra hiệu ứng đáng kể cho ứng dụng của bạn. Ngoài ra, việc mở rộng các lớp khác với chức năng không cần thiết (như được đề cập trong mục 2) làm phồng các đối tượng của bạn, có khả năng tiêu tốn rất nhiều bộ nhớ bổ sung và chi phí hiệu năng.


-1

Đối với tôi, điểm cụ thể này đã bị thiếu: Stack là Themesafe vì nó có nguồn gốc từ Vector, trong khi các triển khai deque nhất thì không, và do đó nhanh hơn nếu bạn chỉ sử dụng nó trong một luồng.


grep "Ngoài điều này"
Pacerier

-1

Hiệu suất có thể là một lý do. Một thuật toán tôi sử dụng đã giảm từ 7.6 phút xuống còn 1.5 phút bằng cách thay thế Stack bằng Deque.


grep "Ngoài điều này"
Pacerier

-6

Deque được sử dụng là tình huống mà bạn muốn lấy các phần tử từ cả đầu và đuôi. Nếu bạn muốn có một ngăn xếp đơn giản, không cần phải đi tìm một deque.


1
xin vui lòng xem các pragraph cuối cùng trong câu hỏi. Các javadoc nói Deques nên được sử dụng và tôi muốn biết tại sao?
Geek

2
Deque được ưa thích bạn không thể truy cập các yếu tố ở giữa. Nó đã kết thúc gấp đôi, vì vậy có thể là PHIM cho FIFO.
Thufir

Tôi nghĩ điều họ dự định nói là lớp hỗ trợ các hoạt động Stack như các phương thức rõ ràng. Xem tài liệu và phương pháp của nó. Nó có hỗ trợ rõ ràng để thực hiện một Stack.
Abhishek Nandgaonkar
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.