├── .gitignore ├── img ├── stack.jpg ├── go-struct.png └── go-struct-2x.png ├── main.go ├── queue-linked.go ├── stack-linked.go ├── queue.go ├── stack.go ├── set.go ├── linked-list.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /img/stack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goavengers/go-datastructure/HEAD/img/stack.jpg -------------------------------------------------------------------------------- /img/go-struct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goavengers/go-datastructure/HEAD/img/go-struct.png -------------------------------------------------------------------------------- /img/go-struct-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goavengers/go-datastructure/HEAD/img/go-struct-2x.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | // UseStack() 5 | // UseQueue() 6 | // UseSet() 7 | // UseLinkedList() 8 | // UseLinkedStack() 9 | // UseLinkedQueue() 10 | } 11 | -------------------------------------------------------------------------------- /queue-linked.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func UseLinkedQueue() { 6 | queue := &LinkedQueue{} 7 | queue.Enqueue(1) 8 | queue.Enqueue(2) 9 | queue.Enqueue(3) 10 | 11 | queue.ToString() 12 | queue.Dequeue() 13 | fmt.Println(queue.Peek()) 14 | queue.ToString() 15 | } 16 | 17 | type LinkedQueue struct { 18 | linkedList LinkedList 19 | } 20 | 21 | func (lq *LinkedQueue) Enqueue(value interface{}) { 22 | lq.linkedList.Add(value) 23 | } 24 | 25 | func (lq *LinkedQueue) Dequeue() interface{} { 26 | if ok, removed := lq.linkedList.RemoveHead(); ok { 27 | return removed.value 28 | } 29 | 30 | return nil 31 | } 32 | 33 | func (lq *LinkedQueue) Peek() interface{} { 34 | if lq.linkedList.head == nil { 35 | return nil 36 | } 37 | 38 | return lq.linkedList.head.value 39 | } 40 | 41 | func (lq *LinkedQueue) ToString() { 42 | lq.linkedList.PrintNodes() 43 | } 44 | -------------------------------------------------------------------------------- /stack-linked.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func UseLinkedStack() { 6 | stack := NewLinkedStack() 7 | stack.Push(1) 8 | stack.Push(2) 9 | 10 | fmt.Println(stack.Peek()) 11 | fmt.Println(stack.Pop()) 12 | fmt.Println(stack.Peek()) 13 | 14 | stack.Push("String") 15 | fmt.Println(stack.Size()) 16 | fmt.Println(stack.Peek()) 17 | fmt.Println(stack.Pop()) 18 | fmt.Println(stack.Peek()) 19 | } 20 | 21 | type LinkedStack struct { 22 | linkedList LinkedList 23 | } 24 | 25 | func NewLinkedStack() LinkedStack { 26 | return LinkedStack{linkedList: LinkedList{ 27 | count: 0, 28 | head: nil, 29 | tail: nil, 30 | }} 31 | } 32 | 33 | func (ls *LinkedStack) Peek() interface{} { 34 | if ls.IsEmpty() { 35 | return nil 36 | } 37 | 38 | return ls.linkedList.head.value 39 | } 40 | 41 | func (ls *LinkedStack) Push(value interface{}) { 42 | ls.linkedList.PreAdd(value) 43 | } 44 | 45 | func (ls *LinkedStack) Pop() interface{} { 46 | if ok, removed := ls.linkedList.RemoveHead(); ok { 47 | return removed.value 48 | } 49 | 50 | return nil 51 | } 52 | 53 | func (ls *LinkedStack) IsEmpty() bool { 54 | return ls.linkedList.head == nil 55 | } 56 | 57 | func (ls *LinkedStack) Size() int { 58 | return ls.linkedList.Size() 59 | } 60 | -------------------------------------------------------------------------------- /queue.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func UseQueue() { 9 | queue := &Queue{} 10 | queue.Enqueue(1) 11 | queue.Enqueue(2) 12 | queue.Enqueue(3) 13 | 14 | fmt.Println(queue.Collections()) 15 | queue.Dequeue() 16 | fmt.Println(queue.Peek()) 17 | fmt.Println(queue.Collections()) 18 | } 19 | 20 | type Queue struct { 21 | mt sync.RWMutex 22 | collection []interface{} 23 | } 24 | 25 | // Добавляет элемент в очередь. 26 | // Новые элементы очереди можно добавлять как в начало списка, так и в конец. 27 | // Важно только, чтобы элементы доставались с противоположного края. 28 | // Сложность: O(1). 29 | func (q *Queue) Enqueue(value interface{}) { 30 | q.mt.Lock() 31 | defer q.mt.Unlock() 32 | q.collection = append(q.collection, value) 33 | } 34 | 35 | // Удаляет первый помещенный элемент из очереди и возвращает его. 36 | // Если очередь пустая, возвращает nil 37 | // Поскольку мы вставляем элементы в конец списка, убирать мы их будем с начала. 38 | // Сложность: O(1). 39 | func (q *Queue) Dequeue() interface{} { 40 | q.mt.Lock() 41 | defer q.mt.Unlock() 42 | 43 | if q.isEmpty() { 44 | return nil 45 | } 46 | 47 | val := q.collection[0] 48 | q.collection = q.collection[1:] 49 | 50 | return val 51 | } 52 | 53 | // Возвращает элемент, который вернет следующий вызов метода Dequeue. 54 | // Очередь остается без изменений. Если очередь пустая, возврщается nil 55 | // Сложность: O(1) 56 | func (q *Queue) Peek() interface{} { 57 | q.mt.RLock() 58 | defer q.mt.RUnlock() 59 | 60 | if q.isEmpty() { 61 | return nil 62 | } 63 | 64 | return q.collection[0] 65 | } 66 | 67 | // Возвращает количество элементов в очереди или 0, если очередь пустая. 68 | // Сложность: O(1). 69 | func (q *Queue) Count() int { 70 | return len(q.collection) 71 | } 72 | 73 | func (q *Queue) isEmpty() bool { 74 | return q.Count() == 0 75 | } 76 | 77 | func (q *Queue) Collections() []interface{} { 78 | return q.collection 79 | } 80 | -------------------------------------------------------------------------------- /stack.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func UseStack() { 9 | stack := NewStack() 10 | stack.Push(1) 11 | stack.Push(2) 12 | 13 | fmt.Println(stack.Peek()) 14 | fmt.Println(stack.Pop()) 15 | fmt.Println(stack.Peek()) 16 | 17 | stack.Push("String") 18 | fmt.Println(stack.Size()) 19 | fmt.Println(stack.Peek()) 20 | fmt.Println(stack.Pop()) 21 | fmt.Println(stack.Peek()) 22 | } 23 | 24 | type Stack struct { 25 | mt sync.RWMutex 26 | collection map[int]interface{} 27 | count int 28 | } 29 | 30 | func NewStack() *Stack { 31 | return &Stack{ 32 | collection: map[int]interface{}{}, 33 | count: 0, 34 | } 35 | } 36 | 37 | // Adds a value onto the end of the stack 38 | // Добавляет элемент на вершину стека. 39 | // Сложность: O(1). 40 | func (s *Stack) Push(value interface{}) { 41 | s.mt.Lock() 42 | defer s.mt.Unlock() 43 | s.collection[s.count] = value 44 | s.Inc() 45 | } 46 | 47 | // Removes and returns the value at the end of the stack 48 | // Удаляет элемент с вершины стека и возвращает его. Если стек пустой, возвращает nil 49 | // Т.к.`Push` добавляет элементы в конец списка, поэтому забирать их будет также с конца. 50 | // Сложность: O(1). 51 | func (s *Stack) Pop() interface{} { 52 | s.mt.Lock() 53 | defer s.mt.Unlock() 54 | if s.Size() == 0 { 55 | return nil 56 | } 57 | 58 | s.Dec() 59 | result := s.collection[s.Size()] 60 | delete(s.collection, s.Size()) 61 | return result 62 | } 63 | 64 | // Returns the value at the end of the stack 65 | // Возвращает верхний элемент стека, но не удаляет его. 66 | // Сложность: O(1). 67 | func (s *Stack) Peek() interface{} { 68 | s.mt.RLock() 69 | defer s.mt.RUnlock() 70 | 71 | if s.Size() == 0 { 72 | return nil 73 | } 74 | 75 | return s.collection[s.Size()-1] 76 | } 77 | 78 | // Get count elements in stack 79 | // Возвращает количество элементов в стеке. 80 | // Зачем нам знать, сколько элементов находится в стеке, если мы все равно не имеем к ним доступа? 81 | // С помощью этого поля мы можем проверить, есть ли элементы на стеке или он пуст. 82 | // Сложность: O(1). 83 | func (s *Stack) Size() int { 84 | return s.count 85 | } 86 | 87 | func (s *Stack) Inc() { 88 | s.count++ 89 | } 90 | 91 | func (s *Stack) Dec() { 92 | s.count-- 93 | } 94 | -------------------------------------------------------------------------------- /set.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func UseSet() { 6 | setA := &Set{} 7 | setB := &Set{} 8 | 9 | setA.Add(10) 10 | setA.Add(20) 11 | 12 | setB.Add(10) 13 | setB.Add(20) 14 | setB.Add(50) 15 | setB.Add(60) 16 | setB.Add(70) 17 | 18 | fmt.Println(setA.Subset(setB)) 19 | fmt.Println(setA.Intersection(setB).Collections()) 20 | fmt.Println(setB.Difference(setA).Collections()) 21 | 22 | fmt.Println(setA.Collections()) 23 | setA.Remove(10) 24 | setA.Add(20) 25 | fmt.Println(setA.Collections()) 26 | 27 | fmt.Println(setA.Contains(10)) 28 | 29 | setA.Add(10) 30 | setA.Add(60) 31 | fmt.Println(setA.Collections()) 32 | fmt.Println(setB.Collections()) 33 | fmt.Println(setB.SymmetricDifference(setA)) 34 | } 35 | 36 | type Set struct { 37 | collection []interface{} 38 | } 39 | 40 | func (s *Set) Collections() []interface{} { 41 | return s.collection 42 | } 43 | 44 | // Поведение: Добавляет элементы в множество. 45 | // Если элемент уже присутствует в множестве, возвращается false, иначе true. 46 | // Сложность: O(n) 47 | func (s *Set) Add(value interface{}) bool { 48 | if _, exist := s.Contains(value); exist == false { 49 | s.collection = append(s.collection, value) 50 | return true 51 | } 52 | 53 | return false 54 | } 55 | 56 | // Поведение: Возвращает index, true, если множество содержит указанный элемент. 57 | // В противном случае возвращает index, false. 58 | // Сложность: O(n) 59 | func (s *Set) Contains(value interface{}) (int, bool) { 60 | for k, v := range s.collection { 61 | if v == value { 62 | return k, true 63 | } 64 | } 65 | 66 | return 0, false 67 | } 68 | 69 | // Поведение: Удаляет указанный элемент из множества и возвращает true. 70 | // В случае, если элемент нет и он не удален, возвращает false. 71 | // Сложность: O(n) 72 | func (s *Set) Remove(value interface{}) bool { 73 | if i, exist := s.Contains(value); exist == true { 74 | length := s.Size() 75 | 76 | s.collection[i] = s.collection[length-1] 77 | s.collection = s.collection[:length-1] 78 | 79 | return true 80 | } 81 | 82 | return false 83 | } 84 | 85 | func (s *Set) Size() int { 86 | return len(s.collection) 87 | } 88 | 89 | // Поведение: Возвращает множество, полученное операцией объединения его с указанным. 90 | // Сложность: O(m·n), где m и n — количество элементов переданного и текущего множеств соответственно. 91 | func (s *Set) Union(set *Set) *Set { 92 | union := &Set{} 93 | 94 | for _, v := range s.Collections() { 95 | union.Add(v) 96 | } 97 | 98 | for _, v := range set.Collections() { 99 | union.Add(v) 100 | } 101 | 102 | return union 103 | } 104 | 105 | // Поведение: Возвращает множество, полученное операцией пересечения его с указанным. 106 | // Сложность: O(m·n), где m и n — количество элементов переданного и текущего множеств соответственно. 107 | func (s *Set) Intersection(set *Set) *Set { 108 | intersection := &Set{} 109 | 110 | for _, v := range s.Collections() { 111 | if _, exist := set.Contains(v); exist == true { 112 | intersection.Add(v) 113 | } 114 | } 115 | 116 | return intersection 117 | } 118 | 119 | // Поведение: Возвращает множество, являющееся разностью текущего с указанным. 120 | // Сложность: O(m·n), где m и n — количество элементов переданного и текущего множеств соответственно. 121 | func (s *Set) Difference(set *Set) *Set { 122 | difference := &Set{} 123 | 124 | for _, v := range s.Collections() { 125 | if _, exist := set.Contains(v); exist == false { 126 | difference.Add(v) 127 | } 128 | } 129 | 130 | return difference 131 | } 132 | 133 | // Поведение: Возвращает true, если второе множество является подмножеством первого, в противном случае возвращает false. 134 | // Сложность: O(m·n), где m и n — количество элементов переданного и текущего множеств соответственно. 135 | func (s *Set) Subset(set *Set) bool { 136 | if s.Size() > set.Size() { 137 | return false 138 | } 139 | 140 | return s.Difference(set).Size() == 0 141 | } 142 | 143 | // Поведение: Возвращает множество, являющееся симметрической разностью текущего с указанным. 144 | // Сложность: O(m·n), где m и n — количество элементов переданного и текущего множеств соответственно. 145 | func (s *Set) SymmetricDifference(set *Set) *Set { 146 | a := s.Difference(set) 147 | b := set.Difference(s) 148 | 149 | return a.Union(b) 150 | } 151 | -------------------------------------------------------------------------------- /linked-list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func UseLinkedList() { 8 | linkedList := &LinkedList{} 9 | linkedList.Add(1) 10 | linkedList.Add(2) 11 | linkedList.Add(3) 12 | linkedList.Add(4) 13 | linkedList.PrintNodes() 14 | 15 | if index, removed := linkedList.Remove(3); removed { 16 | fmt.Println(fmt.Sprintf("Removed index %d", index)) 17 | linkedList.PrintNodes() 18 | } 19 | 20 | if index, exist := linkedList.Contains(2); exist { 21 | fmt.Println(fmt.Sprintf("Exist index %d", index)) 22 | } 23 | 24 | linkedList.RemoveHead() 25 | linkedList.PrintNodes() 26 | linkedList.RemoveTail() 27 | linkedList.PrintNodes() 28 | linkedList.Add(3) 29 | linkedList.Add(4) 30 | linkedList.PreAdd(1) 31 | linkedList.PrintNodes() 32 | fmt.Println(linkedList.Size()) 33 | } 34 | 35 | type LinkedList struct { 36 | count int 37 | head *Node 38 | tail *Node 39 | } 40 | 41 | type Node struct { 42 | value interface{} 43 | next *Node 44 | } 45 | 46 | func (l *LinkedList) Add(value interface{}) { 47 | newNode := &Node{ 48 | value: value, 49 | next: nil, 50 | } 51 | 52 | if l.head == nil { 53 | l.head = newNode 54 | l.tail = newNode 55 | } else { 56 | l.tail.next = newNode 57 | l.tail = newNode 58 | } 59 | 60 | l.count++ 61 | } 62 | 63 | func (l *LinkedList) Remove(element interface{}) (int, bool) { 64 | current := l.head 65 | var previous *Node 66 | 67 | index := 0 68 | for current != nil { 69 | // find element 70 | if current.value == element { 71 | 72 | // is not first node 73 | if previous != nil { 74 | 75 | // before: Head -> 1 -> 2 -> 3 --> null 76 | // after: Head -> 1 -> 2 -------> null 77 | previous.next = current.next 78 | 79 | // set last node if current already is last 80 | if current.next == nil { 81 | l.tail = previous 82 | } 83 | } else { 84 | // before: Head -> 3 -> 5 85 | // after: Head ------> 5 86 | 87 | // Head -> 3 -> null 88 | // Head ------> null 89 | 90 | l.head = current.next 91 | // check is empty list 92 | if l.head == nil { 93 | l.tail = nil 94 | } 95 | } 96 | 97 | l.count-- 98 | return index, true 99 | } 100 | 101 | index++ 102 | previous = current 103 | current = current.next 104 | } 105 | 106 | return index, false 107 | } 108 | 109 | func (l *LinkedList) Contains(element interface{}) (int, bool) { 110 | current := l.head 111 | 112 | index := 0 113 | for current != nil { 114 | if current.value == element { 115 | return index, true 116 | } 117 | 118 | current = current.next 119 | index++ 120 | } 121 | 122 | return index, false 123 | } 124 | 125 | func (l *LinkedList) Size() int { 126 | return l.count 127 | } 128 | 129 | func (l *LinkedList) PreAdd(value interface{}) { 130 | newNode := &Node{ 131 | value: value, 132 | next: l.head, 133 | } 134 | 135 | l.head = newNode 136 | 137 | if l.tail == nil { 138 | l.tail = newNode 139 | } 140 | 141 | l.count++ 142 | } 143 | 144 | func (l *LinkedList) RemoveHead() (bool, *Node) { 145 | if l.head == nil { 146 | return false, nil 147 | } 148 | 149 | tmpHead := l.head 150 | 151 | if l.head.next != nil { 152 | l.head = l.head.next 153 | } else { 154 | l.head = nil 155 | l.head = nil 156 | } 157 | 158 | l.count-- 159 | return true, tmpHead 160 | } 161 | 162 | func (l *LinkedList) RemoveTail() (bool, *Node) { 163 | tmpTail := l.tail 164 | 165 | if l.head.next == nil { 166 | // There is only one node in linked list. 167 | 168 | l.head = nil 169 | l.tail = nil 170 | l.count-- 171 | return true, tmpTail 172 | } 173 | 174 | // If there are many nodes in linked list... 175 | // Rewind to the last node and delete "next" link for the node before the last one. 176 | 177 | currentNode := l.head 178 | for currentNode.next != nil { 179 | if currentNode.next.next == nil { 180 | currentNode.next = nil 181 | } else { 182 | currentNode = currentNode.next 183 | } 184 | } 185 | 186 | l.count-- 187 | l.tail = currentNode 188 | return true, tmpTail 189 | } 190 | 191 | // help method 192 | func (l *LinkedList) PrintNodes() { 193 | currentNode := l.head 194 | chain := "" 195 | 196 | for currentNode.next != nil { 197 | chain += fmt.Sprintf("| %v : next | --> ", currentNode.value) 198 | currentNode = currentNode.next 199 | } 200 | 201 | // last node 202 | chain += fmt.Sprintf("| %v : next |", currentNode.value) 203 | fmt.Println(chain) 204 | } 205 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Структуры данных: 10 типов структур данных, которые необходимо знать каждому

4 |
Вместе мы разберемся!
5 |
6 | 7 | ## Содержание 8 | 9 | 1. [Зачем нужны структуры данных?](#why_are_structures_needed) 10 | 2. [Наиболее распространенные структуры данных](#most_popular_structures) 11 | 3. [Стек (Stack)](#stack) 12 | 1. [Реализация](#stack_implementation) 13 | 2. [Сложность](#stack_complexity) 14 | 3. [Вопросы о стеке, часто задаваемые на собеседованиях](#stack_interview) 15 | 4. [Очереди (Queue)](#queue) 16 | 1. [Реализация](#queue_implementation) 17 | 2. [Сложность](#queue_complexity) 18 | 3. [Вопросы о очередях, часто задаваемые на собеседованиях](#queue_interview) 19 | 5. [Множества (Sets)](#sets) 20 | 1. [Реализация](#sets_implementation) 21 | 2. [Сложность](#sets_complexity) 22 | 3. [Вопросы о множествах, часто задаваемые на собеседованиях](#sets_interview) 23 | 6. [Связанные списки (Linked Lists)](#linked_lists) 24 | 1. [Реализация](#linked_lists_implementation) 25 | 2. [Сложность](#linked_lists_complexity) 26 | 3. [Реализация стека и очереди на основе связанных списков](#linked_lists_stack_queue) 27 | 4. [Двусвязанные списки (Double Linked List)](#linked_lists_double) 28 | 5. [Вопросы о связанных списках, часто задаваемые на собеседованиях](#linked_lists_interview) 29 | 7. Массивы 30 | 1. [Реализация](#array_implementation) 31 | 2. [Сложность](#array_complexity) 32 | 3. [Вопросы о множествах, часто задаваемые на собеседованиях](#array_interview) 33 | 8. Деревья 34 | 1. [Реализация](#tree_implementation) 35 | 2. [Сложность](#tree_complexity) 36 | 3. [Вопросы о множествах, часто задаваемые на собеседованиях](#tree_interview) 37 | 9. Боры 38 | 1. [Реализация](#trie_implementation) 39 | 2. [Сложность](#trie_complexity) 40 | 3. [Вопросы о множествах, часто задаваемые на собеседованиях](#trie_interview) 41 | 10. Графы 42 | 1. [Реализация](#graph_implementation) 43 | 2. [Сложность](#graph_complexity) 44 | 3. [Вопросы о множествах, часто задаваемые на собеседованиях](#graph_interview) 45 | 11. Хеш-таблицы 46 | 1. [Реализация](#hash_table_implementation) 47 | 2. [Сложность](#hash_table_complexity) 48 | 3. [Вопросы о множествах, часто задаваемые на собеседованиях](#hash_table_interview) 49 | 50 | Структуры данных играют важную роль в процессе разработки ПО, а еще по ним часто задают вопросы на собеседованиях для разработчиков. Хорошая новость в том, что по сути они представляют собой всего лишь специальные форматы для организации и хранения данных. 51 | 52 | ### :confused: Зачем нужны структуры данных? 53 | 54 | Поскольку структуры данных используются для хранения информации в упорядоченном виде, а данные — самый важный феномен в информатике, истинная ценность структур данных очевидна. 55 | 56 | Не важно, какую именно задачу вы решаете, так или иначе вам придется иметь дело с данными, будь то зарплата сотрудника, биржевые котировки, список продуктов для похода в магазин или обычный телефонный справочник. 57 | 58 | В зависимости от конкретного сценария, данные нужно хранить в подходящем формате. У нас в распоряжении — ряд структур данных, обеспечивающих нас такими различными форматами. 59 | 60 | ### :yum: Наиболее распространенные структуры данных 61 | 62 | Сначала давайте перечислим наиболее распространенные структуры данных, а затем разберем каждую по очереди: 63 | 64 | - Массивы 65 | - Динамические массивы 66 | - Множества 67 | - Стеки 68 | - Очереди 69 | - Связные списки 70 | - Деревья 71 | - Графы 72 | - Боры (в сущности, это тоже деревья, но их целесообразно рассмотреть отдельно). 73 | - Хеш-таблицы 74 | 75 | ### :sleeping: Реализации 76 | 77 | Все полные исходники реализаций структур данных вы всегда сможете найти в одноименных фалах расширения `.go`. 78 | Все структуры предоставляют функцию `Use!Structure!`, где `!Structure!` — это название структуры или псевдоним, однозначно определяющий с какой структурой мы сейчас работаем, например для стека `UseStack`, для очереди `UseQueue`, для структуры стек, основанной на связанных списках псевдоним `UseLinkedStack`. 79 | 80 | Полный список таких методов всегда предоставляется в файле `main.go` 81 | 82 | ### Очередь и стек (Queue & Stack) 83 | 84 | Начнем с этих двух, т.к. они немного схожи. 85 | По факту сами они не являются конкретными структурами данных, а объектами реализующий две операции: положить в очередь (push) и взять из очереди (pull). 86 | Для очереди порядок выдаются объекты в том же порядке — __FIFO__ (First In First Out), для стека в обратном — __LIFO__ (Last In First Out). Ну, есть ещё очередь с приоритетами — это когда первым выдаёт объект с наивысшим приоритетом. 87 | 88 | --- 89 | 90 | ### :point_right: Стек 91 | 92 | Это коллекция, элементы которой получают по принципу «последний вошел, первый вышел» (Last-In-First-Out или __LIFO__). 93 | Это значит, что мы будем иметь доступ только к последнему добавленному элементу. 94 | 95 | В отличие от списков, мы не можем получить доступ к произвольному элементу стека. 96 | Мы можем только добавлять или удалять элементы с помощью специальных методов. 97 | У стека нет также метода Contains, как у списков. 98 | Кроме того, у стека нет итератора. 99 | Для того, чтобы понимать, почему на стек накладываются такие ограничения, давайте посмотрим на то, как он работает и как используется. 100 | 101 | Наиболее часто встречающаяся аналогия для объяснения стека — стопка тарелок. 102 | Вне зависимости от того, сколько тарелок в стопке, мы всегда можем снять верхнюю. 103 | Чистые тарелки точно так же кладутся на верх стопки, и мы всегда будем первой брать ту тарелку, которая была положена последней. 104 | 105 | ![stack](img/stack.jpg) 106 | 107 | Если мы положим, например, красную тарелку, затем синюю, а затем зеленую, то сначала надо будет снять зеленую, потом синюю, и, наконец, красную. 108 | Главное, что надо запомнить — тарелки всегда ставятся и на верх стопки. 109 | Когда кто-то берет тарелку, он также снимает ее сверху. 110 | Получается, что тарелки разбираются в порядке, обратном тому, в котором ставились. 111 | 112 | Существует три основных операции, которые могут выполняться в стеках: 113 | - вставка элемента в стек (называемый «push»); 114 | - удаление элемента из стека (называемое «pop»); 115 | - отображение содержимого стека (иногда называемого «peak»). 116 | 117 | Теперь, когда мы понимаем, как работает стек, введем несколько терминов. 118 | Операция добавления элемента на стек называется «push», удаления — «pop». 119 | Последний добавленный элемент называется верхушкой стека, или «top», и его можно посмотреть с помощью операции «peek». 120 | 121 | **Реализация** 122 | 123 |
124 | Скрытый код 125 | 126 | ```go 127 | type Stack struct { 128 | mt sync.RWMutex 129 | collection map[int]interface{} 130 | count int 131 | } 132 | 133 | func NewStack() *Stack { 134 | return &Stack{ 135 | collection: map[int]interface{}{}, 136 | count: 0, 137 | } 138 | } 139 | 140 | // Adds a value onto the end of the stack 141 | // Добавляет элемент на вершину стека. 142 | // Сложность: O(1). 143 | func (s *Stack) Push(value interface{}) { 144 | s.mt.Lock() 145 | defer s.mt.Unlock() 146 | s.collection[s.count] = value 147 | s.Inc() 148 | } 149 | 150 | // Removes and returns the value at the end of the stack 151 | // Удаляет элемент с вершины стека и возвращает его. Если стек пустой, возвращает nil 152 | // Т.к.`Push` добавляет элементы в конец списка, поэтому забирать их будет также с конца. 153 | // Сложность: O(1). 154 | func (s *Stack) Pop() interface{} { 155 | s.mt.Lock() 156 | defer s.mt.Unlock() 157 | if s.Size() == 0 { 158 | return nil 159 | } 160 | 161 | s.Dec() 162 | result := s.collection[s.Size()] 163 | delete(s.collection, s.Size()) 164 | return result 165 | } 166 | 167 | // Returns the value at the end of the stack 168 | // Возвращает верхний элемент стека, но не удаляет его. 169 | // Сложность: O(1). 170 | func (s *Stack) Peek() interface{} { 171 | s.mt.RLock() 172 | defer s.mt.RUnlock() 173 | 174 | if s.Size() == 0 { 175 | return nil 176 | } 177 | 178 | return s.collection[s.Size()-1] 179 | } 180 | 181 | // Get count elements in stack 182 | // Возвращает количество элементов в стеке. 183 | // Зачем нам знать, сколько элементов находится в стеке, если мы все равно не имеем к ним доступа? 184 | // С помощью этого поля мы можем проверить, есть ли элементы на стеке или он пуст. 185 | // Сложность: O(1). 186 | func (s *Stack) Size() int { 187 | return s.count 188 | } 189 | 190 | func (s *Stack) Inc() { 191 | s.count++ 192 | } 193 | 194 | func (s *Stack) Dec() { 195 | s.count-- 196 | } 197 | ``` 198 |
199 | 200 | **Примнение** 201 | 202 |
203 | Обра́тная по́льская запись (англ. Reverse Polish notation, RPN) 204 | 205 | ```go 206 | // todo coming soon 207 | ``` 208 |
209 | 210 | **Сложность:** 211 | 212 | Алгоритм | В среднем | Худший случай 213 | --- | --- | --- 214 | Space | O(n) | O(n) | 215 | Search | O(n) | O(n) | 216 | Insert | O(1) | O(1) | 217 | Delete | O(1) | O(1) | 218 | 219 | **Вопросы о стеке, часто задаваемые на собеседованиях:** 220 | 221 | - Вычислить постфиксное выражение при помощи стека 222 | - Отсортировать значения в стеке 223 | - Проверить сбалансированные скобки в выражении 224 | 225 | --- 226 | 227 | ### :point_right: Очередь 228 | 229 | Вы можете думать об этой структуре, как об очереди людей в продуктовом магазине. Стоящий первым будет обслужен первым. Также как очередь. 230 | Если рассматривать очередь с точки доступа к данным, то она является __FIFO__ (First In First Out). 231 | Это означает, что после добавления нового элемента все элементы, которые были добавлены до этого, должны быть удалены до того, как новый элемент будет удален. 232 | 233 | В очереди есть только две основные операции и две вспомогательные: 234 | - enqueue; 235 | - dequeue; 236 | - peek; 237 | - count. 238 | 239 | Enqueue означает вставить элемент в конец очереди, а dequeue означает удаление переднего элемента. 240 | 241 | **Реализация** 242 |
243 | Скрытый код 244 | 245 | ```go 246 | type Queue struct { 247 | mt sync.RWMutex 248 | collection []interface{} 249 | } 250 | 251 | // Добавляет элемент в очередь. 252 | // Новые элементы очереди можно добавлять как в начало списка, так и в конец. 253 | // Важно только, чтобы элементы доставались с противоположного края. 254 | // Сложность: O(1). 255 | func (q *Queue) Enqueue(value interface{}) { 256 | q.mt.Lock() 257 | defer q.mt.Unlock() 258 | q.collection = append(q.collection, value) 259 | } 260 | 261 | // Удаляет первый помещенный элемент из очереди и возвращает его. 262 | // Если очередь пустая, возвращает nil 263 | // Поскольку мы вставляем элементы в конец списка, убирать мы их будем с начала. 264 | // Сложность: O(1). 265 | func (q *Queue) Dequeue() interface{} { 266 | q.mt.Lock() 267 | defer q.mt.Unlock() 268 | 269 | if q.isEmpty() { 270 | return nil 271 | } 272 | 273 | val := q.collection[0] 274 | q.collection = q.collection[1:] 275 | 276 | return val 277 | } 278 | 279 | // Возвращает элемент, который вернет следующий вызов метода Dequeue. 280 | // Очередь остается без изменений. Если очередь пустая, возврщается nil 281 | // Сложность: O(1) 282 | func (q *Queue) Peek() interface{} { 283 | q.mt.RLock() 284 | defer q.mt.RUnlock() 285 | 286 | if q.isEmpty() { 287 | return nil 288 | } 289 | 290 | return q.collection[0] 291 | } 292 | 293 | // Возвращает количество элементов в очереди или 0, если очередь пустая. 294 | // Сложность: O(1). 295 | func (q *Queue) Count() int { 296 | return len(q.collection) 297 | } 298 | 299 | func (q *Queue) isEmpty() bool { 300 | return q.Count() == 0 301 | } 302 | 303 | func (q *Queue) Collections() []interface{} { 304 | return q.collection 305 | } 306 | ``` 307 |
308 | 309 | **Сложность:** 310 | 311 | Алгоритм | В среднем | Худший случай 312 | --- | --- | --- 313 | Space | O(n) | O(n) | 314 | Search | O(n) | O(n) | 315 | Insert | O(1) | O(1) | 316 | Delete | O(1) | O(1) | 317 | 318 | **Вопросы о стеке, часто задаваемые на собеседованиях:** 319 | 320 | - Реализуйте стек при помощи очереди 321 | - Обратите первые k элементов в очереди 322 | - Сгенерируйте двоичные числа от 1 до n при помощи очереди 323 | 324 | --- 325 | 326 | ### :point_right: Множества (Sets) 327 | 328 | Это коллекция, которая реализует основные математические операции над множествами: 329 | 330 | __Пересечения__ (intersection) - если заданы два множества, эта функция вернет другое множество, содержащее элементы, которые имеются и в первом и во втором множестве; 331 | 332 | ```go 333 | [1, 2, 3, 4] intersect [3, 4, 5, 6] = [3, 4] 334 | ``` 335 | 336 | __Объединение__ (union) - объединяет все элементы из двух разных множеств и возвращает результат, как новый набор (без дубликатов); 337 | 338 | ```go 339 | [1, 2, 3, 4] union [3, 4, 5, 6] = [1, 2, 3, 4, 5, 6] 340 | ``` 341 | 342 | __Разность__ (difference) - вернет список элементов, которые находятся в одном множестве, но НЕ повторяются в другом; 343 | 344 | ```go 345 | [1, 2, 3, 4] difference [3, 4, 5, 6] = [1, 2] 346 | ``` 347 | 348 | __Симметрическая разность__ (symmetric difference) - это все элементы, которые содержатся только в одном из рассматриваемых множеств; 349 | 350 | ```go 351 | [1, 2, 3, 4] symmetric difference [3, 4, 5, 6] = [1, 2, 5, 6] 352 | ``` 353 | 354 | Вы, возможно, заметили, что симметрическая разность — это «пересечение наоборот». Учитывая это, давайте попробуем найти ее, используя уже имеющиеся операции. 355 | 356 | Итак, мы хотим получить множество, которое содержит все элементы из двух множеств, за исключением тех, которые есть в обоих. Другими словами, мы хотим получить разность объединения двух множеств и их пересечения. 357 | 358 | Или, если рассматривать по шагам: 359 | 360 | ```go 361 | [1, 2, 3, 4] union [3, 4, 5, 6] = [1, 2, 3, 4, 5, 6] 362 | 363 | [1, 2, 3, 4] intersection [3, 4, 5, 6] = [3, 4] 364 | 365 | [1, 2, 3, 4, 5, 6] set difference [3, 4] = [1, 2, 5, 6] 366 | ``` 367 | 368 | Что дает нам нужный результат: 369 | 370 | ```go 371 | [1, 2, 5, 6] 372 | ``` 373 | 374 | __Подмножество__ (subset) - возвращает булево значение, показывающее, содержит ли одно множество все элементы другого множества. 375 | 376 | ```go 377 | [1, 2, 3] is subset [0, 1, 2, 3, 4, 5] = true 378 | ``` 379 | 380 | В то время как: 381 | 382 | ```go 383 | [1, 2, 3] is subset [0, 1, 2] = false 384 | ``` 385 | 386 | Эту проверку можно провести, используя имещиеся методы: difference и intersection, пустой результат при разности и множество с таким же количеством элементов при пересечении говорит о том, что все элементы первого множества содержатся во втором множестве; 387 | 388 | ```go 389 | [1, 2, 3] difference [0, 1, 2, 3, 4, 5] = [] = true 390 | 391 | [1, 2, 3] intersection [0, 1, 2, 3, 4, 5] = [1, 2, 3] = true 392 | ``` 393 | 394 | В общем случае, конечно, множества (sets) может иметь метод __subset__ (который может быть реализован более эффективно). Однако стоит помнить, что это уже не какая-то новая возможность, а просто другое применение существующих. 395 | 396 | **Помните:** Множества хранят данные без определенного порядка и без повторяющихся значений. 397 | 398 | **Реализация** 399 | 400 |
401 | Скрытый код 402 | 403 | ```go 404 | type Set struct { 405 | collection []interface{} 406 | } 407 | 408 | func (s *Set) Collections() []interface{} { 409 | return s.collection 410 | } 411 | 412 | // Поведение: Добавляет элементы в множество. 413 | // Если элемент уже присутствует в множестве, возвращается false, иначе true. 414 | // Сложность: O(n) 415 | func (s *Set) Add(value interface{}) bool { 416 | if _, exist := s.Contains(value); exist == false { 417 | s.collection = append(s.collection, value) 418 | return true 419 | } 420 | 421 | return false 422 | } 423 | 424 | // Поведение: Возвращает index, true, если множество содержит указанный элемент. 425 | // В противном случае возвращает index, false. 426 | // Сложность: O(n) 427 | func (s *Set) Contains(value interface{}) (int, bool) { 428 | for k, v := range s.collection { 429 | if v == value { 430 | return k, true 431 | } 432 | } 433 | 434 | return 0, false 435 | } 436 | 437 | // Поведение: Удаляет указанный элемент из множества и возвращает true. 438 | // В случае, если элемент нет и он не удален, возвращает false. 439 | // Сложность: O(n) 440 | func (s *Set) Remove(value interface{}) bool { 441 | if i, exist := s.Contains(value); exist == true { 442 | length := s.Size() 443 | 444 | s.collection[i] = s.collection[length-1] 445 | s.collection = s.collection[:length-1] 446 | 447 | return true 448 | } 449 | 450 | return false 451 | } 452 | 453 | func (s *Set) Size() int { 454 | return len(s.collection) 455 | } 456 | 457 | // Поведение: Возвращает множество, полученное операцией объединения его с указанным. 458 | // Сложность: O(m·n), где m и n — количество элементов переданного и текущего множеств соответственно. 459 | func (s *Set) Union(set *Set) *Set { 460 | union := &Set{} 461 | 462 | for _, v := range s.Collections() { 463 | union.Add(v) 464 | } 465 | 466 | for _, v := range set.Collections() { 467 | union.Add(v) 468 | } 469 | 470 | return union 471 | } 472 | 473 | // Поведение: Возвращает множество, полученное операцией пересечения его с указанным. 474 | // Сложность: O(m·n), где m и n — количество элементов переданного и текущего множеств соответственно. 475 | func (s *Set) Intersection(set *Set) *Set { 476 | intersection := &Set{} 477 | 478 | for _, v := range s.Collections() { 479 | if _, exist := set.Contains(v); exist == true { 480 | intersection.Add(v) 481 | } 482 | } 483 | 484 | return intersection 485 | } 486 | 487 | // Поведение: Возвращает множество, являющееся разностью текущего с указанным. 488 | // Сложность: O(m·n), где m и n — количество элементов переданного и текущего множеств соответственно. 489 | func (s *Set) Difference(set *Set) *Set { 490 | difference := &Set{} 491 | 492 | for _, v := range s.Collections() { 493 | if _, exist := set.Contains(v); exist == false { 494 | difference.Add(v) 495 | } 496 | } 497 | 498 | return difference 499 | } 500 | 501 | // Поведение: Возвращает true, если второе множество является подмножеством первого, в противном случае возвращает false. 502 | // Сложность: O(m·n), где m и n — количество элементов переданного и текущего множеств соответственно. 503 | func (s *Set) Subset(set *Set) bool { 504 | if s.Size() > set.Size() { 505 | return false 506 | } 507 | 508 | return s.Difference(set).Size() == 0 509 | } 510 | 511 | // Поведение: Возвращает множество, являющееся симметрической разностью текущего с указанным. 512 | // Сложность: O(m·n), где m и n — количество элементов переданного и текущего множеств соответственно. 513 | func (s *Set) SymmetricDifference(set *Set) *Set { 514 | a := s.Difference(set) 515 | b := set.Difference(s) 516 | 517 | return a.Union(b) 518 | } 519 | ``` 520 |
521 | 522 | --- 523 | 524 | ### :point_right: Связанные списки (Linked List) 525 | 526 | Основное назначение связного списка — предоставление механизма для хранения и доступа к произвольному количеству данных. Как следует из названия, это достигается связыванием данных вместе в список. 527 | 528 | Для начала определим несколько терминов, чтобы в дальнейшем не возникло недопонимания: 529 | 530 | - __указатель__ содержит адрес участка памяти, хранящего определённые данные (для указателей также допустимо нулевое значение); 531 | - __ссылка__, в отличие от указателя, всегда должна указывать на определённый адрес; 532 | - __структура данных__ — способ группировки информации, который может быть реализован на любом языке программирования. 533 | 534 | Простейший способ создать односвязный список — поочерёдно создать и связать узлы: 535 | 536 | ```go 537 | type Node struct { 538 | value interface{} 539 | next *Node 540 | } 541 | 542 | first := &Node{3} 543 | moddle := &Node{5} 544 | last := &Node{7} 545 | 546 | first.next = middle 547 | 548 | // +-----+------+ +-----+------+ 549 | // | 3 | *---+--->| 5 | null + 550 | // +-----+------+ +-----+------+ 551 | 552 | middle.next = last 553 | 554 | // +-----+------+ +-----+------+ +-----+------+ 555 | // | 3 | *---+--->| 5 | *---+-->| 7 | null + 556 | // +-----+------+ +-----+------+ +-----+------+ 557 | 558 | ``` 559 | 560 | **Реализация** 561 | 562 |
563 | Скрытый код 564 | 565 | ```go 566 | type LinkedList struct { 567 | count int 568 | head *Node 569 | tail *Node 570 | } 571 | 572 | type Node struct { 573 | value interface{} 574 | next *Node 575 | } 576 | 577 | func (l *LinkedList) Add(value interface{}) { 578 | newNode := &Node{ 579 | value: value, 580 | next: nil, 581 | } 582 | 583 | if l.head == nil { 584 | l.head = newNode 585 | l.tail = newNode 586 | } else { 587 | l.tail.next = newNode 588 | l.tail = newNode 589 | } 590 | 591 | l.count++ 592 | } 593 | 594 | func (l *LinkedList) Remove(element interface{}) (int, bool) { 595 | current := l.head 596 | var previous *Node 597 | 598 | index := 0 599 | for current != nil { 600 | // find element 601 | if current.value == element { 602 | 603 | // is not first node 604 | if previous != nil { 605 | 606 | // before: Head -> 1 -> 2 -> 3 --> null 607 | // after: Head -> 1 -> 2 -------> null 608 | previous.next = current.next 609 | 610 | // set last node if current already is last 611 | if current.next == nil { 612 | l.tail = previous 613 | } 614 | } else { 615 | // before: Head -> 3 -> 5 616 | // after: Head ------> 5 617 | 618 | // Head -> 3 -> null 619 | // Head ------> null 620 | 621 | l.head = current.next 622 | // check is empty list 623 | if l.head == nil { 624 | l.tail = nil 625 | } 626 | } 627 | 628 | l.count-- 629 | return index, true 630 | } 631 | 632 | index++ 633 | previous = current 634 | current = current.next 635 | } 636 | 637 | return index, false 638 | } 639 | 640 | func (l *LinkedList) Contains(element interface{}) (int, bool) { 641 | current := l.head 642 | 643 | index := 0 644 | for current != nil { 645 | if current.value == element { 646 | return index, true 647 | } 648 | 649 | current = current.next 650 | index++ 651 | } 652 | 653 | return index, false 654 | } 655 | 656 | func (l *LinkedList) Size() int { 657 | return l.count 658 | } 659 | ``` 660 |
661 | 662 | **Реализация стека и очереди на основе связанных списков** 663 | 664 | С помощью связанных списков можно можно создавать такие структуры данных, как [Стеки (Stack)](#stack) и [Очереди (Queue)](#queue), которые мы уже реализовывали выше, давайте попробуем реализовать их с помощью связанных списков. 665 | 666 | Для этого, нам понадобится немного модифицировать существующий код связанных списков и добавить к ним несколько методов, которые сильно напоминают методы из двухсвязанных списков [(Double Linked List)](#linked_list_double) о которых мы поговорим чуть позже. 667 | 668 | ```go 669 | // данный метод добавляет элемент перед первым элементом в списке 670 | func (l *LinkedList) PreAdd(value interface{}) { 671 | newNode := &Node{ 672 | value: value, 673 | next: l.head, 674 | } 675 | 676 | l.head = newNode 677 | 678 | if l.tail == nil { 679 | l.tail = newNode 680 | } 681 | 682 | l.count++ 683 | } 684 | 685 | // следующие два метода удаляют с начала и конца соответственно названиям метода 686 | func (l *LinkedList) RemoveHead() (bool, *Node) { 687 | if l.head == nil { 688 | return false, nil 689 | } 690 | 691 | tmpHead := l.head 692 | 693 | if l.head.next != nil { 694 | l.head = l.head.next 695 | } else { 696 | l.head = nil 697 | l.head = nil 698 | } 699 | 700 | l.count-- 701 | return true, tmpHead 702 | } 703 | 704 | func (l *LinkedList) RemoveTail() (bool, *Node) { 705 | tmpTail := l.tail 706 | 707 | if l.head.next == nil { 708 | // There is only one node in linked list. 709 | 710 | l.head = nil 711 | l.tail = nil 712 | l.count-- 713 | return true, tmpTail 714 | } 715 | 716 | // If there are many nodes in linked list... 717 | // Rewind to the last node and delete "next" link for the node before the last one. 718 | 719 | currentNode := l.head 720 | for currentNode.next != nil { 721 | if currentNode.next.next == nil { 722 | currentNode.next = nil 723 | } else { 724 | currentNode = currentNode.next 725 | } 726 | } 727 | 728 | l.count-- 729 | l.tail = currentNode 730 | return true, tmpTail 731 | } 732 | ``` 733 | 734 | Теперь, когда мы добавили необходимые методы, мы можем реализовывать структуру данных Стек с помощью связанных списков: 735 | 736 |
737 | Реализация структуры Стек с помощью связанного списка 738 | 739 | ```go 740 | type LinkedStack struct { 741 | linkedList LinkedList 742 | } 743 | 744 | func NewLinkedStack() LinkedStack { 745 | return LinkedStack{linkedList: LinkedList { 746 | count: 0, 747 | head: nil, 748 | tail: nil, 749 | }} 750 | } 751 | 752 | func (ls *LinkedStack) Peek() interface{} { 753 | if ls.IsEmpty() { 754 | return nil 755 | } 756 | 757 | return ls.linkedList.head.value 758 | } 759 | 760 | func (ls *LinkedStack) Push(value interface{}) { 761 | ls.linkedList.PreAdd(value) 762 | } 763 | 764 | func (ls *LinkedStack) Pop() interface{} { 765 | if ok, removed := ls.linkedList.RemoveHead(); ok { 766 | return removed.value 767 | } 768 | 769 | return nil 770 | } 771 | 772 | func (ls *LinkedStack) IsEmpty() bool { 773 | return ls.linkedList.head == nil 774 | } 775 | 776 | func (ls *LinkedStack) Size() int { 777 | return ls.linkedList.Size() 778 | } 779 | ``` 780 |
781 | 782 | Таким же образом реализуем структуру очередь: 783 | 784 |
785 | Реализация структуры Очередь с помощью связанного списка 786 | 787 | ```go 788 | type LinkedQueue struct { 789 | linkedList LinkedList 790 | } 791 | 792 | func (lq *LinkedQueue) Enqueue(value interface{}) { 793 | lq.linkedList.Add(value) 794 | } 795 | 796 | func (lq *LinkedQueue) Dequeue() interface{} { 797 | if ok, removed := lq.linkedList.RemoveHead(); ok { 798 | return removed.value 799 | } 800 | 801 | return nil 802 | } 803 | 804 | func (lq *LinkedQueue) Peek() interface{} { 805 | if lq.linkedList.head == nil { 806 | return nil 807 | } 808 | 809 | return lq.linkedList.head.value 810 | } 811 | 812 | func (lq *LinkedQueue) ToString() { 813 | lq.linkedList.PrintNodes() 814 | } 815 | ``` 816 |
817 | 818 | Вот так вот легко реализовывать структуры данных поверх другой структуры данных. 819 | Сравните полученный код с "нативной" реализацией и вы увидите, что код стал короче и компактнее, а так же то, что код выглядит более унифицированным. 820 | 821 | **Сложность:** 822 | 823 | Алгоритм | В среднем | Худший случай 824 | --- | --- | --- 825 | Space | O(n) | O(n) | 826 | Search | O(n) | O(n) | 827 | Insert | O(1) | O(1) | 828 | Delete | O(1) | O(1) | 829 | 830 | **Вопросы о связанных списках, часто задаваемые на собеседованиях:** 831 | 832 | - Обратите связный список 833 | - Найдите петлю в связном списке 834 | - Возвратите N-ный узел с начала связного списка 835 | - Удалите из связного списка дублирующиеся значения 836 | 837 | --- 838 | --------------------------------------------------------------------------------