├── README.md └── tasks ├── Sorts.md ├── Hash.md ├── Intro.md ├── Graphs.md ├── Trees.md ├── DirGraph.md ├── Lists.md └── Dynamics.md /README.md: -------------------------------------------------------------------------------- 1 | # Домашние задания к курсу "Алгоритмы и структуры данных" 2 | 3 | 4 | ### 1. [Введение в алгоритмы и структуры данных](tasks/Intro.md) 5 | 6 | ### 2. [Динамическое программирование и жадность](tasks/Dynamics.md) 7 | 8 | ### 3. [Сортировки](tasks/Sorts.md) 9 | 10 | ### 4. [Списки](tasks/Lists.md) 11 | 12 | ### 5. [Пирамиды и деревья поиска](tasks/Trees.md) 13 | 14 | ### 6. [Хеширование](tasks/Hash.md) 15 | 16 | ### 7. [Графы](tasks/Graphs.md) 17 | 18 | ### 8. [Ориентированные графы](tasks/DirGraph.md) 19 | 20 | ------------ 21 | -------------------------------------------------------------------------------- /tasks/Sorts.md: -------------------------------------------------------------------------------- 1 | # Домашнее задание к занятию 3. Сортировки 2 | ### Инструкция по выполнению домашнего задания: 3 | **1.** Зарегистрируйтесь на сайте **[repl.it](https://repl.it/)**;
4 | **2.** Перейдите в раздел **my repls**;
5 | **3.** Нажмите кнопку **Start coding now**! - если вы приступаете впервые, или **New Repl** - если у вас уже есть работы;
6 | **4.** В списке языков выберите тот **язык, который изучаете**;
7 | **5.** Код пишите в левой части окна - **в редакторе кода**;
8 | **6.** Чтобы посмотреть результат выполнения файла, нажмите на кнопку **Run**. Результат появится в правой части окна;
9 | **7.** После окончания работы скопируйте **ссылку на ваш repl** в адресной строке браузера;
10 | **8.** Скопированную ссылку (ваше решение ДЗ) нужно отправить на проверку. Для этого перейдите в личный кабинет на сайте **[netology.ru](https://netology.ru/)**, в поле комментария к домашней работе вставьте скопированную ссылку и отправьте работу на проверку; 11 | 12 | ------------ 13 | 14 | ## Задача о национальной команде 15 | 16 | В стране есть n региональных команд, каждая состоит из 10 игроков, у каждого игрока есть рейтинг. Вам надо написать программу, которая из всех региональных команд соберёт команду национальную - топ-10 игроков по рейтингу. Каждая команда задаётся в виде массива с рейтингами игроков в порядке убывания. Ваша программа должна вывести такой же массив для национальной команды. Целевая асимптотика: O(n) времени, константная память. 17 | 18 | ### Решение 19 | Давайте воспользуемся алгоритмом слияния. Если мы сольём два массива команд, то получим массив команды из 20 человек, упорядоченный по убыванию. Нам нужно только 10 человек, так что модифицируем алгоритм слияния таким образом, что будем прерывать его как только в итоговом массиве набралось 10 человек (это всегда будут топ-10 человек из двух массивов). 20 | 21 | Возьмём первую региональную команду и сольём её так со второй. Так мы получим топ-10 человек из обеих команд. Давайте полученную команду сольём с третьей - так мы получим топ-10 человек из трёх команд. Проделаем так со всеми региональными командами, в итоге получив топ-10 человек из всех региональных команд. 22 | 23 | ``` 24 | national_team(regional_teams): 25 | team = regional_teams[0] 26 | for i от 1 до длина(regional_teams) 27 | team = merge(team, regional_teams[i]) 28 | return team 29 | ``` 30 | 31 | Операцию `merge` реализуйте сами, модифицировав алгоритм с лекции так, чтобы слияние прекращалось после первых 10 элементов. 32 | 33 | Оценим асимптотику: каждый раз мы сливаем команды длинною 10, т.е. время работы слияния двух команд не зависит от количества регионов, а стало быть это константная для n операция как для времени, так и для памяти. Таких операций мы сделаем n-1 раз, пройдясь по всем регионам. Итого: асимптотика O(n). 34 | 35 | ### Реализация 36 | 1. Создайте массив региональных команд, в котором будут храниться команды: `[45, 31, 24, 22, 20, 17, 14, 13, 12, 10]`, `[31, 18, 15, 12, 10, 8, 6, 4, 2, 1]`, `[51, 30, 10, 9, 8, 7, 6, 5, 2, 1]`. 37 | 2. Напишите метод слияния команд для выбора топ-10 из обеих команд. 38 | 3. Напишите метод для выбора национальной команды из массива региональных команд. 39 | 4. Запустите метод выбора национальной команды на примере и выведите на экран, убедитесь, что она совпадает с: `[51, 45, 31, 31, 30, 24, 22, 20, 18, 17]`. 40 | 5. Загрузите ваше решение на сайт **[repl.it](https://repl.it/)**, отправьте ссылку на него на проверку. 41 | -------------------------------------------------------------------------------- /tasks/Hash.md: -------------------------------------------------------------------------------- 1 | # Домашнее задание к занятию 6. Хеширование 2 | ### Инструкция по выполнению домашнего задания: 3 | **1.** Зарегистрируйтесь на сайте **[repl.it](https://repl.it/)**;
4 | **2.** Перейдите в раздел **my repls**;
5 | **3.** Нажмите кнопку **Start coding now**! - если вы приступаете впервые, или **New Repl** - если у вас уже есть работы;
6 | **4.** В списке языков выберите тот **язык, который изучаете**;
7 | **5.** Код пишите в левой части окна - **в редакторе кода**;
8 | **6.** Чтобы посмотреть результат выполнения файла, нажмите на кнопку **Run**. Результат появится в правой части окна;
9 | **7.** После окончания работы скопируйте **ссылку на ваш repl** в адресной строке браузера;
10 | **8.** Скопированную ссылку (ваше решение ДЗ) нужно отправить на проверку. Для этого перейдите в личный кабинет на сайте **[netology.ru](https://netology.ru/)**, в поле комментария к домашней работе вставьте скопированную ссылку и отправьте работу на проверку; 11 | 12 | ------------ 13 | 14 | ## Рабин-Карп с шаблонами 15 | 16 | Давайте применим упрощённый алгоритм Рабина-Карпа для поиска подстроки в строке, но теперь в шаблоне будет *один* спец-символ `*`, который будет означать, что подойдёт любая буква. 17 | 18 | Давайте рассмотрим на примере. Строка, в которой будем искать: `Alibaba or Alibubab? I do not know!`. В качестве шаблона возьмём `b*b`. Есть три подстроки, который подойдёт под этот шаблон: `bab` (начинается на позиции 3), `bub` (начинается на позиции 14) и второй `bab` (начинается на позиции 16). 19 | 20 | Давайте для этого модифицируем алгоритм Рабина-Карпа. Мы всё ещё будем использовать упрощённую версию, где в качестве хеша считаем просто сумму кодов символов. В чём же будет состоять модификация? Вместо того чтобы считать хеш на всём шаблоне и сравнивать его с хешами на всех символов очередного кандидата на найденную подстроку, будем считать хеш на всём шаблоне без учёта символа `*`, а из хеша кандидата вычитать код символа, который стоит на позиции, соответствующей позиции `*` в шаблоне. Также при равенстве хешей мы будем проверять на ревенство все символы, пропуская позицию с `*`, тк в неё подойдёт любой символ текста. 21 | 22 | Заметим, что тогда если есть подстрока, подходящая под шаблон, то хеш шаблона и хеш кандидата будут совпадать. Также, ничего нам не мешает всё также динамично поддерживать хеш кандидата, подсчитывая его из хеша предыдущего кандидата за O(1). Итоговая асимптотика будет такая же, как и у обычного Рабина-Карпа. 23 | 24 | ``` 25 | def search(source, pattern): 26 | if source короче pattern: 27 | return Такой подстроки точно нет! 28 | found = [] 29 | pattern_hash = сумма кодов символов в pattern без учёта * 30 | asterik_position = позиция '*' в pattern 31 | for start от 0 до длина(source) - длина(pattern) + 1 32 | if start == 0: 33 | window_hash = сумма кодов первых длина(pattern) символов source 34 | window_hash -= код символа в source на позиции asterik_position 35 | else: 36 | window_hash -= код символа в source на позиции start-1 37 | window_hash += код символа в source на позиции start+длина(pattern) - 1 38 | window_hash += код символа в source на позиции где была '*' у прошлого кандидата 39 | window_hash -= код символа в source на позиции где стоит '*' у текущего кандидата 40 | if window_hash == pattern_hash: 41 | for i от 0 до длина(pattern): 42 | if pattern[i] != '*' И source[start + i] != pattern[i]: 43 | не подходит 44 | если подошёл, то добавим start в found 45 | return found 46 | ``` 47 | 48 | ### Реализация 49 | 1. Напишите функцию поиска шаблона в строке по схеме, данной выше 50 | 2. Проверьте её на примере выше, убедитесь что ответы совпали 51 | 3. Загрузите ваше решение на сайт **[repl.it](https://repl.it/)**, отправьте ссылку на него на проверку. 52 | -------------------------------------------------------------------------------- /tasks/Intro.md: -------------------------------------------------------------------------------- 1 | # Домашнее задание к занятию 1. Введение в алгоритмы и структуры данных 2 | Привет! Это ваше первое домашнее задание на курсе "Алгоритмы и структуры данных". После каждой лекции у вас будут 1-3 задачи для самостоятельного решения. Присылать решения можно на одном из трех языков программирования в зависимости от того, какой вы изучаете: Python, Java, JavaScript. 3 | 4 | Вопросы по теме занятия и домашнему заданию задавайте в чате MicrosoftTeams. Ссылку с приглашением вы найдете на почте. 5 | 6 | Удачи! 7 | 8 | ### Инструкция по выполнению домашнего задания: 9 | **1.** Зарегистрируйтесь на сайте **repl.it**;
10 | **2.** Перейдите в раздел **my repls**;
11 | **3.** Нажмите кнопку **Start coding now**! - если вы приступаете впервые, или **New Repl** - если у вас уже есть работы;
12 | **4.** В списке языков выберите тот **язык, который изучаете**;
13 | **5.** Код пишите в левой части окна - **в редакторе кода**;
14 | **6.** Чтобы посмотреть результат выполнения файла, нажмите на кнопку **Run**. Результат появится в правой части окна;
15 | **7.** После окончания работы скопируйте **ссылку на ваш repl** в адресной строке браузера;
16 | **8.** Скопированную ссылку (ваше решение ДЗ) нужно отправить на проверку. Для этого перейдите в личный кабинет на сайте **netology.ru**, в поле комментария к домашней работе вставьте скопированную ссылку и отправьте работу на проверку; 17 | 18 | ------------ 19 | 20 | ## Задача 1. Оценка алгосложности программы 21 | Ниже представлен псевдокод. Ваша задача понять и описать что он делает, определить его асимптотику (время и дополнительная память) и привести аргументы, почему она именно такая. Результатом выполнения задания должен быть текстовый ответ, написание кода не требуется. 22 | 23 | ``` 24 | calc(arr): 25 | ans = 0 26 | for i от 1 до длина(arr) 27 | ans += arr[i] - arr[i-1] 28 | return ans 29 | ``` 30 | 31 | ## Задача 2. Книжная полка 32 | У вас есть книжная полка, у каждой книги есть размер - количество страниц. Книжная полка представлена массивом, в котором хранятся размеры книг в порядке возрастания. Вам надо написать функцию, которая принимала бы этот массив размеров текущих книг, размер новой книги и вычисляла бы количество больших по размеру книг уже на полке. Требуемая алгоритмическая сложность: время O(log2n), дополнительная память O(1). 33 | 34 | Подумайте, как вы решили бы эту задачу, как достигли бы требуемой асимптотики. 35 | 36 | ### Решение 37 | Реализуйте алгоритм бинарного поиска. С его помощью вы найдёте место в массиве, где слева от него будут элементы меньше или равны, а справа строго больше. Работает бинарный поиск как раз за время O(log2n), дополнительную память O(1). 38 | 39 | Обратите внимание на случай когда у нас на полке есть несколько книг с тем же размером, что и у новой книги. Именно поэтому мы не останавливаем бинарный поиск когда найдём какой-то из таких размеров в массиве, ведь для ответа нам важно чтобы справа от найденной позиции были книги только строго большие по размеру. Продолжать поиск нужно именно бинарным поиском, нельзя просто взять и пройтись вправо по равным элементам до тех пор пока не встретим больший, ведь тогда асимптотика упадёт с O(log2n) до O(n). 40 | 41 | ### Процесс реализации 42 | 1. В начале работы программы заведите массив со значениями: `[14, 16, 19, 32, 32, 32, 56, 69, 72]`. 43 | 2. Напишите функцию, которая бы принимала массив, размер новой книги и возвращала бы количество больших по размеру книг по рассмотренному выше алгоритму. **Внимание!** Вызывать готовые реализации бинарного поиска запрещено. 44 | 3. Вызовите эту функцию, передав туда массив и размер новой книги - `32`. Выведите результат на экран. Убедитесь, что ответ верный: `3` (т.к. только три книги на полке строго больше чем 32 страницы). 45 | 4. Вызовите эту функцию, передав туда массив и размер новой книги - `60`. Выведите результат на экран. Убедитесь, что ответ верный: `2` (т.к. только две книги на полке строго больше чем 60 страницы). 46 | 5. Загрузите ваше решение на сайт **repl.it**, отправьте ссылку на него на проверку. 47 | -------------------------------------------------------------------------------- /tasks/Graphs.md: -------------------------------------------------------------------------------- 1 | # Домашнее задание к занятию 7. Графы 2 | ### Инструкция по выполнению домашнего задания: 3 | **1.** Зарегистрируйтесь на сайте **[repl.it](https://repl.it/)**;
4 | **2.** Перейдите в раздел **my repls**;
5 | **3.** Нажмите кнопку **Start coding now**! - если вы приступаете впервые, или **New Repl** - если у вас уже есть работы;
6 | **4.** В списке языков выберите тот **язык, который изучаете**;
7 | **5.** Код пишите в левой части окна - **в редакторе кода**;
8 | **6.** Чтобы посмотреть результат выполнения файла, нажмите на кнопку **Run**. Результат появится в правой части окна;
9 | **7.** После окончания работы скопируйте **ссылку на ваш repl** в адресной строке браузера;
10 | **8.** Скопированную ссылку (ваше решение ДЗ) нужно отправить на проверку. Для этого перейдите в личный кабинет на сайте **[netology.ru](https://netology.ru/)**, в поле комментария к домашней работе вставьте скопированную ссылку и отправьте работу на проверку; 11 | 12 | ------------ 13 | 14 | ## Задача о городах 15 | 16 | Пусть у нас есть группа островов, которые пронумерованы от 0 до V, между некоторыми городами построены мосты. Легко представить такую схему в виде графа, где вершинами являются острова, а рёбрами - мосты. 17 | 18 | Будем считать, что из одного острова можно добраться до другого на автомобиле, если можно проложить такой путь по островам, перемещаясь с одного на другой если между ними есть мост. 19 | 20 | Вам требуется для заданного графа островов для каждого острова посчитать до скольки островов можно доехать с него на машине. 21 | 22 | Например, если у нас есть граф из шести островов и мосты построены между 0м и 1м, 1м и 2м, 3м и 4м островами, то из 0го острова можно добраться до двух островов (до 1го и до 2го через 1й), из 3го можно добраться только до одного острова (4го), а из пятого вообще мостов нет. Ответ вашей программы в таком случае должен быть: `Сколько достижимо городов для каждого города: [2, 2, 2, 1, 1, 0]`. 23 | 24 | Давайте представим граф следующим образом: заведём массив на V элементов, в каждой ячейке будет лежать множество из вершин, соседствующих той, у которой номер совпадает с номером ячейки (в целях упрощения написания кода, в качестве множества можете брать и массивы). 25 | 26 | ``` 27 | Graph: 28 | vertices_info - изначально массив из V пустых множеств / списков 29 | 30 | add_edge(a, b): 31 | vertices_info[a].add(b) 32 | vertices_info[b].add(a) 33 | 34 | adjacent(v): 35 | return vertices_info[v] 36 | 37 | vertices(): 38 | return номера от 0 до V 39 | 40 | size(): 41 | return V 42 | ``` 43 | 44 | Создадим для нашего примера граф: 45 | ``` 46 | graph = Graph(V=6) 47 | 48 | graph.add_edge(0, 1) 49 | graph.add_edge(1, 2) 50 | 51 | graph.add_edge(3, 4) 52 | ``` 53 | 54 | Заметим, что для каждого острова в качестве ответа подойдёт размер компоненты связности, в которой он находится, уменьшенный на 1 (для каждого острова мы не учитываем сам этот остров как тот до которого можно добраться). Поэтому сгодится модифицированный обход в глубину для нахождения компонент связности. 55 | 56 | Теперь обход будет не только помечать компоненты номерами компонент связности (начнём нумерацию с 1, если же стоит у вершины 0, значит мы её ещё не размечали), но и возвращать количество вершин, которое он обошёл. Таким образом, мы сможем получить размеры компонент связности и запомнить их для каждой компоненты в массиве. После обхода нам будет достаточно прогуляться по массиву с информацией о том какая вершина принадлежит какой компоненте связности и заполнить массив для ответа задачи (где для каждой вершины хранится количество вершин, которые из неё достижимы). 57 | 58 | ``` 59 | dfs(graph, v, mark, marks): 60 | marks[v] = mark 61 | size = 1 62 | for vv in graph.adjacent(v): 63 | if в marks[vv] лежит 0: 64 | size += dfs(graph, vv, mark, marks) 65 | return size 66 | 67 | calc_paths(graph): 68 | marks = [V нулей] 69 | mark_sizes = [0] 70 | for v от 0 до V 71 | if в marks[v] лежит 0 72 | next_mark = длина(mark_sizes) 73 | size = dfs(graph, v, next_mark, marks) 74 | добавить size в конец mark_sizes 75 | answer = [V нулей] 76 | for i от 0 до V 77 | answer[i] = mark_sizes[marks[v]] - 1 78 | напечатать "Сколько достижимо городов для каждого города: " answer 79 | ``` 80 | 81 | ### Реализация 82 | 1. Реализуйте структуру данных граф для хранения информации об островах и мостах между ними. 83 | 2. Реализуйте модифицированный алгоритм поиска компонент связности для решения задачи. 84 | 3. Создайте граф, заполнив его информацией из примера про 6 островов. 85 | 4. Запустите решение вашей задачи на этом графе, убедитесь что она выдаёт правильный ответ (дан выше). 86 | 5. Загрузите ваше решение на сайт **[repl.it](https://repl.it/)**, отправьте ссылку на него на проверку. 87 | -------------------------------------------------------------------------------- /tasks/Trees.md: -------------------------------------------------------------------------------- 1 | # Домашнее задание к занятию 5. Пирамиды и бинарные деревья поиска 2 | ### Инструкция по выполнению домашнего задания: 3 | **1.** Зарегистрируйтесь на сайте **[repl.it](https://repl.it/)**;
4 | **2.** Перейдите в раздел **my repls**;
5 | **3.** Нажмите кнопку **Start coding now**! - если вы приступаете впервые, или **New Repl** - если у вас уже есть работы;
6 | **4.** В списке языков выберите тот **язык, который изучаете**;
7 | **5.** Код пишите в левой части окна - **в редакторе кода**;
8 | **6.** Чтобы посмотреть результат выполнения файла, нажмите на кнопку **Run**. Результат появится в правой части окна;
9 | **7.** После окончания работы скопируйте **ссылку на ваш repl** в адресной строке браузера;
10 | **8.** Скопированную ссылку (ваше решение ДЗ) нужно отправить на проверку. Для этого перейдите в личный кабинет на сайте **[netology.ru](https://netology.ru/)**, в поле комментария к домашней работе вставьте скопированную ссылку и отправьте работу на проверку; 11 | 12 | ------------ 13 | 14 | ## Построение идеального биндерева поиска 15 | 16 | У вас есть отсортированный массив двузначных чисел без повторов размером в 2k-1 для какого-нибудь k, т.е. вам гарантируется что размер может быть 3 (22-1), 15 (24-1), 1023 (210-1), но не 11 или 30. Это позволит вам не заморачиваться с проблемами при делении пополам :) 17 | 18 | Вам надо написать функцию, которая возьмёт этот массив и выведет на экран бинарное дерево поиска из элементов этого дерева, которое будет полным двоичным деревом. Условие на размер массива гарантирует, что такое дерево всегда можно построить, более того, только на последнем уровне у узлов не будет детей. 19 | 20 | Например, у вас есть массив `[10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49, 52]`. 21 | 22 | Вам надо вывести бинарное дерево в таком виде: 23 | ``` 24 | 31 25 | 19 43 26 | 13 25 37 49 27 | 10 16 22 28 34 40 46 52 28 | ``` 29 | 30 | Заметьте, что стрелочки мы не рисовали, но кто является чьим ребёнком можно догадаться: у `31` ребёнок `19` и `43`, у `25` - `22` и `28`, у `43` - `37` и `49`. Для каждого узла дерева соблюдается требование, наложенное на бинарные деревья поиска - левый ребёнок и все его потомки меньше, правый и все его потомки - больше. 31 | 32 | Асимптотика в данном задании не важна. 33 | 34 | ### Решение 35 | Давайте сперва найдём для каждого элемента массива уровень дерева, на котором он должен быть (корень на нулевом уровне, дальше по увеличению). 36 | 37 | Тк такое полное бинарное дерево симметрично в плане своей структуры, мы знаем, что слева от корня будет столько же элементов, сколько и справа. Если мы возьмём элемент посередине массива, то слева и справа будет одинаковое количество элементов, также благодаря отсортированности слева будут элементы только меньшие, а справа только большие. Значит этот элемент и есть корень нашего дерева и его уровень 0. 38 | 39 | Решим задачу рекурсивно: левым ребёнком у корня будет элемент, который был бы корнем полного бинарного дерева поиска, запусти мы алгоритм от левой половины исходного массива. Аналогично для правого ребёнка. 40 | 41 | Учитывая всё вышесказанное, давайте напишем рекурсивную функцию `mark`, которая принимает массив, левую и правую границы рассматриваемого участка массива, текущий уровень и массив, куда для каждого элемента исходного массива мы запишем его уровень в дереве. Результатом вызова этой функции `mark(arr, 0, длина(arr) - 1, 0, пустой массив levels)` в массиве `levels` в каждой ячейке будет лежать уровень элемента в дереве. 42 | 43 | ``` 44 | mark(arr, left, right, level, levels): 45 | if left == right 46 | levels[left] = level 47 | выход 48 | middle = (left + right) / 2 49 | levels[middle] = level 50 | mark(arr, left, middle - 1, level + 1, levels) 51 | mark(arr, middle + 1, right, level + 1, levels) 52 | ``` 53 | 54 | После того как мы разметим каждый элемент на соответствующий уровень, будем выводить уровни дерева по очереди, построчно. Для этого пройдёмся циклом со счётчиком текущего уровня, а для каждого уровня пройдёмся циклом по массиву меток. Если для элемента в массиве уровней стоит наш текущий выводимый уровень, то мы напечатаем элемент, если нет, то напечатаем пропуск. 55 | 56 | ``` 57 | build(arr): 58 | levels = [длина(arr) нулей] 59 | mark(arr, 0, длина(arr) - 1, 0, levels) 60 | for level от 0 до максимум_среди(levels) + 1 61 | for i от 0 до длина(arr): 62 | if levels[i] == level: 63 | напечатаем arr[i] 64 | else: 65 | напечатаем два пробела, тк числа двузначные 66 | напечатаем перенос строки 67 | ``` 68 | 69 | ### Реализация 70 | 1. Создайте массив и заполните его из примера 71 | 2. Напишите функции `mark` и `build` 72 | 3. Вызовите функцию `build` от массива, убедитесь, что вывод совпал с тем что в примере 73 | 4. *(необязательно)* Оцените алгоритмическую сложность алгоритма, сколько он занимает времени и памяти. Аргументируйте, почему вы так считаете. 74 | 5. Загрузите ваше решение на сайт **[repl.it](https://repl.it/)**, отправьте ссылку на него на проверку. 75 | -------------------------------------------------------------------------------- /tasks/DirGraph.md: -------------------------------------------------------------------------------- 1 | # Домашнее задание к занятию 8. Ориентированные графы 2 | ### Инструкция по выполнению домашнего задания: 3 | **1.** Зарегистрируйтесь на сайте **[repl.it](https://repl.it/)**;
4 | **2.** Перейдите в раздел **my repls**;
5 | **3.** Нажмите кнопку **Start coding now**! - если вы приступаете впервые, или **New Repl** - если у вас уже есть работы;
6 | **4.** В списке языков выберите тот **язык, который изучаете**;
7 | **5.** Код пишите в левой части окна - **в редакторе кода**;
8 | **6.** Чтобы посмотреть результат выполнения файла, нажмите на кнопку **Run**. Результат появится в правой части окна;
9 | **7.** После окончания работы скопируйте **ссылку на ваш repl** в адресной строке браузера;
10 | **8.** Скопированную ссылку (ваше решение ДЗ) нужно отправить на проверку. Для этого перейдите в личный кабинет на сайте **[netology.ru](https://netology.ru/)**, в поле комментария к домашней работе вставьте скопированную ссылку и отправьте работу на проверку; 11 | 12 | ------------ 13 | 14 | ## Задача о вирусном меме 15 | 16 | Представим себе социальную сеть. В ней зарегистрированы пользователи, у каждого пользователя есть список людей, которым он пересылает мемы. Заметим, что если пользователь А пересылает мемы пользователю B, это необязательно значит, что пользователь B пересылает мемы пользователю А. Если пользователю прислали мем, то он его прочтёт ровно через 55 минут и через 5 минут отправит всем из своего списка людей если до того его ни разу не отправлял. 17 | 18 | Такие данные легко представить в виде ориентированного графа: вершинами будут люди, а из вершины A будет дуга в вершину B, если пользователь A пересылает мемы напрямую пользователю B. Давайте реализуем этот граф: 19 | 20 | ``` 21 | Graph: 22 | vertices_info - изначально массив из V пустых множеств / списков 23 | 24 | add_edge(a, b): 25 | vertices_info[a].add(b) 26 | vertices_info[b].add(a) 27 | 28 | nexts(v): 29 | return vertices_info[v] 30 | 31 | vertices(): 32 | return номера от 0 до V 33 | 34 | size(): 35 | return V 36 | ``` 37 | 38 | Перед вами стоит задача: вам дан такой социальный граф и номер пользователя, который увидел где-то какой-то мем и начал его пересылать. Каждый пользователь, который получил мем, также начал пересылать мемы. Вам нужно найти номер пользователя, который увидит впервые этот мем самым последним по времени. Если таковых несколько, подойдёт любой из них. 39 | 40 | Например, пусть у вас есть 7 пользователей сети. Пользователь с номером 0 всегда пересылает мемы пользователям 1 и 3, а пользователь с номером 1 - пользователям 2 и 3. Если пользователь с номером 0 в 10:00 увидел мем и в 10:05 начнёт пересылать мемы, то: 41 | 42 | 1. 10:05. Пользователь 0 отправил мемы пользователю 1 и 3 43 | 2. 11:00. Пользователь 1 и 3 получили мем 44 | 3. 11:05. Пользователь 3 никому не отправляем мемы, а пользователь 1 отправил мемы пользователям 2 и 3. 45 | 4. 12:00. Пользователь 2 и 3 получили мем. Пользователь 2 получил его впервые, а пользователь 3 уже получал его на час раньше от пользователя 0. Пользователь 2 ни с кем не делится мемами, а пользователь 3 уже пересылал мем час назад, пересылки мема на этом закончились. 46 | 47 | Кто когда впервые увидел мем: 48 | | Пользователь | Когда впервые увидел мем | 49 | | -- | -- | 50 | | 0 | 10:00 | 51 | | 1 | 11:00 | 52 | | 2 | 12:00 | 53 | | 3 | 11:00 | 54 | | 4 | никогда | 55 | | 5 | никогда | 56 | | 6 | никогда | 57 | 58 | Ответом к задаче на данном примере будет пользователь с номером 2. 59 | 60 | Как же решить эту задачу? Давайте сделаем это на основе обхода в ширину. Для этого сначала заведём массив меток - для каждой вершины будем хранить информацию о том, получал ли пользователь уже этот мем. Также заведём пустую очередь, в которую будем складывать пользователей. 61 | 62 | ``` 63 | virusize_meme(graph, start_user): 64 | visited = [V раз нет] 65 | sent = пустая очередь 66 | ... 67 | ``` 68 | 69 | Добавим в очередь стартового пользователя, который начнёт рассылку мемов. Пометим, что он уже видел мем: 70 | ``` 71 | virusize_meme(graph, start_user): 72 | ... 73 | visited[start_user] = да 74 | добавить start_user в sent 75 | ... 76 | ``` 77 | 78 | Теперь будем поочерёдно доставать пользователей и для каждого добавлять в неё тех, кому он отправит мем, но за исключением тех людей, которые уже его видели. Тот пользователь, которого мы достанем из очереди последним (когда он не сможет никому отправить из тех кто ещё не видел мем), и будет искомым ответом: 79 | ``` 80 | virusize_meme(graph, start_user): 81 | ... 82 | while sent не пустая: 83 | last_viewer = вынем из sent следующего пользователя 84 | for v среди graph.nexts(last_viewer): 85 | if не visited[v]: 86 | visited[v] = да 87 | добавим v в sent 88 | напечатаем "Последним увидет: " last_viewer 89 | ``` 90 | 91 | **Примечание.** Заметьте, мы даже не использовали нигде информацию о 55 и 5 минутах, это оказалось не важно. А вот если бы каждый пользователь отправлял или читал бы мемы с разной скоростью, то нам пришлось бы это использовать и решать через модифицированный алгоритм Дейкстры. 92 | 93 | ### Реализация 94 | 1. Реализуйте структуру данных ориентированный граф для хранения информации о пользователях и тех, кому они пересылают мемы. 95 | 2. Реализуйте модифицированный алгоритм обхода в ширину для решения задачи. 96 | 3. Создайте граф, заполнив его информацией из примера про 7 пользователей. 97 | 4. Запустите решение вашей задачи на этом графе, убедитесь что она выдаёт правильный ответ (дан выше). 98 | 5. Загрузите ваше решение на сайт **[repl.it](https://repl.it/)**, отправьте ссылку на него на проверку. 99 | -------------------------------------------------------------------------------- /tasks/Lists.md: -------------------------------------------------------------------------------- 1 | # Домашнее задание к занятию 4. Списки 2 | ### Инструкция по выполнению домашнего задания: 3 | **1.** Зарегистрируйтесь на сайте **[repl.it](https://repl.it/)**;
4 | **2.** Перейдите в раздел **my repls**;
5 | **3.** Нажмите кнопку **Start coding now**! - если вы приступаете впервые, или **New Repl** - если у вас уже есть работы;
6 | **4.** В списке языков выберите тот **язык, который изучаете**;
7 | **5.** Код пишите в левой части окна - **в редакторе кода**;
8 | **6.** Чтобы посмотреть результат выполнения файла, нажмите на кнопку **Run**. Результат появится в правой части окна;
9 | **7.** После окончания работы скопируйте **ссылку на ваш repl** в адресной строке браузера;
10 | **8.** Скопированную ссылку (ваше решение ДЗ) нужно отправить на проверку. Для этого перейдите в личный кабинет на сайте **[netology.ru](https://netology.ru/)**, в поле комментария к домашней работе вставьте скопированную ссылку и отправьте работу на проверку; 11 | 12 | ------------ 13 | 14 | ## Односвязный стек 15 | 16 | Давайте реализуем стек на основе односвязного стека. Значения стека будем хранить в обёртке, где помимо значения будет указатель на ниже в стеке элемент, в самом же стеке будем хранить указатель на самый верхний элемент: 17 | 18 | ``` 19 | Node: 20 | value - значение в обёртке 21 | prev - элемент, ниже в стеке 22 | 23 | Stack: 24 | head - указатель на обёртку с элементом, который надо вынуть следующим 25 | ``` 26 | 27 | Давайте реализуем две основные для стека операции. Для `push` создадим обёртку для нового значения, на который теперь будет указывать голова стека, а на старую голову будет указывать указатель на предыдущий элемент в обёртке. Для `pop` вынем значение из обёртки, на которую указывает голова стека, после чего передвиним голову стека на ту обёртку, на которую указывал указатель на предыдущий элемент в голове стека. 28 | ``` 29 | Stack: 30 | ... 31 | 32 | push(value): 33 | if head пустая 34 | head = Node(value=value, prev=пусто) 35 | else 36 | node = Node(value=value, prev=head) 37 | head = node 38 | 39 | def pop(self): 40 | if head пустая 41 | return нет элементов! 42 | else 43 | value = head.value 44 | head = head.prev 45 | return value 46 | ``` 47 | 48 | Теперь давайте добавим метод вывода стека на экран: 49 | ``` 50 | Stack: 51 | ... 52 | 53 | printme(): 54 | if head пустая 55 | напечатаем "EMPTY" 56 | else 57 | node = head 58 | while node не пустой 59 | напечатаем node.value 60 | if у node есть предыдущий 61 | напечатаем " -> " 62 | node = node.prev 63 | ``` 64 | 65 | ### Дополнительно: reverse 66 | Это необязательная часть. Можно сделать ещё один метод: `reverse()`, который бы возвращал новый стек, в котором те же элементы, но в другом порядке. Это сделаем рекурсивно, напишем вспомогательную функцию, которая будет принимать односвязный список и будет возвращать два значения: голову и хвост нового списка, который является ревёрсом (т.е. в обратном порядке) исходного списка. Для языков, где нельзя вернуть несколько значений, заведите, например, вспомогательный класс, объект которого вы заполните возвращаемыми значениями и вернёте как результат функции. 67 | 68 | ``` 69 | Stack: 70 | ... 71 | 72 | reverse(): 73 | if head пустая: return Stack() 74 | 75 | reversed_head(node): 76 | new_node = Node(value=node.value) 77 | if node.prev пустая: 78 | return (new_node, new_node) 79 | else: 80 | head, tail = reversed_head(node.prev) 81 | tail.prev = new_node 82 | return (head, new_node) 83 | reversed_stack = Stack() 84 | new_head, new_tail = reversed_head(self.head) 85 | reversed_stack.head = new_head 86 | return reversed_stack 87 | ``` 88 | 89 | ### Демонстрация 90 | Теперь протестируем наш стек: 91 | ``` 92 | stack = Stack() 93 | stack.printme() 94 | напечатаем 'Добавим 0' 95 | stack.push(0) 96 | stack.printme() 97 | напечатаем 'Добавим 1' 98 | stack.push(1) 99 | stack.printme() 100 | напечатаем 'Добавим 2' 101 | stack.push(2) 102 | stack.printme() 103 | напечатаем 'Добавим 3' 104 | stack.push(3) 105 | stack.printme() 106 | напечатаем 'Добавим 4' 107 | stack.push(4) 108 | stack.printme() 109 | напечатаем 'Добавим 5' 110 | stack.push(5) 111 | stack.printme() 112 | напечатаем 'Снимем со стека' 113 | напечатаем stack.pop() 114 | stack.printme() 115 | напечатаем 'Снимем со стека' 116 | напечатаем stack.pop() 117 | stack.printme() 118 | напечатаем 'Ревёрс!' 119 | stack = stack.reverse() 120 | stack.printme() 121 | напечатаем 'Снимем со стека' 122 | напечатаем stack.pop() 123 | stack.printme() 124 | напечатаем 'Снимем со стека' 125 | напечатаем stack.pop() 126 | stack.printme() 127 | напечатаем 'Ревёрс!' 128 | stack = stack.reverse() 129 | stack.printme() 130 | напечатаем 'Снимем со стека' 131 | напечатаем stack.pop() 132 | stack.printme() 133 | напечатаем 'Снимем со стека' 134 | напечатаем stack.pop() 135 | stack.printme() 136 | ``` 137 | 138 | Должен получиться такой вывод: 139 | ``` 140 | EMPTY 141 | Добавим 0 142 | 0 143 | Добавим 1 144 | 1 -> 0 145 | Добавим 2 146 | 2 -> 1 -> 0 147 | Добавим 3 148 | 3 -> 2 -> 1 -> 0 149 | Добавим 4 150 | 4 -> 3 -> 2 -> 1 -> 0 151 | Добавим 5 152 | 5 -> 4 -> 3 -> 2 -> 1 -> 0 153 | Снимем со стека 154 | 5 155 | 4 -> 3 -> 2 -> 1 -> 0 156 | Снимем со стека 157 | 4 158 | 3 -> 2 -> 1 -> 0 159 | Ревёрс! 160 | 0 -> 1 -> 2 -> 3 161 | Снимем со стека 162 | 0 163 | 1 -> 2 -> 3 164 | Снимем со стека 165 | 1 166 | 2 -> 3 167 | Ревёрс! 168 | 3 -> 2 169 | Снимем со стека 170 | 3 171 | 2 172 | Снимем со стека 173 | 2 174 | EMPTY 175 | ``` 176 | 177 | ### Реализация 178 | 1. Реализовать структуру данных на односвязном списке. Использовать встроенные структуры данных для хранения элементов (массивы, списки, коллекции и так далее) запрещено, список надо писать самому. 179 | 2. Протестировать работу со стеком командами из примера выше. Убедиться, что вывод совпадает с ответом. 180 | 3. Загрузите ваше решение на сайт **[repl.it](https://repl.it/)**, отправьте ссылку на него на проверку. 181 | -------------------------------------------------------------------------------- /tasks/Dynamics.md: -------------------------------------------------------------------------------- 1 | # Домашнее задание к занятию 2. Динамическое программирование 2 | 3 | ### Инструкция по выполнению домашнего задания: 4 | **1.** Зарегистрируйтесь на сайте **[repl.it](https://repl.it/)**;
5 | **2.** Перейдите в раздел **my repls**;
6 | **3.** Нажмите кнопку **Start coding now**! - если вы приступаете впервые, или **New Repl** - если у вас уже есть работы;
7 | **4.** В списке языков выберите тот **язык, который изучаете**;
8 | **5.** Код пишите в левой части окна - **в редакторе кода**;
9 | **6.** Чтобы посмотреть результат выполнения файла, нажмите на кнопку **Run**. Результат появится в правой части окна;
10 | **7.** После окончания работы скопируйте **ссылку на ваш repl** в адресной строке браузера;
11 | **8.** Скопированную ссылку (ваше решение ДЗ) нужно отправить на проверку. Для этого перейдите в личный кабинет на сайте **[netology.ru](https://netology.ru/)**, в поле комментария к домашней работе вставьте скопированную ссылку и отправьте работу на проверку; 12 | 13 | ------------ 14 | 15 | ## Задача о щенке 16 | 17 | У вас есть квадратное поле клеток n на n. В каждой клетке может находиться кактус. В левом верхнем углу находится щенок. Щенок может перемещаться только на клетку вправо или на клетку вниз, причём он не может премещаться в клетку, в которой находится кактус. 18 | 19 | Вам надо написать функцию, которая принимала бы параметрами поле в виде двумерного массива, две координаты клетки, в которой находится хозяин щенка, и которая выводила бы на экран поле с маршрутом для щенка из левого верхнего угла до человека. Если такого маршрута нет, программа должна сообщить об этом. 20 | 21 | Координаты считаются так: слева сверху x = 0, y = 0, координата x растёт вправо, координата y растёт вниз, т.е. левый нижний угол имеет координаты (0; n-1), правый нижний (n-1; n-1), правый верхний (n-1; 0). 22 | 23 | Например, поле может выглядеть так (`Щ` - щенок, `Ч` - человек, `*` - кактус): 24 | ``` 25 | Щ - - * * - - - - - 26 | - - - - * - * * - - 27 | - - - * - * - - - * 28 | - * - - - - - - Ч - 29 | - - - - - - * - - - 30 | - - * - - * - - - - 31 | - - - * - - * * * - 32 | - - - - - - - * - - 33 | - - - - - - - * - - 34 | - - - - - * * - - - 35 | ``` 36 | 37 | Результат работы программы (`x` - маршрут щенка): 38 | ``` 39 | Щ - - * * - - - - - 40 | x - - - * - * * - - 41 | x x x * - * - - - * 42 | - * x x x x x x Ч - 43 | - - - - - - * - - - 44 | - - * - - * - - - - 45 | - - - * - - * * * - 46 | - - - - - - - * - - 47 | - - - - - - - * - - 48 | - - - - - * * - - - 49 | ``` 50 | 51 | Программа должна работать за время и дополнительную память не хуже чем O(n2). Подумайте, как бы вы решили задачу, используя принцип динамического программирования? 52 | 53 | ### Решение 54 | Давайте решим вспомогательную задачу и напишем функцию, которая вместо прорисовки готового поля будет просто возвращать для полученных в параметрах координат направление, откуда в эту клетку прибежит щенок если такой путь существует: `U` если щенок в эту клетку прибежит сверху, `L` если слева, `N` если щенок не может добраться в эту клетку. Если может прибежать с обоих направлений, то выбираем любое из них. 55 | 56 | Пусть мы уже написали такую функцию и назвали её `where_from(field, x, y)`. Теперь решим нашу исходную задачу через эту вспомогательную: просто вызовем от запрашиваемых координат вспомогательную задачу, она даст нам направление, откуда прибежит щенок. Перейдём по этому направлению и вызовем вспомогательную задачу уже от новых координат. Будем так делать до тех пор, пока не дойдём до клетки с координатами (0; 0), т.е. до изначальной клетки для щенка: 57 | 58 | ``` 59 | find_path(field, x0, y0): 60 | path = [n x n значений "нет"] 61 | memory = двумерный массив заполненный "?" 62 | x = x0 63 | y = y0 64 | while (x; y) это не (0; 0) 65 | direction = where_from(field, x, y) 66 | if direction == 'N' 67 | return Нет такого пути :( 68 | else if direction == 'U' 69 | path[x][y] = "да" 70 | y -= 1 71 | else if direction == 'L' 72 | path[x][y] = "да" 73 | x -= 1 74 | for y от 0 до n 75 | for x от 0 до n 76 | if x == x0 и y == y0 77 | печатаем 'Ч' 78 | else if path[x][y] 79 | печатаем 'x' 80 | else 81 | печатаем field[x][y] 82 | ``` 83 | 84 | Как же теперь быстро и эффективно решить вспомогательную задачу, т.е. написать функцию `where_from`? Давайте решим её динамикой! А именно, решение для заданных координат будем выражать через рекурсивное решение для других координат, а все ответы будем запоминать в двумерном массиве чтобы не пересчитывать. 85 | 86 | Как же нам написать рекурсивное решение? Давайте подумаем, если мы находимся в клетке с координатами (x; y), то в качестве ответа от нас ждут `U` если щенок может прибежать в неё сверху, `L` если слева, `N` если не может сюда прибежать (если может с обоих направлений, то указать любое из них). Рекурсия будет выглядить следующим образом: если слева у нас доступная клетка, не кактус, то вызовем рекурсивно `where_from` от неё и, если этот вызов вернул не `N` (т.е. в эту клетку может попасть щенок), то и мы вернём в качестве ответа `L` (т.е. иди из текущей в левую). Если же нам вернули `N`, то провернём тоже самое с верхней клеткой. Если же и у неё `N` (или там были кактусы, или клеток не было тк мы были на границе поля), то вернём `N`. 87 | 88 | Не забудем запоминать для каждой вызванной координаты ответ в двумерном массиве, который будем передавать дополнительным параметром. Рекурсия гарантировано закончится, тк на каждом вызове мы двигаемся либо влево, либо вверх, а такое делать бесконечно невозможно. Запоминание результатов в массиве гарантирует нам, что для каждой клетки максимум только один раз произойдут рекурсивные вызовы. В результате, мы получим суммарную асимптотику O(n2), тк количество ячеек n2. 89 | 90 | ``` 91 | where_from(field, x, y, memory): 92 | if memory[x][y] != '?' 93 | return memory[x][y] 94 | if x > 0 95 | left_x = x - 1 96 | left_y = y 97 | if left_x == 0 И left_y == 0 98 | memory[x][y] = 'L' 99 | return 'L' 100 | if field[left_x][left_y] != '*' 101 | if where_from(field, left_x, left_y, memory) != 'N' 102 | memory[x][y] = 'L' 103 | return 'L' 104 | if y > 0 105 | up_x = x 106 | up_y = y - 1 107 | if up_x == 0 И up_y == 0 108 | memory[x][y] = 'U' 109 | return 'U' 110 | if field[up_x][up_y] != '*' 111 | if where_from(field, up_x, up_y, memory) != 'N' 112 | memory[x][y] = 'U' 113 | return 'U' 114 | memory[x][y] = 'N' 115 | return 'N' 116 | ``` 117 | 118 | ### Процесс реализации 119 | 1. В начале работы программы заведите `n` равную `10` и двумерный массив для поля, заполните его как в примере выше (клетку `Ч` заменить на `-`). 120 | 2. Напишите функции `find_path` и `where_from`. Не забудьте адаптировать код первого метода так, чтобы передавать в `where_from` массив для памяти динамического программирования `memory`. 121 | 3. Вызовите `find_path` для координаты (8; 3). Убедитесь, что построился верный путь для щенка. 122 | 4. Загрузите ваше решение на сайт **[repl.it](https://repl.it/)**, отправьте ссылку на него на проверку. 123 | --------------------------------------------------------------------------------