Чому я повинен використовувати Deque над Stack?


157

Мені потрібна Stackструктура даних для мого випадку використання. Я повинен мати можливість проштовхувати елементи в структуру даних, і я хочу лише отримати останній елемент із Стек. JavaDoc для стека каже:

Більш повний і послідовний набір операцій стека LIFO надається інтерфейсом Deque та його реалізаціями, який слід використовувати в перевазі цього класу. Наприклад:

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

Я точно не хочу синхронізованої поведінки тут, оскільки я буду використовувати цю структуру даних, локальну для методу. Крім того , чому я вважав за краще Dequeбільш Stackтут?

PS: Явадок з Deque говорить:

Деки також можна використовувати як стеки LIFO (Last-In-First-Out). Цей інтерфейс слід використовувати в перевазі старого класу Stack.


1
Він надає більше вкладених методів, а точніше "більш повний і послідовний набір" з них, що зменшить кількість коду, який ви повинні написати, якщо ви скористаєтесь ними?

Відповіді:


190

По-перше, це більш розумно з точки зору спадкування. Те, що Stackпоширюється Vector, насправді дивне, на мій погляд. На початку Java наслідування IMO - Propertiesце ще один приклад.

Для мене ключове слово в цитованих вами документах є послідовним . Dequeрозкриває набір операцій, який полягає в тому, щоб отримати можливість / вилучення / видалення елементів з початку або в кінці колекції, повторення тощо - і все. Навмисно немає способу отримати доступ до елемента за позицією, який Stackвикривається, тому що це підклас Vector.

Ох, а також Stackне має інтерфейсу, тому якщо ви знаєте, що вам потрібні Stackоперації, ви в кінцевому підсумку здійснюєте певний конкретний клас, що зазвичай не є хорошою ідеєю.

Також, як зазначено в коментарях, Stackі Dequeє накази щодо зворотної ітерації:

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

що також пояснено в JavaDocs для Deque.iterator () :

Повертає ітератор над елементами в цьому деке в правильній послідовності. Елементи будуть повернуті в порядку від першого (голова) до останнього (хвіст).


10
javadoc ArrayDeque каже: "Цей клас, швидше за все, буде швидшим, ніж Stack, коли він використовується як стек, і швидше, ніж LinkedList, коли використовується як черга". ..Як я можу вказати, чи маю я намір використовувати це як стек чи як чергу?
Geek

23
@Geek: Ні. Справа в тому, що якщо ви хочете поведінку в черзі, ви можете скористатися LinkedList, але ArrayDequeue(часто) буде швидше. Якщо ви хочете поведінку стеку, ви можете використовувати, Stackале ArrayDequeбуде (часто) швидше.
Джон Скіт

7
Хіба це не менш розумно з точки зору абстракції? Я маю на увазі, що жодне рішення не є дуже хорошим з точки зору абстракції, тому що Stackє проблема експозиції реп, але якщо я хочу структуру даних стека, я хотів би мати можливість викликати методи, такі як push, pop та peek, а не речі, які мають зробіть з іншим кінцем стека.
PeteyPabPro

4
@JonSkeet також неправильний ітератор Стака, тому що він повторюється знизу вгору замість верху вниз. stackoverflow.com/questions/16992758/…
Павло

1
@PeteyPabPro: Ви маєте рацію. Використання Dequeue як стека все ще дозволяє використовувати не-LIFO як успадковані векторні методи стека. Пояснення проблеми та їх вирішення (з інкапсуляцією) можна знайти тут: baddotrobot.com/blog/2013/01/10/stack-vs-deque
rics

4

Ось моя інтерпретація неузгодженості, згадана в описі класу Stack.

Якщо ви подивитесь на реалізацію загального призначення тут - ви побачите, що існує послідовний підхід до реалізації набору, карти та списку.

  • Для набору та карти у нас є 2 стандартних реалізації з хеш-картами та деревами. Перший використовується найбільш часто, а другий використовується, коли нам потрібна впорядкована структура (а також реалізує власний інтерфейс - SortedSet або SortedMap).

  • Ми можемо використовувати вподобаний стиль декларування, як Set<String> set = new HashSet<String>();див. Тут причини .

Але клас Stack: 1) не має власного інтерфейсу; 2) - підклас класу Vector - який заснований на масиві, що змінює розмір; так де пов'язана реалізація стека стека?

У інтерфейсі Deque у нас немає таких проблем, включаючи дві реалізації (масив змінної масиву - ArrayDeque; пов'язаний список - LinkedList).


2

Ще однією причиною використання Dequeue over Stack є те, що Dequeue має можливість використовувати потоки, перетворені в список, зберігаючи LIFO-концепцію, застосовану, поки Stack цього не робить.

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

Ось кілька причин, чому Deque кращий за Stack:

Об'єктно-орієнтований дизайн - Спадщина, абстракція, класи та інтерфейси: Стек - клас, Deque - інтерфейс. Лише один клас може бути розширений, тоді як будь-яка кількість інтерфейсів може бути реалізована одним класом в Java (множинне успадкування типу). Використання інтерфейсу Deque знімає залежність від конкретного класу Stack та його предків і надає вам більшу гнучкість, наприклад, свободу розширювати інший клас або замінювати різні реалізації Deque (наприклад, LinkedList, ArrayDeque).

Невідповідність: Стек розширює клас Вектор, що дозволяє отримувати доступ до елемента за індексом. Це суперечить тому, що насправді повинен робити Стек, саме тому перевага інтерфейсу Deque (він не допускає таких операцій) - його дозволені операції відповідають тому, що має дозволяти структура даних FIFO або LIFO.

Продуктивність: Клас Vector, який розширює Стек, є в основному "безпечною для потоків" версією ArrayList. Синхронізації можуть потенційно спричинити значну ефективність роботи вашої програми. Крім того, розширення інших класів із непотрібною функціональністю (як згадується у №2) роздуває ваші об'єкти, що потенційно коштує багато додаткової пам'яті та продуктивності.


-1

Для мене цей специфічний момент відсутній: Stack - це Threadsafe, оскільки він походить від Vector, тоді як найдекітніші реалізації не є, і, отже, швидше, якщо ви використовуєте його лише в одному потоці.


grep "Окрім цього"
Pacerier

-1

Виконання може бути причиною. Алгоритм, який я використав, знизився з 7,6 хвилин до 1,5 хвилин, просто замінивши Stack на Deque.


grep "Окрім цього"
Pacerier

-6

Використовується деке - це ситуація, коли ви хочете отримати елементи як з голови, так і з хвоста. Якщо ви хочете простий стек, не потрібно їхати на деке.


1
будь ласка, дивіться останній праграф у питанні. Явадок каже, що Декес слід використовувати, і я хотів знати, чому?
Geek

2
Деке надається перевагу, оскільки ви не можете отримати доступ до елементів посередині. Він подвійний, тому може бути FILO для FIFO.
Туфір

Я думаю, що вони мають намір сказати, що клас підтримує операції Stack як явні методи. Дивіться його документацію та методи. Він має явну підтримку для реалізації стека.
Абхішек Нандгаонкар
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.