├── .gitignore ├── Additional └── Additional-2024-IS │ ├── README.md │ └── solutions │ ├── basic-parser.cpp │ ├── build_from_sorted.cpp │ ├── odd-even-sort.cpp │ ├── remove-nodes-recursion.cpp │ ├── remove-nodes-stack.cpp │ ├── sliding-window.maximum.cpp │ └── sort-by-level-tree.cpp ├── Practicum ├── Additional │ └── lambdas_and_higher_order_functions.md ├── pract01 │ └── readme.md ├── pract02 │ ├── CountingSort.cpp │ └── readme.md ├── pract03 │ ├── README.md │ ├── merge.cpp │ ├── partition.cpp │ └── solutions.cpp ├── pract04 │ └── readme.md ├── pract05 │ ├── readme.md │ └── solutions │ │ ├── Task01.cpp │ │ ├── Task02.cpp │ │ ├── Task03.cpp │ │ └── Task04.cpp ├── pract06 │ ├── readme.md │ └── solutions │ │ ├── Task 01.cpp │ │ └── Task 02.cpp ├── pract07 │ ├── ExampleFunctions.cpp │ ├── Traversals.cpp │ ├── binaryTree.png │ ├── inorder.gif │ ├── postorder.gif │ ├── preorder.gif │ ├── readme.md │ └── tree-dfs-vs-bfs.gif ├── pract08 │ └── readme.md ├── pract09 │ └── readme.md ├── pract10 │ └── readme.md ├── pract11 │ └── readme.md └── pract12 │ ├── LexStable.md │ └── readme.md ├── README.md ├── Seminar01 ├── README.md └── src │ ├── searching-algorithms │ └── binary-search.cpp │ └── sorting-algorithms │ ├── bubble-sort.cpp │ ├── insertion-sort.cpp │ └── selection-sort.cpp ├── Seminar02 ├── README.md ├── media │ ├── merge-func.png │ ├── merge.png │ ├── rec_tree.png │ └── recurr2.png └── src │ └── sorting-algorithms │ ├── merge-sort-dummy.cpp │ ├── merge-sort.cpp │ └── quick-sort.cpp ├── Seminar03 ├── README.md └── src │ ├── amortized-analysis │ └── all-boolean-vectors.cpp │ ├── dynamic-array │ ├── examples │ │ ├── placement-new-memory-strategy.cpp │ │ └── variadic-templates.cpp │ ├── iterator │ │ └── iterator.hpp │ ├── tests.cpp │ └── vector │ │ └── vector.hpp │ └── sorting │ └── counting-sort.cpp ├── Seminar04 └── src │ ├── doubly-linked-list.hpp │ └── tests.cpp ├── Seminar05 └── src │ ├── deque │ ├── deque-tests.cpp │ └── deque.hpp │ └── singly-linked-list │ ├── merge-sort.cpp │ ├── node_utils.h │ ├── quick_sort.cpp │ ├── singly-linked-list-tests.cpp │ └── singly-linked-list.hpp ├── Seminar06 ├── README.md ├── media │ ├── left-child-right-sibling.png │ ├── stick.png │ ├── subtree.png │ ├── tree-first-example.png │ └── tree-second-example.png └── src │ ├── balanced-brackets.cpp │ ├── forward-iterator-bst.hpp │ └── test-bst-iter.cpp ├── Seminar07 ├── README.md ├── media │ └── rotations.png └── src │ └── tasks.cpp ├── Seminar08 ├── README.md └── src │ ├── BST │ ├── indexable-bst.hpp │ └── tests.cpp │ └── rotations.cpp ├── Seminar09 ├── README.md ├── media │ ├── hashmap.png │ ├── insert-first.png │ ├── insert-second.png │ └── separate_chaining.png └── src │ └── priority-queue │ ├── heapsort.cpp │ └── priority-queue.hpp ├── Seminar10 ├── README.md └── src │ ├── hashmap │ ├── harry.txt │ ├── linear-probing.hpp │ ├── separate-chaining.hpp │ └── tests.cpp │ └── lru-cache.cpp ├── Seminar11 ├── README.md ├── find-cycle.cpp └── media │ ├── 1.png │ ├── dfs-example.gif │ ├── dir-graph.png │ ├── graph.png │ ├── incidence-matrix-impl.png │ ├── incidence-matrix.png │ ├── matrix.png │ ├── weighted-dirscted-graph (1).png │ ├── weighted-graph.png │ └── weighted-list.png ├── Seminar12 └── src │ ├── connected-components │ └── count-cc.cpp │ ├── graph_impl │ ├── graph │ │ ├── graph.cpp │ │ └── graph.h │ └── weighted_graph │ │ ├── weighted_graph.cpp │ │ └── weighted_graph.h │ ├── shortest_paths │ ├── shp_no_wight.cpp │ └── shp_weighted.cpp │ └── topological_sort │ └── toposort.cpp └── Seminar13 └── src ├── mst-kruskal.cpp └── mst-prim.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # VScode 35 | .vscode -------------------------------------------------------------------------------- /Additional/Additional-2024-IS/README.md: -------------------------------------------------------------------------------- 1 | # Допълнително упражнение за контролно 1 за специалност информационни системи - 15.11.2024 2 | 3 | ## Задача първа - https://leetcode.com/problems/remove-nodes-from-linked-list/ 4 | Да се напише функция `ListNode* removeNodes(ListNode* node)`, която премахва всеки възел на свързан списък, който има възел с по - голяма стойност вдясно от него, и връща началото на новия списък. 5 | 6 | ![image](https://github.com/user-attachments/assets/718c2e45-ba69-432a-8cde-708a04f99ac2) 7 | 8 | Вход: глава = [5,2,13,3,8] 9 | 10 | Изход: [13,8] 11 | 12 | ## Задача втора 13 | Да се напише функция `node* level_sort(node* root)`, която приема двоично дърво, и връща ново двоично дърво, за което е вярно, че: 14 | * Дървото е пълно - Пълно дърво наричаме дърво, в което всяко ниво (освен евентуално последното) е изцяло запълнено. 15 | * Обхождайки последователно нивата на дървото отляво надясно ще получим елементите на дървото в сортиран вид. 16 | 17 | Решението да работи в O(nlog(n)) време и да използва O(n) памет. 18 | 19 | ## Задача трета 20 | Имаме низ състоящ се от малки латински букви, символа * и символа $. Всеки път, стигайки до символ *, изтриваме най - лявата буква в резултатния низ. Всеки път стигайки до симвой $ добавяме най - лявата буква в резултатния низ. 21 | Да се напише функция `std::string parse(const std::string& str);` която приема низ от този вид и връща низ, който прилага всички символи * и $ върху низа. 22 | 23 | Пример: 24 | 25 | * abc\*\$\$\*d$a$$zz\* => abbddaaaz 26 | * aaa*** => "" 27 | * aaa$ => aaa 28 | 29 | --- 30 | --- 31 | --- 32 | 33 | ## Задача четвърта 34 | Да се напише функция, която приема сортиран масив, и строи балансирано двоично наредено дърво от него. 35 | 36 | ## Задача пета 37 | Да се напише функция, която като вход приема масив и връща дърво, в което лявото поддърво на корена съдържа чеслата по - малки от 0, а дясното поддърво тези по - големи от 0. Стойността на корена може да е произволно число от масива. 38 | 39 | ## Задача шеста 40 | Напишете функция, която получава като аргумент едносвързан списък с елементи цели числа. Функцията трябва да го сортира по следното правило - в началото на списъка трябва да останат четните елементи, подредени в нарастващ ред, след това нечетните елементи, подредени в намаляващ ред. 41 | 42 | Бонус: премахнете повтарящите се елементи. 43 | 44 | ## Задача седма 45 | Даден е масив от цели числа. Представяме си, че имаме прозорец с дължина *k* който се движи отляво надясно. Единственото което можем да видим са числата в прозореца. Да се изведе максимумът на всеки такъв прозорец. -------------------------------------------------------------------------------- /Additional/Additional-2024-IS/solutions/basic-parser.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | std::string parse(const std::string& str) { 6 | std::stack result; 7 | 8 | for (char ch : str) { 9 | if (ch >= 'a' && ch <= 'z') { 10 | result.push(ch); 11 | } else if (ch == '*' && !result.empty()) { 12 | result.pop(); 13 | } else if (ch == '$' && !result.empty()) { 14 | result.push(result.top()); 15 | } 16 | } 17 | 18 | std::string finalResult; 19 | while (!result.empty()) { 20 | finalResult = result.top() + finalResult; 21 | result.pop(); 22 | } 23 | 24 | return finalResult; 25 | } 26 | 27 | int main() { 28 | std::string input = "abc*$$*d$a$$zz*"; 29 | std::cout << "Result: " << parse(input) << std::endl; 30 | 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /Additional/Additional-2024-IS/solutions/build_from_sorted.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct node 5 | { 6 | int data; 7 | node* left = nullptr; 8 | node* right = nullptr; 9 | 10 | node(int d, node* l = nullptr, node* r = nullptr) 11 | : data(d) 12 | , left(l) 13 | , right(r) 14 | {} 15 | }; 16 | 17 | using const_iterator = std::vector::const_iterator; 18 | 19 | node* build_from_range(const_iterator begin, const_iterator end) 20 | { 21 | // When end is before begin std::distance will return 22 | // negative number by standard. 23 | int distance = std::distance(begin, end); 24 | 25 | if(distance < 0) 26 | return nullptr; 27 | 28 | if(distance == 0) 29 | return new node(*begin); 30 | 31 | int mid = distance / 2; 32 | 33 | return new node( 34 | *(begin + mid), 35 | build_from_range(begin, begin + mid - 1), 36 | build_from_range(begin + mid + 1, end) 37 | ); 38 | } 39 | 40 | node* build_from_sorted(const std::vector& v) 41 | { 42 | return build_from_range(v.begin(), v.end() - 1); 43 | } 44 | 45 | void inorder(node* root) 46 | { 47 | if(!root) return; 48 | 49 | inorder(root->left); 50 | std::cout << root->data << " "; 51 | inorder(root->right); 52 | } 53 | 54 | void free_tree(node* tree) 55 | { 56 | if(!tree) 57 | return; 58 | free_tree(tree->left); 59 | free_tree(tree->right); 60 | 61 | delete tree; 62 | } 63 | 64 | int main() 65 | { 66 | std::vector sorted = {1, 2, 4, 8, 16, 32, 64}; 67 | node* result = build_from_sorted(sorted); 68 | 69 | inorder(result); 70 | free_tree(result); 71 | } -------------------------------------------------------------------------------- /Additional/Additional-2024-IS/solutions/odd-even-sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct ListNode 4 | { 5 | int val; 6 | ListNode* next; 7 | ListNode(int x, ListNode* next = nullptr) : val(x), next(next) {} 8 | }; 9 | template 10 | void insert_sorted(ListNode*& head, ListNode*& tail, ListNode* element, const Comparator& cmp) 11 | { 12 | if(head == nullptr) 13 | { 14 | head = tail = element; 15 | return; 16 | } 17 | 18 | if(!cmp(head->val, element->val)) 19 | { 20 | element->next = head; 21 | head = element; 22 | return; 23 | } 24 | 25 | ListNode* iter = head; 26 | 27 | while(iter->next) 28 | { 29 | if(!cmp(iter->next->val, element->val)) 30 | break; 31 | 32 | iter = iter->next; 33 | } 34 | 35 | if(iter->next == nullptr) 36 | tail = element; 37 | 38 | element->next = iter->next; 39 | iter->next = element; 40 | } 41 | 42 | ListNode* odd_even_sort(ListNode* node) 43 | { 44 | if(!node || !node->next) 45 | return node; 46 | 47 | ListNode* even_head = nullptr; 48 | ListNode* even_tail = nullptr; 49 | 50 | ListNode* odd_head = nullptr; 51 | ListNode* odd_tail = nullptr; 52 | 53 | std::less less; 54 | std::greater greater; 55 | 56 | while(node) 57 | { 58 | ListNode* to_insert = node; 59 | node = node->next; 60 | to_insert->next = nullptr; 61 | 62 | if(to_insert->val % 2 == 0) 63 | { 64 | insert_sorted(even_head, even_tail, to_insert, less); 65 | } 66 | else 67 | { 68 | insert_sorted(odd_head, odd_tail, to_insert, greater); 69 | } 70 | } 71 | 72 | if(odd_tail != nullptr) 73 | odd_tail->next = nullptr; 74 | 75 | if(even_head == nullptr) 76 | { 77 | return odd_head; 78 | } 79 | 80 | even_tail->next = odd_head; 81 | return even_head; 82 | } 83 | 84 | 85 | void free_list(ListNode* list) 86 | { 87 | while(list) 88 | { 89 | ListNode* to_delete = list; 90 | list = list->next; 91 | delete list; 92 | } 93 | } 94 | 95 | int main() 96 | { 97 | ListNode* example = new ListNode( 98 | 11, new ListNode(21, new ListNode(31, new ListNode(5, new ListNode(51)))) 99 | ); 100 | 101 | ListNode* result = odd_even_sort(example); 102 | 103 | ListNode* iter = result; 104 | 105 | while(iter) 106 | { 107 | std::cout << iter->val << " "; 108 | iter = iter->next; 109 | } 110 | 111 | free_list(result); 112 | } -------------------------------------------------------------------------------- /Additional/Additional-2024-IS/solutions/remove-nodes-recursion.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct ListNode 5 | { 6 | int val; 7 | ListNode* next; 8 | ListNode(int x, ListNode* next = nullptr) 9 | : val(x) 10 | , next(next) 11 | {} 12 | }; 13 | 14 | class Solution 15 | { 16 | public: 17 | ListNode* removeNodes(ListNode* head) 18 | { 19 | int maxVal = INT_MIN; 20 | return removeNodesRecursive(head, maxVal); 21 | } 22 | 23 | private: 24 | ListNode* removeNodesRecursive(ListNode* node, int& maxVal) 25 | { 26 | if (node == nullptr) 27 | { 28 | return nullptr; 29 | } 30 | 31 | ListNode* filteredNext = removeNodesRecursive(node->next, maxVal); 32 | 33 | if (node->val >= maxVal) 34 | { 35 | maxVal = node->val; 36 | node->next = filteredNext; 37 | return node; 38 | } 39 | else 40 | { 41 | delete node; 42 | return filteredNext; 43 | } 44 | } 45 | }; 46 | 47 | void printList(ListNode* node) 48 | { 49 | while (node) 50 | { 51 | std::cout << node->val << " "; 52 | node = node->next; 53 | } 54 | std::cout << std::endl; 55 | } 56 | 57 | int main() 58 | { 59 | Solution s; 60 | 61 | ListNode* result = 62 | s.removeNodes(new ListNode(5, new ListNode(2, new ListNode(13, new ListNode(3, new ListNode(8)))))); 63 | 64 | printList(result); 65 | 66 | ListNode* current = result; 67 | while (current) 68 | { 69 | ListNode* temp = current; 70 | current = current->next; 71 | delete temp; 72 | } 73 | 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /Additional/Additional-2024-IS/solutions/remove-nodes-stack.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct ListNode 6 | { 7 | int val; 8 | ListNode* next; 9 | ListNode() 10 | : val(0) 11 | , next(nullptr) 12 | {} 13 | ListNode(int x) 14 | : val(x) 15 | , next(nullptr) 16 | {} 17 | ListNode(int x, ListNode* next) 18 | : val(x) 19 | , next(next) 20 | {} 21 | }; 22 | 23 | class Solution 24 | { 25 | public: 26 | ListNode* removeNodes(ListNode* node) 27 | { 28 | if (!node || !node->next) 29 | return node; 30 | 31 | std::stack listNodes; 32 | int maxValue = INT_MIN; 33 | 34 | ListNode* iter = nullptr; 35 | 36 | while (node) 37 | { 38 | listNodes.push(node); 39 | node = node->next; 40 | } 41 | 42 | while (!listNodes.empty()) 43 | { 44 | ListNode* currentNode = listNodes.top(); 45 | listNodes.pop(); 46 | 47 | if (currentNode->val >= maxValue) 48 | { 49 | maxValue = currentNode->val; 50 | currentNode->next = iter; 51 | iter = currentNode; 52 | } 53 | else 54 | { 55 | delete currentNode; 56 | } 57 | } 58 | 59 | return iter; 60 | } 61 | }; 62 | 63 | void freeList(ListNode* node) 64 | { 65 | while (node) 66 | { 67 | ListNode* temp = node; 68 | node = node->next; 69 | delete temp; 70 | } 71 | } 72 | 73 | int main() 74 | { 75 | Solution s; 76 | 77 | ListNode* result = 78 | s.removeNodes(new ListNode(5, new ListNode(2, new ListNode(13, new ListNode(3, new ListNode(8)))))); 79 | 80 | ListNode* toDelete = result; 81 | 82 | while (result) 83 | { 84 | std::cout << result->val << " "; 85 | result = result->next; 86 | } 87 | 88 | freeList(toDelete); 89 | } 90 | -------------------------------------------------------------------------------- /Additional/Additional-2024-IS/solutions/sliding-window.maximum.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | std::vector sliding_window_maximum(const std::vector v, unsigned k) 6 | { 7 | std::vector result; 8 | std::deque window; 9 | 10 | for (size_t i = 0; i < k; i++) 11 | { 12 | while(!window.empty() && window.back() < v[i]) 13 | { 14 | window.pop_back(); 15 | } 16 | window.push_back(v[i]); 17 | } 18 | 19 | result.push_back(window.front()); 20 | 21 | for (size_t i = k; i < v.size(); i++) 22 | { 23 | if(window.front() == v[i-k]) 24 | window.pop_front(); 25 | 26 | while(!window.empty() && window.back() < v[i]) 27 | { 28 | window.pop_back(); 29 | } 30 | 31 | window.push_back(v[i]); 32 | 33 | result.push_back(window.front()); 34 | } 35 | 36 | return result; 37 | } 38 | 39 | int main() 40 | { 41 | 42 | } -------------------------------------------------------------------------------- /Additional/Additional-2024-IS/solutions/sort-by-level-tree.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct node 6 | { 7 | int data; 8 | node* left = nullptr; 9 | node* right = nullptr; 10 | 11 | node(int d, node* l = nullptr, node* r = nullptr) 12 | : data(d) 13 | , left(l) 14 | , right(r) 15 | {} 16 | }; 17 | 18 | void fill(node* root, std::vector& v) 19 | { 20 | if (!root) 21 | return; 22 | 23 | v.push_back(root->data); 24 | fill(root->left, v); 25 | fill(root->right, v); 26 | } 27 | 28 | node* sort_by_level(node* tree) 29 | { 30 | std::vector v; 31 | fill(tree, v); 32 | 33 | std::sort(v.begin(), v.end()); 34 | 35 | node* to_return = new node(v[0]); 36 | int idx = 1; 37 | 38 | std::queue q; 39 | q.push(to_return); 40 | 41 | while (idx < v.size()) 42 | { 43 | node* current = q.front(); 44 | q.pop(); 45 | 46 | current->left = new node(v[idx++]); 47 | if (idx >= v.size()) 48 | break; 49 | current->right = new node(v[idx++]); 50 | 51 | q.push(current->left); 52 | q.push(current->right); 53 | } 54 | 55 | return to_return; 56 | } 57 | 58 | void free_tree(node* root) 59 | { 60 | if (!root) 61 | return; 62 | free_tree(root->left); 63 | free_tree(root->right); 64 | delete root; 65 | } 66 | 67 | // Print tree by levels. 68 | // Not presented - used just for demo. 69 | void print_levels(node* result) 70 | { 71 | std::queue q; 72 | q.push(result); 73 | q.push(nullptr); 74 | 75 | while (q.size()) 76 | { 77 | node* current = q.front(); 78 | q.pop(); 79 | 80 | if (current == nullptr) 81 | { 82 | if (!q.empty()) 83 | { 84 | q.push(nullptr); 85 | std::cout << std::endl; 86 | } 87 | continue; 88 | } 89 | 90 | std::cout << current->data << " "; 91 | 92 | if (current->left) 93 | q.push(current->left); 94 | if (current->right) 95 | q.push(current->right); 96 | } 97 | } 98 | 99 | int main() 100 | { 101 | node* tree = new node(5, 102 | new node(3, 103 | new node(4), 104 | new node(0, 105 | new node(1), 106 | new node(2))), 107 | new node(8, 108 | new node(9), 109 | new node(7, new node(10)))); 110 | 111 | node* result = sort_by_level(tree); 112 | print_levels(result); 113 | 114 | free_tree(result); 115 | free_tree(tree); 116 | } -------------------------------------------------------------------------------- /Practicum/Additional/lambdas_and_higher_order_functions.md: -------------------------------------------------------------------------------- 1 | # Функции от по-висок ред и ламбда функции в C++ 2 | 3 | ## Указатели към функции 4 | 5 | ![image](https://github.com/desiish/OOP_Pract_2023_2024/assets/115353472/ba159e4b-ec0b-4827-a498-ccd469ab60d1) 6 | 7 | Всяка функция си има определено място в паметта. Съответно можем да насочим указател към нея. Този указател представлява всъщност адреса на изпълнимия код на дадена функция. 8 | ```c++ 9 | #include 10 | 11 | int add(int x, int y) { 12 | return x + y; 13 | } 14 | 15 | int main() { 16 | int(*addPtr)(int, int) = add; // pointer to function that accepts 2 integers as arguments and returns an integer as a result 17 | 18 | int resFromPtr = addPtr(2, 3); 19 | int resFromFunc = add(2, 3); 20 | 21 | std::cout << resFromFunc << " " << resFromPtr; // 5 5 22 | } 23 | ``` 24 | ❗Не можем да извършваме аритметични операции върху тези указатели. 25 | 26 | ## Функции от по-висок ред 27 | 28 | Функциите от по-висок ред в C++ са функции, които могат да приемат други функции като аргументи или да връщат функции като резултат. Те позволяват писането на по-кратък и по-четим код, като в същото време се поддържа неговата функционалност. 29 | 30 | Един от най-често срещаните начини за използване на функции от по-висок ред е подаването на функции като аргументи на други функции. Например, функциите `std::sort` и `std::find_if` от стандартната библиотека на C++ приемат функции като аргументи, които задават критерии съответно за сортиране и търсене на данните, които са им подадени. 31 | 32 | ## Ламбда функции 33 | 34 | Ламбда функциите в C++ са безименни (анонимни) функции, които могат да се дефинират на място и да се използват там, където се очаква функция. Те са полезни за кратки и еднократни операции, където не е необходимо да се дефинира отделна функция. 35 | 36 | Синтаксисът на ламбда функциите е: 37 | ```c++ 38 | [capture list] (parameters) -> return_type { function_body } 39 | ``` 40 | Стрелката (->) и типът на връщане не са задължителни при дефиниране на ламбда функции, когато компилаторът може автоматично да определи връщания тип на функцията. В този случай може да се използва следният синтаксис: 41 | ```c++ 42 | [capture list] (parameters) { function_body } 43 | ``` 44 | където: 45 | - `capture list`: Списъкът с capture-нати променливи, които ламбда функцията ще използва. Те са външни (от външен scope) и обикновено не би трябвало да бъдат променяни от ламбдата; 46 | - `parameters`: Списъкът с параметри на ламбда функцията; 47 | - `return_type`: Типът на върнатата стойност от ламбда функцията (опционален); 48 | - `function_body`: Тялото на ламбда функцията, което съдържа операциите, които тя трябва да изпълни. 49 | 50 | 51 | ## Можем ли да подаваме ламбда функции като параметър на други функции? 52 | Отговорът е зависи. Една функция от по-висок ред приема указател към друга функция, която да използва някъде в тялото на кода си. 53 | 54 | Сега си представете една ламбда функция в паметта - тя представлява един обект - обвивка на функцията. Можем да мислим за него като за една кутийка. В нея запазваме както информацията за функцията, така и данните от *capture list*-a й. Когато нямаме нищо в *capture list*-а, тя може да се преобразува в пойнтър към функция от съответния тип, защото няма да изгубим никаква информация по време на това преобразуване. Така можем да я подадем като аргумент на друга функция, очакваща такъв тип указател. Но когато имаме capture-нати променливи, няма как просто да ги пренебрегнем. Съответно преобразуването става невъзможно. 55 | 56 | ![image](https://github.com/desiish/oop_tasks/assets/115353472/b50deece-aec9-4d55-848f-8f4dbc923181) 57 | 58 | ❗Затова ламбда функции ще подаваме като аргумент на други функции само когато в *capture list*-a им нямаме никакви данни. 59 | 60 | ## Пример 61 | **Задача:** Търсим броя символи в даден стринг, отговарящи на някакво условие: 62 | - да са цифри; 63 | - да са малки латински букви; 64 | - да са главни латински букви. 65 | 66 | Примерно решение: 67 | ```c++ 68 | #include 69 | 70 | namespace Constants { 71 | constexpr int INVALID_COUNT = -1; 72 | } 73 | 74 | enum class Criteria { 75 | FIND_UPPERS, 76 | FIND_LOWERS, 77 | FIND_DIGITS, 78 | UNKNOWN 79 | }; 80 | 81 | int getCountOfSymbols(const char* str, bool (*pred) (char ch)) { 82 | if (!str) 83 | return Constants::INVALID_COUNT; 84 | 85 | int count = 0; 86 | while (*str) { 87 | if (pred(*str)) 88 | count++; 89 | 90 | str++; 91 | } 92 | 93 | return count; 94 | } 95 | 96 | //with lambda expressions 97 | 98 | int getCountOfSymbolsByCriteriaLambdas(const char* str, Criteria cr) { 99 | switch (cr) { 100 | case Criteria::FIND_DIGITS: return getCountOfSymbols(str, [](char ch) {return ch >= '0' && ch <= '9'; }); 101 | case Criteria::FIND_LOWERS: return getCountOfSymbols(str, [](char ch) {return ch >= 'a' && ch <= 'z'; }); 102 | case Criteria::FIND_UPPERS: return getCountOfSymbols(str, [](char ch) {return ch >= 'A' && ch <= 'Z'; }); 103 | default: return Constants::INVALID_COUNT; 104 | } 105 | } 106 | 107 | //with already defined functions 108 | 109 | bool isDigit(char ch) { 110 | return ch >= '0' && ch <= '9'; 111 | } 112 | 113 | bool isLower(char ch) { 114 | return ch >= 'a' && ch <= 'z'; 115 | } 116 | 117 | bool isUpper(char ch) { 118 | return ch >= 'A' && ch <= 'Z'; 119 | } 120 | 121 | int getCountOfSymbolsByCriteriaDefinedFuncs(const char* str, Criteria cr) { 122 | switch (cr) { 123 | case Criteria::FIND_DIGITS: return getCountOfSymbols(str, isDigit); 124 | case Criteria::FIND_LOWERS: return getCountOfSymbols(str, isLower); 125 | case Criteria::FIND_UPPERS: return getCountOfSymbols(str, isUpper); 126 | default: return Constants::INVALID_COUNT; 127 | } 128 | } 129 | ``` 130 | -------------------------------------------------------------------------------- /Practicum/pract01/readme.md: -------------------------------------------------------------------------------- 1 | # Практикум №1 по СД, спец. ИС 2 | 3 | ## Функцията std::sort 4 | `std::sort` е стандартна функция в C++, която се използва за сортиране на елементи от дадена колекция от данни. Тя е част от стандартната библиотека ``, която съдържа различни алгоритми за обработка на колекции от данни. 5 | 6 | Най-често е имплементирана или чрез `Quick Sort`, или чрез алгоритъм, комбиниращ `Quick Sort`, `Heap Sort` (за който ще научите по-натам в курса) и `Insertion Sort`. 7 | 8 | Декларация на функцията: 9 | ```c++ 10 | #include 11 | 12 | template 13 | void sort(RandomIt first, RandomIt last); 14 | 15 | template 16 | void sort(RandomIt first, RandomIt last, Compare comp); 17 | 18 | ``` 19 | 20 | - `Параметри`: 21 | - first: Итератор, указващ началото на колекцията елементи, които трябва да бъдат сортирани. 22 | - last: Итератор, указващ края на колекцията елементи, които трябва да бъдат сортирани (сочи елементът след последния елемент в диапазона). 23 | - comp (по избор): критерий за сортиране (по default се използва operator<). 24 | 25 | - `Сложност`: std::sort има средна времева сложност O(n*log n), където n е броят на елементите, които искаме да бъдат сортирани. 26 | - `In-Place`: std::sort не е in-place алгоритъм. 27 | - `Stable`: std::sort не е стабилен алгоритъм. 28 | 29 | - Пример с функция: 30 | ```c++ 31 | #include 32 | #include 33 | #include 34 | 35 | bool compareLastDigit(int a, int b) { 36 | return (a % 10) < (b % 10); 37 | } 38 | 39 | int main() { 40 | std::vector v = {25, 42, 33, 14, 56}; 41 | 42 | std::sort(v.begin(), v.end(), compareLastDigit); 43 | 44 | for (const int& n : v) { 45 | std::cout << n << ' '; 46 | } 47 | return 0; 48 | } 49 | 50 | ``` 51 | 52 | - Пример с **ламбда** функция: 53 | ```c++ 54 | #include 55 | #include 56 | #include 57 | 58 | struct PairOfInt { 59 | int first; 60 | int second; 61 | }; 62 | 63 | int main() { 64 | std::vector v = { {1, 2}, {2, 1}, {5, 7}, {3, 3}, {4, 3} }; 65 | 66 | std::sort(v.begin(), v.end(), [](const PairOfInt& lhs, const PairOfInt& rhs) { return lhs.second < rhs.second; }); 67 | 68 | for (const auto& n : v) { 69 | std::cout << n.first << ' ' << n.second << std::endl; 70 | } 71 | return 0; 72 | } 73 | 74 | ``` 75 | 76 | ## Задачи и линк към методите на `std::vector` 77 | - [**std::vector**](https://cplusplus.com/reference/vector/vector/) 78 | - [**Линк към задачите**](https://leetcode.com/problem-list/an1ryio3/) 79 | - [Линк към таблица с leetcode акаунти](https://docs.google.com/spreadsheets/d/1KVdic02U7f97GT2LFu67t9Z4NqyFLmCj1TjyYdrcLBE/edit?gid=0#gid=0) 80 | -------------------------------------------------------------------------------- /Practicum/pract02/CountingSort.cpp: -------------------------------------------------------------------------------- 1 | #include // size_t 2 | 3 | void counting_sort(int* arr, size_t arrLength) { 4 | constexpr int MAX_SIZE = 1e5; 5 | int arr_copy[MAX_SIZE]; 6 | 7 | int max = arr[0]; 8 | for (int i = 0; i < arrLength; ++i) { 9 | if (max < arr[i]) { 10 | max = arr[i]; 11 | } 12 | arr_copy[i] = arr[i]; 13 | } 14 | 15 | int count[MAX_SIZE]; 16 | for (int i = 0; i < max + 1; ++i) { 17 | count[i] = 0; 18 | } 19 | 20 | for (int i = 0; i < arrLength; ++i) { 21 | count[arr[i]]++; 22 | } 23 | 24 | for (int i = 1; i <= max; ++i) { 25 | count[i] += count[i - 1]; 26 | } 27 | 28 | for (int i = arrLength - 1; i >= 0; --i) { 29 | arr[count[arr_copy[i]] - 1] = arr_copy[i]; 30 | count[arr_copy[i]]--; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Practicum/pract02/readme.md: -------------------------------------------------------------------------------- 1 | # Практикум №2 по СД, спец. ИС 2 | 3 | ## Counting Sort 4 | 5 | ![1_lmzbRxK0FNgxzu8owsm4cA](https://github.com/user-attachments/assets/e679a298-8110-4fca-b375-5f52ec70f87a) 6 | 7 | [**Counting Sort**](https://github.com/stoychoX/Data-structures-and-algorithms/blob/main/Practicum/pract02/CountingSort.cpp) е ефективен алгоритъм за сортиране, подходящ за елементи с ограничен диапазон на стойности. Работи чрез преброяване на броя на срещанията на елементите. 8 | 9 | > [!CAUTION] 10 | > Той не използва директно сравнения между елементите. 11 | 12 | ## Сложност на Counting Sort 13 | 14 | Counting Sort има еднаква сложност във всички случаи: най-добър случай (**best case**), най-лош случай (**worst case**) и среден случай (**average case**). Това е така, защото алгоритъмът изпълнява едни и същи операции независимо от подредбата на елементите. 15 | 16 | Нека масивът ни има **n** елемента и **k** е разликата между максималния и минималния елемент. 17 | 18 | ### 1. Най-добър случай (Best Case) 19 | 20 | Най-добрият случай е, когато входните данни вече са сортирани. Въпреки това, **Counting Sort** няма оптимизация за сортирани масиви и изпълнява еднакви операции за всеки вход. 21 | 22 | > [!NOTE] 23 | > **Времева сложност:** 24 | Θ(n + k) 25 | 26 | Причината е, че: 27 | - Винаги се създава масив за броене с размер **k**. 28 | - Винаги се обхождат всички **n** елемента, за да се преброят стойностите им и след това да се копират обратно. 29 | 30 | ### 2. Най-лош случай (Worst Case) 31 | 32 | Най-лошият случай е, когато елементите са напълно разбъркани. Въпреки това, **Counting Sort** изпълнява същите операции за всички входни масиви, независимо от тяхното разпределение. 33 | 34 | > [!NOTE] 35 | > **Времева сложност:** 36 | Θ(n + k) 37 | 38 | Сложността остава същата, тъй като: 39 | - Преброяваме всички **n** елементи в масива. 40 | - Инициализираме и запълваме масив с размер **k** за броене на честотите на стойностите. 41 | 42 | ### 3. Среден случай (Average Case) 43 | 44 | Средният случай се случва при произволно разпределение на елементите. И тук, **Counting Sort** не е зависим от разпределението на елементите. 45 | 46 | > [!NOTE] 47 | > **Времева сложност:** 48 | Θ(n + k) 49 | 50 | Както в другите два случая, времето за изпълнение е линейно зависимо от броя на елементите **n** и диапазона на стойностите **k**. 51 | 52 | 53 | --- 54 | > [!IMPORTANT] 55 | > Обяснение на сложността Θ(n + k) 56 | > - **Θ(n)**: Обхождане на входния масив с **n** елемента, за да се преброят стойностите. 57 | > - **Θ(k)**: Инициализация и обработка на масива за броене с **k** възможни стойности (диапазонът на елементите). 58 | > - **Θ(n)**: Второто обхождане на входния масив за поставяне на елементите в сортиран вид. 59 | 60 | 61 | --- 62 | - [**Линк към задачите**](https://leetcode.com/problem-list/awdeugtv/) 63 | -------------------------------------------------------------------------------- /Practicum/pract03/README.md: -------------------------------------------------------------------------------- 1 | # Практикум №3 по СД, спец. ИС 2 | 3 | ## Задача 1 4 | Даден е вектор от думи, съставени изцяло от малки латински букви. Напишете програма, която пренарежда вектора така, че в началото да са всички думи, 5 | започващи с буквата 'а', след тях - тези, започващи с буквата 'b' и т.н. Алгоритъмът да работи in-place. 6 | 7 | ***hint*** : търсим сложност Θ(n), където n е големината на масива. 8 | 9 | Пример: 10 | 11 | Вход: 12 | ``` 13 | words = {banana, apple, alpaca, cat, biscuit, elephant, string, house, progress, trousers, mouse} 14 | ``` 15 | 16 | Изход: 17 | ``` 18 | words = {apple, alpaca, banana, biscuit, cat, elephant, house, mouse, progress, string, trousers} 19 | ``` 20 | 21 | ## Задача 2 22 | Дадени са два вектора от числа (сортирани). Напишете програма, която връща нов вектор, който съдържа всички числа от първия вектор и всички числа от втория вектор, 23 | като всички четни числа в новия вектор трябва да предхождат всички нечетни числа и двете групи числа (съответно четни и нечетни) трябва да са в сортиран вид всяка. 24 | 25 | ***hint*** : търсим сложност Θ(size_1 + size_2), където size_1 и size_2 са големините на двата вектора. 26 | 27 | Пример: 28 | 29 | Вход: 30 | ``` 31 | v1 = {1, 1, 2, 3, 4, 5, 6} 32 | v2 = {1, 1, 1, 2, 2, 2, 3, 9} 33 | ``` 34 | 35 | Изход: 36 | ``` 37 | result = {2, 2, 2, 2, 4, 6, 1, 1, 1, 1, 1, 3, 3, 5, 9} 38 | ``` 39 | 40 | ## Задача 3 41 | Даден е вектор от вектори, които са предварително сортирани. Напишете програма която merge-ва всичките 'подвектори' в един голям вектор, който също е в сортиран вид. 42 | 43 | Тук **НЕ** ви трябва `std::sort`. 44 | 45 | ***hint*** : търсим сложност Θ(size_1 + size_2 + ... + size_n), където size_i е размера на i-тия подвектор. 46 | 47 | Пример: 48 | 49 | Вход: 50 | ``` 51 | v = {{9, 10, 12}, {14, 17, 18}, {10, 11, 12, 22, 90}, {13}, {16, 18}} 52 | ``` 53 | 54 | Изход: 55 | ``` 56 | result = {9, 10, 10, 11, 12, 12, 13, 14, 16, 17, 18, 18, 22, 90} 57 | ``` 58 | 59 | ## Задача 4: 60 | Даден е вектор от цели положителни числа. Напишете програма, която разделя масива на две части, 61 | като първата част съдържа всички числа с четен брой цифри, а втората част - всички с нечетен брой. 62 | 63 | ***hint*** : търсим сложност Θ(n), където n е големината на масива. 64 | 65 | Пример: 66 | 67 | Вход: 68 | ``` 69 | v = {10, 2, 12, 12, 4, 100, 102020} 70 | ``` 71 | 72 | Изход: 73 | ``` 74 | result = {10, 12, 12, 102020, 4, 100, 2} 75 | ``` 76 | 77 | ## Задача 5: 78 | Даден е вектор от думи, съставени изцяло от малки и главни латински букви. Напишете програма, която пренарежда вектора така, че в началото да са всички думи, 79 | започващи с буквата 'а' или 'A', след тях - тези, започващи с буквата 'b' или 'B' и т.н. Алгоритъмът да работи in-place. 80 | 81 | ***hint*** : търсим сложност Θ(n), където n е големината на масива. 82 | 83 | Пример: 84 | 85 | Вход: 86 | ``` 87 | words = {banana, apple, Alpaca, Cat, biscuit, elephant, string, house, progress, Trousers, mouse} 88 | ``` 89 | 90 | Изход: 91 | ``` 92 | words = {apple, Alpaca, banana, biscuit, Cat, elephant, house, mouse, progress, string, Trousers} 93 | ``` 94 | 95 | --- 96 | > [!IMPORTANT] 97 | > - Задачи 1 и 2 са за работа в час. 98 | > - Задачи 3 - 5 са за домашно. 99 | > - Задачите предавате на leetcode playground, като линк към него качвате [тук](https://docs.google.com/spreadsheets/d/1KVdic02U7f97GT2LFu67t9Z4NqyFLmCj1TjyYdrcLBE/edit?gid=0#gid=0) (всяка отделна задача я закоментирате с клавишната комбинация `Ctrl` + `/`) 100 | --- 101 | -------------------------------------------------------------------------------- /Practicum/pract03/merge.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // V. 1 5 | template 6 | void merge(RandomIt first1, RandomIt last1, 7 | RandomIt first2, RandomIt last2, 8 | RandomIt buff) 9 | { 10 | for (; first1 != last1; ++buff) 11 | { 12 | if (first2 == last2) 13 | { 14 | for (; first1 != last1; ++first1, ++buff) 15 | *buff = *first1; 16 | 17 | return; 18 | } 19 | 20 | if (*first2 < *first1) 21 | { 22 | *buff = *first2; 23 | ++first2; 24 | } 25 | else 26 | { 27 | *buff = *first1; 28 | ++first1; 29 | } 30 | } 31 | 32 | for (; first2 != last2; ++first2, ++buff) 33 | *buff = *first2; 34 | } 35 | 36 | // V. 2 37 | template 38 | void merge(RandomIt first1, RandomIt last1, 39 | RandomIt first2, RandomIt last2, 40 | RandomIt buff) 41 | { 42 | while (first1 != last1 && first2 != last2) 43 | { 44 | if (*first1 <= *first2) 45 | { 46 | *buff = *first1; 47 | ++first1; 48 | } 49 | else 50 | { 51 | *buff = *first2; 52 | ++first2; 53 | } 54 | 55 | ++buff; 56 | } 57 | 58 | while (first1 != last1) 59 | { 60 | *buff = *first1; 61 | ++first1; 62 | ++buff; 63 | } 64 | 65 | while (first2 != last2) 66 | { 67 | *buff = *first2; 68 | ++first2; 69 | ++buff; 70 | } 71 | } 72 | 73 | int main() 74 | { 75 | std::vector v1; 76 | for (int i = 1; i <= 5; i++) 77 | v1.push_back(i); 78 | 79 | for (int i = 3; i <= 8; i++) 80 | v1.push_back(i); 81 | 82 | std::vector v(11); 83 | 84 | merge(v1.begin(), v1.begin() + 5, v1.begin() + 5, v1.end(), v.begin()); 85 | 86 | for (int i = 0; i < 11; i++) 87 | std::cout << v[i] << " "; 88 | } 89 | -------------------------------------------------------------------------------- /Practicum/pract03/partition.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | bool isEven(int n) { 5 | return !(n % 2); 6 | } 7 | 8 | template 9 | RandomIt partition(RandomIt first, RandomIt last, Predicate p) 10 | { 11 | RandomIt beg = first; 12 | for (; beg != last; ++beg) 13 | { 14 | if (!p(*beg)) 15 | break; 16 | } 17 | if (beg == last) 18 | return beg; 19 | 20 | for (RandomIt it = std::next(beg); it != last; ++it) 21 | { 22 | if (p(*it)) 23 | { 24 | std::swap(*it, *beg); 25 | ++beg; 26 | } 27 | } 28 | 29 | return beg; 30 | } 31 | 32 | int main() 33 | { 34 | std::vector v; 35 | for (int i = 1; i <= 10; i++) 36 | v.push_back(i); 37 | 38 | partition(v.begin(), v.end(), isEven); 39 | 40 | for (int i = 0; i < 10; i++) 41 | std::cout << v[i] << " "; 42 | } 43 | -------------------------------------------------------------------------------- /Practicum/pract03/solutions.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | using namespace std; 5 | 6 | //Task 1 7 | void orderByFirstLetter(vector& words) 8 | { 9 | auto first = words.begin(); 10 | auto last = words.end(); 11 | 12 | for (char letter = 'a'; letter <= 'z'; letter++) 13 | { 14 | auto new_beg = partition(first, last, [letter](const string& str) { return str[0] == letter; }); 15 | first = new_beg; 16 | } 17 | } 18 | 19 | //Task 2 20 | bool isEven(int n) 21 | { 22 | return n % 2 == 0; 23 | } 24 | 25 | bool isOdd(int n) 26 | { 27 | return n % 2; 28 | } 29 | 30 | template 31 | void mergeSortedPred(vector& res, const vector& first, const vector& second, Pred p) 32 | { 33 | auto beg1 = first.begin(); 34 | auto beg2 = second.begin(); 35 | 36 | auto end1 = first.end(); 37 | auto end2 = second.end(); 38 | 39 | while (beg1 != end1 && beg2 != end2) 40 | { 41 | if (p(*beg1) && p(*beg2)) 42 | { 43 | if (*beg1 <= *beg2) 44 | { 45 | res.push_back(*beg1); 46 | ++beg1; 47 | } 48 | else 49 | { 50 | res.push_back(*beg2); 51 | ++beg2; 52 | } 53 | } 54 | else 55 | { 56 | if (!p(*beg1)) 57 | ++beg1; 58 | 59 | if (!p(*beg2)) 60 | ++beg2; 61 | } 62 | } 63 | 64 | while (beg1 != end1) 65 | { 66 | if (p(*beg1)) 67 | res.push_back(*beg1); 68 | 69 | ++beg1; 70 | } 71 | 72 | while (beg2 != end2) 73 | { 74 | if (p(*beg2)) 75 | res.push_back(*beg2); 76 | 77 | ++beg2; 78 | } 79 | } 80 | vector mergeSortedEvensAndOdds(const vector& first, const vector& second) 81 | { 82 | vector res; 83 | mergeSortedPred(res, first, second, isEven); 84 | mergeSortedPred(res, first, second, isOdd); 85 | 86 | return res; 87 | } 88 | 89 | int main() 90 | { 91 | //task 1 92 | vector v = { "banana", "apple", "alpaca", "cat", "biscuit", "elephant", "string", "house", "progress", "trousers", "mouse" }; 93 | orderByFirstLetter(v); 94 | 95 | for (auto it = v.begin(); it != v.end(); ++it) 96 | cout << *it << " "; 97 | 98 | //task 2 99 | vector v1 = { 1, 2, 3, 4, 5 }; 100 | vector v2 = { 5, 17, 32, 80, 81, 82, 83 }; 101 | 102 | vector res = mergeSortedEvensAndOdds(v1, v2); 103 | 104 | for (auto it = res.begin(); it != res.end(); ++it) 105 | std::cout << *it << " "; 106 | } 107 | -------------------------------------------------------------------------------- /Practicum/pract04/readme.md: -------------------------------------------------------------------------------- 1 | # Практикум №4 по Структури от данни, спец. ИС 2 | 3 | Какво представляват алгоритмите за търсене? Може би е интуитивно, но те представляват методи или процедури, 4 | използвани за намиране на конкретен елемент в дадена колекция от данни. Ще се запознаем с няколко по-известни от тях. 5 | 6 | ## Линейно търсене 7 | Когато търсим елемент в дадена колекция, нещото, което ни хрумва първо, е да я обходим цялата и да проверим дали той е в нея. 8 | Съответно, ако го намерим, искаме да върнем позицията му. Цялата тази идея е имплементирана именно в алгоритъма `Linear Search` или линейно търсене на български. 9 | Нека разгледаме примерна имплементация: 10 | 11 | ```c++ 12 | template 13 | int linear_search(const std::vector& data, const T& el) 14 | { 15 | for (int i = 0; i < data.size(); i++) 16 | { 17 | if (data[i] == el) 18 | return i; 19 | } 20 | 21 | return -1; 22 | } 23 | ``` 24 | 25 | Както можем да видим, алгоритъма обхожда цялата колекция и в най-лошия случай ще направи **n** стъпки, т.е. сложността му в `worst-case` е **O(n)**. 26 | В `average-case` ще направи *n/2* стъпки, тъй като предполагаме, че тогава търсеният елемент ще се намира в средата на колекцията, т.е. сложността отново е **O(n)**. 27 | Съответно `best-case` ще ни е, когато елементът се намира в началото на колекцията, т.е. сложността е константна - **O(1)**. 28 | 29 | Хубавото на този алгоритъм е, че не изисква допълнително сортиране на елементите, което явно ще увеличи сложността му. 30 | 31 | Но дали има начин да оптимизираме този алгоритъм? 32 | 33 | Ако се замислим, сложността му няма как да бъде оптимизирана - винаги имаме обхождане на елементите на масива. 34 | 35 | Но какво друго можем да направим? Ако погледнем, ще видим, че на всяка стъпка извършваме 2 проверки - за елемента и за индекса i дали не е излезнал извън границите на нашата колекция. 36 | Всъщност можем да направим така, че да спестим втората проверка. Как? Ами като сложим търсения елемент в края на нашата колекция на мястото на последния елемент. 37 | Тази техника се нарича добавяне на `sentinel` в края на колекцията и е често срещана в практическата сфера. 38 | Нека видим как бихме могли да го имплементираме: 39 | 40 | ```c++ 41 | template 42 | int linear_search_sentinel(const std::vector& data, const T& el) 43 | { 44 | T last = data.back(); 45 | data.back() = el; 46 | 47 | int i = 0; 48 | while (el != data[i]) 49 | { 50 | i++; 51 | } 52 | 53 | data.back() = last; 54 | 55 | if(i < data.size() - 1 || data.back() == el) 56 | return i; 57 | 58 | return -1; 59 | } 60 | ``` 61 | 62 | Както виждаме, сложността си остава същата и в трите случая, но проверките ни драстично намаляват, което за по-обемни колекции ще е от голямо значение. 63 | 64 | ## Binary Search 65 | Разгледахме вече случая, в който обхождаме цялата колекция. Както сами се убедихме, той не е много оптимален (особено за големи колекции), тъй като ще се наложи да ги обхожда целите. 66 | Нека се замислим - ако елементите ни са в сортиран вид, няма ли по-оптимален начин да намерим търсения от нас такъв? Отговорът е - да, има, и се нарича `Binary Search` или двоично търсене на български. 67 | 68 | Идеята му е да вземем средния елемент на колекцията. Той я разделя на две половини - лява и дясна. 69 | Първо ще сравним търсения със средния елемент - ако са равни, значи сме го открили и можем да върнем неговата позиция. Ако не са равни, ще направим следното: 70 | тъй като колекцията ни е сортирана, ако търсеният елемент е по-голям от средния, значи се намира в дясната половина, а ако е по-малък - съответно в лявата. По този начин ще приложим същото 71 | търсене върху половината, в която вече сме сигурни, че се намира нашия елемент и така, докато не стигнем до него. 72 | 73 | Нека разгледаме как бихме могли да имплементираме алгоритъма: 74 | 75 | ```c++ 76 | template 77 | int binary_search(const std::vector& data, T el) 78 | { 79 | int beg = 0, end = data.size() - 1; 80 | 81 | while (beg <= end) 82 | { 83 | int mid = beg + (end - beg) / 2; 84 | 85 | if (data[mid] == el) 86 | return mid; 87 | 88 | if (el < data[mid]) 89 | end = mid - 1; 90 | else 91 | beg = mid + 1; 92 | } 93 | 94 | return -1; 95 | } 96 | ``` 97 | 98 | Както забелязваме, на всяка итерация от цикъла размерът на интервала, в който търсим, намалява наполовина и така, докато не стане с размер 1 (когато beg и end се срещнат). 99 | Съответно ако размера на колекцията ни е n и сме направили x стъпки, то за да намерим x, трябва да решим следното уравнение: 100 | 101 | $$ 102 | \frac{n}{2^x} = 1 103 | $$ 104 | 105 | От уравнението имаме: 106 | 107 | $$ 108 | \frac{n}{2^x} = 1 \implies n = 2^x \implies x = \log_2 n \implies \Theta(\log n) 109 | $$ 110 | 111 | Получихме, че алгоритъмът има сложност O(logn) в `worst-case`. 112 | 113 | `Best-case` ще имаме, когато елементът, който търсим, е точно в средата. Съответно сложността ни тогава е константна (O(1)) - няма да разглеждаме други интервали. 114 | 115 | ## Стандартни алгоритми за търсене - `std::find`, `std::find_if` и `std::find_if_not` 116 | В стандартната библиотека имаме алгоритъм, който ни намира даден елемент в колекция. Този алгоритъм се нарича `std::find`. Имплементиран е на базата на линейното търсене, което прави и слоцността му линейна. 117 | Използва итератори съответно към началото и края на range-a, в който ще търсим елемента, и връща итератор към намерения елемент. Ако не го намери, връща итератор към края на колекцията. 118 | Ето една възможна имплементация, използваща итератори: 119 | 120 | ```c++ 121 | template::value_type> 122 | constexpr RandomIt find(RandomIt first, RandomIt last, const T& value) 123 | { 124 | for (; first != last; ++first) 125 | { 126 | if (*first == value) 127 | return first; 128 | } 129 | 130 | return last; 131 | } 132 | ``` 133 | 134 | Съответно има негови модификации, които ни позволяват да търсим елемент, който отговаря (или не отговаря) на даден критерий. Това са методите `std::find_if` и `std::find_if_not`. 135 | И двата метода приемат предикат, като връщат първия елемент, който отговаря (респективно не отговаря) на него. Ако няма такъв, отново връщат итератор към края. 136 | 137 | Нека видим възможни имплементации: 138 | 139 | ```c++ 140 | template 141 | const RandomIt find_if(RandomIt first, RandomIt last, UnaryPred p) 142 | { 143 | for (; first != last; ++first) 144 | if (p(*first)) 145 | return first; 146 | 147 | return last; 148 | } 149 | ``` 150 | 151 | ```c++ 152 | template 153 | const RandomIt find_if_not(RandomIt first, RandomIt last, UnaryPred p) 154 | { 155 | for (; first != last; ++first) 156 | if (!p(*first)) 157 | return first; 158 | 159 | return last; 160 | } 161 | ``` 162 | 163 | 164 | ## `std::upper_bound` и `std::lower_bound` 165 | `std::upper_bound` и `std::lower_bound` отново са методи от стандартната библиотека, базирани на алгоритми за търсене. Особеното тук е, че са базирани на двоичното търсене, т.е. задължават колекцията ни да е сортирана предварително, за да ги използваме. 166 | Сложността им е съответно O(logn). 167 | 168 | `std::upper_bound` приема итератори към началото и края на range-a, в който ще търси, както и елемент. Връща итератор към първия елемент от колекцията, който е по-голям от подадения. 169 | Ако няма такъв, връща итератор към края. 170 | 171 | `std::lower_bound` е подобна - приема итератори към началото и края на range-a, в който ще търси, както и елемент. Връща итератор към първия елемент от колекцията, който е по-голям **или равен** на подадения. 172 | Ако няма такъв, връща итератор към края. 173 | 174 | Да разгледаме и примерни имплементации: 175 | 176 | ```c++ 177 | template ::value_type> 178 | RandomIt lower_bound(RandomIt beg, RandomIt end, const T& value) { 179 | while (beg < end) { 180 | RandomIt mid = beg + (end - beg) / 2; 181 | 182 | if (*mid < value) 183 | beg = std::next(mid); 184 | else 185 | end = mid; 186 | } 187 | return beg; 188 | } 189 | ``` 190 | 191 | ```c++ 192 | template ::value_type> 193 | RandomIt upper_bound(RandomIt beg, RandomIt end, const T& value) { 194 | while (beg < end) { 195 | RandomIt mid = beg + (end - beg) / 2; 196 | 197 | if (*mid <= value) 198 | beg = std::next(mid); 199 | else 200 | end = mid; 201 | } 202 | return beg; 203 | } 204 | ``` 205 | 206 | --- 207 | 208 | ## Задачи 209 | [**Линк към задачите**](https://leetcode.com/problem-list/auzfnnwr/) 210 | -------------------------------------------------------------------------------- /Practicum/pract05/readme.md: -------------------------------------------------------------------------------- 1 | # Практикум №5 по Структури от данни, спец. ИС 2 | 3 | ## Основни задачи за свързани списъци, които ще решим заедно: 4 | - [**Reverse a linked list**](https://www.hackerrank.com/challenges/reverse-a-linked-list/problem) 5 | - [**Middle of linked list**](https://leetcode.com/problems/middle-of-the-linked-list/description/) 6 | - [**Cycle Detection**](https://www.hackerrank.com/challenges/detect-whether-a-linked-list-contains-a-cycle/problem) 7 | - [**Intersection of two linked lists**](https://leetcode.com/problems/intersection-of-two-linked-lists/description/) 8 | 9 | --- 10 | [**Линк към задачите**](https://leetcode.com/problem-list/anqpylct/) 11 | -------------------------------------------------------------------------------- /Practicum/pract05/solutions/Task01.cpp: -------------------------------------------------------------------------------- 1 | SinglyLinkedListNode* reverse(SinglyLinkedListNode* llist) { 2 | if(!llist) 3 | return nullptr; 4 | 5 | SinglyLinkedListNode* curr = llist; 6 | SinglyLinkedListNode* prev = nullptr; 7 | 8 | while(curr) 9 | { 10 | SinglyLinkedListNode* next = curr->next; 11 | curr->next = prev; 12 | prev = curr; 13 | curr = next; 14 | } 15 | 16 | return prev; 17 | } 18 | -------------------------------------------------------------------------------- /Practicum/pract05/solutions/Task02.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | ListNode* middleNode(ListNode* head) { 4 | if(!head) 5 | return nullptr; 6 | 7 | ListNode* middle = head; 8 | ListNode* end = head; 9 | 10 | while(end != nullptr && end->next != nullptr) { 11 | middle = middle->next; 12 | end = end->next->next; 13 | } 14 | return middle; 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /Practicum/pract05/solutions/Task03.cpp: -------------------------------------------------------------------------------- 1 | bool has_cycle(SinglyLinkedListNode* head) { 2 | if(!head) 3 | return false; 4 | 5 | SinglyLinkedListNode* slow = head; 6 | SinglyLinkedListNode* fast = head; 7 | 8 | while(fast && fast->next) 9 | { 10 | slow = slow->next; 11 | fast = fast->next->next; 12 | 13 | if(slow == fast) 14 | return true; 15 | } 16 | 17 | return false; 18 | } 19 | -------------------------------------------------------------------------------- /Practicum/pract05/solutions/Task04.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * struct ListNode { 4 | * int val; 5 | * ListNode *next; 6 | * ListNode(int x) : val(x), next(NULL) {} 7 | * }; 8 | */ 9 | class Solution { 10 | public: 11 | size_t getLen(ListNode* head) 12 | { 13 | size_t cnt = 0; 14 | while(head) 15 | { 16 | head = head->next; 17 | cnt++; 18 | } 19 | 20 | return cnt; 21 | } 22 | ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { 23 | if(!headA || !headB) 24 | return nullptr; 25 | 26 | size_t sizeA = getLen(headA); 27 | size_t sizeB = getLen(headB); 28 | 29 | while(sizeA > sizeB) 30 | { 31 | headA = headA->next; 32 | sizeA--; 33 | } 34 | 35 | while(sizeB > sizeA) 36 | { 37 | headB = headB->next; 38 | sizeB--; 39 | } 40 | 41 | while(headA && headB) 42 | { 43 | if(headA == headB) 44 | return headA; 45 | 46 | headA = headA->next; 47 | headB = headB->next; 48 | } 49 | 50 | return nullptr; 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /Practicum/pract06/readme.md: -------------------------------------------------------------------------------- 1 | # Практикум №6 по Структури от данни, спец. ИС 2 | 3 | --- 4 | 5 | [**Next Greatest Element**](https://leetcode.com/problems/next-greater-element-ii/description/) 6 | 7 | [**Asteroid Collision**](https://leetcode.com/problems/asteroid-collision/description/) 8 | 9 | --- 10 | 11 | [**Линк към задачите**](https://leetcode.com/problem-list/awthx4gv/) 12 | -------------------------------------------------------------------------------- /Practicum/pract06/solutions/Task 01.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class Solution 5 | { 6 | public: 7 | std::vector nextGreaterElements(std::vector &nums) 8 | { 9 | std::stack> stack; 10 | std::vector ans(nums.size(), -1); 11 | for (int i = 0; i < nums.size(); ++i) 12 | { 13 | while (!stack.empty() && stack.top().first < nums[i]) 14 | { 15 | ans[stack.top().second] = nums[i]; 16 | stack.pop(); 17 | } 18 | 19 | stack.emplace(nums[i], i); 20 | } 21 | 22 | for (int i = 0; i < nums.size(); ++i) 23 | { 24 | while (!stack.empty() && stack.top().first < nums[i]) 25 | { 26 | ans[stack.top().second] = nums[i]; 27 | stack.pop(); 28 | } 29 | } 30 | 31 | return ans; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /Practicum/pract06/solutions/Task 02.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | class Solution 6 | { 7 | public: 8 | std::vector asteroidCollision(std::vector &asteroids) 9 | { 10 | std::stack stack; 11 | 12 | for (int i = 0; i < asteroids.size(); ++i) 13 | { 14 | if (asteroids[i] > 0 || (stack.size() && stack.top() < 0)) 15 | { 16 | stack.push(asteroids[i]); 17 | continue; 18 | } 19 | 20 | bool addNeg = true; 21 | while (stack.size() && stack.top() > 0 && stack.top() + asteroids[i] <= 0) 22 | { 23 | if (stack.top() + asteroids[i] == 0) 24 | { 25 | stack.pop(); 26 | addNeg = false; 27 | break; 28 | } 29 | stack.pop(); 30 | } 31 | 32 | if (addNeg && (stack.empty() || stack.top() < 0)) 33 | { 34 | stack.push(asteroids[i]); 35 | } 36 | } 37 | 38 | int idx = stack.size(); 39 | std::vector result(idx); 40 | while (stack.size()) 41 | { 42 | result[--idx] = stack.top(); 43 | stack.pop(); 44 | } 45 | 46 | return result; 47 | } 48 | }; -------------------------------------------------------------------------------- /Practicum/pract07/ExampleFunctions.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | template 6 | struct TreeNode { 7 | T data; 8 | TreeNode* left; 9 | TreeNode* right; 10 | 11 | TreeNode(const T& val) : data(val), left(nullptr), right(nullptr) {} 12 | }; 13 | 14 | // Проверка дали даден възел е листо 15 | // Листо е възел, който няма деца 16 | template 17 | bool isLeaf(TreeNode* node) { 18 | return node && !node->left && !node->right; 19 | } 20 | 21 | // Проверка дали даден елемент се съдържа в дърво 22 | template 23 | bool contains(TreeNode* root, T value) { 24 | if (root == nullptr) return false; 25 | 26 | if (root->data == value) return true; 27 | 28 | return contains(root->left, value) || contains(root->right, value); 29 | } 30 | 31 | // Намиране на височина на дърво 32 | // Височината на дърво е дължината на най-дългия път от корена до някое листо 33 | template 34 | int getHeight(TreeNode* root) { 35 | if (!root) return -1; 36 | 37 | int leftHeight = getHeight(root->left); 38 | int rightHeight = getHeight(root->right); 39 | 40 | return 1 + std::max(leftHeight, rightHeight); 41 | } 42 | 43 | // Преброяване на възлите в дърво 44 | template 45 | int countNodes(TreeNode* root) { 46 | if (!root) return 0; 47 | 48 | if (!root->left && !root->right) return 1; 49 | 50 | return 1 + countNodes(root->left) + countNodes(root->right); 51 | } 52 | 53 | int main() { 54 | // Създаване на двоично дърво от цели числа 55 | TreeNode* root = new TreeNode(1); 56 | root->left = new TreeNode(2); 57 | root->right = new TreeNode(3); 58 | root->left->left = new TreeNode(4); 59 | root->left->right = new TreeNode(5); 60 | root->right->left = new TreeNode(6); 61 | root->right->right = new TreeNode(7); 62 | 63 | // Как изглежда дървото: 64 | // 1 65 | // / \ 66 | // 2 3 67 | // / \ / \ 68 | // 4 5 6 7 69 | 70 | // Проверка дали даден възел е листо 71 | std::cout << "Is 4 a leaf: " << std::boolalpha << isLeaf(root->left->left) << std::endl; // true 72 | std::cout << "Is 3 a leaf: " << std::boolalpha << isLeaf(root->right) << std::endl; // false 73 | 74 | // Проверка дали даден елемент се съдържа в дървото 75 | std::cout << "Does the tree contain 5: " << std::boolalpha << contains(root, 5) << std::endl; // true 76 | std::cout << "Does the tree contain 8: " << std::boolalpha << contains(root, 8) << std::endl; // false 77 | 78 | // Намиране на височината на дървото 79 | std::cout << "Height of the tree: " << getHeight(root) << std::endl; // 2 80 | 81 | // Преброяване на възлите в дървото 82 | std::cout << "Total nodes in the tree: " << countNodes(root) << std::endl; // 7 83 | 84 | return 0; 85 | } 86 | -------------------------------------------------------------------------------- /Practicum/pract07/Traversals.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | template 6 | struct TreeNode { 7 | T data; 8 | TreeNode* left; 9 | TreeNode* right; 10 | 11 | TreeNode(const T& val) : data(val), left(nullptr), right(nullptr) {} 12 | }; 13 | 14 | // Preorder обхождане (корен, ляво, дясно) 15 | template 16 | void preorderTraversal(TreeNode* root) { 17 | if (!root) return; 18 | std::cout << root->data << " "; 19 | preorderTraversal(root->left); 20 | preorderTraversal(root->right); 21 | } 22 | 23 | // Inorder обхождане (ляво, корен, дясно) 24 | template 25 | void inorderTraversal(TreeNode* root) { 26 | if (!root) return; 27 | inorderTraversal(root->left); 28 | std::cout << root->data << " "; 29 | inorderTraversal(root->right); 30 | } 31 | 32 | // Postorder обхождане (ляво, дясно, корен) 33 | template 34 | void postorderTraversal(TreeNode* root) { 35 | if (!root) return; 36 | postorderTraversal(root->left); 37 | postorderTraversal(root->right); 38 | std::cout << root->data << " "; 39 | } 40 | 41 | // Обхождане в широчина 42 | template 43 | void bfs(TreeNode* root) { 44 | if (!root) return; 45 | 46 | std::queue*> q; 47 | q.push(root); 48 | 49 | while (!q.empty()) { 50 | TreeNode* current = q.front(); 51 | q.pop(); 52 | 53 | std::cout << current->data << " "; 54 | 55 | if (current->left) q.push(current->left); 56 | if (current->right) q.push(current->right); 57 | } 58 | } 59 | 60 | int main() { 61 | // Създаване на двоично дърво от цели числа 62 | TreeNode* root = new TreeNode(1); 63 | root->left = new TreeNode(2); 64 | root->right = new TreeNode(3); 65 | root->left->left = new TreeNode(4); 66 | root->left->right = new TreeNode(5); 67 | root->right->left = new TreeNode(6); 68 | root->right->right = new TreeNode(7); 69 | 70 | // Как изглежда дървото: 71 | // 1 72 | // / \ 73 | // 2 3 74 | // / \ / \ 75 | // 4 5 6 7 76 | 77 | // Preorder обхождане 78 | std::cout << "Preorder traversal: "; 79 | preorderTraversal(root); // 1 2 4 5 3 6 7 80 | std::cout << std::endl; 81 | 82 | // Inorder обхождане 83 | std::cout << "Inorder traversal: "; 84 | inorderTraversal(root); // 4 2 5 1 6 3 7 85 | std::cout << std::endl; 86 | 87 | // Postorder обхождане 88 | std::cout << "Postorder traversal: "; 89 | postorderTraversal(root); // 4 5 2 6 7 3 1 90 | std::cout << std::endl; 91 | 92 | // Обхождане в широчина 93 | std::cout << "BFS traversal: "; 94 | bfs(root); // 1 2 3 4 5 6 7 95 | std::cout << std::endl; 96 | 97 | return 0; 98 | } 99 | -------------------------------------------------------------------------------- /Practicum/pract07/binaryTree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Practicum/pract07/binaryTree.png -------------------------------------------------------------------------------- /Practicum/pract07/inorder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Practicum/pract07/inorder.gif -------------------------------------------------------------------------------- /Practicum/pract07/postorder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Practicum/pract07/postorder.gif -------------------------------------------------------------------------------- /Practicum/pract07/preorder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Practicum/pract07/preorder.gif -------------------------------------------------------------------------------- /Practicum/pract07/readme.md: -------------------------------------------------------------------------------- 1 | # Практикум №7 по Структури от данни, спец. ИС 2 | 3 | ## Какво е дърво? 4 | 5 | ***Дървото*** е йерархична структура от данни, съставена от **върхове** и **ръбове**. То започва с един основен връх, наречен **корен** (root), от който се разклоняват други върхове (наричани **деца**), образувайки по-малки поддървета. 6 | 7 | ### Основни термини и характеристики: 8 | - **корен** (root): началният връх на дървото, който няма родител 9 | - **връх** (node): всеки елемент в дървото 10 | - **деца**: върховете, които са непосредствено свързани към даден връх 11 | - **родител**: връх, който има деца 12 | - **листо** (leaf): връх, който няма деца 13 | - **вътрешен връх**: връх, който има поне едно дете и не е листо 14 | - **поддърво**: множеството от върхове, което включва даден връх и всичките му наследници 15 | - **височина на връх**: максималното разстояние от този връх до някое листо в неговото поддърво 16 | - **дълбочина на връх**: разстоянието от този връх до корена на дървото 17 | - **разклоненост**: максималният брой деца, които има който и да е връх в дървото 18 | 19 | > **Note:** Нека `T` е дърво с корен `r`, и `v` е връх в `T`: 20 | > - **Поддървото, вкоренено** във `v`, включва `v` и всички негови наследници
21 | > - **Височината** на `v` е максималното разстояние от `v` до някое листо в поддървото му
22 | > - **Дълбочината** на v е разстоянието от `v` до корена `r` на `T` 23 | 24 | ![binaryTree.png](binaryTree.png) 25 | 26 | ## Основни алгоритми за обхождане на двоично дърво 27 | **Обхождането** на дърво е процесът на посещаване на всеки връх в определен ред. 28 | Съществуват два основни начина за обхождане на дървета: 29 | 30 | ### 1. Обхождане в дълбочина (DFS) 31 | При **DFS** преминаваме на максимална дълбочина в поддървото, преди да се върнем към съседни възли на по-горно ниво. 32 | 33 | **Сложност:** O(n), където n е броя на върховете в дървото. 34 | 35 | Съществуват три основни типа обхождане в дълбочина: 36 | 37 | #### 1.1 Префиксно обхождане (Preorder) 38 | При префиксното обхождане винаги посещаваме текущия връх преди неговите деца. Коренът на дървото е първият връх, който се посещава. Започваме, като следваме пътя наляво, посещавайки всеки връх по реда на срещането му. Когато достигнем листо, се връщаме назад и продължаваме с дясната част на дървото, като отново посещаваме върховете в реда на срещането им. 39 | 40 | Първо се обработва коренът, след това лявото поддърво и накрая дясното поддърво.
41 | **_Корен → Ляво поддърво → Дясно поддърво_**
42 | 43 | 44 | ![preorder.gif](preorder.gif) 45 | 46 | #### 1.2 Инфиксно обхождане (Inorder) 47 | Първо се обработва лявото поддърво, след това коренът и накрая дясното поддърво.
48 | **_Ляво поддърво → Корен → Дясно поддърво_** 49 | 50 | > **Забележка:** При двоичните дървета за търсене (**BST**) този тип обхождане извежда елементите в сортиран ред. 51 | 52 | ![inorder.gif](inorder.gif) 53 | 54 | #### 1.3 Постфиксно обхождане (Postorder) 55 | При постфиксното обхождане първо посещаваме децата на възела, преди да посетим самия него. Първо се обработва лявото поддърво, след това дясното поддърво и накрая коренът.
56 | **_Ляво поддърво → Дясно поддърво → Корен_** 57 | 58 | ![postorder.gif](postorder.gif) 59 | 60 | ### 2. Обхождане в ширoчина (BFS) 61 | При **BFS** се преминава през възлите ниво по ниво, започвайки от корена и преминавайки към всяко следващо ниво. Изследват се всички възли на дадено ниво, преди да се премине към следващото ниво. Най-често се използва **опашка** (queue), за да се следи реда на върховете, които трябва да се посетят. Започваме от корена, добавяме го в опашката и след това последователно добавяме неговите деца, докато обработваме всеки връх. 62 | 63 | **Сложност:** O(n), където n е броя на върховете в дървото. 64 | 65 | ![tree-dfs-vs-bfs.gif](tree-dfs-vs-bfs.gif) 66 | 67 | ## Задачи 68 | [Линк към задачите](https://leetcode.com/problem-list/aw76m7ld/) 69 | -------------------------------------------------------------------------------- /Practicum/pract07/tree-dfs-vs-bfs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Practicum/pract07/tree-dfs-vs-bfs.gif -------------------------------------------------------------------------------- /Practicum/pract08/readme.md: -------------------------------------------------------------------------------- 1 | # Практикум №8 по Структури от данни, спец. ИС 2 | 3 | На семинари вече сте разгледали балансирани дървета. Едни много известни балансирани дървета са именно т.нар. Red-Black Trees. Те работят по малко по-различен начил от AVL. 4 | Балансираността тук не е дефинирана чрез balance factor, а чрез редица свойства, които винаги трябва да са в сила (така наречения **инвариант**). 5 | 6 | Инвариантът на Red-Black Tree гласи: 7 | 1) Всеки възел има цвят - черен или червен 8 | 9 | 2) Всеки NIL възел (празен възел) е черен 10 | 11 | 3) Червен възел не може да има червен наследник 12 | 13 | 4) Всеки път от произволен връх до всички NIL елементи в поддървото му трябва да преминава през равен брой черни върхове 14 | 15 | 5) Коренът винаги е черен (не е задължително, но често се прилага) 16 | 17 | * Следствие: Ако произволен връх има точно 1 наследник, то той е червен 18 | 19 | Балансирането отново се извършва по време на самите операции за добавяне и премахване. Отново се разчита на принципа на ротациите, но също така има случаи в които вместо ротация се предпочита обикновенно преоцветяване на върховете. 20 | 21 | [Визуализация на RB tree](https://ds2-iiith.vlabs.ac.in/exp/red-black-tree/red-black-tree-oprations/simulation/redblack.html) 22 | 23 | На базата на червено-черното дърво са имплеметирани 2 от най-използваните структури от данни - std::set и std::map. 24 | 25 | ## `std::set` 26 | std::set представлява дърво от уникални стойности, наречени ключове. 27 | При създаване на обект от тип `std::set`, могат да се зададат три типа параметри: 28 | 29 | ```c++ 30 | template< 31 | class Key, 32 | class Compare = std::less, 33 | class Allocator = std::allocator 34 | > class set; 35 | ``` 36 | - **Key** 37 | Типът на обектите, които ще се съхраняват в множеството. 38 | 39 | - **Compare** 40 | Функция или обект за сравнение, която определя критерия за сравнение между елементите. Това влияе върху начина, по който множеството подрежда елементите. 41 | *По подразбиране:* Използва се стандартният оператор за сравнение за съответния тип (`std::less`). 42 | 43 | - **Allocator** 44 | Механизъм за управление на паметта, който определя как ще се разпределя и освобождава паметта за елементите в множеството. 45 | *По подразбиране:* Използва се стандартният алокатор `std::allocator`. 46 | 47 | Поддържа следните методи: 48 | - **Добавяне на елементи**: `set.insert(value)` - O(logN) (връща pair); 49 | - **Премахване на елементи**: `set.erase(value)`- O(logN) (връща колко елементи са били премахнати); 50 | - **Премахване на елементи чрез итератор**: `set.erase(iterator)`- Θ(1) / O(logN) (връща итератор към следващия елемент); 51 | - **Търсене на елементи**: `set.find(value);` - O(logN) (връща итератор или `set.end()`, ако не е намерен); 52 | - **Проверка на размера**: `set.size()` - O(1) 53 | 54 | ```c++ 55 | template< 56 | class Key, 57 | class T, 58 | class Compare = std::less, 59 | class Allocator = std::allocator> 60 | 61 | > class map; 62 | ``` 63 | 64 | ## `std::map` 65 | std::map представлява дърво от двойки от ключ и стойност. Всеки ключ е уникален. 66 | 67 | - **Key** 68 | Типът на обектите, които ще се съхраняват в множеството. 69 | 70 | - **T** 71 | Типът на обектите, които ще се съхраняват в множеството. 72 | 73 | - **Compare** 74 | Обект или функция за сравнение, която определя как ключовете се подреждат в контейнера. 75 | *По подразбиране:* Използва се стандартният оператор за сравнение за съответния тип на ключа (`std::less`). 76 | 77 | - **Allocator** 78 | Механизъм за управление на паметта, който определя как ще се разпределя и освобождава паметта за елементите в множеството. 79 | *По подразбиране:* Използва се стандартният алокатор `std::allocator>`. 80 | 81 | Поддържа следните операции: 82 | - **Добавяне на елементи**: `map[key] = value`- O(logN) 83 | - **Достъп до елементи**: `map[key]` - O(logN) 84 | - **Премахване на елементи**: `map.erase(key)` - O(logN) (връща колко елементи са били премахнати) 85 | - **Премахване на елементи чрез итератор**: `map.erase(iterator)` - Θ(1) / O(logN) (връща итератор към следващия елемент) 86 | - **Търсене на елементи**: `map.find(key)` - O(logN) (връща итератор или `map.end()`, ако не е намерен) 87 | - **Проверка на размера**: `map.size()` - O(1) 88 | 89 | --- 90 | [Линк към задачите](https://leetcode.com/problem-list/a6culvq1/) 91 | -------------------------------------------------------------------------------- /Practicum/pract09/readme.md: -------------------------------------------------------------------------------- 1 | # Практикум №9 по Структури от данни, спец. ИС 2 | 3 | ## `Binary Heap` 4 | **Binary Heap**-a представлява дървовидна структура от данни, която изпълнява 2 условия: 5 | 1) пълно двоично дърво е (всички нива са запълнени, освен последното); 6 | 2) всеки връх има стойност <= (респективно >=) на тази на родителя си (heap property); 7 | 8 | > [!IMPORTANT] 9 | > От условията следва, че винаги най-големият (респективно най-малкият) елемент е на върха на пирамидата, НО данните вътре не са винаги сортирани. 10 | 11 | Обикновено се представя чрез масив, като е изпълнено следното: 12 | - за всеки родител на индекс i лявото му дете е на индекс 2i + 1, а дясното - на 2i + 2; 13 | - за всяко дете на индекс i родителят му се намира на индекс (i - 1) / 2; 14 | - коренът е на индекс 0. 15 | 16 | Разграничаваме 2 вида heap: 17 | - `minHeap` - най-малкият елемент е най-отгоре; 18 | - `maxHeap` - най-големият елемент е най-отгоре; 19 | 20 | ### Как от вектор можем да създадем heap? 21 | STL поддържа методи, които ни позволяват да работим с вектор, като го превърнем в heap. Това са следните: 22 | - **Превръщане на heap**: `std::make_heap(begin_it, end_it)` - O(N) 23 | - **Добавяне на елемент**: 24 | - Добавяме елемент в края на контейнера. 25 | - Викаме `std::push_heap(begin, end)` - O(logN) 26 | - **Извличане на най-големия елемент**: 27 | - Викаме `std::pop_heap(begin, end)` - O(logN) 28 | - Премахваме елемента в края. 29 | - **Проверка дали е heap**: `std::is_heap(begin, end)` - O(N) 30 | 31 | Нека разгледаме и техните имплементации: 32 | ```c++ 33 | #include 34 | #include 35 | 36 | template 37 | void heapify(RandomIt first, RandomIt last, RandomIt root, const Compare& comp) { 38 | size_t size = std::distance(first, last); 39 | size_t rootIdx = std::distance(first, root); 40 | 41 | while (true) { 42 | size_t largest = rootIdx; 43 | size_t leftChild = 2 * rootIdx + 1; 44 | size_t rightChild = 2 * rootIdx + 2; 45 | 46 | if (leftChild < size && comp(*(first + largest), *(first + leftChild))) 47 | largest = leftChild; 48 | 49 | if (rightChild < size&& comp(*(first + largest), *(first + rightChild))) 50 | largest = rightChild; 51 | 52 | if (largest == rootIdx) 53 | break; 54 | 55 | std::swap(*(first + rootIdx), *(first + largest)); 56 | rootIdx = largest; 57 | } 58 | } 59 | 60 | template > 61 | void make_heap(RandomIt first, RandomIt last, Compare comp = Compare()) { 62 | size_t size = std::distance(first, last); 63 | 64 | for (int i = (size / 2); i >= 0; --i) 65 | heapify(first, last, first + i, comp); 66 | } 67 | 68 | template > 69 | void pop_heap(RandomIt first, RandomIt last, Compare comp = Compare()) { 70 | size_t size = std::distance(first, last); 71 | 72 | if (size > 1) { 73 | std::swap(*first, *(last - 1)); 74 | heapify(first, last, first, comp); 75 | } 76 | } 77 | 78 | template 79 | void bubble_up(RandomIt first, RandomIt last, const Compare& comp) { 80 | int childIndex = std::distance(first, last) - 1; 81 | 82 | while (childIndex > 0) { 83 | int parentIndex = (childIndex - 1) / 2; 84 | auto parent = first + parentIndex; 85 | 86 | if (!comp(*parent, *(first + childIndex))) 87 | break; 88 | 89 | std::swap(*parent, *(first + childIndex)); 90 | childIndex = parentIndex; 91 | } 92 | } 93 | 94 | template > 95 | void push_heap(RandomIt first, RandomIt last, Compare comp = Compare()) { 96 | size_t size = std::distance(first, last); 97 | 98 | if (size > 1) 99 | bubble_up(first, last, comp); 100 | } 101 | 102 | int main() 103 | { 104 | std::vector v = { 1, 9, 2, 5, 3, 15, 6, 98, 10 }; 105 | 106 | make_heap(v.begin(), v.end()); 107 | 108 | for (auto i : v) 109 | std::cout << i << " "; 110 | 111 | pop_heap(v.begin(), v.end()); 112 | 113 | for (auto i : v) 114 | std::cout << i << " "; 115 | 116 | push_heap(v.begin(), v.end()); 117 | 118 | for (auto i : v) 119 | std::cout << i << " "; 120 | } 121 | ``` 122 | 123 | Както се вижда, по default методите използват `std::less<>` за сравнение на обектите, така че default-ния heap, който ще създадем, е `maxHeap`. 124 | 125 | > [!NOTE] 126 | > Ако искаме да създадем `minHeap`, то като comparator трябва да подадем `std::greater<>`. 127 | 128 | Структурата от данни, която е създадена на базата на `Binary Heap`-a, е т.нар. `Priority Queue` или приоритетна опашка на български. 129 | 130 | ## `std::priority_queue` 131 | 132 | `std::priority_queue` представлява обвиващ клас за контейнер (std::vector по default), изграден на основата на heap-a. По default е изграден като maxHeap, т.е. 133 | ни гарантира, че винаги най-големия елемент ще е първи. Поддържа следните операции: 134 | - **Добавяне на елемент**: `pq.push(value)` - O(logN) 135 | - **Премахване на елемента с най-висок приоритет**: `pq.pop()` - O(logN) 136 | - **Достъп до елемента с най-висок приоритет**: `pq.top()` - O(1) 137 | - **Проверка на размера**: `pq.size()` - O(1) 138 | - **Проверка за празнота**: `pq.empty()` - O(1) 139 | 140 | Нека разгледаме какви параметри приема в шаблона си: 141 | ```c++ 142 | template< 143 | class T, 144 | class Container = std::vector, 145 | class Compare = std::less 146 | > class priority_queue; 147 | ``` 148 | 149 | 150 | Ето и примерна имплементация: 151 | ```c++ 152 | #include 153 | #include 154 | #include 155 | 156 | template, class Comp = std::less> 157 | class priority_queue 158 | { 159 | Container _c; 160 | Comp _comp; 161 | 162 | public: 163 | void push(const T& value) 164 | { 165 | _c.push_back(value); 166 | std::push_heap(_c.begin(), _c.end(), _comp); 167 | } 168 | 169 | void push(T&& value) 170 | { 171 | _c.push_back(std::move(value)); 172 | std::push_heap(_c.begin(), _c.end(), _comp); 173 | } 174 | 175 | void pop() 176 | { 177 | std::pop_heap(_c.begin(), _c.end(), _comp); 178 | _c.pop_back(); 179 | } 180 | 181 | bool empty() const 182 | { 183 | return _c.empty(); 184 | } 185 | 186 | const T& top() const 187 | { 188 | if (empty()) 189 | throw std::runtime_error("Empty container"); 190 | 191 | return _c.front(); 192 | } 193 | 194 | size_t size() const 195 | { 196 | return _c.size(); 197 | } 198 | }; 199 | ``` 200 | 201 | И тук, ако искаме да създадем minHeap, трябва да подадем `std::greater<>` като custom comparator. 202 | 203 | ### Пример за създаване на min-heap 204 | 205 | ```c++ 206 | #include 207 | #include 208 | #include 209 | 210 | int main() 211 | { 212 | std::vector v = {3, 1, 4, 1, 5, 9}; 213 | std::priority_queue, std::greater> pq(v.begin(), v.end()); 214 | 215 | while (!pq.empty()) 216 | { 217 | std::cout << pq.top() << ' '; 218 | pq.pop(); 219 | } 220 | } 221 | ``` 222 | 223 | --- 224 | [**Линк към задачите**](https://leetcode.com/problem-list/aglkjy9v/) 225 | -------------------------------------------------------------------------------- /Practicum/pract10/readme.md: -------------------------------------------------------------------------------- 1 | 2 | # `std::unordered_map` и `std::unordered_set` 3 | 4 | ## Въведение в `std::unordered_set` 5 | 6 | `std::unordered_set` е асоциативен контейнер, който съхранява уникални обекти в произволен ред. 7 | Обикновено е реализиран чрез хеш таблица използваща separate chaining. 8 | 9 | При създаване на обект от тип `std::unordered_set`, могат да се зададат следните параметри: 10 | 11 | ```c++ 12 | template< 13 | class Key, 14 | class Hash = std::hash, 15 | class KeyEqual = std::equal_to, 16 | class Allocator = std::allocator 17 | > class unordered_set; 18 | ``` 19 | 20 | - **Key** 21 | Типът на обектите, които ще се съхраняват в множеството. 22 | 23 | - **Hash** 24 | Хешираща функция, която изчислява хеш стойности за обектите. 25 | *По подразбиране:* Използва се `std::hash`. 26 | 27 | - **KeyEqual** 28 | Функция за сравнение, която проверява дали два обекта са равни. 29 | *По подразбиране:* Използва се `std::equal_to`. 30 | 31 | - **Allocator** 32 | Механизъм за управление на паметта, който определя как ще се разпределя и освобождава паметта за елементите в множеството. 33 | *По подразбиране:* Използва се стандартният алокатор `std::allocator`. 34 | 35 | --- 36 | 37 | ### Операции 38 | 39 | - **Добавяне на елементи**: `unordered_set.insert(value)` - Θ(1) (average-case), O(N) (worst-case) 40 | - **Премахване на елементи**: `unordered_set.erase(value)`- Θ(1) (average-case), O(N) (worst-case) 41 | - **Търсене на елементи**: `unordered_set.find(value)` - Θ(1) (average-case), O(N) (worst-case) 42 | - **Проверка на размера**: `unordered_set.size()` - O(1) 43 | 44 | --- 45 | 46 | ```c++ 47 | #include 48 | #include 49 | 50 | int main() { 51 | std::unordered_set mySet = {3, 1, 2, 5, 4, 8, 3}; 52 | 53 | for (auto it = mySet.begin(); it != mySet.end();) { 54 | if (*it % 2 == 0) { 55 | it = mySet.erase(it); 56 | } else { 57 | ++it; 58 | } 59 | } 60 | 61 | for (auto& el : mySet) { 62 | std::cout << el << ' '; 63 | } 64 | 65 | return 0; 66 | } 67 | ``` 68 | 69 | --- 70 | 71 | ## Въведение в `std::unordered_map` 72 | 73 | `std::unordered_map` е асоциативен контейнер, който съхранява двойки ключ-стойност в произволен ред. Ключовете са уникални, като всеки ключ съответства на една стойност. Обикновено е реализиран чрез хеш таблица иползващ separate chaining. 74 | 75 | --- 76 | 77 | ### Шаблон за декларация 78 | 79 | ```c++ 80 | template< 81 | class Key, 82 | class T, 83 | class Hash = std::hash, 84 | class KeyEqual = std::equal_to, 85 | class Allocator = std::allocator> 86 | > class unordered_map; 87 | ``` 88 | 89 | - **Key** 90 | Типът на ключовете. 91 | 92 | - **T** 93 | Типът на стойностите. 94 | 95 | - **Hash** 96 | Хешираща функция, която изчислява хеш стойности за ключовете. 97 | *По подразбиране:* Използва се `std::hash`. 98 | 99 | - **KeyEqual** 100 | Функция за сравнение, която проверява дали два ключа са равни. 101 | *По подразбиране:* Използва се `std::equal_to`. 102 | 103 | - **Allocator** 104 | Механизъм за управление на паметта, който определя как ще се разпределя и освобождава паметта за елементите в контейнера. 105 | *По подразбиране:* Използва се `std::allocator>`. 106 | 107 | --- 108 | 109 | ### Операции 110 | 111 | - **Добавяне на елементи**: `unordered_map[key] = value` - Θ(1) (average-case), O(N) (worst-case) 112 | - **Достъп до елементи**: `unordered_map[key]` - Θ(1) (average-case), O(N) (worst-case) 113 | - **Премахване на елементи**: `unordered_map.erase(key)` - Θ(1) (average-case), O(N) (worst-case) 114 | - **Търсене на елементи**: `unordered_map.find(key)` - Θ(1) (average-case), O(N) (worst-case) 115 | - **Проверка на размера**: `unordered_map.size()` - O(1) 116 | 117 | --- 118 | 119 | ```c++ 120 | #include 121 | #include 122 | 123 | int main() { 124 | std::unordered_map myMap = { 125 | {"Three", 3}, 126 | {"One", 1}, 127 | {"Two", 2} 128 | }; 129 | 130 | for (auto it = myMap.begin(); it != myMap.end(); ++it) { 131 | std::cout << "Key: " << it->first << ", Value: " << it->second << '\n'; 132 | } 133 | 134 | for (auto& pair : myMap) { 135 | std::cout << "Key: " << pair.first << ", Value: " << pair.second << '\n'; 136 | } 137 | 138 | // C++ 17 139 | for (auto& [key, value] : myMap) { 140 | std::cout << "Key: " << key << ", Value: " << value << '\n'; 141 | } 142 | 143 | return 0; 144 | } 145 | ``` 146 | ---- 147 | [Линк към задачите](https://leetcode.com/problem-list/asohzosr/) 148 | 149 | В час : №3, №500, №3330 150 | 151 | Вкъщи: №49, №859 152 | -------------------------------------------------------------------------------- /Practicum/pract11/readme.md: -------------------------------------------------------------------------------- 1 | # Практикум №11 по Структури от данни, спец. ИС 2 | 3 | [**Линк към задачите**](https://leetcode.com/problem-list/a1e3ot5r/) 4 | -------------------------------------------------------------------------------- /Practicum/pract12/LexStable.md: -------------------------------------------------------------------------------- 1 | # Lex Stable Формат за дата и време в C++ 2 | 3 | ## Форматът `Lex Stable` - overview 4 | Форматът **Lex Stable** следва следната структура: 5 | 6 | ``` 7 | YYYY-MM-DD HH:MM:SS 8 | ``` 9 | 10 | - `YYYY`: 4-цифрено число, обозначаващо година (напр. 2025) 11 | - `MM`: 2-цифрено число, обозначаващо месец (01–12) 12 | - `DD`: 2-цифрено число, обозначаващо ден (01–31) 13 | - `HH`: 2-цифрено число, обозначаващо часа във формат 24 часа (00–23) 14 | - `MM`: 2-цифрено число, обозначаващо минутите (00–59) 15 | - `SS`: 2-цифрено число, обозначаващо секундите (00–59) 16 | 17 | ### Примерен Код 18 | Следният код демонстрира как да форматирате `std::time_t` във формат Lex Stable и да го запазите в `std::string`: 19 | 20 | ```cpp 21 | #include 22 | #include 23 | #include 24 | 25 | std::string formatTimeAsLexStable(std::time_t time) { 26 | // Конвертирайте std::time_t в структура std::tm (структура, която държи датата и часа във формат, разбит на компоненти - секунди, минути, час, ...) 27 | std::tm* localTime = std::localtime(&time); 28 | 29 | // Форматирайте времето като "YYYY-MM-DD HH:MM:SS" и го запазете в string 30 | char buffer[20]; 31 | std::snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d %02d:%02d:%02d", 32 | localTime->tm_year + 1900, 33 | localTime->tm_mon + 1, 34 | localTime->tm_mday, 35 | localTime->tm_hour, 36 | localTime->tm_min, 37 | localTime->tm_sec); 38 | return std::string(buffer); 39 | } 40 | 41 | int main() { 42 | // Вземете текущото време 43 | std::time_t now = std::time(nullptr); 44 | 45 | // Форматирайте текущото време като Lex Stable 46 | std::string formattedTime = formatTimeAsLexStable(now); 47 | 48 | // Отпечатайте форматираното време 49 | std::cout << "Текуща дата и време: " << formattedTime << std::endl; 50 | 51 | return 0; 52 | } 53 | ``` 54 | 55 | ### Обяснение на Кода 56 | 1. **`std::time_t` и `std::localtime`**: 57 | - `std::time_t` представлява текущото време като брой секунди от 1 януари 1970 г. (епохата). 58 | - `std::localtime` конвертира това време в структура `std::tm`, която съдържа компоненти като година, месец, ден и др. 59 | 2. **`std::snprintf`**: 60 | - Форматира компонентите на времето в стринг с формат Lex Stable. 61 | 3. **Гъвкавост**: 62 | - Функцията `formatTimeAsLexStable` може да бъде използвана с всяко време от тип `std::time_t`, не само с текущото. 63 | 64 | ### Примерен Резултат 65 | Когато стартирате програмата, изходът ще изглежда по следния начин: 66 | 67 | ``` 68 | Текуща дата и време: 2025-01-07 15:45:12 69 | ``` 70 | 71 | ## Приложения 72 | - **Логове**: Гарантира, че логовете са подредени хронологично. 73 | - **Организиране на данни**: Използвайте го като ключ за съхранение или извличане на данни. 74 | - **Timestamps**: Добавете Timestamps към събития в приложенията. 75 | -------------------------------------------------------------------------------- /Practicum/pract12/readme.md: -------------------------------------------------------------------------------- 1 | # Практикум №12 по Структури от данни, спец.ИС 2 | 3 | [Линк към задачите за в час](https://leetcode.com/problem-list/awk2vn56/) 4 | 5 | [Линк към задачите за домашно](https://leetcode.com/problem-list/2hernc7d/) 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Код от семинаритe по структури от данни 2024/2025 - Информационни системи 2 | 3 | ## Тема 1 4 | Сложност на алгоритми. Сложност по време и сложност по памет. Анализ на итеративни алгоритми и примери. Алгоритми за сортиране (bubble sort, insertion sort, selection sort) и алгоритми за търсене (linear search, binary search). 5 | 6 | ## Тема 2 7 | Анализ на рекурсивни алгоритми. Метод на развиването и метод с анализ на дървото на рекурсията. Quicksort и merge sort. 8 | 9 | ## Тема 3 10 | Долна граница на сортиране с преки сравнения. Counting sort. Структури от данни. Динамичен масив (vector) използващ итератори. Амортизиран анализ. Агрегатен метод и примери за функции с амортизирана сложност. 11 | 12 | ## Тема 4 13 | Свързан списък - едносвързан и двусвързан списък. 14 | 15 | ## Тема 5 16 | Едносвързани списъци. MergeSort и QuickSort за свързани списъци. Въвеждане и имплементация на структура от данни deque. 17 | 18 | ## Тема 6 19 | Въвеждане и начини за имплементация на структура от данни опашка. Въвеждане и начини за имплементация на структура от данни стек. Задачата за разпознаване на балансиран низ от скоби. Имплементация 20 | на ForwardIterator за двоично наредено дърво. Въвеждане и начини за имплементация на структура от данни дърво. Основни задачи при работа с дървета. 21 | 22 | ## Тема 7 23 | Задачи за дървета. Балансирани дървета. Ротации и AVL дървета. 24 | 25 | ## Тема 8 26 | Имплементация на ротации. Индексация в двоично наредено дърво за логаритмично време. Heaps и абстрактна структура от данни приоритетна опашка. 27 | 28 | ## Тема 9 29 | Имплементация на приоритетна опашка. Запознаване с асд dictionary и въвеждане на структурата от данни хеш таблица. Въвждане на различни методи за имплементация на хеш таблица. -------------------------------------------------------------------------------- /Seminar01/src/searching-algorithms/binary-search.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | template 4 | int binarySearch(const T* arr, size_t size, T elem) 5 | { 6 | int left = 0; 7 | int right = size - 1; 8 | 9 | while (right - left >= 0) 10 | { 11 | int mid = left + (right - left) / 2; 12 | 13 | if(arr[mid] == elem) 14 | { 15 | return mid; 16 | } 17 | else if (arr[mid] > elem) 18 | { 19 | right = mid - 1; 20 | } 21 | else 22 | { 23 | left = mid + 1; 24 | } 25 | } 26 | 27 | return -1; 28 | } 29 | 30 | constexpr unsigned EXAMPLE_SIZE = 7; 31 | 32 | int main() 33 | { 34 | int example[EXAMPLE_SIZE] = { 1, 2, 4, 8, 16, 32, 64 }; 35 | int needle = 4; 36 | 37 | int index = binarySearch(example, EXAMPLE_SIZE, needle); 38 | if(index == -1) 39 | { 40 | std::cout << "Unable to find the element" << std::endl; 41 | } 42 | else 43 | { 44 | std::cout << "Element " << needle << " found at " 45 | << index << " position: " << example[index] << std::endl; 46 | } 47 | } -------------------------------------------------------------------------------- /Seminar01/src/sorting-algorithms/bubble-sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | template 4 | void bubbleSort(T* arr, unsigned length) 5 | { 6 | size_t lastSwappedIndex = length - 1; 7 | 8 | for (size_t i = 0; i < length; i++) 9 | { 10 | unsigned lastSwappedIndexTemp = lastSwappedIndex; 11 | for (size_t j = 0; j < lastSwappedIndex; j++) 12 | { 13 | if(arr[j] > arr[j + 1]) 14 | { 15 | std::swap(arr[j], arr[j + 1]); 16 | lastSwappedIndexTemp = j; 17 | } 18 | } 19 | 20 | if(lastSwappedIndex == lastSwappedIndexTemp) { break; } 21 | lastSwappedIndex = lastSwappedIndexTemp; 22 | } 23 | } 24 | 25 | int main() 26 | { 27 | int arr[] = {5, 2, 3, 1, 4}; 28 | bubbleSort(arr, 5); 29 | 30 | for(int element : arr) 31 | { 32 | std::cout << element << " "; 33 | } 34 | } -------------------------------------------------------------------------------- /Seminar01/src/sorting-algorithms/insertion-sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | template 4 | void insertionSort(T* arr, unsigned length) 5 | { 6 | for (size_t i = 1; i < length; i++) 7 | { 8 | T elementToInsert = arr[i]; 9 | int j = i - 1; 10 | while (j >= 0 && arr[j] > elementToInsert) 11 | { 12 | arr[j + 1] = arr[j]; 13 | --j; 14 | } 15 | arr[j + 1] = elementToInsert; 16 | } 17 | } 18 | 19 | int main() 20 | { 21 | int arr[] = {4, 3, 1, 5, 2, 6}; 22 | insertionSort(arr, 6); 23 | 24 | for(int element : arr) 25 | { 26 | std::cout << element << " "; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Seminar01/src/sorting-algorithms/selection-sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | template 4 | void selectionSort(T* arr, unsigned lenght) 5 | { 6 | for (size_t i = 0; i < lenght; i++) 7 | { 8 | size_t minElementIndex = i; 9 | for (size_t j = i + 1; j < lenght; j++) 10 | { 11 | if(arr[j] < arr[minElementIndex]) 12 | { 13 | minElementIndex = j; 14 | } 15 | } 16 | 17 | if(i != minElementIndex) 18 | { 19 | std::swap(arr[i], arr[minElementIndex]); 20 | } 21 | } 22 | } 23 | 24 | int main() 25 | { 26 | int arr[] = {1, 5, 4, 2, 3, 6}; 27 | selectionSort(arr, 6); 28 | 29 | for(int element : arr) 30 | { 31 | std::cout << element << " "; 32 | } 33 | } -------------------------------------------------------------------------------- /Seminar02/README.md: -------------------------------------------------------------------------------- 1 | # Втори семинар по структури от данни - 17.10.2024 2 | 3 | ## Анализ на рекурсивни алгоритми. 4 | В първи семинар разгледахме метод за анализиране на итеративни алгоритми. Този метод не работи когато анализираме рекурсивни алгоритми. За да намерим сложността на рекурсивен алгоритъм използваме рекурентни уравнения. 5 | 6 | ### Рекурентно уравнение 7 | * Рекурентно уравнение е уравнение, което изразява общия член на някаква редица от числа чрез предишните членове. 8 | 9 | Рекурентните уравнения описват рекурсивни алгоритми. Разбира се, както рекурсивните алгоритми имат дъно, така и рекурентните уравнения имат начални условия. 10 | 11 | Нека разгледаме следния пример: 12 | ```cpp 13 | void sum_rec(int* arr, unsigned size, int& sum) 14 | { 15 | if(size == 0) 16 | return; 17 | 18 | sum += arr[0]; 19 | 20 | return sum_rec(arr + 1, size - 1, sum); 21 | } 22 | ``` 23 | 24 | Какво би било рекурентното уравнение, което описва този рекурсивен алгоритъм? Нека с променливата `n` означаваме големината на масива. 25 | 26 | * Разглеждаме дъното. При n = 0 имаме Θ(1) работа. 27 | * В противен случай, имаме едно добавяне и извикване на алгоритъма с вход `n - 1`. 28 | 29 | Съставяме уравнението: 30 | * T(0) = 1 31 | * T(n) = T(n - 1) + 1 32 | 33 | Решение на рекурентно уравнение наричаме затворена формула. Решение на горния пример би било: 34 | 35 | T(n) = n + 1 36 | 37 | ## Методи за решаване на рекурентни уравнения 38 | 39 | ### 1. Чрез развиване 40 | Винаги можем да започнем да развиваме рекурентното уравнение търсейки някаква закономерност. 41 | 42 | $$ 43 | T(n) = T(n - 1) + \theta(1) = T(n - 2) + \theta(1) + \theta(1) = \newline 44 | T(0) + \theta(1) + \dots + \theta(1) = \newline 45 | (n + 1) * \theta(1) = \theta(n) 46 | $$ 47 | 48 | ### Чрез дърво на рекурсията 49 | Всеки рекурсивен алгоритъм образува дърво на рекурсията. Разглеждайки броя върхове и работата, която се "извършва" във всеки връх, височината на дървото и т.н. можем да направим изводи за решението на рекурентната редица. 50 | 51 | Нека разгледаме следното рекурентно уравнение: 52 | * T(0) = 1 53 | * T(1) = 1 54 | * T(n) = 2 * T(n / 2) + n 55 | 56 | Това рекурентно уравнение описва работата на алгоритъмът `Merge Sort`, който ще разгледаме съвсем скоро. Как изглежда дървото на рекурсията? 57 | 58 | ![tree-merge](media/rec_tree.png) 59 | 60 | Височината на това дърво е приблизително log(n). При стойност на параметъра `k = log_2(n)` стойността в листото е 1. 61 | На всяко ниво имаме Θ(n) работа. Трябва да забележим, че работата за ниво j се смята по следната формула: 62 | 63 | $$ 64 | 2^j * \frac{\theta(n)}{2^j} = \theta(n) 65 | $$ 66 | 67 | Това ще рече броят на върховете на всяко ниво умножен по работата, която се извършва във върха на дървото. 68 | 69 | Щом броя на нивата е Θ(log(n)) а на всяко ниво се извършва Θ(n) работа, то търсената сложност е Θ(n log(n)). 70 | 71 | ### Други методи 72 | 73 | ## Примери 74 | Нека разгледаме някои примери за рекурентни уравнения. 75 | 76 | $$ 77 | T(0) = \theta(1) 78 | \newline 79 | T(n) = 3T(n - 1) + \theta(1) 80 | $$ 81 | 82 | Дървото на рекурсията би изглеждало по следния начин: 83 | ![tree-recur](media/recurr2.png) 84 | 85 | Нивата на това дърво ще са n на брой. Всеки елемент е с единица по - малък от баща си. Също така, на всяко ниво има 3^j върха, където j е номерът на нивото (номерираме от 0). Всеки възел извършва Θ(1) работа. Сумираме по нива: 86 | 87 | $$ 88 | \sum_{i = 0}^n 3^i * \theta(1) = \frac{3^{n + 1} - 1}{2} * \theta(1) = \theta(3^n) 89 | $$ 90 | 91 | ## Алгоритмична схема "разделяй и владей" 92 | * Разделяй - Ако входът е достатъчно голям той се разделя на части и се преминава към стъпката **владей**. В противен случай (ако входът е достатъчно малък) задачата се решава по някакъв тривиален начин. (пример е дъното на рекурсията в `Merge Sort` където масив с големина 0 или 1 е тривиално сортиран.) 93 | * Владей - Пускаме алгоритъма върху всяка от частите и получаваме резултатите. 94 | * Комбинирай - Използваме тези резултати за да получим желания от нас резултат. 95 | 96 | ## Алгоритъмът Merge Sort 97 | Първо, нека се запознаем с функцията `merge`. Тя приема два сортирани масива и като резултат създава сортиран масив. Важно условие е масивите да са сортирани. Ако това не е така функцията не прави нищо логично. 98 | 99 | Ако имаме масив с n елемента и успешно сортираме лявата и дясната половина, то извиквайки функцията `merge` ще получим желания сортиран масив. Но как да сортираме лявата и дясната половина? 100 | 101 | Спомняме си, че пишем сортиращ алгоритъм. Извиквам mergeSort рекурсивно върху лявата и дясната половина на масива и след като ги сортира викаме функцията merge. mergeSort ще продължи да разбива масива на две, после левия и десния подмасив на още две и т.н. 102 | 103 | Масив от един елемент е сортиран и ще използваме този случай като дъно на рекурсията. 104 | 105 | Нека разгледаме следната диаграма: 106 | ![merge](media/merge.png) 107 | 108 | Оцветените в зелено клетки са подмасивите, на които вече е бил извикан merge. 109 | 110 | Следната диаграма представя визуално как работи функцията `merge`: 111 | ![merge-func](media/merge-func.png) 112 | 113 | Двата масива са сортирани, та има смисъл да гледаме единствено най - левите елементи. На всяка стъпка избираме по - малкия и го слагаме в резултатен масив. Трябва, разбира се, да се разгледа и случая в който масивите не са с равни дължини. 114 | 115 | Сложността на `Merge Sort` се описва чрез рекурентната редица, използвана като пример за решение използващо метода с дърво на рекурсията. И в трита случая имаме Θ(n log(n)) 116 | 117 | Стабилен - Да 118 | 119 | Адаптивен - Не 120 | 121 | in-place - Не 122 | 123 | Сложност по памет - Θ(n) 124 | 125 | ## Алгоритъмът Quicksort 126 | Нека предположим, че избираме произволен елемент от `p` от `n` елементи, които искаме да сортираме. `Quicksort` разделя останалите `n - 1` елемента на две групи - тези, които са по - малки от `p` и тези, които са по - големи. Всеки от по - малките елементи се намира вляво от всеки от по - големите. 127 | 128 | Подобно разделяне ни носи две неща: 129 | 130 | * Избраният елемент `p` остава на точната позиция на която ще бъде в сортирания масив. 131 | * След това разбиване никой елемент не се премества от лявата част в дясната. Това означава, че можем да разглеждаме двете части като отделни масиви! 132 | 133 | Функцията, която използваме за да направи такова разделение, наричаме `partition`. След като изберем елемент (наричан `pivot`) извикваме `partition` върху масива и след като тя приключи рекурсивно извикваме `Quicksort` върху лявата подмасива на по - малките елементи и този на по - големите. 134 | 135 | * Worst case complextiy = O(n^2) - Можем винаги да избираме най - големия или най - малкия. Тогава няма да имаме два подмасива а един с големина `n - 1`. 136 | * Average case = O(nlog(n)) - Добре е тук да обърнем внимание на средния случай като той ни дава много по - добра представа за реалния потенциал на `Quicksort`. Въпрки, че в най - лошия случай това е квадратичен алгоритъм, очакваме че няма да вземем най - големия, нито най - малкия, а елемент близък до средния по големина. 137 | * Стабилен - не, `partition` не гарантира, че ще запази първоначалната наредба на елементите. 138 | * in place - да 139 | 140 | ## std::partition, std::merge -------------------------------------------------------------------------------- /Seminar02/media/merge-func.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar02/media/merge-func.png -------------------------------------------------------------------------------- /Seminar02/media/merge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar02/media/merge.png -------------------------------------------------------------------------------- /Seminar02/media/rec_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar02/media/rec_tree.png -------------------------------------------------------------------------------- /Seminar02/media/recurr2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar02/media/recurr2.png -------------------------------------------------------------------------------- /Seminar02/src/sorting-algorithms/merge-sort-dummy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | template 4 | void merge(T* arr1, unsigned len1, 5 | T* arr2, unsigned len2) 6 | { 7 | T* resultArray = new T[len1 + len2]; 8 | 9 | unsigned cursor1 = 0; 10 | unsigned cursor2 = 0; 11 | unsigned resultCursor = 0; 12 | 13 | while (cursor1 < len1 && cursor2 < len2) 14 | { 15 | if (arr1[cursor1] <= arr2[cursor2]) 16 | resultArray[resultCursor++] = arr1[cursor1++]; 17 | else 18 | resultArray[resultCursor++] = arr2[cursor2++]; 19 | } 20 | 21 | while (cursor1 < len1) 22 | resultArray[resultCursor++] = arr1[cursor1++]; 23 | 24 | while (cursor2 < len2) 25 | resultArray[resultCursor++] = arr2[cursor2++]; 26 | 27 | for (size_t i = 0; i < len1 + len2; i++) 28 | arr1[i] = resultArray[i]; 29 | 30 | delete[] resultArray; 31 | } 32 | 33 | template 34 | void mergeSort(T* arr, unsigned len) 35 | { 36 | if (len < 2) 37 | return; 38 | 39 | unsigned mid = len / 2; 40 | 41 | mergeSort(arr, mid); 42 | mergeSort(arr + mid, len - mid); 43 | 44 | merge(arr, mid, arr + mid, len - mid); 45 | } 46 | 47 | int main() 48 | { 49 | int arr[4] = { 9, 6, 5, 8 }; 50 | mergeSort(arr, 4); 51 | 52 | for (int i = 0; i < 4; i++) 53 | std::cout << arr[i] << " "; 54 | } -------------------------------------------------------------------------------- /Seminar02/src/sorting-algorithms/merge-sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | template 6 | void merge(T* firstArray, unsigned firstSize, 7 | T* secondArray, unsigned secondSize, 8 | T* buffer) 9 | { 10 | unsigned firstIter = 0; 11 | unsigned secondIter = 0; 12 | unsigned resultIter = 0; 13 | 14 | while(firstIter < firstSize && secondIter < secondSize) 15 | { 16 | if(firstArray[firstIter] > secondArray[secondIter]) 17 | { 18 | buffer[resultIter] = secondArray[secondIter]; 19 | secondIter++; 20 | } 21 | else 22 | { 23 | buffer[resultIter] = firstArray[firstIter]; 24 | firstIter++; 25 | } 26 | resultIter++; 27 | } 28 | 29 | while(firstIter < firstSize) 30 | { 31 | buffer[resultIter++] = firstArray[firstIter++]; 32 | } 33 | 34 | while(secondIter < secondSize) 35 | { 36 | buffer[resultIter++] = secondArray[secondIter++]; 37 | } 38 | } 39 | 40 | 41 | template 42 | void mergeSortStep(T* arr, unsigned size, T* buffer) 43 | { 44 | if(size < 2) 45 | return; 46 | 47 | unsigned mid = size / 2; 48 | mergeSortStep(arr, mid, buffer); 49 | mergeSortStep(arr + mid, size - mid, buffer); 50 | 51 | merge(arr, mid, arr + mid, size - mid, buffer); 52 | 53 | for (size_t i = 0; i < size; i++) 54 | arr[i] = buffer[i]; 55 | } 56 | 57 | template 58 | void mergeSort(T* arr, unsigned size) 59 | { 60 | if(size < 2) 61 | return; 62 | 63 | T* buffer = new T[size]; 64 | mergeSortStep(arr, size, buffer); 65 | delete[] buffer; 66 | } 67 | 68 | int main() 69 | { 70 | std::vector numbers; 71 | unsigned size = rand() % 10000; 72 | numbers.resize(size); 73 | 74 | for (size_t i = 0; i < size; i++) 75 | { 76 | numbers[i] = rand() % 10000; 77 | } 78 | 79 | mergeSort(numbers.data(), numbers.size()); 80 | 81 | std::cout << std::is_sorted(numbers.begin(), numbers.end()) << std::endl; 82 | } 83 | -------------------------------------------------------------------------------- /Seminar02/src/sorting-algorithms/quick-sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include // std::is_sorted 3 | #include // std::vector 4 | 5 | /// @brief Reorders the elements in such a way that all elements for which are less than pivot 6 | // precede all elements which are greater or equal to pivot. Relative order of the elements is not preserved. 7 | template 8 | size_t partition(T* pArr, size_t len) 9 | { 10 | if (pArr[0] > pArr[len - 1]) 11 | std::swap(pArr[0], pArr[len - 1]); 12 | 13 | T& partitioningElement = pArr[len - 1]; 14 | size_t left = 0; 15 | size_t right = len - 1; 16 | 17 | while (true) 18 | { 19 | while (pArr[++left] < partitioningElement) 20 | ; 21 | 22 | while (pArr[--right] > partitioningElement) 23 | { 24 | if (left == right) 25 | break; 26 | } 27 | 28 | if (left >= right) 29 | break; 30 | 31 | std::swap(pArr[left], pArr[right]); 32 | } 33 | 34 | std::swap(pArr[left], partitioningElement); 35 | return left; 36 | } 37 | 38 | template 39 | void quickSort(T* arr, size_t size) 40 | { 41 | if(size < 2) 42 | return; 43 | 44 | size_t pivotPos = partition(arr, size); 45 | 46 | quickSort(arr, pivotPos); 47 | quickSort(arr + pivotPos + 1, size - pivotPos - 1); 48 | } 49 | 50 | int main() 51 | { 52 | std::vector numbers; 53 | size_t size = rand() % 10000; 54 | numbers.resize(size); 55 | 56 | for (size_t i = 0; i < size; i++) 57 | { 58 | numbers[i] = rand() % 10000; 59 | } 60 | 61 | quickSort(numbers.data(), numbers.size()); 62 | 63 | std::cout << std::is_sorted(numbers.begin(), numbers.end()); 64 | } -------------------------------------------------------------------------------- /Seminar03/README.md: -------------------------------------------------------------------------------- 1 | # Трети семинар по структури от данни - 24.10.2024 2 | 3 | ## Долна граници на сортиране с преки сравнения. Алгоритъмът Counting sort. 4 | За пряко сравнение можем да си мислим като за отговор с "да" или "не" на въпроса "а по-голям ли е от b". Алгоритъм за сортиране с преки сравнения задава такива въпроси за да стигне до отговора. Разгледаните до момента алгоритми са алгоритми за сортиране с преки сравнения. Видяхме, че задачата сортиране може да се реши във време O(nlog(n)). Възможно ли е да се справим по - добре? Възможно ли е да намерим алгоритъм с по - ниска асимптотика от O(nlog(n)). 5 | 6 | Отговорът на този въпрос е не. Всеки алгоритъм за сортиране използващ преки сравнения има **долна граница Ω(nlogn)**. Това означава, че не можем да съставим алгоритъм за сортиране използващ преки сравнения и имащ сложност по-ниска от nlogn. 7 | 8 | Все пак съществуват и други алгоритми за сортиране, които не използват преки сравнения. Тяхното предимство е, че асимптотично работят по-бързо. Недостатъкът е, че не работят върху произволен тип елементи. 9 | 10 | Пример за такъв алгоритъм е **Counting sort**. Входът е масив от цели числа. Работейки с числа знаем, че всеки елемент в масива е от интервала `[min, ..., max]` (където min и max са съответно най - малкия и най - големия елемент в масива). 11 | 12 | Накратко, можем да обобщим работата му по следния начин: 13 | 1. Създай два масива `count` и `copy`. `copy` е копия на входния масив. `count` е масив с размер max - min + 1 пълен с нули. 14 | 2. Преброй колко пъти се среща всеки елемент на входния масив. 15 | * След изпълнение на `стъпка 2` count[i - min] - колко пъти се среща i във входния масив. 16 | 3. Последователно приложи count[i] += count[i - 1] за i от 1 до `count.size()`. 17 | * Тази стъпка променя масива `count` по следния начин - count[i - min] отговаря на въпроса **коя е най - дясната позиция на i в сортираната последователност**. Наистина, преди `стъпка 3` в `count` има брой на елементите. Елементът на 0 позиция е ясен - ако имаме 3 единици най - дясната позиция на единицата е 3 (ако индексираме от 1, иначе 2). Ако имаме 3 единици и две двойки, най - дясната позиция на двойката е 5. Ако имаме 3 единици 2 двойки и 7 четворки най - дясната позиция на 4 е 12 и тн... 18 | 4. След като знаем най - дясната позиция на всеки елемент за всеки елемент от `copy` **отдясно наляво** 19 | * Постави елемента `copy[i]` на правилната (най - дясната) позиция във входния масив. 20 | * Намали стойността на count[copy[i] - min]. - Това ни дава следващата позиция (тази вляво от дясната) за да може като го видим следващия път да го сложим на правилната позиция. 21 | 22 | Всички тези магии се правят за да може алгоритъма да е стабилен. Тръгването отдясно наляво (от n - 1 до 0) не е случайно - разполагаме с най - дясната позиция на всеки елемент. Логично е при три елемента първо да се справиш с най - десния. 23 | 24 | ## Структури от данни 25 | TBI 26 | 27 | ## Реализация на динамичен масив. -------------------------------------------------------------------------------- /Seminar03/src/amortized-analysis/all-boolean-vectors.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void next_iterators(std::vector& number) 5 | { 6 | auto it = number.rbegin(); 7 | while(it != number.rend() && *it == 1) 8 | { 9 | *it = 0; 10 | ++it; 11 | } 12 | if (it != number.rend()) 13 | *it = 1; 14 | } 15 | 16 | void next(std::vector& number) 17 | { 18 | int idx = number.size() - 1; 19 | 20 | while (idx >= 0 && number[idx] == 1) 21 | number[idx--] = 0; 22 | 23 | if (idx >= 0) 24 | number[idx] = 1; 25 | } 26 | 27 | void print(const std::vector& number) 28 | { 29 | std::cout << "[ "; 30 | for(auto it = number.begin(); it != number.end(); ++it) 31 | { 32 | std::cout << *it << " "; 33 | } 34 | std::cout << "]" << std::endl; 35 | } 36 | 37 | void generate_boolean_vectors(unsigned n) 38 | { 39 | std::vector number(n, 0); 40 | size_t count = 1 << n; 41 | 42 | for (size_t i = 0; i < count; i++) 43 | { 44 | print(number); 45 | next(number); 46 | } 47 | } 48 | 49 | int main() 50 | { 51 | generate_boolean_vectors(3); 52 | } -------------------------------------------------------------------------------- /Seminar03/src/dynamic-array/examples/placement-new-memory-strategy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct E 4 | { 5 | E() 6 | { 7 | std::cout << "constr" << std::endl; 8 | } 9 | 10 | ~E() 11 | { 12 | std::cout << "destr" << std::endl; 13 | } 14 | }; 15 | 16 | int main() 17 | { 18 | std::cout << "operator new called" << std::endl; 19 | E* e = static_cast(operator new(sizeof(E))); 20 | std::cout << "placement new called" << std::endl; 21 | new (e) E(); 22 | 23 | std::cout << "destructor manually called" << std::endl; 24 | e->~E(); 25 | std::cout << "operator delete called" << std::endl; 26 | operator delete(e, sizeof(E)); 27 | } -------------------------------------------------------------------------------- /Seminar03/src/dynamic-array/examples/variadic-templates.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // std::forward example - preserve the value category (lvalue or rvalue) 4 | void f(const int& x) 5 | { 6 | std::cout << "lvalue" << std::endl; 7 | } 8 | 9 | void f(const int&& x) 10 | { 11 | std::cout << "rvalue" << std::endl; 12 | } 13 | 14 | template 15 | void g(T&& x) 16 | { 17 | f(std::forward(x)); 18 | } 19 | 20 | struct printer 21 | { 22 | static void print() 23 | { 24 | std::cout << std::endl; 25 | } 26 | 27 | template 28 | static void print(const T& first, const Args&... args) 29 | { 30 | std::cout << first << " "; 31 | print(std::forward(args)...); 32 | } 33 | }; 34 | 35 | struct Entity 36 | { 37 | int x; 38 | friend std::ostream& operator<<(std::ostream& os, const Entity& e); 39 | 40 | Entity(int x_) : x(x_) {} 41 | 42 | Entity(Entity&) 43 | { 44 | std::cout << "Copied " << x << std::endl; 45 | } 46 | }; 47 | 48 | std::ostream& operator<<(std::ostream& os, const Entity& e) 49 | { 50 | os << e.x; 51 | return os; 52 | } 53 | 54 | int main() 55 | { 56 | int x = 10; 57 | g(x); 58 | 59 | g(std::move(x)); 60 | 61 | } -------------------------------------------------------------------------------- /Seminar03/src/dynamic-array/iterator/iterator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | template 5 | class const_vector_iterator 6 | { 7 | public: 8 | const_vector_iterator(T* passedVal) 9 | : memPointer{passedVal} 10 | {} 11 | const_vector_iterator(T* passedVal, size_t _push) 12 | : memPointer{passedVal + _push} 13 | {} 14 | 15 | const_vector_iterator operator+(int off) const 16 | { 17 | return {memPointer + off}; 18 | } 19 | 20 | const_vector_iterator operator-(int off) const 21 | { 22 | return {memPointer - off}; 23 | } 24 | 25 | int operator-(const_vector_iterator other) const 26 | { 27 | return memPointer - other.memPointer; 28 | } 29 | 30 | const T* operator->() const noexcept 31 | { 32 | return memPointer; 33 | } 34 | 35 | T& operator*() const noexcept 36 | { 37 | return *(memPointer); 38 | } 39 | 40 | bool operator==(const const_vector_iterator& it) const 41 | { 42 | return (memPointer == it.memPointer); 43 | } 44 | 45 | bool operator!=(const const_vector_iterator& it) const 46 | { 47 | return !(memPointer == it.memPointer); 48 | } 49 | 50 | private: 51 | T* memPointer; 52 | }; 53 | 54 | template 55 | class vector_iterator 56 | { 57 | public: 58 | vector_iterator(T* passedVal) 59 | : memPointer{passedVal} {}; 60 | vector_iterator(T* passedVal, size_t _push) 61 | : memPointer{passedVal + _push} {}; 62 | 63 | vector_iterator& operator++() 64 | { 65 | memPointer++; 66 | return *this; 67 | } 68 | 69 | vector_iterator operator++(int) const 70 | { 71 | vector_iterator it = *this; 72 | ++(*this); 73 | return it; 74 | } 75 | 76 | vector_iterator& operator--() 77 | { 78 | memPointer--; 79 | return *this; 80 | } 81 | 82 | vector_iterator operator--(int) const 83 | { 84 | vector_iterator it = *this; 85 | --(*this); 86 | return it; 87 | } 88 | 89 | operator const_vector_iterator() const 90 | { 91 | return const_vector_iterator(memPointer); 92 | } 93 | 94 | vector_iterator operator+(int off) const 95 | { 96 | return {memPointer + off}; 97 | } 98 | 99 | vector_iterator operator-(int off) const 100 | { 101 | return {memPointer - off}; 102 | } 103 | 104 | T* operator->() 105 | { 106 | return memPointer; 107 | } 108 | 109 | const T* operator->() const 110 | { 111 | return memPointer; 112 | } 113 | 114 | T& operator*() 115 | { 116 | return *(memPointer); 117 | } 118 | 119 | bool operator==(const vector_iterator& it) const 120 | { 121 | return (memPointer == it.memPointer); 122 | } 123 | 124 | bool operator!=(const vector_iterator& it) const 125 | { 126 | return !(memPointer == it.memPointer); 127 | } 128 | 129 | private: 130 | T* memPointer; 131 | }; 132 | 133 | template 134 | class reverse_vector_iterator 135 | { 136 | public: 137 | reverse_vector_iterator(T* passedVal) 138 | : memPointer{passedVal} {}; 139 | reverse_vector_iterator(T* passedVal, size_t _push) 140 | : memPointer{passedVal + _push} {}; 141 | 142 | reverse_vector_iterator& operator++() 143 | { 144 | memPointer--; 145 | return *this; 146 | } 147 | 148 | reverse_vector_iterator operator++(int) const 149 | { 150 | reverse_vector_iterator it = *this; 151 | ++(*this); 152 | return it; 153 | } 154 | 155 | reverse_vector_iterator& operator--() 156 | { 157 | memPointer++; 158 | return *this; 159 | } 160 | 161 | reverse_vector_iterator operator--(int) const 162 | { 163 | reverse_vector_iterator it = *this; 164 | --(*this); 165 | return it; 166 | } 167 | 168 | reverse_vector_iterator operator+(int off) const 169 | { 170 | return {memPointer - off}; 171 | } 172 | 173 | reverse_vector_iterator operator-(int off) const 174 | { 175 | return {memPointer + off}; 176 | } 177 | 178 | T* operator->() 179 | { 180 | return memPointer; 181 | } 182 | 183 | const T* operator->() const 184 | { 185 | return memPointer; 186 | } 187 | 188 | T& operator*() 189 | { 190 | return *(memPointer); 191 | } 192 | 193 | bool operator==(const reverse_vector_iterator& it) const 194 | { 195 | return (memPointer == it.memPointer); 196 | } 197 | 198 | bool operator!=(const reverse_vector_iterator& it) const 199 | { 200 | return !(memPointer == it.memPointer); 201 | } 202 | 203 | private: 204 | T* memPointer; 205 | }; -------------------------------------------------------------------------------- /Seminar03/src/dynamic-array/tests.cpp: -------------------------------------------------------------------------------- 1 | #include "vector/vector.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct Entity 10 | { 11 | int x; 12 | 13 | Entity(int _x) 14 | : x(_x) 15 | {} 16 | Entity() 17 | : x() 18 | {} 19 | 20 | ~Entity() 21 | { 22 | x = -1; 23 | } 24 | }; 25 | 26 | void tests_default() 27 | { 28 | vector v; 29 | 30 | v.push_back(Entity(1)); 31 | assert(v.back().x == 1); 32 | assert(v.size() == 1); 33 | assert(v.capacity() == 1); 34 | 35 | v.emplace_back(2); 36 | assert(v.back().x == 2); 37 | assert(v.size() == 2); 38 | assert(v.capacity() == 2); 39 | 40 | for (int i = 3; i <= 10; i++) 41 | v.emplace_back(i); 42 | 43 | for (int i = 1; i <= 10; i++) 44 | assert(v[i - 1].x == i); 45 | 46 | for (int i = 1; i <= 10; i++) 47 | assert((v.begin() + (i - 1))->x == i); 48 | 49 | assert(v.size() == 10); 50 | assert(v.capacity() == 16); 51 | 52 | int cnt = 1; 53 | for (auto e : v) 54 | assert(e.x == cnt++); 55 | } 56 | 57 | void test_resize_reserve() 58 | { 59 | std::vector v; 60 | vector v1; 61 | 62 | assert(v1.size() == v.size() && v1.capacity() == v.capacity()); 63 | assert(v.data() == nullptr && v1.data() == nullptr); 64 | 65 | v.reserve(10); 66 | v1.reserve(10); 67 | assert(v1.size() == v.size() && v1.capacity() == v.capacity()); 68 | 69 | v.reserve(20); 70 | v1.reserve(20); 71 | assert(v1.size() == v.size() && v1.capacity() == v.capacity()); 72 | 73 | v.reserve(10); 74 | v1.reserve(10); 75 | assert(v1.size() == v.size() && v1.capacity() == v.capacity()); 76 | 77 | v.shrink_to_fit(); 78 | v1.shrink_to_fit(); 79 | assert(v1.size() == v.size() && v1.capacity() == v.capacity()); 80 | 81 | v.resize(10); 82 | v1.resize(10); 83 | assert(v1.size() == v.size() && v1.capacity() == v.capacity()); 84 | 85 | v.resize(20); 86 | v1.resize(20); 87 | assert(v1.size() == v.size() && v1.capacity() == v.capacity()); 88 | 89 | for (size_t i = 0; i < v1.size(); i++) 90 | v1[i].x = i; 91 | 92 | v.resize(10); 93 | v1.resize(10); 94 | 95 | for (size_t i = 0; i < v1.size(); i++) 96 | assert(v1[i].x == i); 97 | 98 | assert(v1.size() == v.size() && v1.capacity() == v.capacity()); 99 | assert(v1.front().x == 0 && v1.back().x == 9); 100 | 101 | v.push_back(1); 102 | v.push_back(2); 103 | v.emplace_back(3); 104 | 105 | v1.push_back(1); 106 | v1.push_back(2); 107 | v1.emplace_back(3); 108 | 109 | v.shrink_to_fit(); 110 | v1.shrink_to_fit(); 111 | assert(v1.size() == v.size() && v1.capacity() == v.capacity()); 112 | } 113 | 114 | void push_pop_tests() 115 | { 116 | vector v; 117 | 118 | for (size_t i = 0; i < 100; i++) 119 | v.push_back(i); 120 | 121 | int j = 100; 122 | while (!v.empty()) 123 | { 124 | assert(v.back() == --j); 125 | v.pop_back(); 126 | } 127 | } 128 | 129 | void erase_tests() 130 | { 131 | vector v; 132 | for (size_t i = 1; i <= 100; i++) 133 | { 134 | v.push_back(i); 135 | } 136 | 137 | v.erase(v.begin(), v.begin() + 49); 138 | 139 | int j = 50; 140 | 141 | for (auto x : v) 142 | { 143 | assert(x == j++); 144 | } 145 | 146 | assert(v.size() == 51); 147 | 148 | v.erase(v.begin()); 149 | assert(v.front() == 51 && v.size() == 50); 150 | 151 | v.erase(v.begin() + 10, v.begin() + 20); 152 | assert(v.size() == 40); 153 | 154 | for (auto x : v) 155 | { 156 | assert(!(x >= 61 && x <= 70)); 157 | } 158 | 159 | v.erase(v.begin(), v.end()); 160 | assert(v.empty()); 161 | } 162 | 163 | int called = 0; 164 | 165 | struct constructor_called 166 | { 167 | constructor_called() 168 | { 169 | ++called; 170 | } 171 | }; 172 | 173 | void constructor_tests() 174 | { 175 | // Default construct 176 | vector start; 177 | assert(start.size() == 0 && start.capacity() == 0 && start.data() == nullptr); 178 | start.push_back(0); 179 | assert(start.size() == 1 && start.capacity() == 1 && start.data() != nullptr); 180 | start.pop_back(); 181 | assert(start.size() == 0 && start.capacity() == 1 && start.data() != nullptr); 182 | 183 | size_t length = 200; 184 | 185 | for (size_t i = 0; i < length; i++) 186 | start.push_back(rand() % 1000); 187 | 188 | // Copy constructor tests 189 | vector copy(start); 190 | for (size_t i = 0; i < length; i++) 191 | assert(copy[i] == start[i]); 192 | 193 | // Move constructor tests 194 | vector moved(std::move(copy)); 195 | assert(copy.data() == nullptr && copy.size() == 0 && copy.capacity() == 0); 196 | 197 | for (size_t i = 0; i < length; i++) 198 | assert(moved[i] == start[i]); 199 | 200 | // Parameter constructors 201 | std::vector v(10); 202 | assert(called == 10); 203 | v.reserve(20); 204 | assert(called == 10); 205 | 206 | std::vector v2(20, 20); 207 | for (const auto& e : v2) 208 | { 209 | assert(e.x == 20); 210 | } 211 | } 212 | 213 | void push_back_tests() 214 | { 215 | vector v; 216 | 217 | for (size_t i = 0; i < 1000; i++) 218 | v.push_back(i); 219 | 220 | assert(v.size() == 1000); 221 | 222 | for (int i = 0; i < 1000; i++) 223 | assert(v[i] == i); 224 | } 225 | 226 | void erase_test() 227 | { 228 | vector v; 229 | 230 | for (size_t i = 0; i < 1001; i++) 231 | v.push_back(i); 232 | 233 | auto it = v.begin(); 234 | 235 | while (it != v.end()) 236 | { 237 | if ((*it % 2) == 0) 238 | { 239 | v.erase(it); 240 | it = v.begin(); 241 | } 242 | ++it; 243 | } 244 | 245 | for (auto x : v) 246 | assert(x % 2); 247 | 248 | assert(v.size() == 500); 249 | } 250 | 251 | void iterator_tests() 252 | { 253 | vector v; 254 | for (size_t i = 0; i < 100; i++) 255 | { 256 | v.push_back(i); 257 | } 258 | 259 | for (size_t i = 0; i < 100; i++) 260 | { 261 | assert(*(v.begin() + i) == i); 262 | assert(*(v.end() - i - 1) == 100 - i - 1); 263 | 264 | if (i) 265 | assert(v.begin() != (v.begin() + i) && (v.begin() + i) != v.end()); 266 | } 267 | 268 | auto it = v.begin(); 269 | assert(*it == 0); 270 | vector v1; 271 | v1.emplace_back(1); 272 | v1.emplace_back(2); 273 | 274 | assert((++v1.begin())->x == 2); 275 | assert(++(++v1.begin()) == v1.end()); 276 | assert(--v1.end() == ++v1.begin()); 277 | 278 | int j = 99; 279 | for (auto rit = v.rbegin(); rit != v.rend(); ++rit) 280 | { 281 | assert(*rit == j--); 282 | } 283 | 284 | assert((v1.rbegin())->x == 2); 285 | assert((++v1.rbegin())->x == 1); 286 | } 287 | 288 | // Still a test with more complex objects 289 | void run_all() 290 | { 291 | vector v; 292 | vector> tests; 293 | 294 | tests.push_back(constructor_tests); 295 | tests.push_back(tests_default); 296 | tests.push_back(test_resize_reserve); 297 | tests.push_back(push_back_tests); 298 | tests.push_back(push_pop_tests); 299 | tests.push_back(erase_tests); 300 | tests.push_back(erase_test); 301 | tests.push_back(iterator_tests); 302 | 303 | for (const auto& func : tests) 304 | { 305 | v.emplace_back(func); 306 | } 307 | 308 | for (auto& c_thread : v) 309 | { 310 | c_thread.join(); 311 | } 312 | } 313 | 314 | int main() 315 | { 316 | std::cout << "Start tests...\n"; 317 | run_all(); 318 | std::cout << "No assertions failed!"; 319 | } -------------------------------------------------------------------------------- /Seminar03/src/sorting/counting-sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void counting_sort(std::vector& data) 6 | { 7 | if (data.empty()) 8 | return; 9 | 10 | auto min_max_pair = std::minmax_element(data.begin(), data.end()); 11 | unsigned count_buffer_size = *min_max_pair.second - *min_max_pair.first + 1; 12 | 13 | // All elements are the same. 14 | if (count_buffer_size == 1) 15 | return; 16 | 17 | int lower = *min_max_pair.first; 18 | std::vector count(count_buffer_size, 0); 19 | std::vector copy(data); 20 | 21 | for (int number : data) 22 | { 23 | count[number - lower]++; 24 | } 25 | 26 | for (size_t i = 1; i < count.size(); i++) 27 | { 28 | count[i] += count[i - 1]; 29 | } 30 | 31 | for (int i = copy.size() - 1; i >= 0; i--) 32 | { 33 | data[count[copy[i] - lower] - 1] = copy[i]; 34 | --count[copy[i] - lower]; 35 | } 36 | } 37 | 38 | int main() 39 | { 40 | std::vector numbers; 41 | unsigned size = rand() % 2000; 42 | numbers.resize(size); 43 | 44 | for (size_t i = 0; i < size; i++) 45 | { 46 | numbers[i] = rand() % 100; 47 | } 48 | 49 | counting_sort(numbers); 50 | 51 | std::cout << std::is_sorted(numbers.begin(), numbers.end()) << std::endl; 52 | } -------------------------------------------------------------------------------- /Seminar04/src/tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "doubly-linked-list.hpp" 4 | 5 | void insert_basic_tests() 6 | { 7 | doubly_linked_list ll; 8 | ll.push_back(1); 9 | assert(ll.front() == ll.back() && ll.front() == 1); 10 | 11 | ll.pop_back(); 12 | assert(ll.empty()); 13 | 14 | ll.push_front(2); 15 | assert(ll.front() == ll.back() && ll.front() == 2); 16 | 17 | ll.pop_back(); 18 | assert(ll.empty()); 19 | 20 | ll.push_back(1); 21 | ll.push_front(2); 22 | 23 | assert(ll.front() == 2 && ll.back() == 1 && ll.size() == 2); 24 | } 25 | 26 | void insert_erase_tests() 27 | { 28 | // push front and back 29 | doubly_linked_list ll; 30 | for (size_t i = 10; i < 20; i++) 31 | { 32 | ll.push_back(i); 33 | } 34 | 35 | for (size_t i = 0; i < 10; i++) 36 | { 37 | ll.push_front(9 - i); 38 | } 39 | 40 | int start = 0; 41 | for (auto it = ll.begin(); it != ll.end(); ++it) 42 | { 43 | assert(*it == start++); 44 | } 45 | 46 | // remove from the middle 47 | auto it = ll.begin(); 48 | for (size_t i = 0; i < 10; i++) 49 | ++it; 50 | 51 | while (it != ll.end()) 52 | { 53 | auto prev = it++; 54 | if (*prev == 19) 55 | int x = 10; 56 | ll.remove(prev); 57 | } 58 | 59 | start = 0; 60 | for (const auto& x : ll) 61 | { 62 | assert(x == start++); 63 | } 64 | 65 | // insert into the middle 66 | { 67 | auto it = ll.begin(); 68 | for (size_t i = 0; i < 5; i++) 69 | ++it; 70 | 71 | for (size_t i = 0; i < 10; i++) 72 | { 73 | ll.insert(it, i + 10); 74 | } 75 | } 76 | 77 | { 78 | auto it = ll.begin(); 79 | for (size_t i = 0; i < 5; i++) 80 | { 81 | assert(*it == i); 82 | ++it; 83 | } 84 | 85 | for (size_t i = 0; i < 10; i++) 86 | { 87 | assert(*it == i + 10); 88 | ++it; 89 | } 90 | 91 | for (size_t i = 0; i < 5; i++) 92 | { 93 | assert(*it == i + 5); 94 | ++it; 95 | } 96 | 97 | assert(it == ll.end()); 98 | } 99 | } 100 | 101 | void constructor_tests() 102 | { 103 | doubly_linked_list ll; 104 | 105 | for (size_t i = 0; i < 100; i++) 106 | { 107 | ll.push_back(i); 108 | } 109 | 110 | doubly_linked_list copy(ll); 111 | assert(copy.size() == ll.size()); 112 | 113 | auto it = ll.begin(); 114 | for (const auto& x : copy) 115 | { 116 | assert(x == *it); 117 | ++it; 118 | } 119 | 120 | doubly_linked_list move(std::move(copy)); 121 | assert(copy.empty()); 122 | 123 | assert(move.size() == ll.size()); 124 | 125 | auto it1 = ll.begin(); 126 | for (const auto& x : move) 127 | { 128 | assert(x == *it1); 129 | ++it1; 130 | } 131 | } 132 | 133 | int main() 134 | { 135 | std::cout << "Starting tests..." << std::endl; 136 | insert_basic_tests(); 137 | insert_erase_tests(); 138 | constructor_tests(); 139 | std::cout << "No assertions failed" << std::endl; 140 | } -------------------------------------------------------------------------------- /Seminar05/src/singly-linked-list/merge-sort.cpp: -------------------------------------------------------------------------------- 1 | #include "node_utils.h" 2 | #include 3 | 4 | template 5 | node* merge(node* lhs, node* rhs) 6 | { 7 | // result linked list 8 | node* head = nullptr; 9 | node* tail = nullptr; 10 | 11 | while (lhs && rhs) 12 | { 13 | if (lhs->data < rhs->data) 14 | { 15 | append_back(head, tail, lhs); 16 | lhs = lhs->next; 17 | } 18 | else 19 | { 20 | append_back(head, tail, rhs); 21 | rhs = rhs->next; 22 | } 23 | } 24 | 25 | if (lhs) 26 | { 27 | tail->next = lhs; 28 | } 29 | 30 | if (rhs) 31 | { 32 | tail->next = rhs; 33 | } 34 | 35 | return head; 36 | } 37 | 38 | template 39 | node* get_middle(node* list) 40 | { 41 | if (!list || !list->next) 42 | return list; 43 | 44 | node* fast = list->next; 45 | node* slow = list; 46 | 47 | while (fast && fast->next) 48 | { 49 | slow = slow->next; 50 | fast = fast->next->next; 51 | } 52 | return slow; 53 | } 54 | 55 | template 56 | node* merge_sort(node* list) 57 | { 58 | if (!list || !list->next) 59 | return list; 60 | 61 | node* middle = get_middle(list); 62 | 63 | node* right_part = middle->next; 64 | middle->next = nullptr; 65 | 66 | node* left_sorted = merge_sort(list); 67 | node* right_sorted = merge_sort(right_part); 68 | 69 | return merge(left_sorted, right_sorted); 70 | } 71 | 72 | int main() 73 | { 74 | node* ll = new node(1, new node(0, new node(2, new node(-1, new node(10))))); 75 | ll = merge_sort(ll); 76 | if (is_sorted(ll) && list_size(ll) == 5) 77 | std::cout << "passed"; 78 | else 79 | std::cout << "failed"; 80 | free_list(ll); 81 | } -------------------------------------------------------------------------------- /Seminar05/src/singly-linked-list/node_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // We represent a singly linked list 4 | // with an pointer to its first element. 5 | template 6 | struct node 7 | { 8 | T data; 9 | node* next; 10 | 11 | node(const T& data_, node* next_ = nullptr) : 12 | data(data_), 13 | next(next_) {} 14 | }; 15 | 16 | template 17 | void append_back(node*& head, node*& tail, node* element) 18 | { 19 | if(head == nullptr) 20 | { 21 | head = tail = element; 22 | } 23 | else 24 | { 25 | tail->next = element; 26 | tail = tail->next; 27 | } 28 | } 29 | 30 | 31 | template 32 | bool is_sorted(node* list) 33 | { 34 | if(!list || !list->next) 35 | return true; 36 | 37 | node* prev = list; 38 | node* next = list->next; 39 | 40 | while(next) 41 | { 42 | if(prev->data > next->data) 43 | { 44 | return false; 45 | } 46 | prev = prev->next; 47 | next = next->next; 48 | } 49 | return true; 50 | } 51 | 52 | template 53 | void free_list(node* ll) 54 | { 55 | while(ll) 56 | { 57 | node* to_delete = ll; 58 | ll = ll->next; 59 | delete to_delete; 60 | } 61 | } 62 | 63 | template 64 | unsigned list_size(node* list) 65 | { 66 | unsigned size = 0; 67 | while(list) 68 | { 69 | ++size; 70 | list = list->next; 71 | } 72 | return size; 73 | } 74 | 75 | template 76 | node* push_front(node* head, const T& data) 77 | { 78 | return new node(data, head); 79 | } -------------------------------------------------------------------------------- /Seminar05/src/singly-linked-list/quick_sort.cpp: -------------------------------------------------------------------------------- 1 | #include "node_utils.h" 2 | #include 3 | #include 4 | 5 | // Implementation is inspired by: 6 | // https://github.com/Angeld55/Data_structures_and_algorithms_FMI/blob/main/Miscellaneous/singlyLinkedList_quickSort.cpp 7 | 8 | template 9 | using list_info = std::pair*, node*>; 10 | 11 | template 12 | list_info partition(node* list, Predicate pred) 13 | { 14 | node* true_begin = nullptr; 15 | node* true_end = nullptr; 16 | 17 | node* false_begin = nullptr; 18 | node* false_end = nullptr; 19 | 20 | while (list) 21 | { 22 | if (pred(list->data)) 23 | { 24 | append_back(true_begin, true_end, list); 25 | } 26 | else 27 | { 28 | append_back(false_begin, false_end, list); 29 | } 30 | list = list->next; 31 | } 32 | 33 | if (true_end) 34 | { 35 | true_end->next = nullptr; 36 | } 37 | if (false_end) 38 | { 39 | false_end->next = nullptr; 40 | } 41 | 42 | return {true_begin, false_begin}; 43 | } 44 | 45 | template 46 | list_info concat_lists(list_info left, list_info right) 47 | { 48 | if (left.first == nullptr) 49 | return right; 50 | if (right.first == nullptr) 51 | return left; 52 | 53 | left.second->next = right.first; 54 | return {left.first, right.second}; 55 | } 56 | 57 | template 58 | list_info quick_sort_impl(node* list) 59 | { 60 | if (!list || !list->next) 61 | return {list, list}; 62 | 63 | const T& pivot_element = list->data; 64 | list_info result = partition(list, [&pivot_element](const T& element) { return element < pivot_element; }); 65 | 66 | node* pivot = result.second; 67 | 68 | list_info left_sorted = quick_sort_impl(result.first); 69 | 70 | // Skip the pivot element. 71 | list_info right_sorted = quick_sort_impl(result.second->next); 72 | 73 | // Attach pivot as the first element. 74 | pivot->next = right_sorted.first; 75 | right_sorted.first = pivot; 76 | 77 | if (!right_sorted.second) 78 | right_sorted.second = pivot; 79 | 80 | return concat_lists(left_sorted, right_sorted); 81 | } 82 | 83 | template 84 | void quick_sort(node*& list) 85 | { 86 | list_info result = quick_sort_impl(list); 87 | list = result.first; 88 | } 89 | 90 | void tests() 91 | { 92 | using inode = node; 93 | 94 | // test empty list 95 | inode* ll = nullptr; 96 | quick_sort(ll); 97 | assert(ll == nullptr); 98 | 99 | // One element case 100 | ll = new inode(1); 101 | quick_sort(ll); 102 | assert(ll && ll->data == 1 && !ll->next); 103 | 104 | // Two elements case (sorted & unsorted) 105 | ll->next = new inode(0); 106 | quick_sort(ll); 107 | assert(ll && ll->data == 0 && ll->next && ll->next->data == 1 && !ll->next->next); 108 | quick_sort(ll); 109 | assert(ll && ll->data == 0 && ll->next && ll->next->data == 1 && !ll->next->next); 110 | 111 | ll = push_front(ll, 2); 112 | ll = push_front(ll, 3); 113 | 114 | quick_sort(ll); 115 | assert(is_sorted(ll) && list_size(ll) == 4); 116 | quick_sort(ll); 117 | assert(is_sorted(ll) && list_size(ll) == 4); 118 | 119 | // Random elements 120 | srand(time(0)); 121 | 122 | for (size_t i = 0; i < 1000; i++) 123 | { 124 | ll = push_front(ll, rand() % 10000); 125 | } 126 | assert(list_size(ll) == 1004); 127 | quick_sort(ll); 128 | assert(is_sorted(ll) && list_size(ll) == 1004); 129 | 130 | free_list(ll); 131 | } 132 | 133 | int main() 134 | { 135 | tests(); 136 | } -------------------------------------------------------------------------------- /Seminar05/src/singly-linked-list/singly-linked-list-tests.cpp: -------------------------------------------------------------------------------- 1 | #include "singly-linked-list.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | void push_tests() 7 | { 8 | singly_linked_list ll; 9 | ll.push_back(1); 10 | 11 | assert(ll.front() == 1 && ll.size() == 1); 12 | ll.push_front(0); 13 | assert(ll.front() == 0 && ll.back() == 1 && ll.size() == 2); 14 | 15 | for (size_t i = 2; i < 100; i++) 16 | ll.push_back(i); 17 | 18 | assert(ll.size() == 100); 19 | 20 | auto it = ll.begin(); 21 | 22 | for (size_t i = 0; i < 100; i++) 23 | { 24 | assert(*it == i); 25 | ++it; 26 | } 27 | 28 | ll.remove_after(ll.begin()); 29 | assert(*(++ll.begin()) == 2 && ll.size() == 99); 30 | } 31 | 32 | void construct_tests() 33 | { 34 | singly_linked_list ll; 35 | for (size_t i = 0; i < 100; i++) 36 | { 37 | ll.push_back(i); 38 | } 39 | 40 | singly_linked_list copy(ll); 41 | 42 | auto it = ll.begin(); 43 | auto it_copy = copy.begin(); 44 | 45 | while (true) 46 | { 47 | assert(*it == *it_copy); 48 | ++it; 49 | ++it_copy; 50 | 51 | if(it == ll.end() || it_copy == copy.end()) 52 | break; 53 | } 54 | 55 | assert(ll.size() == copy.size()); 56 | 57 | singly_linked_list move(std::move(ll)); 58 | ll = std::move(move); 59 | move = std::move(ll); 60 | 61 | auto its = move.begin(); 62 | for(const auto& x : copy) 63 | { 64 | assert(x == *its); 65 | assert(its != move.end()); 66 | ++its; 67 | } 68 | assert(its == move.end()); 69 | assert(move.size() == 100); 70 | } 71 | 72 | void concat_tests() 73 | { 74 | singly_linked_list ll; 75 | singly_linked_list lls; 76 | 77 | for (size_t i = 0; i < 50; i++) 78 | { 79 | ll.push_back(i); 80 | lls.push_back(50 + i); 81 | } 82 | 83 | singly_linked_list cat = concat(ll, lls); 84 | assert(cat.size() == 100); 85 | int val = 0; 86 | 87 | for(const auto& x : cat) 88 | { 89 | assert(x == val++); 90 | } 91 | } 92 | 93 | int main() 94 | { 95 | push_tests(); 96 | construct_tests(); 97 | concat_tests(); 98 | } -------------------------------------------------------------------------------- /Seminar06/media/left-child-right-sibling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar06/media/left-child-right-sibling.png -------------------------------------------------------------------------------- /Seminar06/media/stick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar06/media/stick.png -------------------------------------------------------------------------------- /Seminar06/media/subtree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar06/media/subtree.png -------------------------------------------------------------------------------- /Seminar06/media/tree-first-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar06/media/tree-first-example.png -------------------------------------------------------------------------------- /Seminar06/media/tree-second-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar06/media/tree-second-example.png -------------------------------------------------------------------------------- /Seminar06/src/balanced-brackets.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | bool is_opening_bracket(char ch) 5 | { 6 | return ch == '(' || ch == '{' || ch == '['; 7 | } 8 | 9 | bool is_closing_bracket(char ch) 10 | { 11 | return ch == ')' || ch == ']' || ch == '}'; 12 | } 13 | 14 | bool is_corresponding_brackets(char left, char right) 15 | { 16 | return left == '(' && right == ')' || left == '{' && right == '}' || left == '[' && right == ']'; 17 | } 18 | 19 | bool balanced_brackets(const std::string& str) 20 | { 21 | std::stack s; 22 | 23 | for (char c : str) 24 | { 25 | if (is_opening_bracket(c)) 26 | { 27 | s.push(c); 28 | } 29 | else if (is_closing_bracket(c)) 30 | { 31 | if (s.empty()) 32 | return false; 33 | 34 | char top = s.top(); 35 | s.pop(); 36 | 37 | if (!is_corresponding_brackets(top, c)) 38 | return false; 39 | } 40 | } 41 | 42 | return s.empty(); 43 | } -------------------------------------------------------------------------------- /Seminar06/src/forward-iterator-bst.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | template > 8 | class Bst 9 | { 10 | private: 11 | struct Node 12 | { 13 | T data; 14 | Node* left; 15 | Node* right; 16 | Node(const T& data, Node* left = nullptr, Node* right = nullptr) : data(data), left(left), right(right) {} 17 | }; 18 | 19 | Node* root = nullptr; 20 | size_t size = 0; 21 | Compare comp; // comparator instance 22 | 23 | Node** findMinNode(Node** root); 24 | void free(Node* current); 25 | Node* copy(Node* current); 26 | 27 | public: 28 | Bst() = default; 29 | explicit Bst(const Compare& comparator) : comp(comparator) {} 30 | Bst(const Bst& other); 31 | Bst& operator=(const Bst& other); 32 | ~Bst(); 33 | 34 | bool insert(const T& data); 35 | bool contains(const T& data) const; 36 | bool remove(const T& data); 37 | 38 | size_t getSize() const; 39 | bool isEmpty() const; 40 | 41 | class ForwardIterator 42 | { 43 | private: 44 | std::stack nodeStack; 45 | 46 | void pushLeft(Node* node) 47 | { 48 | while (node != nullptr) 49 | { 50 | nodeStack.push(node); 51 | node = node->left; 52 | } 53 | } 54 | 55 | public: 56 | ForwardIterator(Node* root = nullptr) 57 | { 58 | pushLeft(root); 59 | } 60 | 61 | T& operator*() const 62 | { 63 | return nodeStack.top()->data; 64 | } 65 | 66 | ForwardIterator& operator++() 67 | { 68 | Node* right = nodeStack.top()->right; 69 | nodeStack.pop(); 70 | pushLeft(right); 71 | 72 | return *this; 73 | } 74 | ForwardIterator operator++(int) 75 | { 76 | ForwardIterator copy(*this); 77 | ++(*this); 78 | return copy; 79 | } 80 | 81 | bool operator!=(const ForwardIterator& other) const 82 | { 83 | return !(*this == other); 84 | } 85 | 86 | bool operator==(const ForwardIterator& other) const 87 | { 88 | return (other.nodeStack.empty() && nodeStack.empty()) || (nodeStack.size() && other.nodeStack.size() && nodeStack.top() == other.nodeStack.top()); 89 | } 90 | }; 91 | 92 | ForwardIterator begin() const 93 | { 94 | return ForwardIterator(root); 95 | } 96 | 97 | ForwardIterator end() const 98 | { 99 | return ForwardIterator(nullptr); 100 | } 101 | }; 102 | 103 | template 104 | bool Bst::insert(const T& data) 105 | { 106 | Node** current = &root; 107 | 108 | while (*current) 109 | { 110 | if (comp(data, (*current)->data)) 111 | current = &(*current)->left; 112 | else if (comp((*current)->data, data)) 113 | current = &(*current)->right; 114 | else 115 | return false; // Data already exists 116 | } 117 | *current = new Node(data); 118 | size++; 119 | return true; 120 | } 121 | 122 | template 123 | bool Bst::contains(const T& data) const 124 | { 125 | Node* current = root; 126 | 127 | while (current) 128 | { 129 | if (comp(data, current->data)) 130 | current = current->left; 131 | else if (comp(current->data, data)) 132 | current = current->right; 133 | else 134 | return true; 135 | } 136 | return false; 137 | } 138 | 139 | template 140 | typename Bst::Node** Bst::findMinNode(Node** root) 141 | { 142 | Node** current = root; 143 | 144 | while ((*current)->left) 145 | { 146 | current = &(*current)->left; 147 | } 148 | return current; 149 | } 150 | 151 | template 152 | bool Bst::remove(const T& data) 153 | { 154 | Node** current = &root; 155 | 156 | while (*current) 157 | { 158 | if (comp(data, (*current)->data)) 159 | current = &(*current)->left; 160 | else if (comp((*current)->data, data)) 161 | current = &(*current)->right; 162 | else 163 | break; 164 | } 165 | 166 | if (!(*current)) 167 | return false; // Data not found 168 | 169 | Node* toDelete = *current; 170 | 171 | if (!(*current)->left && !(*current)->right) 172 | *current = nullptr; // Node has no children 173 | else if (!(*current)->right) 174 | *current = (*current)->left; // Node has only left child 175 | else if (!(*current)->left) 176 | *current = (*current)->right; // Node has only right child 177 | else 178 | { 179 | Node** rightMin = findMinNode(&(*current)->right); 180 | 181 | *current = *rightMin; 182 | *rightMin = (*rightMin)->right; 183 | 184 | (*current)->left = toDelete->left; 185 | (*current)->right = toDelete->right; 186 | } 187 | delete toDelete; 188 | size--; 189 | return true; 190 | } 191 | 192 | template 193 | size_t Bst::getSize() const 194 | { 195 | return size; 196 | } 197 | 198 | template 199 | bool Bst::isEmpty() const 200 | { 201 | return getSize() == 0; 202 | } 203 | 204 | template 205 | typename Bst::Node* Bst::copy(Node* current) 206 | { 207 | if (!current) 208 | return nullptr; 209 | Node* res = new Node(current->data); 210 | res->left = copy(current->left); 211 | res->right = copy(current->right); 212 | return res; 213 | } 214 | 215 | template 216 | void Bst::free(Node* current) 217 | { 218 | if (!current) 219 | return; 220 | free(current->left); 221 | free(current->right); 222 | delete current; 223 | } 224 | 225 | template 226 | Bst::Bst(const Bst& other) : comp(other.comp) 227 | { 228 | root = copy(other.root); 229 | size = other.size; 230 | } 231 | 232 | template 233 | Bst& Bst::operator=(const Bst& other) 234 | { 235 | if (this != &other) 236 | { 237 | free(root); 238 | root = copy(other.root); 239 | size = other.size; 240 | comp = other.comp; 241 | } 242 | return *this; 243 | } 244 | 245 | template 246 | Bst::~Bst() 247 | { 248 | free(root); 249 | } 250 | 251 | void treeSort(std::vector& v) 252 | { 253 | Bst bst; 254 | for (int i = 0; i < v.size(); i++) 255 | bst.insert(v.at(i)); 256 | 257 | unsigned index = 0; 258 | for (auto it = bst.begin(); it != bst.end(); it++) 259 | v.at(index++) = *it; 260 | } -------------------------------------------------------------------------------- /Seminar06/src/test-bst-iter.cpp: -------------------------------------------------------------------------------- 1 | #include "forward-iterator-bst.hpp" 2 | #include 3 | 4 | int main() 5 | { 6 | Bst b; 7 | b.insert(1); 8 | b.insert(2); 9 | b.insert(3); 10 | b.insert(0); 11 | b.insert(4); 12 | b.insert(5); 13 | b.insert(6); 14 | 15 | for(int x : b) 16 | { 17 | std::cout << x << " "; 18 | } 19 | } -------------------------------------------------------------------------------- /Seminar07/README.md: -------------------------------------------------------------------------------- 1 | # Седми семинар по структури от данни - 21.11.2024 2 | 3 | ## Балансирани дървета 4 | Последния семинар дефинирахме височина на дърво и се разбрахме, че това ще бъде важно понятие за нас. Намекнахме, че е желателно да държим дърветата си ниски. Едно свойство на дърветата, което ги държи сравнително ниски е **балансираността.** 5 | 6 | Двоично дърво наричаме **балансирано** ако за всеки негов възел височините на лявото поддърво и на дясното поддърво се различават **най - много с единица**. 7 | 8 | Също така дърво ще наричаме **идеално балансирано** ако за всеки негов възел броя на възлите в лявото поддърво и броя на възлите в дясното поддърво се различават най - много с единица. 9 | 10 | В балансираните и идеално балансираните дървета сложността на операциите добавяне, търсене, изтриване стават $\mathcal{O} (\log_2 n)$. 11 | 12 | ## Ротации и запазване на балансираността 13 | Ротацията е операция върху наредени двоични дървета, която запазва свойството им да са наредени (след произволен брой ротации върху наредено двоично дърво то остава наредено). 14 | 15 | ![](media/rotations.png) 16 | 17 | Ротациите обаче променят височината на лявото и дясното поддърво на корена. След лява ротация височината на лявото поддърво нараства с поне единица а височината на лявото поддърво намалява с поне единица (защо?). 18 | 19 | Разбрахме се, че ще се стараем да си държим дърветата ниски. Тези операции ще ни бъдат полезни в тази ни задача. 20 | 21 | ## Балансирани дървета 22 | Двоично дърво, което поддържа log(n) височина при динамични операции се нарича **балансирано дърво**. 23 | 24 | Има много стратегии за балансиране на дървета което влече и съществуването на различни видове балансирани дървета. 25 | 26 | ``` 27 | Баланс фактор на възел k определяме като: bf(k) = h(k->right) - h(k->left). 28 | ``` 29 | 30 | ``` 31 | Целта ни е за всеки възел k bf(k) да е в множеството {-1, 0, 1}. 32 | ``` 33 | 34 | Трябва да поддържаме това свойство при всяко добавяне или премахване на възел. Тук на помощ идват ротациите. 35 | 36 | ## Как разбираме, че сме нарушили балансираността на възел? 37 | 38 | ## insert 39 | 40 | ## erase -------------------------------------------------------------------------------- /Seminar07/media/rotations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar07/media/rotations.png -------------------------------------------------------------------------------- /Seminar07/src/tasks.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct node 7 | { 8 | int data; 9 | node* left = nullptr; 10 | node* right = nullptr; 11 | 12 | node(int data_, node* left_ = nullptr, node* right_ = nullptr) : 13 | data(data_), 14 | left(left_), 15 | right(right_) {} 16 | }; 17 | 18 | // Task 01: Check if tree is bst 19 | bool is_bst_rec(node* root, int left_interval, int right_interval) 20 | { 21 | if(root == nullptr) 22 | return true; 23 | 24 | if(root->data < left_interval || root->data > right_interval) 25 | return false; 26 | 27 | return is_bst_rec(root->left, left_interval, root->data) && 28 | is_bst_rec(root->right, root->data, right_interval); 29 | } 30 | 31 | bool is_bst(node* root) 32 | { 33 | return is_bst_rec(root, INT_MIN, INT_MAX); 34 | } 35 | 36 | // Task 02: check if root to leaf paths form a progression 37 | // Solved in two ways. 38 | bool is_progression(const std::vector& v) 39 | { 40 | if(v.size() <= 2) 41 | return true; 42 | 43 | int offset = v[1] - v[0]; 44 | 45 | for (size_t i = 1; i < v.size() - 1; i++) 46 | { 47 | if(v[i+1] - v[i] != offset) 48 | return false; 49 | } 50 | return true; 51 | } 52 | 53 | void progression_rec(node* root, std::vector& current_path) 54 | { 55 | if(root == nullptr) 56 | { 57 | return; 58 | } 59 | 60 | current_path.push_back(root->data); 61 | 62 | if(!root->left && !root->right) 63 | { 64 | if(is_progression(current_path)) 65 | { 66 | for(int x : current_path) 67 | { 68 | std::cout << x << " "; 69 | } 70 | std::cout << std::endl; 71 | } 72 | } 73 | 74 | progression_rec(root->right, current_path); 75 | progression_rec(root->left, current_path); 76 | current_path.pop_back(); 77 | } 78 | 79 | void get_progression_dummy(node* root) 80 | { 81 | std::vector path; 82 | progression_rec(root, path); 83 | } 84 | 85 | void get_progression_offset(node* root, int offset, std::vector& current_path) 86 | { 87 | if(root == nullptr) 88 | return; 89 | 90 | current_path.push_back(root->data); 91 | 92 | if(!root->left && !root->right) 93 | { 94 | for(int x : current_path) 95 | { 96 | std::cout << x << " "; 97 | } 98 | std::cout << std::endl; 99 | } 100 | 101 | if(root->left) 102 | { 103 | int left_offset = root->left->data - root->data; 104 | 105 | if(left_offset == offset) 106 | { 107 | get_progression_offset(root->left, offset, current_path); 108 | } 109 | } 110 | 111 | if(root->right) 112 | { 113 | int eight_offset = root->right->data - root->data; 114 | 115 | if(eight_offset == offset) 116 | { 117 | get_progression_offset(root->right, offset, current_path); 118 | } 119 | } 120 | 121 | current_path.pop_back(); 122 | } 123 | 124 | void get_progression(node* root) 125 | { 126 | if(root == nullptr) 127 | return; 128 | std::vector path; 129 | 130 | path.push_back(root->data); 131 | 132 | if(root->left) 133 | { 134 | int offset = root->left->data - root->data; 135 | get_progression_offset(root->left, offset, path); 136 | } 137 | 138 | if(root->right) 139 | { 140 | int offset = root->right->data - root->data; 141 | get_progression_offset(root->right, offset, path); 142 | } 143 | } 144 | 145 | // Task 03: check if any level forms a progression 146 | // solved in two ways 147 | unsigned height(node* root) 148 | { 149 | if(root == nullptr) 150 | return 0; 151 | 152 | return 1 + std::max(height(root->left), height(root->right)); 153 | } 154 | 155 | void fill_levels(node* root, std::vector>& levels, unsigned current_level) 156 | { 157 | if(root == nullptr) 158 | return; 159 | 160 | levels[current_level].push_back(root->data); 161 | fill_levels(root->left, levels, current_level + 1); 162 | fill_levels(root->right, levels, current_level + 1); 163 | } 164 | 165 | void get_progression_dummy_levels(node* root) 166 | { 167 | if(root == nullptr) 168 | return; 169 | 170 | std::vector> levels; 171 | levels.resize(height(root)); 172 | 173 | fill_levels(root, levels, 0); 174 | for(const std::vector& level : levels) 175 | { 176 | if(is_progression(level)) 177 | { 178 | for(int x : level) 179 | { 180 | std::cout << x << ' '; 181 | } 182 | std::cout << std::endl; 183 | } 184 | } 185 | } 186 | 187 | void get_progression_levels(node* root) 188 | { 189 | if(root == nullptr) 190 | return; 191 | 192 | std::queue q; 193 | q.push(root); 194 | 195 | while(q.size()) 196 | { 197 | std::vector current_level; 198 | 199 | current_level.resize(q.size()); 200 | unsigned idx = 0; 201 | size_t elements_in_level = q.size(); 202 | 203 | for (size_t i = 0; i < elements_in_level; i++) 204 | { 205 | node* current_node = q.front(); 206 | q.pop(); 207 | current_level[idx++] = current_node->data; 208 | 209 | if(current_node->left) 210 | q.push(current_node->left); 211 | 212 | 213 | if(current_node->right) 214 | q.push(current_node->right); 215 | } 216 | 217 | if(is_progression(current_level)) 218 | { 219 | for(int x : current_level) 220 | { 221 | std::cout << x << " "; 222 | } 223 | std::cout << std::endl; 224 | } 225 | 226 | } 227 | } 228 | 229 | int main() 230 | { 231 | 232 | } -------------------------------------------------------------------------------- /Seminar08/README.md: -------------------------------------------------------------------------------- 1 | # Осми семинар по структури от данни - 28.10.2024 2 | 3 | ## Имплементация на ротации. Индексация в двоично наредено дърво за логаритмично време. Heaps и асд приоритетна опашка. -------------------------------------------------------------------------------- /Seminar08/src/BST/indexable-bst.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | template > 8 | class Bst 9 | { 10 | private: 11 | struct Node 12 | { 13 | T data; 14 | Node* left; 15 | Node* right; 16 | size_t size; 17 | Node(const T& data, Node* left = nullptr, Node* right = nullptr) 18 | : data(data) 19 | , left(left) 20 | , right(right) 21 | , size(1) 22 | {} 23 | }; 24 | 25 | Node* root = nullptr; 26 | Compare comp; // comparator instance 27 | 28 | Node** findMinNode(Node** root, std::stack& toDecrease); 29 | void free(Node* current); 30 | Node* copy(Node* current); 31 | 32 | const T& indexRec(Node* root, unsigned idx) const; 33 | public: 34 | Bst() = default; 35 | explicit Bst(const Compare& comparator) 36 | : comp(comparator) 37 | {} 38 | Bst(const Bst& other); 39 | Bst& operator=(const Bst& other); 40 | ~Bst(); 41 | 42 | bool insert(const T& data); 43 | bool contains(const T& data) const; 44 | bool remove(const T& data); 45 | 46 | size_t getSize() const; 47 | bool isEmpty() const; 48 | 49 | const T& index(unsigned idx) const; 50 | 51 | class ForwardIterator 52 | { 53 | private: 54 | std::stack nodeStack; 55 | 56 | void pushLeft(Node* node) 57 | { 58 | while (node) 59 | { 60 | nodeStack.push(node); 61 | node = node->left; 62 | } 63 | } 64 | 65 | public: 66 | ForwardIterator(Node* root = nullptr) 67 | { 68 | pushLeft(root); 69 | } 70 | 71 | T& operator*() const 72 | { 73 | return nodeStack.top()->data; 74 | } 75 | 76 | ForwardIterator& operator++() 77 | { 78 | Node* node = nodeStack.top(); 79 | nodeStack.pop(); 80 | if (node->right) 81 | { 82 | pushLeft(node->right); 83 | } 84 | return *this; 85 | } 86 | ForwardIterator operator++(int) 87 | { 88 | ForwardIterator old = *this; 89 | ++(*this); 90 | return old; 91 | } 92 | 93 | bool operator!=(const ForwardIterator& other) const 94 | { 95 | return nodeStack != other.nodeStack; 96 | } 97 | 98 | bool operator==(const ForwardIterator& other) const 99 | { 100 | return nodeStack == other.nodeStack; 101 | } 102 | }; 103 | 104 | ForwardIterator begin() const 105 | { 106 | return ForwardIterator(root); 107 | } 108 | 109 | ForwardIterator end() const 110 | { 111 | return ForwardIterator(nullptr); 112 | } 113 | }; 114 | 115 | template 116 | bool Bst::insert(const T& data) 117 | { 118 | Node** current = &root; 119 | std::stack toIncrease; 120 | while (*current) 121 | { 122 | toIncrease.push(*current); 123 | 124 | if (comp(data, (*current)->data)) 125 | current = &(*current)->left; 126 | else if (comp((*current)->data, data)) 127 | current = &(*current)->right; 128 | else 129 | return false; // Data already exists 130 | } 131 | *current = new Node(data); 132 | 133 | while (toIncrease.size()) 134 | { 135 | Node* currentNode = toIncrease.top(); 136 | currentNode->size++; 137 | toIncrease.pop(); 138 | } 139 | 140 | return true; 141 | } 142 | 143 | template 144 | bool Bst::contains(const T& data) const 145 | { 146 | Node* current = root; 147 | 148 | while (current) 149 | { 150 | if (comp(data, current->data)) 151 | current = current->left; 152 | else if (comp(current->data, data)) 153 | current = current->right; 154 | else 155 | return true; 156 | } 157 | return false; 158 | } 159 | 160 | template 161 | typename Bst::Node** Bst::findMinNode(Node** root, std::stack& toDecrease) 162 | { 163 | Node** current = root; 164 | 165 | while ((*current)->left) 166 | { 167 | toDecrease.push(*current); 168 | current = &(*current)->left; 169 | } 170 | return current; 171 | } 172 | 173 | template 174 | bool Bst::remove(const T& data) 175 | { 176 | Node** current = &root; 177 | std::stack toDecrease; 178 | 179 | while (*current) 180 | { 181 | if (comp(data, (*current)->data)) 182 | current = &(*current)->left; 183 | else if (comp((*current)->data, data)) 184 | current = &(*current)->right; 185 | else 186 | break; 187 | 188 | toDecrease.push(*current); 189 | } 190 | 191 | if (!(*current)) 192 | return false; // Data not found 193 | 194 | Node* toDelete = *current; 195 | 196 | if (!(*current)->left && !(*current)->right) 197 | *current = nullptr; // Node has no children 198 | else if (!(*current)->right) 199 | *current = (*current)->left; // Node has only left child 200 | else if (!(*current)->left) 201 | *current = (*current)->right; // Node has only right child 202 | else 203 | { 204 | Node** rightMin = findMinNode(&(*current)->right, toDecrease); 205 | 206 | *current = *rightMin; 207 | *rightMin = (*rightMin)->right; 208 | 209 | (*current)->left = toDelete->left; 210 | (*current)->right = toDelete->right; 211 | } 212 | delete toDelete; 213 | 214 | while (toDecrease.size()) 215 | { 216 | Node* currentNode = toDecrease.top(); 217 | toDecrease.pop(); 218 | --currentNode->size; 219 | } 220 | 221 | return true; 222 | } 223 | 224 | template 225 | size_t Bst::getSize() const 226 | { 227 | return root ? root->size : 0; 228 | } 229 | 230 | template 231 | bool Bst::isEmpty() const 232 | { 233 | return getSize() == 0; 234 | } 235 | 236 | template 237 | typename Bst::Node* Bst::copy(Node* current) 238 | { 239 | if (!current) 240 | return nullptr; 241 | Node* res = new Node(current->data); 242 | res->left = copy(current->left); 243 | res->right = copy(current->right); 244 | res->size = current->size; 245 | return res; 246 | } 247 | 248 | template 249 | const T& Bst::indexRec(Node* root, unsigned idx) const 250 | { 251 | size_t currentIndex = root->left ? root->left->size : 0; 252 | 253 | if(currentIndex == idx) 254 | { 255 | return root->data; 256 | } 257 | 258 | if(currentIndex > idx) 259 | { 260 | return indexRec(root->left, idx); 261 | } 262 | 263 | return indexRec(root->right, idx - currentIndex - 1); 264 | } 265 | 266 | template 267 | const T& Bst::index(unsigned idx) const 268 | { 269 | if(idx >= getSize()) 270 | throw std::runtime_error("Out of bound"); 271 | 272 | return indexRec(root, idx); 273 | } 274 | 275 | template 276 | void Bst::free(Node* current) 277 | { 278 | if (!current) 279 | return; 280 | free(current->left); 281 | free(current->right); 282 | delete current; 283 | } 284 | 285 | template 286 | Bst::Bst(const Bst& other) 287 | : comp(other.comp) 288 | { 289 | root = copy(other.root); 290 | } 291 | 292 | template 293 | Bst& Bst::operator=(const Bst& other) 294 | { 295 | if (this != &other) 296 | { 297 | free(root); 298 | root = copy(other.root); 299 | comp = other.comp; 300 | } 301 | return *this; 302 | } 303 | 304 | template 305 | Bst::~Bst() 306 | { 307 | free(root); 308 | } 309 | 310 | void treeSort(std::vector& v) 311 | { 312 | Bst bst; 313 | for (int i = 0; i < v.size(); i++) 314 | bst.insert(v.at(i)); 315 | 316 | unsigned index = 0; 317 | for (auto it = bst.begin(); it != bst.end(); it++) 318 | v.at(index++) = *it; 319 | } -------------------------------------------------------------------------------- /Seminar08/src/BST/tests.cpp: -------------------------------------------------------------------------------- 1 | #include "indexable-bst.hpp" 2 | 3 | int main() 4 | { 5 | Bst bst; 6 | 7 | for (size_t i = 0; i < 100; i++) 8 | { 9 | bst.insert(i); 10 | } 11 | 12 | for (size_t i = 0; i < bst.getSize(); i++) 13 | { 14 | std::cout << bst.index(i) << " "; 15 | } 16 | 17 | std::cout << std::endl; 18 | 19 | for (size_t i = 0; i < 50; i++) 20 | { 21 | bst.remove(i); 22 | } 23 | 24 | for (size_t i = 0; i < bst.getSize(); i++) 25 | { 26 | std::cout << bst.index(i) << " "; 27 | } 28 | } -------------------------------------------------------------------------------- /Seminar08/src/rotations.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct node 4 | { 5 | int data; 6 | node* left; 7 | node* right; 8 | 9 | node(int data_, node* left_ = nullptr, node* right_ = nullptr) : 10 | data(data_), 11 | left(left_), 12 | right(right_) {} 13 | }; 14 | 15 | node* leftRotate(node* root) 16 | { 17 | if(root == nullptr || root->right == nullptr) 18 | return nullptr; 19 | 20 | node* originalRight = root->right; 21 | root->right = originalRight->left; 22 | originalRight->left = root; 23 | 24 | return originalRight; 25 | } 26 | 27 | node* rightRotate(node* root) 28 | { 29 | if(root == nullptr || root->left == nullptr) 30 | return nullptr; 31 | 32 | node* originalLeft = root->left; 33 | root->left = originalLeft->right; 34 | originalLeft->right = root; 35 | 36 | return originalLeft; 37 | } 38 | 39 | int main() 40 | { 41 | node* example = new node(2, new node(1), new node(3)); 42 | example = leftRotate(example); 43 | example = rightRotate(example); 44 | 45 | std::cout << example->data << " " << example->left->data << " " << example->right->data; 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Seminar09/README.md: -------------------------------------------------------------------------------- 1 | # Девети семинар по структури от данни - 04.12.2024 2 | 3 | ## Имплементация на приоритетна опашка 4 | 5 | ## Heap sort 6 | 7 | # Абстрактен тип данни dictionary. Хеш таблици. Реализация на хеш таблици. 8 | *A common problem in computer science is the representation of a mapping between two sets.* 9 | 10 | ## Абстрактен тип данни множество (set) и речник (dictionary/map/associative array) 11 | 12 | За абстрактен тип данни множество поддържаме: 13 | * insert(key) 14 | * contains(key) 15 | * erase(key) 16 | 17 | В абстрактния тип данни "речник" всеки елемент е обвързан с ключ. 18 | * insert(key, value) 19 | * contains(key) / find(key) 20 | * erase(key) 21 | 22 | Една възможна имплементация на речник би била двоично наредено (балансирано) дърво. Наредбата ще се поражда от ключовете (искаме те да са сравними). Във възлите на дървото ще пазим ключ и стойност - намирайки ключа в логаритмично време имаме и стойността му. 23 | 24 | Тази имплементация влече логаритмично време по големината на колекцията за всички операции. Хеш таблиците са структура от данни, която подобрява това време, и премахва нуждата да имаме сравними ключове. 25 | 26 | ## Хеш функции 27 | Нека U е съвкупност от всички възможни ключове с които работим. Ако ключовете ни са числа U ще бъде множеството от възможните стойности на числата. Възможно е да имаме и ключове, които не са числа (примерно низове). 28 | 29 | Хеш функция наричаме функция от типа: h: U -> {0, ..., n}. Ако си представим, че пазим ключовете в масив с големина `n`, то хеш функцията ще даде индекс на всеки ключ. 30 | 31 | Съществуват много феш функции. Примерно: 32 | ```cpp 33 | int hashDummy(int key) { 34 | return 0; 35 | } 36 | ``` 37 | е валидна, но неизползваема хеш функция. 38 | 39 | Какво наричаме добра (или използваема) хеш функция? 40 | * Функцията трябва да използва всички данни от ключа. 41 | * Функцията трябва равномерно да разпределя елементите. 42 | * Функцията трябва да е бърза за изчисляване. 43 | * Желателно е за подобни ключове функцията да връща различни резултати. 44 | 45 | Добрата хеш функция се "държи добре" върху определен вход, но няма универсална хеш функция. 46 | 47 | Също така, никой не ни гарантира, че броят на възможните ключове е по - малък от `n` (т.е. е съвсем възможно |U| > n). Това означава, че е възможно да съществуват два елемента за които `x != y && h(x) == h(y)`. По - просто казано - възможно е да има два елемента, които имат еднакъв индекс. 48 | 49 | Това наричаме **колизия**. 50 | 51 | ## Използване на хеш функции в хеш таблиците. 52 | Структурата от данни хеш таблица трябва да поддържа вмъкване, търсене и премахване по подаден ключ (и стойност във вмъкването). Ако разполагаме с добра хеш функция за съвкупността от ключове, с които нашата таблица работи, можем да приложим следната стратегия: 53 | 54 | Създаване: 55 | 1. Създай масив с големина n който пази стойности. 56 | 57 | Вмъкване: 58 | 1. Намери индекса на ключа използвайки хеш функцията 59 | 2. Добави стойността на намерения индекс. 60 | 61 | Търсене: 62 | 1. Намери стойността на ключа използвайки хеш функцията 63 | 2. Промери дали има елемент на този индекс. 64 | 65 | Премахване: 66 | 1. Намери стойността на ключа използвайки хеш функцията 67 | 2. Ако има елемент на този индекс то премахни. 68 | 69 | Тази стратегия е **твърде оптимистична!** Ако хеш функцията ни няма колизии всичко ще е наред. Ако има колизии тази стратегия е обречена на провал - какво правим когато искаме да вмъкнем елемент, чийто ключ има същия хеш индекс като вече съществяващ такъв? 70 | 71 | В следващите няколко секции ще се занимаем с две неща: 72 | 1. Техники за справяне с колизии. 73 | 2. Техники за оптимална реализация на хеш таблица. 74 | 75 | ![hashmap](media/hashmap.png) 76 | 77 | ## Справяне с колизии - Separate chaining 78 | При представената неуспешна стратегия, създавайки хеш таблицата, заделяхме масив с n елемента, който пази стойности. Нека елементите на този масив не са стойности **а контейнери, които пазят такива.** Такива контейнери наричаме buckets или кофи. 79 | 80 | Сега, ако имаме колизия, просто добавяме елемента най - отзад на свързания списък. При търсенето също има промени - вече обхождаме свързания списък за да намерим дали наистина ключа съществува, или просто кофата е напълнена с ключове, съдържащи други стойности. 81 | 82 | Аналогично е при премахването - отново трябва да обходим свързания списък. 83 | 84 | ![separate-chaining](media/separate_chaining.png) 85 | 86 | За момента не сме казали нищо за сложността на която и да е операция. Тази имплементация ни води до O(n) worst case complexity. Това би се случило, примерно, ако добавим n елемента с ключове, образуващи колизия. И се опитаме да добавим, премахнем или потърсим ключ, също образуващ колизия. 87 | 88 | Такова поведение **е слабо вероятно** използвайки добра хеш функция, но ако използваме функция от типа на `hashDummy` е очаквано. 89 | 90 | ## Използване на други структури за да подобрим O(n) worst case 91 | Лошата горна граница за търсенето ни идва от факта, че използваме свързани списъци. Вече имаме дървета, които ни позволяват O(log(n)) търсене, защо вместо свързани списъци не използваме тях примерно? 92 | 93 | Това е интересна тема за разсъждение - от една страна лошото търсене не се дължи толкова на свързания списък колкото на факта, че елементите не са разпределени добре. 94 | 95 | Дървото ще забърза нещата, но така не поддържаме ли просто едно бавно дърво (или няколко такива)? 96 | 97 | Такива техники все пак се използват. [Тук](https://openjdk.org/jeps/180) можем да прочетем: 98 | 99 | ``` 100 | The principal idea is that once the number of items in a hash bucket grows beyond a certain threshold, that bucket will switch from using a linked list of entries to a balanced tree. In the case of high hash collisions, this will improve worst-case performance from O(n) to O(log n). 101 | ``` 102 | 103 | 104 | ## Недостатъци на Separate chaining 105 | Освен O(n) сложността в най - лошия случай това решение добавя ниво на забавяне към търсенето, понеже свързаните списъци нямат cache locality. Следващата стратегия се стреми да подобри този проблем. 106 | 107 | ## Open adressing 108 | В предишната стратегия елементите се държаха извън хеш таблицата (в свързани списъци). В отвореното адресиране **всички** елементи се държат в хеш таблицата. 109 | 110 | Все пак това ни връща на първоначалната стратегия. Този път няма да променяме паметта, в която държим елементите, а ще променяме начините по които ги търсим, добавяме или изтриваме. 111 | 112 | Един работещ начин за подобна промяна е следния: 113 | 114 | ## Linear probing 115 | 116 | ### Добавяне 117 | Разглеждаме елементите и техните хеш кодове: {A, 2}, {B, 3}, {C, 7}, {D, 2}. 118 | 119 | Добавянето на първите три в някакъв смисъл е лесно. Имаме следния сценарий: 120 | 121 | ![](media/insert-first.png) 122 | 123 | Какво правим обаче когато имаме колизия (както е в случая с D). Ами започваме линейно да търсим първата свободна клетка. Там добавяме D. 124 | 125 | ![](media/insert-second.png) 126 | 127 | ## Търсене 128 | Сега, да предположим, че търсим D. Генерираме неговия хеш код, но на негово място стои А. Логично е, познавайки стратегията си, да не се отказваме и да продължим да търсим. Търсим до момента в който не срещнем първата празна клетка. 129 | 130 | ## Изтриване 131 | Тук нещата стават малко по - сложни. Да кажем, че изтрием B. След това търсим D. Но сега между A и D **има празна клетка**. Това погрешно ще ни каже, че елементът не е в колекцията. 132 | 133 | **Решение:** добавяме два флага на клетката: empty и deleted. Ако клетката е била изтрита **не спираме да търсим**. Спираме при първата **празна клетка**. 134 | 135 | **Няма проблем да добавяме елементи в изтрита клетка.** 136 | 137 | ## Недостатъци на linear probing стратегията 138 | Този метод работи, но какво правим когато всички клетки се запълнят? При n + 1 - вото добавяне тази стратегия спира да работи. Момента в който запълним всички клетки можем да преоразмерим масива (resize). 139 | 140 | ## Преоразмеряване на хеш таблици 141 | При стратегията Separate chaining нямахме проблема с преоразмеряването. Обикновено обаче такова присъства. В случай на 10 кофи и 10000 елемента операциите започват да стават по - бавни. 142 | 143 | ``` 144 | LoadFactor = #elements / #buckets 145 | ``` 146 | 147 | Обикновено хеш таблиците имат два начални параметъра - първоначална големина на масива (initial capacity) и load factor. Втория ни помага да разберем кога да направим resize. Обикновено се използват стойности като 0.7 или 0.75. 148 | 149 | ## Някои хеш функции 150 | * Метод на деленето (division method): h(k) = k mod n. 151 | * Метод на умножението (multiplication method): h(k) = floor( n * (kA mod 1) ) 152 | Където А е някакво число между 0 и 1. mod 1 изважда единствено дробната част от умножението. 153 | 154 | * Готови хеширащи функции: 155 | Добрите хеширащи функции често са сложни. Съществуват готови хеширащи функции, които можем да използваме като черни кутии (MD5, SHA, ...) 156 | -------------------------------------------------------------------------------- /Seminar09/media/hashmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar09/media/hashmap.png -------------------------------------------------------------------------------- /Seminar09/media/insert-first.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar09/media/insert-first.png -------------------------------------------------------------------------------- /Seminar09/media/insert-second.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar09/media/insert-second.png -------------------------------------------------------------------------------- /Seminar09/media/separate_chaining.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar09/media/separate_chaining.png -------------------------------------------------------------------------------- /Seminar09/src/priority-queue/heapsort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void heapify(std::vector& _data, size_t index, size_t array_size) 5 | { 6 | int current_index = index; 7 | 8 | while(true) 9 | { 10 | size_t left_index = 2 * current_index + 1; 11 | size_t right_index = 2 * current_index + 2; 12 | 13 | bool go_left = left_index < array_size && _data[current_index] < _data[left_index]; 14 | bool go_right = right_index < array_size && _data[current_index] < _data[right_index]; 15 | 16 | if(!go_left && !go_right) 17 | { 18 | break; 19 | } 20 | 21 | if(go_left && go_right) 22 | { 23 | if(_data[left_index] < _data[right_index]) 24 | { 25 | std::swap(_data[current_index], _data[right_index]); 26 | current_index = right_index; 27 | } 28 | else 29 | { 30 | std::swap(_data[current_index], _data[left_index]); 31 | current_index = left_index; 32 | } 33 | } 34 | else if(go_left) 35 | { 36 | std::swap(_data[current_index], _data[left_index]); 37 | current_index = left_index; 38 | } 39 | else 40 | { 41 | std::swap(_data[current_index], _data[right_index]); 42 | current_index = right_index; 43 | } 44 | } 45 | } 46 | 47 | void build_heap(std::vector& data) 48 | { 49 | for(int i = data.size() / 2 - 1; i >= 0; i--) 50 | heapify(data, i, data.size()); 51 | } 52 | 53 | void heap_sort(std::vector& data) 54 | { 55 | build_heap(data); 56 | 57 | int current_min = data.size() - 1; 58 | 59 | for (size_t i = 0; i < data.size() - 1; i++) 60 | { 61 | std::swap(data[0], data[current_min]); 62 | heapify(data, 0, current_min--); 63 | } 64 | } 65 | 66 | int main() 67 | { 68 | std::vector v; 69 | for (size_t i = 0; i < 100; i++) 70 | { 71 | v.push_back(rand() % 100); 72 | } 73 | 74 | heap_sort(v); 75 | 76 | for(int x : v) 77 | { 78 | std::cout << x << " "; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Seminar09/src/priority-queue/priority-queue.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | class priority_queue 6 | { 7 | private: 8 | static size_t left_child(size_t index); 9 | static size_t right_child(size_t index); 10 | static int parent(int index); 11 | 12 | void heapify(size_t index); 13 | public: 14 | priority_queue() = default; 15 | 16 | // build_heap 17 | priority_queue(const std::vector& data); 18 | 19 | void push(int element); 20 | int peek() const; 21 | void pop(); 22 | 23 | bool empty() const; 24 | size_t size() const; 25 | 26 | private: 27 | std::vector _data; 28 | }; 29 | 30 | size_t priority_queue::left_child(size_t index) 31 | { 32 | return 2 * index + 1; 33 | } 34 | 35 | size_t priority_queue::right_child(size_t index) 36 | { 37 | return 2 * index + 2; 38 | } 39 | 40 | int priority_queue::parent(int index) 41 | { 42 | return (index - 1) / 2; 43 | } 44 | 45 | void priority_queue::heapify(size_t index) 46 | { 47 | size_t current_index = index; 48 | 49 | while(true) 50 | { 51 | size_t left_index = left_child(current_index); 52 | size_t right_index = right_child(current_index); 53 | 54 | bool go_left = left_index < _data.size() && _data[current_index] < _data[left_index]; 55 | bool go_right = right_index < _data.size() && _data[current_index] < _data[right_index]; 56 | 57 | if(!go_left && !go_right) 58 | { 59 | break; 60 | } 61 | 62 | if(go_left && go_right) 63 | { 64 | if(_data[left_index] > _data[right_index]) 65 | { 66 | std::swap(_data[current_index], _data[left_index]); 67 | current_index = left_index; 68 | } 69 | else 70 | { 71 | std::swap(_data[current_index], _data[right_index]); 72 | current_index = right_index; 73 | } 74 | } 75 | else if(go_left) 76 | { 77 | std::swap(_data[current_index], _data[left_index]); 78 | current_index = left_index; 79 | } 80 | else // go_right 81 | { 82 | std::swap(_data[current_index], _data[right_index]); 83 | current_index = right_index; 84 | } 85 | } 86 | } 87 | 88 | priority_queue::priority_queue(const std::vector& data) : 89 | _data(data) 90 | { 91 | // build_heap 92 | for (int i = _data.size() / 2 - 1; i >= 0; i--) 93 | heapify(i); 94 | } 95 | 96 | void priority_queue::push(int element) 97 | { 98 | _data.push_back(element); 99 | int index = _data.size() - 1; 100 | int parent_index = parent(index); 101 | 102 | while(index > 0 && _data[index] > _data[parent_index]) 103 | { 104 | std::swap(_data[index], _data[parent_index]); 105 | index = parent_index; 106 | parent_index = parent(index); 107 | } 108 | } 109 | 110 | int priority_queue::peek() const 111 | { 112 | return _data[0]; 113 | } 114 | 115 | void priority_queue::pop() 116 | { 117 | if(empty()) 118 | throw std::runtime_error("Pop on empty queue"); 119 | 120 | _data[0] = _data.back(); 121 | _data.pop_back(); 122 | heapify(0); 123 | } 124 | 125 | size_t priority_queue::size() const 126 | { 127 | return _data.size(); 128 | } 129 | 130 | bool priority_queue::empty() const 131 | { 132 | return size() == 0; 133 | } -------------------------------------------------------------------------------- /Seminar10/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar10/README.md -------------------------------------------------------------------------------- /Seminar10/src/hashmap/linear-probing.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | template > 6 | class linear_probing_hashmap 7 | { 8 | private: 9 | using pair_type = std::pair; 10 | 11 | struct node 12 | { 13 | pair_type data; 14 | bool empty = true; 15 | bool deleted = false; 16 | 17 | // TODO: This might be wasteful. 18 | node() = default; 19 | 20 | node(const Key& key, const Value& value) 21 | : data(std::make_pair(key, value)) 22 | {} 23 | }; 24 | 25 | std::vector _data; 26 | Hash _hash; 27 | double _load_factor; 28 | size_t _size = 0; 29 | size_t _jump_value = 1; 30 | 31 | size_t get_hash_index(const Key& key) const 32 | { 33 | return _hash(key) % bucket_count(); 34 | } 35 | 36 | void increment_index(size_t& index, size_t max_size) const 37 | { 38 | index += _jump_value; 39 | 40 | // TODO: empty? 41 | if (index >= max_size) 42 | index %= max_size; 43 | } 44 | 45 | size_t get_iterations_needed() const 46 | { 47 | return _data.size(); 48 | } 49 | 50 | public: 51 | linear_probing_hashmap(size_t initial_capacity, double load_factor, size_t jump_value = 1) 52 | : _data(initial_capacity) 53 | , _load_factor(load_factor) 54 | , _jump_value(jump_value) 55 | {} 56 | 57 | linear_probing_hashmap() 58 | : linear_probing_hashmap(10, 0.75) 59 | {} 60 | 61 | void insert(const Key& key, const Value& value); 62 | 63 | const Value& find(const Key& key) const; 64 | Value& modify(const Key& key); 65 | 66 | void erase(const Key& key); 67 | 68 | void resize(size_t count); 69 | 70 | size_t size() const 71 | { 72 | return _size; 73 | } 74 | 75 | bool empty() const 76 | { 77 | return _size == 0; 78 | } 79 | 80 | size_t bucket_count() const 81 | { 82 | return _data.size(); 83 | } 84 | 85 | double max_load_factor() const 86 | { 87 | return _load_factor; 88 | } 89 | }; 90 | 91 | template 92 | void linear_probing_hashmap::insert(const Key& key, const Value& value) 93 | { 94 | size_t current_index = get_hash_index(key); 95 | size_t iterations_needed = get_iterations_needed(); 96 | 97 | for (size_t i = 0; i < iterations_needed; i++) 98 | { 99 | node& current_node = _data[current_index]; 100 | 101 | // Element is contained in the collection. 102 | if (!current_node.empty && !current_node.deleted && current_node.data.first == key) 103 | { 104 | current_node.data.second = value; 105 | return; 106 | } 107 | 108 | // We don't need to check for deleted flag here. 109 | if (current_node.empty) 110 | { 111 | current_node = node(key, value); 112 | current_node.empty = false; 113 | current_node.deleted = false; 114 | 115 | ++_size; 116 | 117 | if (size() >= bucket_count() * max_load_factor()) 118 | { 119 | resize(2 * bucket_count()); 120 | } 121 | 122 | return; 123 | } 124 | increment_index(current_index, _data.size()); 125 | } 126 | throw std::runtime_error("Numbers are not co-prime!"); 127 | } 128 | 129 | template 130 | const Value& linear_probing_hashmap::find(const Key& key) const 131 | { 132 | size_t current_index = get_hash_index(key); 133 | size_t iterations_needed = get_iterations_needed(); 134 | 135 | for (size_t i = 0; i < iterations_needed; i++) 136 | { 137 | const node& current_node = _data[current_index]; 138 | 139 | if (current_node.empty && !current_node.deleted) 140 | { 141 | break; 142 | } 143 | 144 | if (!current_node.empty && !current_node.deleted && current_node.data.first == key) 145 | { 146 | return current_node.data.second; 147 | } 148 | 149 | increment_index(current_index, _data.size()); 150 | } 151 | 152 | throw std::out_of_range("[linear_probing_hashmap]: Element not found."); 153 | } 154 | 155 | template 156 | Value& linear_probing_hashmap::modify(const Key& key) 157 | { 158 | size_t current_index = get_hash_index(key); 159 | size_t iterations_needed = get_iterations_needed(); 160 | 161 | for (size_t i = 0; i < iterations_needed; i++) 162 | { 163 | node& current_node = _data[current_index]; 164 | 165 | if (current_node.empty && !current_node.deleted) 166 | { 167 | break; 168 | } 169 | 170 | if (!current_node.empty && !current_node.deleted && current_node.data.first == key) 171 | { 172 | return current_node.data.second; 173 | } 174 | 175 | increment_index(current_index, _data.size()); 176 | } 177 | 178 | throw std::out_of_range("[linear_probing_hashmap]: Element not found."); 179 | } 180 | 181 | template 182 | void linear_probing_hashmap::erase(const Key& key) 183 | { 184 | size_t current_index = get_hash_index(key); 185 | size_t iterations_needed = get_iterations_needed(); 186 | 187 | for (size_t i = 0; i < iterations_needed; i++) 188 | { 189 | node& current_node = _data[current_index]; 190 | 191 | if (current_node.empty && !current_node.deleted) 192 | { 193 | break; 194 | } 195 | 196 | if (current_node.data.first == key) 197 | { 198 | current_node.deleted = true; 199 | current_node.empty = true; 200 | _size--; 201 | break; 202 | } 203 | 204 | increment_index(current_index, _data.size()); 205 | } 206 | } 207 | 208 | template 209 | void linear_probing_hashmap::resize(size_t count) 210 | { 211 | // TODO: Ensure count is big enough. 212 | std::vector new_data(count); 213 | 214 | for (const auto& current_element : _data) 215 | { 216 | if (current_element.empty) 217 | continue; 218 | 219 | size_t current_index = _hash(current_element.data.first) % new_data.size(); 220 | size_t iterations_needed = new_data.size(); 221 | 222 | for (size_t i = 0; i < iterations_needed; i++) 223 | { 224 | node& current_node = new_data[current_index]; 225 | 226 | if (current_node.empty) 227 | { 228 | current_node = current_element; 229 | break; 230 | } 231 | 232 | increment_index(current_index, new_data.size()); 233 | } 234 | } 235 | 236 | _data = std::move(new_data); 237 | } 238 | -------------------------------------------------------------------------------- /Seminar10/src/hashmap/separate-chaining.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | template > 8 | class separate_chaining_hashmap 9 | { 10 | private: 11 | using pair_type = std::pair; 12 | using pair_iterator_type = typename std::list::iterator; 13 | using bucket_type = std::list; 14 | 15 | // Keeps all the data inserten in the hashmap; 16 | std::list _data; 17 | std::vector _hash_map; 18 | Hash _hash; 19 | double _load_factor; 20 | 21 | size_t get_hash_index(const Key& key) const 22 | { 23 | return _hash(key) % _hash_map.size(); 24 | } 25 | 26 | public: 27 | separate_chaining_hashmap() 28 | : separate_chaining_hashmap(10, 0.75) 29 | {} 30 | 31 | separate_chaining_hashmap(size_t initial_capacity, double load_factor) 32 | : _hash_map(initial_capacity) 33 | , _load_factor(load_factor) 34 | {} 35 | 36 | void insert(const Key& key, const Value& value); 37 | 38 | const Value& find(const Key& key) const; 39 | Value& modify(const Key& key); 40 | 41 | void erase(const Key& key); 42 | 43 | // Return the number of elements in the hashmap. 44 | size_t size() const 45 | { 46 | return _data.size(); 47 | } 48 | 49 | // Returns the load factor. 50 | double load_factor() const 51 | { 52 | return _load_factor; 53 | } 54 | 55 | // Return the number of buckets. 56 | size_t bucket_count() const 57 | { 58 | return _hash_map.size(); 59 | } 60 | 61 | bool empty() const 62 | { 63 | return size() == 0; 64 | } 65 | 66 | void resize(size_t count); 67 | }; 68 | 69 | template 70 | void separate_chaining_hashmap::insert(const Key& key, const Value& value) 71 | { 72 | size_t current_index = get_hash_index(key); 73 | bucket_type& current_bucket = _hash_map[current_index]; 74 | 75 | auto equal_key_iterator = std::find_if(current_bucket.begin(), current_bucket.end(), 76 | [&key](const pair_iterator_type& element) -> bool { return element->first == key; }); 77 | 78 | if(equal_key_iterator != current_bucket.end()) 79 | { 80 | (*equal_key_iterator)->second = value; 81 | return; 82 | } 83 | 84 | _data.push_back(std::make_pair(key, value)); 85 | current_bucket.push_back(--_data.end()); 86 | 87 | if(size() >= load_factor() * bucket_count()) 88 | { 89 | resize(2 * bucket_count()); 90 | } 91 | } 92 | 93 | template 94 | const Value& separate_chaining_hashmap::find(const Key& key) const 95 | { 96 | size_t current_index = get_hash_index(key); 97 | bucket_type& current_bucket = _hash_map[current_index]; 98 | 99 | auto equal_key_iterator = std::find_if(current_bucket.begin(), current_bucket.end(), 100 | [&key](const pair_iterator_type& element) -> bool { return element->first == key; }); 101 | 102 | if(equal_key_iterator != current_bucket.end()) 103 | { 104 | return (*equal_key_iterator)->second; 105 | } 106 | 107 | throw std::runtime_error("Element not found."); 108 | } 109 | 110 | template 111 | Value& separate_chaining_hashmap::modify(const Key& key) 112 | { 113 | size_t current_index = get_hash_index(key); 114 | bucket_type& current_bucket = _hash_map[current_index]; 115 | 116 | auto equal_key_iterator = std::find_if(current_bucket.begin(), current_bucket.end(), 117 | [&key](const pair_iterator_type& element) -> bool { return element->first == key; }); 118 | 119 | if(equal_key_iterator != current_bucket.end()) 120 | { 121 | return (*equal_key_iterator)->second; 122 | } 123 | 124 | throw std::runtime_error("Element not found."); 125 | } 126 | 127 | template 128 | void separate_chaining_hashmap::erase(const Key& key) 129 | { 130 | size_t current_index = get_hash_index(key); 131 | bucket_type& current_bucket = _hash_map[current_index]; 132 | 133 | auto equal_key_iterator = std::find_if(current_bucket.begin(), current_bucket.end(), 134 | [&key](const pair_iterator_type& element) -> bool { return element->first == key; }); 135 | 136 | if(equal_key_iterator != current_bucket.end()) 137 | { 138 | _data.erase(*equal_key_iterator); 139 | current_bucket.erase(equal_key_iterator); 140 | } 141 | } 142 | 143 | template 144 | void separate_chaining_hashmap::resize(size_t count) 145 | { 146 | _hash_map.clear(); 147 | _hash_map.resize(count); 148 | 149 | for(auto it = _data.begin(); it != _data.end(); ++it) 150 | { 151 | _hash_map[get_hash_index(it->first)].push_back(it); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /Seminar10/src/hashmap/tests.cpp: -------------------------------------------------------------------------------- 1 | #include "linear-probing.hpp" 2 | #include "separate-chaining.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | std::vector get_words(const std::string& file) 10 | { 11 | std::vector words; 12 | std::ifstream ifs("harry.txt"); 13 | 14 | if (!ifs.is_open()) 15 | { 16 | std::cout << "File retrieval failed" << std::endl; 17 | return {}; 18 | } 19 | 20 | while (!ifs.eof()) 21 | { 22 | std::string word; 23 | ifs >> word; 24 | words.emplace_back(word); 25 | } 26 | 27 | return words; 28 | } 29 | 30 | void test_hashmap_separate_chaining(size_t initial_capacity, double lf) 31 | { 32 | std::cout << "[Separate chaining tests]: Running test for " << initial_capacity << " initial capacity and " << lf 33 | << " load factor." << std::endl; 34 | 35 | separate_chaining_hashmap mp; 36 | std::vector words = get_words("harry.txt"); 37 | 38 | std::cout << "[Separate chaining tests]: Parsed " << words.size() << " words in std::vector." << std::endl; 39 | 40 | unsigned dict_size = 0; 41 | auto begin = std::chrono::high_resolution_clock::now(); 42 | for (const auto& word : words) 43 | { 44 | try 45 | { 46 | ++mp.modify(word); 47 | } 48 | catch (...) 49 | { 50 | ++dict_size; 51 | mp.insert(word, 1); 52 | } 53 | } 54 | 55 | auto end = std::chrono::high_resolution_clock::now(); 56 | auto duration = std::chrono::duration_cast(end - begin).count(); 57 | 58 | std::cout << "[Separate chaining tests]: Parsed " << words.size() << " words in separate_chaining_hashmap." 59 | << std::endl; 60 | std::cout << "[Separate chaining tests]: Dict size: " << dict_size << std::endl; 61 | std::cout << "[Separate chaining tests]: Buckets count: " << mp.bucket_count() << std::endl; 62 | std::cout << "[Separate chaining tests]: Size: " << mp.size() << std::endl; 63 | std::cout << "[Separate chaining tests]: Time: " << duration << " ms." << std::endl; 64 | } 65 | 66 | void test_hashmap_linear_probing(size_t jump_value, size_t initial_capacity, double lf) 67 | { 68 | std::cout << "[Linear probing tests]: Running test for " << initial_capacity << " initial capacity and " << lf 69 | << " load factor and " << jump_value << " as jump value." << std::endl; 70 | 71 | linear_probing_hashmap mp(initial_capacity, lf, jump_value); 72 | std::vector words = get_words("harry.txt"); 73 | 74 | std::cout << "[Linear probing tests]: Parsed " << words.size() << " words in std::vector." << std::endl; 75 | 76 | int dict_size = 0; 77 | auto begin = std::chrono::high_resolution_clock::now(); 78 | for (const auto& word : words) 79 | { 80 | try 81 | { 82 | ++mp.modify(word); 83 | } 84 | catch (...) 85 | { 86 | ++dict_size; 87 | mp.insert(word, 1); 88 | } 89 | } 90 | 91 | auto end = std::chrono::high_resolution_clock::now(); 92 | 93 | auto duration = std::chrono::duration_cast(end - begin).count(); 94 | 95 | std::cout << "[Linear probing tests]: Parsed " << words.size() << " words in linear_probing_hashmap." 96 | << std::endl; 97 | std::cout << "[Linear probing tests]: Dict size: " << dict_size << std::endl; 98 | std::cout << "[Linear probing tests]: Buckets count: " << mp.bucket_count() << std::endl; 99 | std::cout << "[Linear probing tests]: Size: " << mp.size() << std::endl; 100 | std::cout << "[Linear probing tests]: Time: " << duration << " ms." << std::endl; 101 | } 102 | 103 | 104 | void test_hashmap_std() 105 | { 106 | std::unordered_map mp; 107 | std::vector words = get_words("harry.txt"); 108 | 109 | std::cout << "[std::unordered_map tests]: Parsed " << words.size() << " words in std::vector." << std::endl; 110 | 111 | auto begin = std::chrono::high_resolution_clock::now(); 112 | for (const auto& word : words) 113 | { 114 | ++mp[word]; 115 | } 116 | 117 | auto end = std::chrono::high_resolution_clock::now(); 118 | 119 | auto duration = std::chrono::duration_cast(end - begin).count(); 120 | 121 | std::cout << "[std::unordered_map tests]: Parsed " << words.size() << " words in std::unordered_map." 122 | << std::endl; 123 | 124 | std::cout << "[std::unordered_map tests]: Buckets count: " << mp.bucket_count() << std::endl; 125 | std::cout << "[std::unordered_map tests]: Size: " << mp.size() << std::endl; 126 | std::cout << "[std::unordered_map tests]: Time: " << duration << " ms." << std::endl; 127 | } 128 | 129 | void test_map_std() 130 | { 131 | std::map mp; 132 | std::vector words = get_words("harry.txt"); 133 | 134 | std::cout << "[std::map tests]: Parsed " << words.size() << " words in std::vector." << std::endl; 135 | 136 | auto begin = std::chrono::high_resolution_clock::now(); 137 | for (const auto& word : words) 138 | { 139 | ++mp[word]; 140 | } 141 | 142 | auto end = std::chrono::high_resolution_clock::now(); 143 | 144 | auto duration = std::chrono::duration_cast(end - begin).count(); 145 | 146 | std::cout << "[std::map tests]: Parsed " << words.size() << " words in std::map." 147 | << std::endl; 148 | 149 | std::cout << "[std::map tests]: Size: " << mp.size() << std::endl; 150 | std::cout << "[std::map tests]: Time: " << duration << " ms." << std::endl; 151 | } 152 | 153 | int main() 154 | { 155 | test_hashmap_separate_chaining(1000, 0.7); 156 | 157 | std::cout << std::endl; 158 | 159 | test_hashmap_linear_probing(1, 997, 0.7); 160 | 161 | std::cout << std::endl; 162 | 163 | test_hashmap_std(); 164 | 165 | std::cout << std::endl; 166 | 167 | test_map_std(); 168 | } -------------------------------------------------------------------------------- /Seminar10/src/lru-cache.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | class LRUCache 6 | { 7 | size_t capacity; 8 | std::list> data; 9 | std::unordered_map>::iterator> dataMap; 10 | 11 | public: 12 | LRUCache(size_t capacity) : capacity(capacity) { } 13 | 14 | int get(int key) 15 | { 16 | if (dataMap.find(key) == dataMap.end()) 17 | { 18 | throw std::runtime_error("Data not in cache"); 19 | } 20 | 21 | auto it = dataMap[key]; 22 | 23 | // Transfers elements from one list to another. 24 | // No elements are copied or moved, only the internal pointers of the list nodes are re-pointed. 25 | data.splice(data.begin(), data, it); 26 | return it->second; 27 | } 28 | 29 | void put(int key, int value) 30 | { 31 | if (dataMap.find(key) != dataMap.end()) 32 | { 33 | auto it = dataMap[key]; 34 | data.erase(it); 35 | } 36 | 37 | data.push_front({ key, value }); 38 | dataMap[key] = data.begin(); 39 | if (data.size() > capacity) 40 | { 41 | dataMap.erase(data.back().first); 42 | data.pop_back(); 43 | } 44 | } 45 | }; -------------------------------------------------------------------------------- /Seminar11/find-cycle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using graph = std::vector>; 6 | 7 | enum class color : char 8 | { 9 | white, 10 | gray, 11 | black 12 | }; 13 | 14 | bool has_cycle_cc(const graph& g, int vertex, std::vector& colors) 15 | { 16 | const auto& adjacent = g[vertex]; 17 | colors[vertex] = color::gray; 18 | 19 | for(int child : adjacent) 20 | { 21 | if(colors[child] == color::gray) 22 | return true; 23 | 24 | if(has_cycle_cc(g, child, colors)) 25 | return true; 26 | } 27 | 28 | colors[vertex] = color::black; 29 | return false; 30 | } 31 | 32 | bool has_cycle(const graph& g) 33 | { 34 | std::vector colors(g.size(), color::white); 35 | 36 | for(int vertex = 0; vertex < g.size(); vertex++) 37 | { 38 | if(colors[vertex] == color::white && has_cycle_cc(g, vertex, colors)) 39 | return true; 40 | } 41 | return false; 42 | } 43 | 44 | int main() 45 | { 46 | graph g = { 47 | /* 0 */ {1, 2}, 48 | /* 1 */ {3, 2}, 49 | /* 2 */ {}, 50 | /* 3 */ {2, 0} 51 | }; // cycle: 0 -> 1 -> 3 -> 0 52 | 53 | std::cout << has_cycle(g); 54 | } -------------------------------------------------------------------------------- /Seminar11/media/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar11/media/1.png -------------------------------------------------------------------------------- /Seminar11/media/dfs-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar11/media/dfs-example.gif -------------------------------------------------------------------------------- /Seminar11/media/dir-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar11/media/dir-graph.png -------------------------------------------------------------------------------- /Seminar11/media/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar11/media/graph.png -------------------------------------------------------------------------------- /Seminar11/media/incidence-matrix-impl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar11/media/incidence-matrix-impl.png -------------------------------------------------------------------------------- /Seminar11/media/incidence-matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar11/media/incidence-matrix.png -------------------------------------------------------------------------------- /Seminar11/media/matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar11/media/matrix.png -------------------------------------------------------------------------------- /Seminar11/media/weighted-dirscted-graph (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar11/media/weighted-dirscted-graph (1).png -------------------------------------------------------------------------------- /Seminar11/media/weighted-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar11/media/weighted-graph.png -------------------------------------------------------------------------------- /Seminar11/media/weighted-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stoychoX/Data-structures-and-algorithms/c2b5cde65de3a74f1c5665a710911a4c1800bc0a/Seminar11/media/weighted-list.png -------------------------------------------------------------------------------- /Seminar12/src/connected-components/count-cc.cpp: -------------------------------------------------------------------------------- 1 | #include "../graph_impl/graph/graph.h" 2 | #include 3 | 4 | void dfs_visit(const graph& g, graph::vertex_t from, std::vector& visited) 5 | { 6 | visited[from] = true; 7 | 8 | const auto& adjacent = g.adjacent(from); 9 | 10 | for(const auto& vertex : adjacent) 11 | { 12 | if(!visited[vertex]) 13 | { 14 | dfs_visit(g, vertex, visited); 15 | } 16 | } 17 | } 18 | 19 | size_t count_cc(const graph& g) 20 | { 21 | std::vector visited(g.vertex_count(), false); 22 | size_t cnt = 0; 23 | 24 | for (int i = 0; i < g.vertex_count(); i++) 25 | { 26 | if(!visited[i]) 27 | { 28 | ++cnt; 29 | dfs_visit(g, i, visited); 30 | } 31 | } 32 | 33 | return cnt; 34 | } 35 | 36 | int main() 37 | { 38 | graph g(6, false); 39 | 40 | g.add_edge(0, 1); 41 | g.add_edge(0, 2); 42 | g.add_edge(2, 3); 43 | g.add_edge(1, 4); 44 | g.add_edge(3, 5); 45 | g.add_edge(4, 5); 46 | g.add_edge(2, 5); 47 | 48 | g.add_vertex(); 49 | 50 | std::cout << count_cc(g); 51 | } -------------------------------------------------------------------------------- /Seminar12/src/graph_impl/graph/graph.cpp: -------------------------------------------------------------------------------- 1 | #include "graph.h" 2 | #include 3 | 4 | graph::graph(size_t vertex_count, bool is_oriented) : 5 | _adjacent(vertex_count), 6 | _oriented(is_oriented) {} 7 | 8 | void graph::add_edge(vertex_t from, vertex_t to) 9 | { 10 | if(from >= _adjacent.size() || to >= _adjacent.size()) 11 | throw std::runtime_error("Invalid vertex passed"); 12 | 13 | _adjacent[from].push_back(to); 14 | if(!_oriented) 15 | _adjacent[to].push_back(from); 16 | } 17 | 18 | void graph::add_vertex() 19 | { 20 | _adjacent.emplace_back(); 21 | } 22 | 23 | size_t graph::vertex_count() const 24 | { 25 | return _adjacent.size(); 26 | } 27 | 28 | bool graph::is_oriented() const 29 | { 30 | return _oriented; 31 | } 32 | 33 | const std::vector& graph::adjacent(vertex_t vertex) const 34 | { 35 | if(vertex >= _adjacent.size()) 36 | throw std::runtime_error("Invalid vertex passed"); 37 | 38 | return _adjacent[vertex]; 39 | } 40 | 41 | bool graph::are_adjacent(vertex_t v1, vertex_t v2) const 42 | { 43 | if(v1 >= _adjacent.size() || v2 >= _adjacent.size()) 44 | throw std::runtime_error("Invalid vertex passed"); 45 | 46 | for (const auto& v : _adjacent[v1]) 47 | { 48 | if(v == v2) 49 | return true; 50 | } 51 | 52 | return false; 53 | } -------------------------------------------------------------------------------- /Seminar12/src/graph_impl/graph/graph.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class graph 5 | { 6 | public: 7 | using vertex_t = int; 8 | private: 9 | std::vector> _adjacent; 10 | bool _oriented; 11 | 12 | public: 13 | graph(size_t vertex_count, bool is_oriented); 14 | 15 | void add_edge(vertex_t from, vertex_t to); 16 | void add_vertex(); 17 | 18 | size_t vertex_count() const; 19 | bool is_oriented() const; 20 | 21 | const std::vector& adjacent(vertex_t vertex) const; 22 | bool are_adjacent(vertex_t v1, vertex_t v2) const; 23 | }; -------------------------------------------------------------------------------- /Seminar12/src/graph_impl/weighted_graph/weighted_graph.cpp: -------------------------------------------------------------------------------- 1 | #include "weighted_graph.h" 2 | #include 3 | 4 | weighted_graph::weighted_graph(size_t vertex_count, bool is_oriented) : 5 | _adjacent(vertex_count), 6 | _oriented(is_oriented) {} 7 | 8 | void weighted_graph::add_edge(vertex_index_t from, vertex_index_t to, weight_t weight) 9 | { 10 | if(from >= _adjacent.size() || to >= _adjacent.size()) 11 | throw std::runtime_error("Invalid vertex passed"); 12 | 13 | _adjacent[from].push_back(std::make_pair(to, weight)); 14 | if(!_oriented) 15 | _adjacent[to].push_back(std::make_pair(from, weight)); 16 | } 17 | 18 | void weighted_graph::add_vertex() 19 | { 20 | _adjacent.emplace_back(); 21 | } 22 | 23 | size_t weighted_graph::vertex_count() const 24 | { 25 | return _adjacent.size(); 26 | } 27 | 28 | bool weighted_graph::is_oriented() const 29 | { 30 | return _oriented; 31 | } 32 | 33 | const std::vector& weighted_graph::adjacent(vertex_index_t vertex) const 34 | { 35 | if(vertex >= _adjacent.size()) 36 | throw std::runtime_error("Invalid vertex passed"); 37 | 38 | return _adjacent[vertex]; 39 | } 40 | 41 | bool weighted_graph::are_adjacent(vertex_index_t v1, vertex_index_t v2) const 42 | { 43 | if(v1 >= _adjacent.size() || v2 >= _adjacent.size()) 44 | throw std::runtime_error("Invalid vertex passed"); 45 | 46 | for (const auto& v : _adjacent[v1]) 47 | { 48 | if(v.first == v2) 49 | return true; 50 | } 51 | 52 | return false; 53 | } 54 | 55 | weighted_graph::weight_t weighted_graph::get_weight(vertex_index_t from, vertex_index_t to) const 56 | { 57 | if(from >= _adjacent.size() || to >= _adjacent.size()) 58 | throw std::runtime_error("Invalid vertex passed"); 59 | 60 | 61 | for(const auto& vertex : _adjacent[from]) 62 | { 63 | if(vertex.first == to) 64 | { 65 | return vertex.second; 66 | } 67 | } 68 | 69 | // TODO: This is ugly, maybe use another invaid edge error handling machanism? 70 | throw std::runtime_error("No such edge found!"); 71 | } 72 | -------------------------------------------------------------------------------- /Seminar12/src/graph_impl/weighted_graph/weighted_graph.h: -------------------------------------------------------------------------------- 1 | /* 2 | A simplistic representation of weighted graph represented by adjacency list. 3 | */ 4 | 5 | #pragma once 6 | #include 7 | 8 | class weighted_graph 9 | { 10 | public: 11 | using weight_t = int; // Type used to represent weight. 12 | using vertex_index_t = int; // Type used to represent how we index a vertex. 13 | using vertex_t = std::pair; // Type used to represent a vertex. 14 | 15 | private: 16 | std::vector> _adjacent; 17 | bool _oriented; 18 | 19 | public: 20 | weighted_graph(size_t vertex_count, bool is_oriented); 21 | 22 | void add_edge(vertex_index_t from, vertex_index_t to, weight_t weight); 23 | void add_vertex(); 24 | 25 | size_t vertex_count() const; 26 | bool is_oriented() const; 27 | 28 | const std::vector& adjacent(vertex_index_t vertex) const; 29 | bool are_adjacent(vertex_index_t v1, vertex_index_t v2) const; 30 | 31 | weight_t get_weight(vertex_index_t from, vertex_index_t to) const; 32 | }; -------------------------------------------------------------------------------- /Seminar12/src/shortest_paths/shp_no_wight.cpp: -------------------------------------------------------------------------------- 1 | #include "../graph_impl/graph/graph.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using vertex_t = graph::vertex_t; 8 | 9 | std::vector shortest_path(const graph& g, vertex_t from, vertex_t to) 10 | { 11 | std::queue q; 12 | q.push(from); 13 | 14 | std::unordered_map parent; 15 | bool found_path = false; 16 | 17 | while(!q.empty() && !found_path) 18 | { 19 | vertex_t current = q.front(); 20 | q.pop(); 21 | 22 | if(current == to) 23 | { 24 | found_path = true; 25 | continue; 26 | } 27 | 28 | const auto& adjacent = g.adjacent(current); 29 | 30 | for(const auto& vertex : adjacent) 31 | { 32 | if(parent.find(vertex) == parent.end()) 33 | { 34 | // This means the vertex is yet to be visited... 35 | q.push(vertex); 36 | parent[vertex] = current; 37 | } 38 | } 39 | } 40 | 41 | if(!found_path) 42 | return {}; 43 | 44 | std::vector path; 45 | vertex_t it = to; 46 | 47 | while (true) 48 | { 49 | auto child_it = parent.find(it); 50 | 51 | if(child_it == parent.end()) 52 | break; 53 | 54 | path.push_back(it); 55 | it = child_it->second; 56 | } 57 | 58 | path.push_back(from); 59 | std::reverse(path.begin(), path.end()); 60 | 61 | return path; 62 | } 63 | 64 | int main() 65 | { 66 | graph g(6, false); 67 | 68 | g.add_edge(0, 1); 69 | g.add_edge(0, 2); 70 | g.add_edge(1, 2); 71 | g.add_edge(1, 3); 72 | g.add_edge(2, 4); 73 | g.add_edge(3, 4); 74 | g.add_edge(4, 5); 75 | 76 | auto path = shortest_path(g, 0, 5); 77 | 78 | for(auto vertex : path) 79 | { 80 | std::cout << vertex << " "; 81 | } 82 | } -------------------------------------------------------------------------------- /Seminar12/src/shortest_paths/shp_weighted.cpp: -------------------------------------------------------------------------------- 1 | #include "../graph_impl/weighted_graph/weighted_graph.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using vertex_t = weighted_graph::vertex_t; 9 | using vertex_index_t = weighted_graph::vertex_index_t; 10 | using weight_t = weighted_graph::weight_t; 11 | 12 | // A structure representind information which we 13 | // obtained using dijkstra's algorithm 14 | struct shp_info 15 | { 16 | std::vector path; 17 | size_t cost = 0; 18 | }; 19 | 20 | bool relax(std::vector& distance, 21 | const weighted_graph& g, 22 | vertex_index_t reached, 23 | vertex_index_t current_vertex) 24 | { 25 | if (distance[reached] > distance[current_vertex] + g.get_weight(current_vertex, reached)) 26 | { 27 | distance[reached] = distance[current_vertex] + g.get_weight(current_vertex, reached); 28 | return true; 29 | } 30 | return false; 31 | } 32 | 33 | shp_info dijkstra_shortest_path(const weighted_graph& g, vertex_index_t from, vertex_index_t to) 34 | { 35 | std::vector distances(g.vertex_count(), INT_MAX); 36 | std::vector parent(g.vertex_count(), -1); 37 | 38 | struct vertex_comparator 39 | { 40 | bool operator()(const vertex_t& lhs, const vertex_t& rhs) const 41 | { 42 | return lhs.second > rhs.second; 43 | } 44 | }; 45 | 46 | std::priority_queue, vertex_comparator> q; 47 | 48 | q.push({from, 0}); 49 | distances[from] = 0; 50 | bool found_path = false; 51 | 52 | while(!q.empty() && !found_path) 53 | { 54 | auto current_vertex = q.top(); 55 | q.pop(); 56 | 57 | if (current_vertex.first == to) 58 | { 59 | found_path = true; 60 | continue; 61 | } 62 | 63 | const auto& adjacent = g.adjacent(current_vertex.first); 64 | 65 | for(const auto& vertex : adjacent) 66 | { 67 | if(relax(distances, g, vertex.first, current_vertex.first)) 68 | { 69 | q.push({vertex.first, distances[vertex.first]}); 70 | parent[vertex.first] = current_vertex.first; 71 | } 72 | } 73 | } 74 | 75 | shp_info result; 76 | 77 | if(!found_path) 78 | return result; 79 | 80 | vertex_index_t it = to; 81 | while (true) 82 | { 83 | vertex_index_t it_parent = parent[it]; 84 | result.path.push_back(it); 85 | it = it_parent; 86 | 87 | if(it_parent == -1) 88 | { 89 | break; 90 | } 91 | } 92 | 93 | result.cost = distances[to]; 94 | std::reverse(result.path.begin(), result.path.end()); 95 | 96 | return result; 97 | } 98 | 99 | int main() 100 | { 101 | weighted_graph g(6, false); 102 | g.add_edge(0, 1, 2); 103 | g.add_edge(0, 2, 1); 104 | g.add_edge(2, 3, 6); 105 | g.add_edge(1, 4, 3); 106 | g.add_edge(3, 5, 1); 107 | g.add_edge(4, 5, 2); 108 | g.add_edge(2, 5, 4); 109 | 110 | auto result = dijkstra_shortest_path(g, 0, 5); 111 | 112 | if (result.path.empty()) 113 | { 114 | std::cout << "No path found from 0 to 5.\n"; 115 | } 116 | else 117 | { 118 | std::cout << "Shortest path cost: " << result.cost << "\n"; 119 | std::cout << "Path: "; 120 | for (int vertex : result.path) 121 | { 122 | std::cout << vertex << " "; 123 | } 124 | std::cout << "\n"; 125 | } 126 | 127 | return 0; 128 | } -------------------------------------------------------------------------------- /Seminar12/src/topological_sort/toposort.cpp: -------------------------------------------------------------------------------- 1 | #include "../graph_impl/graph/graph.h" 2 | #include 3 | 4 | using vertex_t = graph::vertex_t; 5 | 6 | void tsort_visit(const graph& g, 7 | vertex_t vertex, 8 | std::vector& visited, 9 | std::vector& result) 10 | { 11 | visited[vertex] = true; 12 | 13 | const auto& adjacent = g.adjacent(vertex); 14 | 15 | for (const auto& v : adjacent) 16 | { 17 | if(!visited[v]) 18 | { 19 | tsort_visit(g, v, visited, result); 20 | } 21 | } 22 | 23 | result.push_back(vertex); 24 | } 25 | 26 | std::vector topological_sort(const graph& g) 27 | { 28 | std::vector visited(g.vertex_count(), false); 29 | std::vector result(g.vertex_count()); 30 | 31 | for (int i = 0; i < g.vertex_count(); i++) 32 | { 33 | if(!visited[i]) 34 | tsort_visit(g, i, visited, result); 35 | } 36 | 37 | std::reverse(result.begin(), result.end()); 38 | 39 | return result; 40 | } 41 | 42 | void tsort_visit_dummy(const graph& g, 43 | vertex_t vertex, 44 | std::vector& finalized, 45 | std::vector& visited, 46 | int& time) 47 | { 48 | visited[vertex] = true; 49 | 50 | const auto& adjacent = g.adjacent(vertex); 51 | 52 | for (const auto& v : adjacent) 53 | { 54 | if(!visited[v]) 55 | { 56 | tsort_visit_dummy(g, v, finalized, visited, time); 57 | } 58 | } 59 | 60 | finalized[vertex] = time++; 61 | } 62 | 63 | std::vector topological_sort_dummy(const graph& g) 64 | { 65 | std::vector finalized(g.vertex_count(), -1); 66 | std::vector visited(g.vertex_count(), false); 67 | int time = 0; 68 | 69 | // Подобно обхождане е лошо, но върши работа за примера. 70 | // Трябва да се имплементира итератор 71 | // за вектора в противен случай разчитаме върховете да са числа... 72 | for (int i = 0; i < g.vertex_count(); i++) 73 | { 74 | if(!visited[i]) 75 | tsort_visit_dummy(g, i, finalized, visited, time); 76 | } 77 | 78 | std::vector result(g.vertex_count()); 79 | 80 | for (size_t i = 0; i < g.vertex_count(); i++) 81 | { 82 | result[i] = i; 83 | } 84 | 85 | std::sort(result.begin(), 86 | result.end(), 87 | [&finalized](const vertex_t& lhs, const vertex_t& rhs) 88 | { 89 | return finalized[lhs] < finalized[rhs]; 90 | }); 91 | 92 | return result; 93 | } 94 | 95 | int main() 96 | { 97 | 98 | } -------------------------------------------------------------------------------- /Seminar13/src/mst-kruskal.cpp: -------------------------------------------------------------------------------- 1 | #include "../../Seminar12/src/graph_impl/weighted_graph/weighted_graph.h" 2 | #include "../../Seminar12/src/graph_impl/graph/graph.h" 3 | #include 4 | #include 5 | 6 | using vertex_t = weighted_graph::vertex_t; 7 | using vertex_index_t = weighted_graph::vertex_index_t; 8 | using weight_t = weighted_graph::weight_t; 9 | using union_find_t = std::vector; 10 | 11 | vertex_index_t find(union_find_t& uf, vertex_index_t v) 12 | { 13 | if(uf[v] != v) 14 | uf[v] = find(uf, uf[v]); 15 | 16 | return uf[v]; 17 | } 18 | 19 | void my_union(union_find_t& uf, vertex_index_t parent, vertex_index_t child) 20 | { 21 | uf[find(uf, child)] = uf[find(uf, parent)]; 22 | } 23 | 24 | struct mst_info 25 | { 26 | weighted_graph tree; 27 | int cost = 0; 28 | 29 | mst_info(const weighted_graph& g) : 30 | tree(g.vertex_count(), g.is_oriented()) {} 31 | }; 32 | 33 | mst_info kruskal(const weighted_graph& g) 34 | { 35 | mst_info result(g); 36 | 37 | union_find_t uf(g.vertex_count()); 38 | 39 | for (size_t i = 0; i < g.vertex_count(); i++) 40 | uf[i] = i; 41 | 42 | // weight, from, to 43 | std::vector> edges; 44 | 45 | for (size_t i = 0; i < g.vertex_count(); i++) 46 | { 47 | const auto& adjacent = g.adjacent(i); 48 | 49 | for(const auto& vertex : adjacent) 50 | { 51 | if(i > vertex.first) 52 | edges.emplace_back(vertex.second, i, vertex.first); 53 | } 54 | } 55 | 56 | std::sort(edges.begin(), edges.end()); 57 | 58 | size_t added_edges = 0; 59 | 60 | for(const auto& edge : edges) 61 | { 62 | weight_t current_weight = std::get<0>(edge); 63 | vertex_index_t current_from = std::get<1>(edge); 64 | vertex_index_t current_to = std::get<2>(edge); 65 | 66 | if(find(uf, current_from) != find(uf, current_to)) 67 | { 68 | my_union(uf, current_from, current_to); 69 | result.cost += current_weight; 70 | result.tree.add_edge(current_from, current_to, current_weight); 71 | ++added_edges; 72 | } 73 | 74 | if(added_edges == g.vertex_count() - 1) 75 | break; 76 | } 77 | 78 | return result; 79 | } 80 | 81 | int main() 82 | { 83 | weighted_graph g(7, false); 84 | 85 | g.add_edge(0, 1, 4); 86 | g.add_edge(0, 2, 3); 87 | g.add_edge(1, 2, 1); 88 | g.add_edge(1, 3, 2); 89 | g.add_edge(2, 4, 5); 90 | g.add_edge(3, 4, 6); 91 | g.add_edge(3, 5, 7); 92 | g.add_edge(4, 6, 8); 93 | g.add_edge(5, 6, 4); 94 | 95 | auto result = kruskal(g); 96 | 97 | std::cout << "Total cost of MST: " << result.cost << '\n'; 98 | 99 | std::cout << "Tree structure (graph representation): " << std::endl; 100 | 101 | for(size_t current = 0; current < result.tree.vertex_count(); current++) 102 | { 103 | const auto& adj = result.tree.adjacent(current); 104 | for(const auto& vertex : adj) 105 | { 106 | if(current < vertex.first) 107 | std::cout << "Tree edge from " << current << " to " << vertex.first << 108 | " with weight " << vertex.second << std::endl; 109 | } 110 | } 111 | 112 | std::cout << '\n'; 113 | 114 | return 0; 115 | } -------------------------------------------------------------------------------- /Seminar13/src/mst-prim.cpp: -------------------------------------------------------------------------------- 1 | #include "../../Seminar12/src/graph_impl/weighted_graph/weighted_graph.h" 2 | #include 3 | #include 4 | #include 5 | 6 | using vertex_t = weighted_graph::vertex_t; 7 | using vertex_index_t = weighted_graph::vertex_index_t; 8 | using weight_t = weighted_graph::weight_t; 9 | 10 | struct mst_info 11 | { 12 | std::vector tree; 13 | int cost = 0; 14 | }; 15 | 16 | mst_info prim(const weighted_graph& g, vertex_index_t start = 0) 17 | { 18 | mst_info result; 19 | 20 | if(g.vertex_count() == 0) 21 | return result; 22 | 23 | std::vector visited(g.vertex_count(), false); 24 | result.tree.resize(g.vertex_count(), -1); 25 | // min_cost[i] = minimum cost found! 26 | std::vector min_cost(g.vertex_count(), INT_MAX); 27 | 28 | struct vertex_comparator 29 | { 30 | bool operator()(const vertex_t& lhs, const vertex_t& rhs) const 31 | { 32 | return lhs.second > rhs.second; 33 | } 34 | }; 35 | 36 | std::priority_queue, vertex_comparator> p; 37 | 38 | // initialize structures: 39 | p.push({start, 0}); 40 | min_cost[start] = 0; 41 | 42 | size_t found_edges = 0; 43 | 44 | while(found_edges != g.vertex_count()) 45 | { 46 | vertex_index_t current_vertex = p.top().first; 47 | weight_t current_weight = p.top().second; 48 | 49 | p.pop(); 50 | 51 | if(visited[current_vertex]) 52 | continue; 53 | 54 | visited[current_vertex] = true; 55 | result.cost += current_weight; 56 | ++found_edges; 57 | 58 | const auto& adjacent = g.adjacent(current_vertex); 59 | 60 | for(const vertex_t& vertex : adjacent) 61 | { 62 | vertex_index_t found_index = vertex.first; 63 | weight_t found_weight = vertex.second; 64 | 65 | if(!visited[found_index] && found_weight < min_cost[found_index]) 66 | { 67 | result.tree[found_index] = current_vertex; 68 | p.push({found_index, found_weight}); 69 | min_cost[found_index] = found_weight; 70 | } 71 | } 72 | } 73 | 74 | return result; 75 | } 76 | 77 | int main() 78 | { 79 | weighted_graph g(7, false); 80 | 81 | g.add_edge(0, 1, 4); 82 | g.add_edge(0, 2, 3); 83 | g.add_edge(1, 2, 1); 84 | g.add_edge(1, 3, 2); 85 | g.add_edge(2, 4, 5); 86 | g.add_edge(3, 4, 6); 87 | g.add_edge(3, 5, 7); 88 | g.add_edge(4, 6, 8); 89 | g.add_edge(5, 6, 4); 90 | 91 | 92 | auto result = prim(g); 93 | 94 | std::cout << "Total cost of MST: " << result.cost << '\n'; 95 | 96 | std::cout << "Tree structure (parent array): "; 97 | for (const auto x : result.tree) 98 | { 99 | std::cout << x << " "; 100 | } 101 | 102 | std::cout << '\n'; 103 | 104 | return 0; 105 | } --------------------------------------------------------------------------------