Skip to content

Commit 2c5fb47

Browse files
agorinenkoAnton Gorinenko
andauthored
Queue stack (#9)
* Очередь и стек --------- Co-authored-by: Anton Gorinenko <[email protected]>
1 parent df609fb commit 2c5fb47

File tree

8 files changed

+179
-2
lines changed

8 files changed

+179
-2
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,13 @@
4444
[Метод двух указателей](tutorial/two_pointers.md)
4545

4646
[Алгоритмы на строках](tutorial/string_algorithms.md)
47+
48+
[Обход в ширину и глубину](tutorial/bfs_and_dfs.md)
49+
50+
### Используемые ресурсы
51+
52+
Для подготовки данных конспектов использовались следующие ресурсы:
53+
54+
1. https://leetcode.com/explore/ Платформа для подготовки к техническим собеседованиям
55+
2. https://yandex.ru/yaintern/algorithm-training_1 Тренировки по алгоритмам от Яндекса
56+
3. https://stepik.org/course/134212/syllabus Добрые, добрые структуры данных - курс от Сергея Балакирева

img/queue_stack_1.png

6.82 KB
Loading

img/queue_stack_2.png

11.6 KB
Loading

img/queue_stack_3.png

16.7 KB
Loading

img/queue_stack_4.png

8.8 KB
Loading

tutorial/bfs_and_dfs.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Обход в ширину и глубину
2+
https://leetcode.com/explore/learn/card/queue-stack/231/practical-application-queue/1376/
3+
https://leetcode.com/explore/learn/card/queue-stack/232/practical-application-stack/1377/

tutorial/heap.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
# Куча
1+
# Куча
2+
3+
https://www.youtube.com/watch?v=sAyOhkMZae4

tutorial/queue_and_stack.md

Lines changed: 163 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,163 @@
1-
# Очередь и стек
1+
# Очередь и стек
2+
3+
В этом разделе мы познакомимся с абстрактными структурами данных такими как очередь и стек. В них обработка элементов
4+
происходит в определенном, строго заданном порядке.
5+
6+
## Очередь
7+
8+
Очередь - структура типа ``First-in-first-out (FIFO)`` или первый пришел, первый вышел. В ней элемент, добавленный
9+
первым, первым пройдет обработку. На рисунке изображена очередь, которая имеет начало(голову) и конец(хвост). Операция
10+
вставки ``enqueue`` всегда добавляет новый элемент в конец очереди. Операция удаления ``dequeue`` всегда удаляет элемент
11+
из начала очереди.
12+
13+
![Очередь](../img/queue_stack_1.png)
14+
15+
Для реализации очереди мы могли бы использовать обычный динамический массив и указатель ``head`` на индекс начального
16+
элемента в голове. Однако данный подход имеет один недостаток. Рассмотрим рисунок ниже.
17+
18+
![Очередь на динамическом массиве](../img/queue_stack_2.png)
19+
20+
Предположим мы добавили в очередь 6 элементов, затем удалили 2 элемента из головы. Указатель ``head`` установлен на
21+
число 8. Удаленные элементы 10 и 7 занимают больше неиспользуемое место. С ростом количества элементов этот объем будет
22+
только увеличиваться. Как видим, в данном варианте реализации память расходуется очень неэффективно.
23+
24+
Для решения данной проблемы используется такой прием как **кольцевой буфер**. Здесь следует заранее знать максимальную
25+
длину очереди и при добавлении элементов в конец возможна перезапись уже неиспользуемых элементов. Для реализации
26+
данного вида очереди удобно использовать массив фиксированного размера и два указателя ``head`` и ``tail``.
27+
28+
![Кольцевой буфер](../img/queue_stack_3.png)
29+
30+
Пример реализации кольцевого буфера представлена ниже:
31+
32+
```python
33+
from typing import Optional
34+
35+
36+
class MyCircularQueue:
37+
"""
38+
Реализация очереди в виде кольцевого буфера
39+
"""
40+
41+
def __init__(self, k: int):
42+
"""
43+
Очередь
44+
:param k: максимальный размер очереди
45+
"""
46+
self.capacity = k
47+
self._len = 0
48+
self._data = [None for _ in range(k)]
49+
self._head = -1
50+
self._tail = -1
51+
52+
def enqueue(self, value: int) -> bool:
53+
"""
54+
Операция вставки всегда добавляет новый элемент в конец очереди
55+
:param value: элемент для вставки
56+
:return: True, если операция успешна
57+
"""
58+
if self.is_full():
59+
return False
60+
61+
if self._head == -1 and self._tail == -1:
62+
self._head = 0
63+
self._tail = 0
64+
else:
65+
self._tail += 1
66+
67+
if self._tail == self.capacity:
68+
self._tail = 0
69+
70+
self._len += 1
71+
self._data[self._tail] = value
72+
73+
return True
74+
75+
def dequeue(self) -> bool:
76+
"""
77+
Операция удаления всегда удаляет элемент из начала очереди.
78+
:return: True, если операция успешна
79+
"""
80+
if self.is_empty():
81+
return False
82+
83+
self._data[self._head] = None
84+
self._head += 1
85+
86+
if self._head == self.capacity:
87+
self._head = 0
88+
89+
self._len -= 1
90+
91+
return True
92+
93+
def front(self) -> Optional[int]:
94+
"""
95+
Получение элемента из головы.
96+
:return: элемент
97+
"""
98+
if self.is_empty():
99+
return None
100+
101+
return self._data[self._head]
102+
103+
def rear(self) -> Optional[int]:
104+
"""
105+
Получение элемента из хвоста
106+
:return:
107+
"""
108+
if self.is_empty():
109+
return None
110+
111+
return self._data[self._tail]
112+
113+
def is_empty(self) -> bool:
114+
"""
115+
Очередь пуста.
116+
:return: True, если очередь пуста.
117+
"""
118+
return self._len == 0
119+
120+
def is_full(self) -> bool:
121+
"""
122+
Очередь заполнена.
123+
:return: True, если очередь заполнена.
124+
"""
125+
return self._len == self.capacity
126+
```
127+
128+
## Двусторонняя очередь
129+
130+
Очереди также можно реализовать не только на динамическом массиве, но и на двусвязном списке. В python есть реализация
131+
двусторонней очереди "deque", которая является обобщением очереди и стека. Данная структура поддерживает эффективное
132+
извлечение элемента из любого конца за **O(1)**. Доступ к первому и последнему элементу осуществляется также за
133+
**O(1)**, но имеет временную сложность за **O(n)** для доступа к произвольному элементу.
134+
135+
Ниже приведена временная сложность операций для двусторонней очереди:
136+
137+
1. Добавление нового элемента справа/слева - **O(1)**.
138+
2. Удаление элемента справа/слева - **O(1)**.
139+
3. Расширение на m элементов справа/слева - **O(m)**.
140+
4. Вставка нового элемента в произвольную позицию - **O(n)**.
141+
5. Удаление элемента с заданным значением - **O(n)**.
142+
143+
Интересна реализация двусторонней очереди в С++, где используется сочетание статических массивов, объединенных в
144+
связанный список. Это ускоряет доступ к отдельным элементам очереди по индексу.
145+
146+
## Стек
147+
148+
Стек - структура типа ``Last-in-first-out (LIFO)`` или последний пришел, первый вышел. В ней элемент, добавленный
149+
последним, первым пройдет обработку. На рисунке изображен стек, где типичные операции вставки и изъятия работают с
150+
вершиной стека. Операция вставки ``push`` кладет новый элемент на вершину стека. Операция удаления ``pop`` всегда
151+
снимает элемент с вершины. Получить элемент по его индексу, например, из середины данная структура данных не позволяет.
152+
153+
![Стек](../img/queue_stack_4.png)
154+
155+
Для реализации стека достаточно динамического массива. В python объект list легко использовать как стек. Для добавления
156+
элемента на вершину следует использовать метод ``append()``, а для снятия с вершины - метод ``pop()`` без указания
157+
индекса. Также для реализации стека подойдут односвязный или двусвязный списки.
158+
159+
Ниже приведена временная сложность операций для стека:
160+
161+
1. Получение верхнего элемента стека(top) - **O(1)**.
162+
2. Добавление нового элемента на вершину(push) - **O(1)**.
163+
3. Удаление элемента с вершины(pop) - **O(1)**.

0 commit comments

Comments
 (0)