Website Summary Files Commits Branches @commit @diff @stats
diff options
context:
space:
mode:
Commit46a31d1c2bfeca7429df19a5e989a52b665b0bb3 (patch)
Author
 Stoycho Kyosev <stoycho.kk@gmail.com>
2023-01-04 18:01:53 +0200
Committer
 Stoycho Kyosev <stoycho.kk@gmail.com>
2023-01-04 18:01:53 +0200
Files84c78178afbc28d74edaf0918cfb3c3fb273e23a
Data-structures-and-algorithms-46a31d1c2bfeca7429df19a5e989a52b665b0bb3.tar
Data-structures-and-algorithms-46a31d1c2bfeca7429df19a5e989a52b665b0bb3.tar.gz
Data-structures-and-algorithms-46a31d1c2bfeca7429df19a5e989a52b665b0bb3.zip
Parent63f4cb943f81c257e78ebeef828edbc04e78264b (diff)
Parent76904781d28d13e321c48827680bdb0d7c039bd9 (diff)
pls no more merge conficts
9 files changed, 40 insertions, 33 deletions
-rw-r--r--Seminar01/README.md12
-rw-r--r--Seminar02/README.md18
-rw-r--r--Seminar03/README.md2
-rw-r--r--Seminar04/README.md8
-rw-r--r--Seminar05/README.md6
-rw-r--r--Seminar06/README.md4
-rw-r--r--Seminar08/README.md8
-rw-r--r--Seminar09/README.md12
-rw-r--r--Seminar11/README.md3
diff --git a/Seminar01/README.md b/Seminar01/README.md
index 79424b2..47f9f83 100644
--- a/Seminar01/README.md
+++ b/Seminar01/README.md
@@ -42,7 +42,7 @@ void redirect(int*& ptr, int& dest) {
}
```
то вече функцията върши желаната работа. Когато приемаме обект по референция **копие не се прави**. Това важи за всички типове - включително и за указателите.
-Можем да правим указатели към всякакви типове включително и към указатели. Типът **int**** не е нищо повече от указател към указател.
+Можем да правим указатели към всякакви типове включително и към указатели. Типът **int\*\*** не е нищо повече от указател към указател.
Тогава не можем ли просто да вземем указател към указателя и да го променим по този начин?
```c++
@@ -63,12 +63,12 @@ int main() {
```
Този код също върши работа. Тогава каква е разликата между указател и референция?
-Разликата е, че указателите са по - гъвкави. Не можем да пренасочим референция. Също така не можем да имаме референция която не сочи към обект. При указателите имаме специален тип наречен nullptr, който сочи към "нищото". Често референцията е представяна като указател, който не може да се пренасочва, вика * вместо теб и винаги сочи към обект.
+Разликата е, че указателите са по - гъвкави. Не можем да пренасочим референция. Също така не можем да имаме референция която не сочи към обект. При указателите имаме специален тип наречен nullptr, който сочи към "нищото". Често референцията е представяна като указател, който не може да се пренасочва, вика \* вместо теб и винаги сочи към обект.
## const и указателите
За *const* важи следното правило - *const* винаги модифицира това което е в негово ляво, освен когато няма нищо отляво, тогава модифицира това което е отдясно.
-* const int* -> Указател, който сочи към константен *int*. Можем да пренасочваме, но не можем да променяме стойността.
-* int const* -> Абсолютно същото.
-* int * const -> Константен указател, не можем да го пренасочваме.
-* const int* const -> Константен указател към константен *int*. Нито можем да пренасочваме нито да променяме стойността. \ No newline at end of file
+* const int\* -> Указател, който сочи към константен *int*. Можем да пренасочваме, но не можем да променяме стойността.
+* int const\* -> Абсолютно същото.
+* int \* const -> Константен указател, не можем да го пренасочваме.
+* const int\* const -> Константен указател към константен *int*. Нито можем да пренасочваме нито да променяме стойността.
diff --git a/Seminar02/README.md b/Seminar02/README.md
index 97d7a86..d1779b2 100644
--- a/Seminar02/README.md
+++ b/Seminar02/README.md
@@ -1,10 +1,10 @@
# Задачи за стекове 12.10.22
## Задача първа
-Имаме низ състоящ се от английски букви и символа *. Всеки път когато видим * трябва да премахнем символът отляво както и самата звезда.
+Имаме низ състоящ се от английски букви и символа \*. Всеки път когато видим \* трябва да премахнем символът отляво както и самата звезда.
Пример:
-* aa* -> "a"
-* erase***** -> ""
+* aa\* -> "a"
+* erase\*\*\*\*\* -> ""
* thi\*s i\*s exampl\*e -> "ths s exampe"
Задачата да се реализира за линейно време с линейна памет.
@@ -69,15 +69,15 @@ void monotonicStackExample(const std::vector<int>& v) {
Защо това е вярно?
Нека
-$$
-arr = [a_1 \dots a_k \dots a_m \dots a_n]
-$$
-и нека в цикъла i = m. Твърдя, че ако a_k е първото по - голямо число от a_m то ще бъде задължително на върха на стека след приключване на while цикъла.
+$$arr = [a_1 \dots a_k \dots a_m \dots a_n]$$
-Ако има други числа на върха на стека, то по предположение те са по - малки от a_m и ще бъдат премахнати.
+и нека в цикъла i = m. Твърдя, че ако $a_k$ е първото по - голямо число от $a_m$ то ще бъде задължително на върха на стека след приключване на while цикъла.
-Ами ако a_k не е в стека? Ако то не е в стека то някое по - голямо от него число, намиращо се между a_k и a_m, го е премахнало. Тоест то не е първото по - голямо число намиращо се вляво.
+
+Ако има други числа на върха на стека, то по предположение те са по - малки от $a_m$ и ще бъдат премахнати.
+
+Ами ако $a_k$ не е в стека? Ако то не е в стека то някое по - голямо от него число, намиращо се между $a_k$ и $a_m$, го е премахнало. Тоест то не е първото по - голямо число намиращо се вляво.
## Бонус задачи
1. Да се сортира стек като използвате само един допълнителен стек.
diff --git a/Seminar03/README.md b/Seminar03/README.md
index 3dad403..dc36329 100644
--- a/Seminar03/README.md
+++ b/Seminar03/README.md
@@ -138,7 +138,7 @@ int main() {
}
```
Тук компилаторът няма да ни се скара, понеже maxSize задължително трябва да е константа.
-Едно хубаво свойство на нетиповите параметри е, че staticArray<T, 50> и staticArray<T, 100> са два различни типа.
+Едно хубаво свойство на нетиповите параметри е, че `staticArray<T, 50>` и `staticArray<T, 100>` са два различни типа.
# Задачи
diff --git a/Seminar04/README.md b/Seminar04/README.md
index 18eae0d..eb35f37 100644
--- a/Seminar04/README.md
+++ b/Seminar04/README.md
@@ -64,7 +64,7 @@ class DynArray {
size_t capacity = 8;
};
```
-Нека в началото *capacity* да е 8. Кога ще имаме алокация? Първо, ще алокираме памет при създаването на масива. След това, когато се опитаме да добавим девети елемент ще алокираме 16 * sizeof(T) байта. След това когато се опитаме да добавим седемнадесети елемент ще алокираме 32 * sizeof(T) байта.
+Нека в началото *capacity* да е 8. Кога ще имаме алокация? Първо, ще алокираме памет при създаването на масива. След това, когато се опитаме да добавим девети елемент ще алокираме 16 \* sizeof(T) байта. След това когато се опитаме да добавим седемнадесети елемент ще алокираме 32 \* sizeof(T) байта.
Тоест, когато въртя for цикъла във втория пример, всеки път когато *i = 2^k + 1* ще имам алокация на памет.
Това за наше щастие има как да се оправи. Когато знаем колко елемента ще пазим в динамичния масив можем да извикаме *resize(size_t)*.
@@ -99,7 +99,7 @@ std::vector<int> filter(const std::vector<int>& arr, bool (*pred)(int)) {
```
## Сложността на push_back
-До момента разгледахме няколко структури от данни и се стремяхме главно към O(1) сложност. Виждоме, че push_back() е доста използвана операция, каква е нейната сложност? Ами тя доста често е константна, но когато ни се наложи да направим алокация е линейна. Тоест, в най - лошия случай тя е O(n). Но това не е достатъчно добра оценка.
+До момента разгледахме няколко структури от данни и се стремяхме главно към O(1) сложност. Виждаме, че push_back() е доста използвана операция, каква е нейната сложност? Ами тя доста често е константна, но когато ни се наложи да направим алокация е линейна. Тоест, в най - лошия случай тя е O(n). Но това не е достатъчно добра оценка.
В случая worst case сложността е твърде песимистична. Интересува ни по - скоро колко време ще отнеме цялото изпълнение на алгоритъма използващ push_back() а не колко време ще отнеме на 17 стъпка, примерно. Приемаме по - оптимистичен подход - нека не се интересуваме от това колко е най - лошия случай на операцията а да разгледаме колко време ни отнема да направим всички операции.
Това неформално е целта на така наречения амортизиран анализ на сложност. С него няма да се занимаваме особено много, но е добре да го знаем от обща култура.
@@ -187,9 +187,11 @@ LL = 5 -> 4 -> 3 -> 2 -> 1
Да се напише функция, която открива средата на свързан списък.
Пример:
+```
1 -> 2 -> 3 -> 4 -> 5 => 3
1 -> 2 -> 3 -> 4 => 3
+```
## Задача четвърта
да се напише функция която пренарежда свързания списък по следния начин:
@@ -204,4 +206,4 @@ LL = 1 -> 5 -> 2 -> 4 -> 3
* Имате празен списък
* Имате списък с един елемент
* Имате списък и четни елементи
-* Имате списък с нечетни елементи \ No newline at end of file
+* Имате списък с нечетни елементи
diff --git a/Seminar05/README.md b/Seminar05/README.md
index 865e784..1a493aa 100644
--- a/Seminar05/README.md
+++ b/Seminar05/README.md
@@ -88,7 +88,7 @@ Iterator operator++(int) {
```
Напомняме, че разликата между префиксния и постфиксния operator++ е просто един "служебен" int като аргумент на постфиксния.
-Супер, вече можем да се движим, но това движение става безполезно ако нямаме начин да видим каква информация имаме в currentNode. Тази задача по конвенция се изпълнява от operator*. Супер, нека го предефинираме:
+Супер, вече можем да се движим, но това движение става безполезно ако нямаме начин да видим каква информация имаме в currentNode. Тази задача по конвенция се изпълнява от operator\*. Супер, нека го предефинираме:
```cpp
T& operator*() {
return currentNode->data;
@@ -288,7 +288,7 @@ int main() {
## Задача първа:
-Даден е списък l с n елемента.
+Даден е списък `l` с `n` елемента.
Да се дефинира функция shuffle(), която получава **адреса на първия елемент на списъка**.
@@ -339,4 +339,4 @@ LinkedListNode<int>* partition(LinkedListNode<int>* head, int x);
**HINT:**
-Можете да си направите два указателя, чрез които да строите някакви полезни помощни списъци. После, след като построите тези полезни помощни списъци, можете да пренасочите правилните указатели и да получите коректен отговор. \ No newline at end of file
+Можете да си направите два указателя, чрез които да строите някакви полезни помощни списъци. После, след като построите тези полезни помощни списъци, можете да пренасочите правилните указатели и да получите коректен отговор.
diff --git a/Seminar06/README.md b/Seminar06/README.md
index bfd184f..e333dc2 100644
--- a/Seminar06/README.md
+++ b/Seminar06/README.md
@@ -31,7 +31,7 @@ void insertSorted(std::list<T>& ll, const T& elem);
която приема сортиран свързан списък и добавя *elem* на правилното място.
## Задача втора
-Нека имаме двусвързан списък от опашки и символи. Във всяка опашка от налачото към края е записан валиден израз в обратна полска нотация. Операторите са [+, -, *, /] а операндите за улеснение са **едноцифрени числа**.
+Нека имаме двусвързан списък от опашки и символи. Във всяка опашка от началото към края е записан валиден израз в обратна полска нотация. Операторите са [+, -, \*, /] а операндите за улеснение са **едноцифрени числа**.
Да се напише функция, която приема такъв списък, изчислява стойността на всички опашки, и връща дали списъка от резултатите е симетричен спрямо средата.
@@ -70,4 +70,4 @@ void insertSorted(std::list<T>& ll, const T& elem);
Вход:
``` 1 -> 2 -> 3 ```
-Изход: false \ No newline at end of file
+Изход: false
diff --git a/Seminar08/README.md b/Seminar08/README.md
index 2243f09..a61b2bd 100644
--- a/Seminar08/README.md
+++ b/Seminar08/README.md
@@ -39,7 +39,7 @@
Има ли долна граница за височината на дърво? Ако нищо не знаем за дървото, то височината му най - малко може да е 1. Това се случва когато всички възли, различни от корена, са негови наследници (т.е. когато нямаме вътрешни върхове).
Доста често обаче, когато работим с дървета, знаем тяхната разклоненост.
-Нека имаме дърво с *n* върха и разклоненост *k*. Тогава то не може да стане по - ниско от floor(log<sub>k</sub>(n)).
+Нека имаме дърво с *n* върха и разклоненост *k*. Тогава то не може да стане по - ниско от $\lfloor \log_k n \rfloor$.
Опитайте се да начертаете дърво с разклоненост 2 и 8 върха. Възможно ли е височината му да е по - малка от 3?
@@ -86,7 +86,7 @@ struct Tree {
}
```
-От тук всеки указател от типа *Tree\<T>** е валидно дърво. Ако то е празно стойността му би била nullptr. Ако е непразно, данните записани във възела му достъпваме чрез data полето, а лявото и дясното поддърво достъпваме съответно чрез left и right.
+От тук всеки указател от типа *Tree\<T>\** е валидно дърво. Ако то е празно стойността му би била nullptr. Ако е непразно, данните записани във възела му достъпваме чрез data полето, а лявото и дясното поддърво достъпваме съответно чрез left и right.
Дървото от пример 1, представено по този начин, би изглеждало така:
@@ -181,10 +181,10 @@ struct Tree {
Да се реализира функция, която добавя елемент в двоично наредено дърво.
## Задача втора
-Да се напише функция *contains(Tree\<T>* tree, const T& elem)* която връща дали елемента се съдържа в двоично дърво. Да се реализира същата функция за двоично наредено дърво.
+Да се напише функция *contains(Tree\<T>\* tree, const T& elem)* която връща дали елемента се съдържа в двоично дърво. Да се реализира същата функция за двоично наредено дърво.
## Задача трета
-Да се напише функция *max((Tree\<T>* tree)* която връща най - големия елемент в двоично дърво. Да се реализира същата функция за двоично наредено дърво. Да се реализира функция намираща най - малкия елемент.
+Да се напише функция *max((Tree\<T>\* tree)* която връща най - големия елемент в двоично дърво. Да се реализира същата функция за двоично наредено дърво. Да се реализира функция намираща най - малкия елемент.
## Задача четвърта
Да се реализира функция, която връща височината на двоично дърво.
diff --git a/Seminar09/README.md b/Seminar09/README.md
index 92a4fdb..617232d 100644
--- a/Seminar09/README.md
+++ b/Seminar09/README.md
@@ -20,7 +20,7 @@ T maxElem(const Tree<T>* t) {
Това решение работи почти винаги. Какво става ако t e nullptr?
Ако знаехме, че дървото е от тип int, лесно можехме да връщаме INT_MAX, но в случая не знаем какъв тип е скрит зад Т.
-Един от начините да се справим с този проблем е да връщаме T* вместо Т. Така, ако дървото е nullptr ние също ще връщаме nullptr. Това е валиден начин, и може би стандартния начин по който бихме подходили в С или във версиите на С++ преди С++17.
+Един от начините да се справим с този проблем е да връщаме T\* вместо Т. Така, ако дървото е nullptr ние също ще връщаме nullptr. Това е валиден начин, и може би стандартния начин по който бихме подходили в С или във версиите на С++ преди С++17.
## std::optional\<T>
std::optional\<T> е клас който в някакъв смисъл "обвива" елемент от тип Т. Както името подсказва той или има стойност.. или няма.
@@ -71,7 +71,7 @@ int main() {
Също така дърво ще наричаме **идеално балансирано** ако за всеки негов възел броя на възлите в лявото поддърво и броя на възлите в дясното поддърво се различават най - много с единица.
-В балансираните и идеално балансираните дървета сложността на операциите добавяне, търсене, изтриване стават O(log<sub>2</sub>(n)).
+В балансираните и идеално балансираните дървета сложността на операциите добавяне, търсене, изтриване стават $\mathcal{O} (\log_2 n)$.
С балансирани дървета ще се занимаваме много подробно следващия път. Засега само дефинираме неща, които ще ни бъдат от голяма полза при реализацията на едно такова дърво.
@@ -105,11 +105,15 @@ int main() {
Да се реализират ляво и дясна ротация за произволно дърво.
## Задача шеста (За домашно)
-Нека имаме двоично наредено дърво T от цели числа.
+Нека имаме двоично наредено дърво T от цели числа.
+
+```
pred(x) = max{y | y е ключ на възел в Т и y < x} // Предшественик
succ(x) = min{y | y е ключ на възел в T и y > x} // Наследник
+```
+
Да се реализира pred(t, x) и succ(t, x).
Бележка: Не е нужно х да се съдържа в дървото.
## Задача седма (Контролно)
-Да се напише функция, която намира сумата на всички елементи на двоично наредено дърво от цели числа, които имат нечетен брой наследници в интервала [x, y] (т.е. сумата на броя елементи в лявото поддърво и броя елементи в дясното поддърво в интервала [x, y] е нечетно число). Нека x и y са параметри на функцията. \ No newline at end of file
+Да се напише функция, която намира сумата на всички елементи на двоично наредено дърво от цели числа, които имат нечетен брой наследници в интервала [x, y] (т.е. сумата на броя елементи в лявото поддърво и броя елементи в дясното поддърво в интервала [x, y] е нечетно число). Нека x и y са параметри на функцията.
diff --git a/Seminar11/README.md b/Seminar11/README.md
index 7f28913..8ef5c54 100644
--- a/Seminar11/README.md
+++ b/Seminar11/README.md
@@ -30,7 +30,7 @@
### Тегловен граф
-Често е полезно да "закачим" някакви тегла на ребрата. Примерно, ако върходете на графа представляват някакви забележителности, а ребрата са пътища между тях, теглата могат да представляват времето в минути, което отнема да стигнем от v1 до v2.
+Често е полезно да "закачим" някакви тегла на ребрата. Примерно, ако върховете на графа представляват някакви забележителности, а ребрата са пътища между тях, теглата могат да представляват времето в минути, което отнема да стигнем от v1 до v2.
Примерно, това е **рисунка** на тегловен граф:
@@ -143,6 +143,7 @@ matrix[7][5] = false
Предимството на тази реализация е, че бързо можем да добавяме върхове и ребра. Също така паметта ни вече не е O(|V|^2) а O(|V| + |E|).
Част от материалите са взаимствани от [тук](https://github.com/MariaGrozdeva/Data_structures_and_algorithms_FMI/tree/main/Sem_10)
+<<<<<<< HEAD
## BFS
Breadth-first search е сравнително прост алгоритъм за търсене в граф, но върху идеята, която той носи се базират други алгоритми върху графи. Примерно, алгоритъмът на Прим за минимално покриващо дърво, алгоритъмът на Dijkstra за най - къси пътища се базират на идеи подобни на тази на BFS.