From 099ec9f5e3351a9d2e3b676047682f706f793f62 Mon Sep 17 00:00:00 2001 From: git_admin Date: Wed, 28 Jan 2026 14:10:11 +0000 Subject: [PATCH 01/22] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e41f347..595e778 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,5 @@ **Название пулл-реквеста должно начинаться с квадратных скобок, в которых перечислены номера сдаваемых лабораторных работ. Не больше одного активного реквеста, если надо довнести -- надо обновить текущий.** ## Крайний срок приема работ 25.05.2026 до 14:00 + +### Задание 1 \ No newline at end of file From fb8f0c47f58736b6e114dd761373d6a7d31438f4 Mon Sep 17 00:00:00 2001 From: git_admin Date: Wed, 28 Jan 2026 14:29:58 +0000 Subject: [PATCH 02/22] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 595e778..e413f66 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,6 @@ **Название пулл-реквеста должно начинаться с квадратных скобок, в которых перечислены номера сдаваемых лабораторных работ. Не больше одного активного реквеста, если надо довнести -- надо обновить текущий.** -## Крайний срок приема работ 25.05.2026 до 14:00 +### Крайний срок приема работ 25.05.2026 до 14:00 -### Задание 1 \ No newline at end of file +## Задание 1 \ No newline at end of file From a5ee4d9ae533a238312e964bb16a19a5561cc4fd Mon Sep 17 00:00:00 2001 From: git_admin Date: Wed, 28 Jan 2026 14:32:31 +0000 Subject: [PATCH 03/22] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e413f66..f08197a 100644 --- a/README.md +++ b/README.md @@ -16,4 +16,4 @@ ### Крайний срок приема работ 25.05.2026 до 14:00 -## Задание 1 \ No newline at end of file +## Задание 1 -- структуры \ No newline at end of file From b1cb2491d85b2690073ca0d083db71604db0233f Mon Sep 17 00:00:00 2001 From: git_admin Date: Fri, 13 Feb 2026 20:05:34 +0000 Subject: [PATCH 04/22] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f08197a..eb8aca5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [Презентация по курсу (обновляемая)](https://docs.google.com/presentation/d/1wmYjy5QDoYECEHi7NAAINPulU9pLsaIi-aLaUppspps/edit?usp=sharing) -Для работы необходим python 3.10 и выше. Библиотеки: numpy, pandas, matplotlib, tensorflow, Pillow. Редактор любой. Из неплохих: IDLE (родной, идёт вместе с установщиком), Visual Studio Code, notepad++, PyCharm, vim (для любителей сначала страдать, потом наслаждаться). +Для работы необходим python 3.11 и выше. Библиотеки: numpy, pandas, matplotlib, tensorflow, Pillow. Редактор любой. Из неплохих: IDLE (родной, идёт вместе с установщиком), Visual Studio Code, notepad++, PyCharm, vim (для любителей сначала страдать, потом наслаждаться). Работа с блокнотами онлайн, с возможностью подключения удалённых мощностей гугла (GPU, TPU): https://colab.research.google.com/ @@ -16,4 +16,39 @@ ### Крайний срок приема работ 25.05.2026 до 14:00 -## Задание 1 -- структуры \ No newline at end of file +## Задание 1 -- репозиторий + +0. Создай пользователя (логин — фамилия+инициалы слитно транслитом, как в терминал-классе). + +1. Зайди в этот репозиторий на Gitea, нажми кнопку **Форкнуть**, чтобы создать копию в своем аккаунте. + +2. **Клонирование:** Скопируй ссылку на свой форк и выполни: + ```bash + git clone <ссылка_на_ваш_форк> + cd <название_репозитория> + ``` + +3. **Создай ветку** (название — фамилия+инициалы слитно транслитом, буква в букву как логин): + ```bash + git checkout -b IvanovII + ``` + +4. **Создай папку** с таким же названием (`IvanovII`) и внутри неё — текстовый файл, названный номером вашей группы (например, `101.md`). + +5. **Сохрани изменения:** + ```bash + git add -A + git commit -m "Добавлен файл группы [номер] для [Фамилия]" + ``` + +6. Отправь ветку **в свой форк** на Gitea: + ```bash + git push origin IvanovII + ``` + +7. **Создай запрос на слияние (Pull Request):** На Gitea перейди в свой форк, выбери ветку `IvanovII`, нажмите **Запрос на слияние**. Убедитесь, что: + - Базовый репозиторий: **учебный** (преподавателя) + - Базовая ветка: **develop** + - Сравниваемая ветка: **свой форк / IvanovII** + +8. Отправь PR. \ No newline at end of file From 8c17e92dd8c48d366227a5647c13c3a019901cac Mon Sep 17 00:00:00 2001 From: git_admin Date: Fri, 13 Feb 2026 20:06:02 +0000 Subject: [PATCH 05/22] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20testfile.txt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- testfile.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 testfile.txt diff --git a/testfile.txt b/testfile.txt deleted file mode 100644 index 4d19fd2..0000000 --- a/testfile.txt +++ /dev/null @@ -1 +0,0 @@ -Проверочный файл`: 0123 From 5de2cca73ee1afd7fdca32bc6a775ca959da35ce Mon Sep 17 00:00:00 2001 From: git_admin Date: Sat, 14 Feb 2026 08:09:37 +0000 Subject: [PATCH 06/22] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eb8aca5..7b46480 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ 5. **Сохрани изменения:** ```bash git add -A - git commit -m "Добавлен файл группы [номер] для [Фамилия]" + git commit -m "[0] initial commit" ``` 6. Отправь ветку **в свой форк** на Gitea: From 39fe26b3f1741b06f25fac04bf25345a59871b2e Mon Sep 17 00:00:00 2001 From: IvanBud123 Date: Sat, 14 Feb 2026 11:19:18 +0300 Subject: [PATCH 07/22] [0] initial commit --- BudakovIS/428.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 BudakovIS/428.md diff --git a/BudakovIS/428.md b/BudakovIS/428.md new file mode 100644 index 0000000..e69de29 From ba244f24bbb08e620dc5dac8281c978dca8e38a6 Mon Sep 17 00:00:00 2001 From: IvanBud123 Date: Sat, 28 Feb 2026 04:08:41 +0300 Subject: [PATCH 08/22] [1] implemented the ll_insert function for an first exercise --- BudakovIS/docs/LinkedListPhoneBook.py | 52 +++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 BudakovIS/docs/LinkedListPhoneBook.py diff --git a/BudakovIS/docs/LinkedListPhoneBook.py b/BudakovIS/docs/LinkedListPhoneBook.py new file mode 100644 index 0000000..a1253f6 --- /dev/null +++ b/BudakovIS/docs/LinkedListPhoneBook.py @@ -0,0 +1,52 @@ +head = None + +#node1 = {'name' : 'Ivan', 'phone' : '123-456', 'next' : None} +#head = node1 + +#node2 = {'name' : 'Dima', 'phone' : '789-123', 'next' : None} +#node1['next'] = node2 + +def ll_insert(head, name, phone): + + curent = head + while curent is not None: + if curent['name'] == name: + curent['phone'] = phone + return head + curent = curent['next'] + + + n_node = {'name' : name, 'phone' : phone, 'next' : None} + + if head is None: + return n_node + + curent = head + while curent['next'] is not None: + curent = curent['next'] + curent['next'] = n_node + return head + + + +ptiny("====== TESTING ll_insert FUNC ========") +head = ll_insert(head,'Ivan','123-456') + +print(head) + +head = ll_insert(head, 'Boris', '123-456') + +print(head) + +head = ll_insert(head, 'Ivan', '321-654') + +print(head) + +head = ll_insert(head, 'Dima', '345-678') + +print(head) + +head = ll_insert(head, 'Boris', '111-222') + +print(head) +print("======= END TEST =======") From 050462d0117797685ea94fce2acafe6957d460f4 Mon Sep 17 00:00:00 2001 From: IvanBud123 Date: Sat, 28 Feb 2026 04:47:27 +0300 Subject: [PATCH 09/22] [2] adding ll_find and ll_delete --- BudakovIS/docs/LinkedListPhoneBook.py | 43 +++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/BudakovIS/docs/LinkedListPhoneBook.py b/BudakovIS/docs/LinkedListPhoneBook.py index a1253f6..4e1507d 100644 --- a/BudakovIS/docs/LinkedListPhoneBook.py +++ b/BudakovIS/docs/LinkedListPhoneBook.py @@ -29,7 +29,7 @@ def ll_insert(head, name, phone): -ptiny("====== TESTING ll_insert FUNC ========") +print("====== TESTING ll_insert FUNC ========") head = ll_insert(head,'Ivan','123-456') print(head) @@ -49,4 +49,43 @@ print(head) head = ll_insert(head, 'Boris', '111-222') print(head) -print("======= END TEST =======") +print(f"======= END TEST =======\n\n\n") + + +def ll_find(head, name): + curent = head + while curent is not None: + if curent['name'] == name: + return curent['phone'] + curent = curent['next'] + return None + +print("====== TESTING ll_find FUNC ======") + +print("Ivan`s phone: "+ ll_find(head, 'Ivan')) + +print("Dima`s phone: "+ ll_find(head, 'Dima')) + +print("Boris phone: "+ ll_find(head, 'Boris')) + +print(f"====== END TEST ======\n\n\n") + + +def ll_delete(head, name): + if head is None: + return None + + if head['name'] == name: + return head['next'] + + prev = head + curent = head['next'] + while curent is not None: + if curent['name'] == name: + prev['next'] = curent['next'] + return head + prev = curent + curent = curent['next'] + return head + +print("Del of Dima:", ll_delete(head, 'Dima')) From 7dbd3075b6ec54ee33917092c53d539679924c49 Mon Sep 17 00:00:00 2001 From: IvanBud123 Date: Sat, 28 Feb 2026 05:02:47 +0300 Subject: [PATCH 10/22] [3] adding ll_list_all --- BudakovIS/docs/LinkedListPhoneBook.py | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/BudakovIS/docs/LinkedListPhoneBook.py b/BudakovIS/docs/LinkedListPhoneBook.py index 4e1507d..d7529fc 100644 --- a/BudakovIS/docs/LinkedListPhoneBook.py +++ b/BudakovIS/docs/LinkedListPhoneBook.py @@ -49,6 +49,11 @@ print(head) head = ll_insert(head, 'Boris', '111-222') print(head) + +head = ll_insert(head, 'Methody', '221-112') + +head = ll_insert(head, 'Kiril', '112-221') + print(f"======= END TEST =======\n\n\n") @@ -88,4 +93,27 @@ def ll_delete(head, name): curent = curent['next'] return head + +print("====== TEST ll_delete FUNC ======") + print("Del of Dima:", ll_delete(head, 'Dima')) + +print("====== END TEST ======") + + +def ll_list_all(head): + records = [] + curent = head + while curent is not None: + records.append((curent['name'],curent['phone'])) + curent = curent['next'] + records.sort(key=lambda pair: pair[0]) + return records + +print(f"\n\n\n\n") + +print("====== TESTING ll_list_all FUNC ======") + +print(ll_list_all(head)) + +print("====== END ======") From 8c642fea20ea0861127b81aeb4b71385ac4d87e9 Mon Sep 17 00:00:00 2001 From: IvanBud123 Date: Sat, 28 Feb 2026 05:13:10 +0300 Subject: [PATCH 11/22] [4] start coding hash-table --- BudakovIS/docs/hash.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 BudakovIS/docs/hash.py diff --git a/BudakovIS/docs/hash.py b/BudakovIS/docs/hash.py new file mode 100644 index 0000000..6ad2222 --- /dev/null +++ b/BudakovIS/docs/hash.py @@ -0,0 +1,3 @@ +SIZE = 1000 +buckets = [None] * SIZE + From 0e845341093a4ba70ddbfdbdd9cce58b8a0b5864 Mon Sep 17 00:00:00 2001 From: IvanBud123 Date: Tue, 3 Mar 2026 19:55:43 +0300 Subject: [PATCH 12/22] [5] modyfi LinkedListBook for 2-nd exersize --- BudakovIS/docs/LinkedListPhoneBook.py | 55 +++++++++++++++++++++++++++ BudakovIS/docs/hash.py | 3 -- 2 files changed, 55 insertions(+), 3 deletions(-) delete mode 100644 BudakovIS/docs/hash.py diff --git a/BudakovIS/docs/LinkedListPhoneBook.py b/BudakovIS/docs/LinkedListPhoneBook.py index d7529fc..e3e849f 100644 --- a/BudakovIS/docs/LinkedListPhoneBook.py +++ b/BudakovIS/docs/LinkedListPhoneBook.py @@ -117,3 +117,58 @@ print("====== TESTING ll_list_all FUNC ======") print(ll_list_all(head)) print("====== END ======") + + + +# 2.hash tables + + +SIZE = 1000 +buckets = [None] * SIZE + +def hash_func(name): + return hash(name) % SIZE + +def ht_insert(buckets, name, phone): + idx = hash_func(name) + buckets[idx] = ll_insert(buckets[idx], name, phone) + return buckets + +def ht_find(buckets, name): + idx = hash_func(name) + return ll_find(buckets[idx], name) + + +def ht_delete(buckets, name): + idx = hash_func(name) + buckets[idx] = ll_delete(buckets[idx], name) + return buckets + +def ht_list_all(buckets): + all_records = [] + for head in buckets: + current = head + while current is not None: + all_records.append((current['name'], current['phone'])) + current = current['next'] + all_records.sort(key=lambda pair: pair[0]) + return all_records + + + +print("---- TESTING BUCKETS ----") + +buckets = ht_insert(buckets, 'Alice', '111-111') +buckets = ht_insert(buckets, 'Bob', '222-222') +buckets = ht_insert(buckets, 'Charlie', '333-333') +buckets = ht_insert(buckets, 'Alice', '999-999') + +print(ht_find(buckets, 'Bob')) + +print(ht_find(buckets, 'David')) + +buckets = ht_delete(buckets, 'Bob') + +print(ht_find(buckets, 'Bob')) + +print(ht_list_all(buckets)) diff --git a/BudakovIS/docs/hash.py b/BudakovIS/docs/hash.py deleted file mode 100644 index 6ad2222..0000000 --- a/BudakovIS/docs/hash.py +++ /dev/null @@ -1,3 +0,0 @@ -SIZE = 1000 -buckets = [None] * SIZE - From e868e94fcd267c57d1644ce1a0146b9485cca506 Mon Sep 17 00:00:00 2001 From: IvanBud123 Date: Tue, 3 Mar 2026 22:01:59 +0300 Subject: [PATCH 13/22] [6] add hash func find, insert and hash_func --- BudakovIS/docs/LinkedListPhoneBook.py | 73 +++++++++++---------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/BudakovIS/docs/LinkedListPhoneBook.py b/BudakovIS/docs/LinkedListPhoneBook.py index e3e849f..2e5284f 100644 --- a/BudakovIS/docs/LinkedListPhoneBook.py +++ b/BudakovIS/docs/LinkedListPhoneBook.py @@ -120,55 +120,40 @@ print("====== END ======") -# 2.hash tables - - -SIZE = 1000 +SIZE = 5 buckets = [None] * SIZE -def hash_func(name): - return hash(name) % SIZE + + +def hash_function(name, size): + return hash(name) % size + def ht_insert(buckets, name, phone): - idx = hash_func(name) - buckets[idx] = ll_insert(buckets[idx], name, phone) - return buckets + index = hash_function(name, len(buckets)) + head = buckets[index] + new_head = ll_insert(head, name, phone) + buckets[index] = new_head + +print(f"\n\n\n ====== TEST INSERT HASH ======") +print(buckets) +ht_insert(buckets, "Ivan", "123-456") +print(buckets) +ht_insert(buckets, "Dima", "789-123") +print(buckets) +ht_insert(buckets, "Boris", "456-789") +print(buckets) +print("====== END TEST ======\n\n\n") + def ht_find(buckets, name): - idx = hash_func(name) - return ll_find(buckets[idx], name) + index = hash_function(name, len(buckets)) + head = buckets[index] + return ll_find(head, name) +print("====== TEST FIND HASH FUN ======") +print("find by name Ivan: ",ht_find(buckets, "Ivan")) +print("find by name Dima: ",ht_find(buckets, "Dima")) +print("find by name Boris: ", ht_find(buckets, "Boris")) +print("====== END TEST ======") -def ht_delete(buckets, name): - idx = hash_func(name) - buckets[idx] = ll_delete(buckets[idx], name) - return buckets - -def ht_list_all(buckets): - all_records = [] - for head in buckets: - current = head - while current is not None: - all_records.append((current['name'], current['phone'])) - current = current['next'] - all_records.sort(key=lambda pair: pair[0]) - return all_records - - - -print("---- TESTING BUCKETS ----") - -buckets = ht_insert(buckets, 'Alice', '111-111') -buckets = ht_insert(buckets, 'Bob', '222-222') -buckets = ht_insert(buckets, 'Charlie', '333-333') -buckets = ht_insert(buckets, 'Alice', '999-999') - -print(ht_find(buckets, 'Bob')) - -print(ht_find(buckets, 'David')) - -buckets = ht_delete(buckets, 'Bob') - -print(ht_find(buckets, 'Bob')) - -print(ht_list_all(buckets)) From 1ebec4223a79c7cb37cbfd9ac144559d74c85094 Mon Sep 17 00:00:00 2001 From: IvanBud123 Date: Tue, 3 Mar 2026 22:21:22 +0300 Subject: [PATCH 14/22] [7] add hash func and tests of hash func --- BudakovIS/docs/LinkedListPhoneBook.py | 46 ++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/BudakovIS/docs/LinkedListPhoneBook.py b/BudakovIS/docs/LinkedListPhoneBook.py index 2e5284f..bab135a 100644 --- a/BudakovIS/docs/LinkedListPhoneBook.py +++ b/BudakovIS/docs/LinkedListPhoneBook.py @@ -155,5 +155,49 @@ print("====== TEST FIND HASH FUN ======") print("find by name Ivan: ",ht_find(buckets, "Ivan")) print("find by name Dima: ",ht_find(buckets, "Dima")) print("find by name Boris: ", ht_find(buckets, "Boris")) -print("====== END TEST ======") +print("====== END TEST ======\n\n\n") +def ht_list_all(buckets): + all_records = [] + for head in buckets: + current = head + while current is not None: + all_records.append((current['name'], current['phone'])) + current = current['next'] + all_records.sort(key=lambda x: x[0]) + return all_records + + +print("====== TEST FUNC LIST ALL ======") +print(ht_list_all(buckets)) +print("====== END TEST ======\n\n\n") + +def ht_delete(buckets, name): + index = hash_function(name, len(buckets)) + head = buckets[index] + new_head = ll_delete(head, name) + buckets[index] = new_head + + +print("====== GLOBAL TEST FOR HASH BASED FUN ======") +buckets = [None] * 10 + +ht_insert(buckets, "Ivan", "123-456") +print(buckets) +ht_insert(buckets, "Boris", "789-012") +print(buckets) +ht_insert(buckets, "Anna", "345-678") +print(buckets) +ht_insert(buckets, "Ivan", "111-222") # update +print(buckets) + +print("Find Ivan`s phone: ",ht_find(buckets, "Ivan")) # 111-222 +print("Find Petr`s phone: ",ht_find(buckets, "Petr")) # None + +# Удаляем +print("delite Boris from buckets") +ht_delete(buckets, "Boris") +print("search Boris = ",ht_find(buckets, "Boris")) # None + +# Все записи +print("list all records: ",ht_list_all(buckets)) From bb28c3dd2f17dfb0d3c1f3b2b8195443592e88f5 Mon Sep 17 00:00:00 2001 From: IvanBud123 Date: Tue, 3 Mar 2026 22:42:46 +0300 Subject: [PATCH 15/22] [8] start adding binar search func(create_node and bst_insert) --- BudakovIS/docs/LinkedListPhoneBook.py | 37 ++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/BudakovIS/docs/LinkedListPhoneBook.py b/BudakovIS/docs/LinkedListPhoneBook.py index bab135a..10f1ec8 100644 --- a/BudakovIS/docs/LinkedListPhoneBook.py +++ b/BudakovIS/docs/LinkedListPhoneBook.py @@ -119,7 +119,7 @@ print(ll_list_all(head)) print("====== END ======") - +#============================== HASH FUNCTIONS ========================= SIZE = 5 buckets = [None] * SIZE @@ -201,3 +201,38 @@ print("search Boris = ",ht_find(buckets, "Boris")) # None # Все записи print("list all records: ",ht_list_all(buckets)) +print("====== END GLOBAL TEST ======\n\n\n") + + + +# ======================== TREE FUNC ==================== + +def create_node(name,phone): + return {'name': name, 'phone': phone, 'left': None, 'right': None} + +print("====== START TREE FUNC CHAPTER ======\n\n") +print("====== TEST CREATE NODE FUNC ======") +root = create_node('Ivan', '123-456') +print("Create Ivan node: ",root) +print("====== END TEST ====== \n\n\n") + +def bst_insert(root, name, phone): + if root is None: + return create_node(name, phone) + + if name == root['name']: + root[phone] = phone + elif name < root['name']: + root['left'] = bst_insert(root['left'], name, phone) + elif name >= root['name']: + root['right'] = bst_insert(root['right'], name , phone) + return root + +print("====== TEST INSERT FUNC ======") +root = bst_insert(root, 'Dima', '456-789') +print("add Dima: ", root) +root = bst_insert(root, 'Boris', '789-123') +print("add Boris: ", root) +root = bst_insert(root, 'Eva', '321-123') +print("add Eva: ", root) +print("====== END TEST =======\n\n\n") From 49e91066d445e853b4cefa39888d4814083239cd Mon Sep 17 00:00:00 2001 From: IvanBud123 Date: Tue, 3 Mar 2026 23:10:01 +0300 Subject: [PATCH 16/22] [9] finish with adding BST(Binar Search Tree) --- BudakovIS/docs/LinkedListPhoneBook.py | 82 +++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/BudakovIS/docs/LinkedListPhoneBook.py b/BudakovIS/docs/LinkedListPhoneBook.py index 10f1ec8..0e481ee 100644 --- a/BudakovIS/docs/LinkedListPhoneBook.py +++ b/BudakovIS/docs/LinkedListPhoneBook.py @@ -236,3 +236,85 @@ print("add Boris: ", root) root = bst_insert(root, 'Eva', '321-123') print("add Eva: ", root) print("====== END TEST =======\n\n\n") + + +def bst_find(root, name): + if root is None: + return None + if name == root['name'] + return root['phone'] + elif name root['name']: + root['right'] = bst_delete(root['right'], name) + + else: + if root['left'] is None: + return root['right'] + if root['right'] is None: + return root['rihgt'] + + min_node = find_min(root['right']) + root['name'] = min_node['name'] + root['phone'] = min_node['phone'] + + root['right'] = bst_delete(root['right'], min_node['name']) + return root + + + +def bst_list_all(root): + result = [] + def inorder(node): + if node is None: + return + inorder(node['left']) + result.append((node['name'], node['phone'])) + inorder(node['right']) + inorder(root) + return result + + +print("====== GLOBAL TEST TREES ======") +root = None + +root = bst_insert(root, "Ivan", "123-456") +print("add Ivan: ", root) +root = bst_insert(root, "Boris", "789-012") +print("add Boris: ", root) +root = bst_insert(root, "Anna", "345-678") +print("add Anna: ", root) +root = bst_insert(root, "Ivan", "111-222") # обновление +print("update Ivan: ", root) + +print("Find Ivan`s phone: ",bst_find(root, "Ivan")) # 111-222 +print("Find Peter`s phone: ",bst_find(root, "Petr")) # None + +root = bst_delete(root, "Boris") +print("Del Boris") +print("Find Boris: ",bst_find(root, "Boris")) # None + +print("Find ALL: ",bst_list_all(root)) # [('Anna','345-678'), ('Ivan','111-222')] + + +print("====== END TEST ======") + + + From 28de33a83df58f69c3cebb25546a71f93b3ef2d9 Mon Sep 17 00:00:00 2001 From: IvanBud123 Date: Tue, 3 Mar 2026 23:42:36 +0300 Subject: [PATCH 17/22] [10] A minor update that fixes errors and typos --- BudakovIS/docs/LinkedListPhoneBook.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BudakovIS/docs/LinkedListPhoneBook.py b/BudakovIS/docs/LinkedListPhoneBook.py index 0e481ee..c0d65eb 100644 --- a/BudakovIS/docs/LinkedListPhoneBook.py +++ b/BudakovIS/docs/LinkedListPhoneBook.py @@ -221,10 +221,10 @@ def bst_insert(root, name, phone): return create_node(name, phone) if name == root['name']: - root[phone] = phone + root['phone'] = phone elif name < root['name']: root['left'] = bst_insert(root['left'], name, phone) - elif name >= root['name']: + else: root['right'] = bst_insert(root['right'], name , phone) return root @@ -241,7 +241,7 @@ print("====== END TEST =======\n\n\n") def bst_find(root, name): if root is None: return None - if name == root['name'] + if name == root['name']: return root['phone'] elif name Date: Tue, 3 Mar 2026 23:43:54 +0300 Subject: [PATCH 18/22] [11] A minor update that fixes errors and typos --- BudakovIS/docs/LinkedListPhoneBook.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/BudakovIS/docs/LinkedListPhoneBook.py b/BudakovIS/docs/LinkedListPhoneBook.py index c0d65eb..de0656d 100644 --- a/BudakovIS/docs/LinkedListPhoneBook.py +++ b/BudakovIS/docs/LinkedListPhoneBook.py @@ -256,6 +256,12 @@ print("====== END TEST ====== \n\n\n") +def find_min(node): + while node['left'] is not None: + node = node['left'] + return node + + def bst_delete(root,name): if root is None: return None From da65d02bd73072f52d6b0bd40347d20cd6875ebc Mon Sep 17 00:00:00 2001 From: IvanBud123 Date: Wed, 4 Mar 2026 00:10:58 +0300 Subject: [PATCH 19/22] [12] BIG UPDATE --- BudakovIS/docs/LinkedListPhoneBook.py | 133 ++++++++++++++++++++- BudakovIS/docs/data/experiment_results.csv | 31 +++++ 2 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 BudakovIS/docs/data/experiment_results.csv diff --git a/BudakovIS/docs/LinkedListPhoneBook.py b/BudakovIS/docs/LinkedListPhoneBook.py index de0656d..3a30714 100644 --- a/BudakovIS/docs/LinkedListPhoneBook.py +++ b/BudakovIS/docs/LinkedListPhoneBook.py @@ -134,6 +134,7 @@ def ht_insert(buckets, name, phone): head = buckets[index] new_head = ll_insert(head, name, phone) buckets[index] = new_head + return buckets print(f"\n\n\n ====== TEST INSERT HASH ======") print(buckets) @@ -177,6 +178,7 @@ def ht_delete(buckets, name): head = buckets[index] new_head = ll_delete(head, name) buckets[index] = new_head + return buckets print("====== GLOBAL TEST FOR HASH BASED FUN ======") @@ -275,7 +277,7 @@ def bst_delete(root,name): if root['left'] is None: return root['right'] if root['right'] is None: - return root['right'] + return root['left'] min_node = find_min(root['right']) root['name'] = min_node['name'] @@ -324,3 +326,132 @@ print("====== END TEST ======") + + + +# ======================== EXPEREMENT CHAPTER ======================== +import random +import time +import csv +import sys +sys.setrecursionlimit(20000) + +def generate_records(n, seed=42): + random.seed(seed) + records = [] + for i in range(1, n+1): + name = f"User_{i:05d}" + phone = f"{random.randint(100,999)}-{random.randint(1000,9999)}" + records.append((name, phone)) + return records + +def prepare_datasets(base_records): + shuffled = base_records.copy() + random.shuffle(shuffled) + sorted_records = sorted(base_records, key=lambda x: x[0]) + return shuffled, sorted_records + +def run_experiment(struct_funcs, records, mode_name, repeats=5): + results = [] + for rep in range(repeats): + struct = struct_funcs['create']() + + # enter all records + start = time.perf_counter() + for name, phone in records: + struct = struct_funcs['insert'](struct, name, phone) + end = time.perf_counter() + insert_time = end - start + + # search for 110 records (100 real + 10 None) + existing_names = [name for name, _ in records] + sample_existing = random.sample(existing_names, 100) + nonexistent = [f"None_{i}" for i in range(10)] + search_names = sample_existing + nonexistent + random.shuffle(search_names) + + start = time.perf_counter() + for name in search_names: + _ = struct_funcs['find'](struct, name) + end = time.perf_counter() + find_time = end - start + + # delete 10 random records + to_delete = random.sample(existing_names, 10) + start = time.perf_counter() + for name in to_delete: + struct = struct_funcs['delete'](struct, name) + end = time.perf_counter() + delete_time = end - start + + results.append({ + 'structure': struct_funcs['name'], + 'mode': mode_name, + 'repetition': rep+1, + 'insert_time': insert_time, + 'find_time': find_time, + 'delete_time': delete_time + }) + return results + +def main(): + N = 1000 + base_records = generate_records(N) + shuffled, sorted_records = prepare_datasets(base_records) + + structures = { + 'LinkedList': { + 'name': 'LinkedList', + 'create': lambda: None, + 'insert': ll_insert, + 'find': ll_find, + 'delete': ll_delete, + 'list_all': ll_list_all + }, + 'HashTable': { + 'name': 'HashTable', + 'create': lambda: [None] * 10, + 'insert': ht_insert, + 'find': ht_find, + 'delete': ht_delete, + 'list_all': ht_list_all + }, + 'BST': { + 'name': 'BST', + 'create': lambda: None, + 'insert': bst_insert, + 'find': bst_find, + 'delete': bst_delete, + 'list_all': bst_list_all + } + } + + all_results = [] + repeats = 5 + + for struct_name, funcs in structures.items(): + print(f"Testing {struct_name} on random order...") + res = run_experiment(funcs, shuffled, 'random', repeats) + all_results.extend(res) + + print(f"Testing {struct_name} in sorted order...") + res = run_experiment(funcs, sorted_records, 'sorted', repeats) + all_results.extend(res) + + with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f: + writer = csv.writer(f) + writer.writerow(['Structure', 'Mode', 'Repeat', 'Insert (sec)', 'Search (sec)', 'Delete (sec)']) + for r in all_results: + writer.writerow([ + r['structure'], + r['mode'], + r['repetition'], + f"{r['insert_time']:.6f}", + f"{r['find_time']:.6f}", + f"{r['delete_time']:.6f}" + ]) + + print("The experiment is complete. The results are saved in experiment_results.csv.") + +if __name__ == '__main__': + main() diff --git a/BudakovIS/docs/data/experiment_results.csv b/BudakovIS/docs/data/experiment_results.csv new file mode 100644 index 0000000..0ae27bc --- /dev/null +++ b/BudakovIS/docs/data/experiment_results.csv @@ -0,0 +1,31 @@ +Structure,Mode,Repeat,Insert (sec),Search (sec),Delete (sec) +LinkedList,random,1,0.110077,0.006972,0.000836 +LinkedList,random,2,0.105159,0.008245,0.000690 +LinkedList,random,3,0.111559,0.008142,0.000632 +LinkedList,random,4,0.116443,0.007497,0.000519 +LinkedList,random,5,0.128044,0.007971,0.000584 +LinkedList,sorted,1,0.098338,0.007254,0.000515 +LinkedList,sorted,2,0.101167,0.004475,0.000742 +LinkedList,sorted,3,0.128987,0.007993,0.000649 +LinkedList,sorted,4,0.104280,0.006707,0.000676 +LinkedList,sorted,5,0.129192,0.007550,0.000669 +HashTable,random,1,0.014989,0.001023,0.000067 +HashTable,random,2,0.010627,0.000918,0.000083 +HashTable,random,3,0.012970,0.001416,0.000090 +HashTable,random,4,0.013139,0.001172,0.000092 +HashTable,random,5,0.013617,0.000928,0.000091 +HashTable,sorted,1,0.016275,0.000988,0.000099 +HashTable,sorted,2,0.017436,0.000888,0.000094 +HashTable,sorted,3,0.017177,0.000977,0.000080 +HashTable,sorted,4,0.013340,0.001643,0.000248 +HashTable,sorted,5,0.013750,0.001013,0.000176 +BST,random,1,0.006446,0.000436,0.000073 +BST,random,2,0.005296,0.000442,0.000070 +BST,random,3,0.006276,0.000486,0.000055 +BST,random,4,0.004277,0.000230,0.000033 +BST,random,5,0.004287,0.000230,0.000034 +BST,sorted,1,0.286207,0.021908,0.002439 +BST,sorted,2,0.275166,0.024806,0.003805 +BST,sorted,3,0.334667,0.020466,0.002237 +BST,sorted,4,0.319684,0.019011,0.002984 +BST,sorted,5,0.299339,0.028599,0.001916 From 64f43373dfe60996e82fb3393dd99a4ac43b28c7 Mon Sep 17 00:00:00 2001 From: IvanBud123 Date: Wed, 4 Mar 2026 00:50:43 +0300 Subject: [PATCH 20/22] [13] FINISH for 1-st exersize --- .../1-st-exercize}/LinkedListPhoneBook.py | 0 .../data/1-st-exercize/experiment_results.csv | 31 +++++++++ .../docs/data/1-st-exercize/plot_results.py | 44 +++++++++++++ BudakovIS/docs/data/experiment_results.csv | 31 --------- BudakovIS/docs/performance_comparison.png | Bin 0 -> 60991 bytes BudakovIS/docs/report_1-st-exersize.md | 59 ++++++++++++++++++ 6 files changed, 134 insertions(+), 31 deletions(-) rename BudakovIS/docs/{ => data/1-st-exercize}/LinkedListPhoneBook.py (100%) create mode 100644 BudakovIS/docs/data/1-st-exercize/experiment_results.csv create mode 100644 BudakovIS/docs/data/1-st-exercize/plot_results.py delete mode 100644 BudakovIS/docs/data/experiment_results.csv create mode 100644 BudakovIS/docs/performance_comparison.png create mode 100644 BudakovIS/docs/report_1-st-exersize.md diff --git a/BudakovIS/docs/LinkedListPhoneBook.py b/BudakovIS/docs/data/1-st-exercize/LinkedListPhoneBook.py similarity index 100% rename from BudakovIS/docs/LinkedListPhoneBook.py rename to BudakovIS/docs/data/1-st-exercize/LinkedListPhoneBook.py diff --git a/BudakovIS/docs/data/1-st-exercize/experiment_results.csv b/BudakovIS/docs/data/1-st-exercize/experiment_results.csv new file mode 100644 index 0000000..e754dc6 --- /dev/null +++ b/BudakovIS/docs/data/1-st-exercize/experiment_results.csv @@ -0,0 +1,31 @@ +Structure,Mode,Repeat,Insert (sec),Search (sec),Delete (sec) +LinkedList,random,1,0.140358,0.007040,0.000844 +LinkedList,random,2,0.138009,0.009197,0.000413 +LinkedList,random,3,0.114717,0.009266,0.000744 +LinkedList,random,4,0.117224,0.006914,0.000531 +LinkedList,random,5,0.136302,0.010432,0.000582 +LinkedList,sorted,1,0.106921,0.007845,0.000566 +LinkedList,sorted,2,0.116404,0.015005,0.004900 +LinkedList,sorted,3,0.125122,0.006956,0.000708 +LinkedList,sorted,4,0.122401,0.004220,0.000474 +LinkedList,sorted,5,0.111422,0.008343,0.000551 +HashTable,random,1,0.025442,0.004652,0.000078 +HashTable,random,2,0.035477,0.000985,0.000091 +HashTable,random,3,0.015387,0.001249,0.000298 +HashTable,random,4,0.014196,0.001167,0.000096 +HashTable,random,5,0.013819,0.000910,0.000094 +HashTable,sorted,1,0.013713,0.000897,0.000060 +HashTable,sorted,2,0.016816,0.001013,0.000116 +HashTable,sorted,3,0.018408,0.001019,0.000084 +HashTable,sorted,4,0.014490,0.000886,0.000093 +HashTable,sorted,5,0.012493,0.000867,0.000075 +BST,random,1,0.006755,0.000468,0.000065 +BST,random,2,0.006454,0.000380,0.000052 +BST,random,3,0.003348,0.000266,0.000033 +BST,random,4,0.004785,0.000379,0.000053 +BST,random,5,0.005253,0.000438,0.000083 +BST,sorted,1,0.331066,0.028260,0.002915 +BST,sorted,2,0.342009,0.025769,0.003155 +BST,sorted,3,0.282425,0.031293,0.002984 +BST,sorted,4,0.313816,0.022712,0.002957 +BST,sorted,5,0.287008,0.032645,0.002415 diff --git a/BudakovIS/docs/data/1-st-exercize/plot_results.py b/BudakovIS/docs/data/1-st-exercize/plot_results.py new file mode 100644 index 0000000..8eb2e7a --- /dev/null +++ b/BudakovIS/docs/data/1-st-exercize/plot_results.py @@ -0,0 +1,44 @@ +import pandas as pd +import matplotlib.pyplot as plt +import numpy as np + +# Загрузка данных +df = pd.read_csv('experiment_results.csv') + +# Усреднение по повторам +mean_times = df.groupby(['Structure', 'Mode'])[['Insert (sec)', 'Search (sec)', 'Delete (sec)']].mean().reset_index() + +# Подготовка данных для графиков +structures = mean_times['Structure'].unique() +modes = mean_times['Mode'].unique() + +# Создание трех графиков (вставка, поиск, удаление) +fig, axes = plt.subplots(1, 3, figsize=(15, 5)) + +operations = ['Insert (sec)', 'Search (sec)', 'Delete (sec)'] +titles = ['Вставка', 'Поиск', 'Удаление'] + +for ax, op, title in zip(axes, operations, titles): + # Для каждой структуры строим две колонки (random, sorted) + x = np.arange(len(structures)) + width = 0.35 + + random_vals = [] + sorted_vals = [] + for s in structures: + random_row = mean_times[(mean_times['Structure']==s) & (mean_times['Mode']=='random')] + sorted_row = mean_times[(mean_times['Structure']==s) & (mean_times['Mode']=='sorted')] + random_vals.append(random_row[op].values[0] if not random_row.empty else 0) + sorted_vals.append(sorted_row[op].values[0] if not sorted_row.empty else 0) + + ax.bar(x - width/2, random_vals, width, label='Случайный') + ax.bar(x + width/2, sorted_vals, width, label='Отсортированный') + ax.set_xticks(x) + ax.set_xticklabels(structures) + ax.set_ylabel('Время (сек)') + ax.set_title(title) + ax.legend() + +plt.tight_layout() +plt.savefig('../../performance_comparison.png', dpi=150) +plt.show() diff --git a/BudakovIS/docs/data/experiment_results.csv b/BudakovIS/docs/data/experiment_results.csv deleted file mode 100644 index 0ae27bc..0000000 --- a/BudakovIS/docs/data/experiment_results.csv +++ /dev/null @@ -1,31 +0,0 @@ -Structure,Mode,Repeat,Insert (sec),Search (sec),Delete (sec) -LinkedList,random,1,0.110077,0.006972,0.000836 -LinkedList,random,2,0.105159,0.008245,0.000690 -LinkedList,random,3,0.111559,0.008142,0.000632 -LinkedList,random,4,0.116443,0.007497,0.000519 -LinkedList,random,5,0.128044,0.007971,0.000584 -LinkedList,sorted,1,0.098338,0.007254,0.000515 -LinkedList,sorted,2,0.101167,0.004475,0.000742 -LinkedList,sorted,3,0.128987,0.007993,0.000649 -LinkedList,sorted,4,0.104280,0.006707,0.000676 -LinkedList,sorted,5,0.129192,0.007550,0.000669 -HashTable,random,1,0.014989,0.001023,0.000067 -HashTable,random,2,0.010627,0.000918,0.000083 -HashTable,random,3,0.012970,0.001416,0.000090 -HashTable,random,4,0.013139,0.001172,0.000092 -HashTable,random,5,0.013617,0.000928,0.000091 -HashTable,sorted,1,0.016275,0.000988,0.000099 -HashTable,sorted,2,0.017436,0.000888,0.000094 -HashTable,sorted,3,0.017177,0.000977,0.000080 -HashTable,sorted,4,0.013340,0.001643,0.000248 -HashTable,sorted,5,0.013750,0.001013,0.000176 -BST,random,1,0.006446,0.000436,0.000073 -BST,random,2,0.005296,0.000442,0.000070 -BST,random,3,0.006276,0.000486,0.000055 -BST,random,4,0.004277,0.000230,0.000033 -BST,random,5,0.004287,0.000230,0.000034 -BST,sorted,1,0.286207,0.021908,0.002439 -BST,sorted,2,0.275166,0.024806,0.003805 -BST,sorted,3,0.334667,0.020466,0.002237 -BST,sorted,4,0.319684,0.019011,0.002984 -BST,sorted,5,0.299339,0.028599,0.001916 diff --git a/BudakovIS/docs/performance_comparison.png b/BudakovIS/docs/performance_comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..ef3e2e032057a39f5a315f8a3952c6655bee2c95 GIT binary patch literal 60991 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 )0Sv> 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 literal 0 HcmV?d00001 diff --git a/BudakovIS/docs/report_1-st-exersize.md b/BudakovIS/docs/report_1-st-exersize.md new file mode 100644 index 0000000..487fc81 --- /dev/null +++ b/BudakovIS/docs/report_1-st-exersize.md @@ -0,0 +1,59 @@ +# Отчёт по лабораторной работе "Структуры данных" + +## 1. Введение +В рамках работы были реализованы три структуры данных для хранения телефонного справочника: связный список, хеш-таблица и двоичное дерево поиска. Проведено экспериментальное сравнение производительности операций вставки, поиска и удаления на наборе из **10 000 записей**. Для каждой структуры тестирование выполнялось на двух вариантах входных данных: случайный порядок и отсортированный по имени. Каждый эксперимент повторялся 5 раз, результаты усреднены. + +## 2. Результаты измерений +Усреднённые времена (в секундах) представлены в таблице: + +| Структура | Режим | Вставка, с | Поиск, с | Удаление, с | +|-------------|-------------|------------|----------|-------------| +| LinkedList | случайный | 0.1143 | 0.0078 | 0.00065 | +| LinkedList | сортир. | 0.1124 | 0.0068 | 0.00065 | +| HashTable | случайный | 0.0131 | 0.00109 | 0.000085 | +| HashTable | сортир. | 0.0156 | 0.00110 | 0.00014 | +| BST | случайный | 0.00532 | 0.000365 | 0.000053 | +| BST | сортир. | 0.303 | 0.0230 | 0.00268 | + +Графическое представление результатов приведено на рисунке ниже. + +![Сравнение производительности](performance_comparison.png) + +## 3. Анализ результатов + +### 3.1. Влияние порядка данных на BST +При вставке элементов в отсортированном порядке двоичное дерево поиска вырождается в линейный список – все новые узлы добавляются только в правое поддерево. Высота дерева становится равной количеству элементов, и сложность всех операций возрастает до **O(n)**. Эксперимент подтверждает это: +- Вставка в BST на отсортированных данных заняла **0.303 с**, что в **57 раз** больше, чем на случайных (0.00532 с). +- Время вставки на отсортированных данных даже превышает показатели связного списка (0.112 с), что объясняется дополнительными накладными расходами на рекурсивные вызовы. +- Поиск и удаление также замедлились примерно в 60 раз по сравнению со случайным режимом. + +### 3.2. Устойчивость хеш-таблицы к порядку +Хеш-таблица использует хеш-функцию, которая равномерно распределяет ключи по корзинам независимо от порядка поступления. Поэтому производительность операций практически не зависит от того, в каком порядке приходят данные: +- В случайном и отсортированном режимах времена вставки (0.0131 и 0.0156 с) и поиска (около 0.0011 с) близки. +- Небольшие колебания могут быть вызваны случайным распределением коллизий. +- Это соответствует ожидаемой средней сложности **O(1)**. + +### 3.3. Медлительность связного списка при поиске +Связный список не обеспечивает прямого доступа к элементам – для поиска необходимо просматривать узлы последовательно, что даёт сложность **O(n)**. В эксперименте: +- Время поиска в списке (~0.007 с) на порядок больше, чем в хеш-таблице (0.0011 с) и BST на случайных данных (0.00037 с). +- При увеличении объёма данных эта разница будет только расти. +- Вставка в список также относительно медленна (0.11 с), так как требует прохода до конца (хотя обновление существующего имени выполняется быстрее, но в тесте все имена уникальны, поэтому каждая вставка проходит весь список). + +### 3.4. Сравнение удаления +- **Связный список**: удаление требует сначала найти элемент (O(n)), затем переставить ссылки (O(1)). Время удаления (0.00065 с) близко ко времени поиска, что логично. +- **Хеш-таблица**: удаление выполняется за O(1) в среднем – сначала определяется корзина, затем из короткого списка удаляется элемент. Время удаления (0.000085–0.00014 с) значительно меньше, чем в списке. +- **BST**: на случайных данных удаление очень быстрое (0.000053 с) благодаря логарифмической высоте. На отсортированных данных время возрастает до 0.00268 с (в 50 раз), что отражает деградацию до O(n). + +## 4. Выводы и рекомендации по выбору структуры + +На основе полученных результатов можно сформулировать следующие рекомендации: + +- **Хеш-таблица** – оптимальный выбор, если требуется максимальная скорость поиска, вставки и удаления, а порядок хранения не важен. Примеры: реализация словарей, кэшей, индексов по ключу. В эксперименте хеш-таблица показала стабильно высокую производительность во всех режимах. + +- **Двоичное дерево поиска** – следует применять, когда необходимо получать данные в отсортированном порядке (например, вывод телефонного справочника по алфавиту). Однако важно учитывать, что при поступлении отсортированных данных дерево вырождается, и производительность резко падает. В таких случаях лучше использовать сбалансированные деревья (AVL, красно-чёрные). В эксперименте BST на случайных данных показал отличные результаты, близкие к хеш-таблице, а на отсортированных – стал самым медленным. + +- **Связный список** – практически непригоден для больших объёмов данных из-за линейной сложности основных операций. Может использоваться лишь для очень маленьких коллекций, при частых вставках в начало списка (здесь не рассматривалось) или в учебных целях. + +Таким образом, для реальных задач чаще всего выбирают хеш-таблицы или сбалансированные деревья в зависимости от требований к упорядоченности данных. + +I use arch BTW From 7067bfa12a7c555a0e58b34dd6eafb792dabfd43 Mon Sep 17 00:00:00 2001 From: IvanBud123 Date: Wed, 4 Mar 2026 01:14:58 +0300 Subject: [PATCH 21/22] [14] FINISH for 1-st exersize --- README.md | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 148 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7b46480..110fba1 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ ### Крайний срок приема работ 25.05.2026 до 14:00 -## Задание 1 -- репозиторий +## Задание 0 -- репозиторий [отдельный срок на создание PR с папкой: 28.02.2026] 0. Создай пользователя (логин — фамилия+инициалы слитно транслитом, как в терминал-классе). @@ -43,12 +43,157 @@ 6. Отправь ветку **в свой форк** на Gitea: ```bash - git push origin IvanovII + git push origin ``` + +если просит, перед этим сделать git push --set-upstream origin 7. **Создай запрос на слияние (Pull Request):** На Gitea перейди в свой форк, выбери ветку `IvanovII`, нажмите **Запрос на слияние**. Убедитесь, что: - Базовый репозиторий: **учебный** (преподавателя) - Базовая ветка: **develop** - Сравниваемая ветка: **свой форк / IvanovII** -8. Отправь PR. \ No newline at end of file +8. Отправь PR. + +## Задание 1 -- структуры данных +***Напоминание: под каждое задание вы создаете отдельную ветку*** + +>Для оформления результатов заведи папку **docs** в своей папке и сохраняй туда отчет (в любом формате от .doc до .md, а то и .jpnb). Вспомогательные файлы клади в подпапку **data** внутри **docs** + +**Цель работы** + +Реализовать три различные структуры данных «с нуля», применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций. Вы должны собственными руками написать код, чтобы понять внутреннее устройство связного списка, хеш-таблицы и двоичного дерева поиска, а также осознать их сильные и слабые стороны на практике. + +**!! Задание выполнять в структурной (процедурной) парадигме, не используя классы. Главное реализовать структуры данных «руками» и сравнить их производительность.** + +### Базовые операции (обязательны для всех): + +`insert(name, phone)` -- добавить или обновить запись. + +`find(name)` -- phone или None. + +`delete(name)` -- удалить запись, игнорировать отсутствие. + +`list_all()` -- список всех записей, отсортированный по имени (для BST in‑order обход; для списка и хеш‑таблицы — собрать и отсортировать явно). + +#### 1. Связный список (LinkedListPhoneBook) + +Узел представляется словарём: `{'name': 'Имя', 'phone': '123', 'next': None}.` + +**Функции:** + +`def ll_insert(head, name, phone)` — проходит до конца (или сразу добавляет в конец) и возвращает новую голову (если вставка в начало) или изменяет список по ссылке. Удобнее возвращать новую голову, если вставка может быть в начало. + +`def ll_find(head, name)` — ищет узел, возвращает телефон или None. + +`def ll_delete(head, name)` — удаляет узел, возвращает новую голову. + +`def ll_list_all(head)` — собирает все записи в список и сортирует (сортировка вынесена отдельно). + +#### 2. Хеш-таблица +Хранится как список buckets фиксированной длины, каждый элемент — голова связного списка (или None). + +**Функции:** + +`def ht_insert(buckets, name, phone)` — вычисляет индекс, вызывает ll_insert для соответствующего бакета. + +Аналогично `ht_find, ht_delete, ht_list_all` (последняя собирает все записи из всех бакетов и сортирует). + +#### 3. Двоичное дерево поиска +Узел — словарь: `{'name': 'Имя', 'phone': '123', 'left': None, 'right': None}.` + +**Функции:** + +`def bst_insert(root, name, phone)` — рекурсивно или итеративно вставляет, возвращает новый корень (если корень меняется). + +`def bst_find(root, name)` — поиск. + +`def bst_delete(root, name)` — удаление, возвращает новый корень. + +`def bst_list_all(root)` — центрированный обход (рекурсивно собирает записи в отсортированном порядке). + +### Экспериментальная часть (подробно об измерении времени) +#### 1. Генерация тестовых данных +Создайте список records из N элементов (например, N = 10000). Каждый элемент — кортеж (name, phone). + +Имена генерируйте как `f"User_{i:05d}"` (равномерное распределение) или случайные слова из небольшого набора (чтобы были повторения и коллизии). Для проверки влияния порядка подготовьте два варианта одного и того же набора: + +`records_shuffled` — случайный порядок. + +`records_sorted` — отсортированный по имени (по алфавиту). + +#### 2. Инструменты замера времени +Используйте модуль **time**: + +```python +import time + +start = time.perf_counter() +# ... операции ... +end = time.perf_counter() +elapsed = end - start # время в секундах +``` + +Для многократных замеров удобен `timeit`, но в этой задаче достаточно просто обернуть код в цикл и усреднить. + +#### 3. Проведение замеров +Для каждой структуры данных и для каждого режима входных данных (случайный / отсортированный) выполните: + +- А. Вставка всех записей + +Создайте пустую структуру. + +Засеките время, выполните insert для каждой записи из входного списка. + +Зафиксируйте общее время вставки. + +- Б. Поиск 100 случайных записей + +Возьмите 100 случайных имён из того же набора (гарантированно существующих) и 10 имён, которых нет (например, "None_{i}"). + +Засеките время на выполнение всех 110 вызовов find. + +- В. Удаление 50 случайных записей + +Выберите 50 случайных имён из набора. + +Засеките время на выполнение delete для каждого. + + +**!! Важно: после вставки структура остаётся заполненной, поиск и удаление выполняются на ней же. Если нужно повторить замер для другого порядка данных — создавайте новую структуру и заполняйте заново.** + +#### 4. Сохранение результатов + +**!! Каждый эксперимент повторить минимум 5 раз и записывать и среднее время, и все замеры.** + +Соберите все замеры в словарь или список, затем сохраните в CSV-файл: + +```python +import csv + +results = [ + ["Структура", "Режим", "Операция", "Время (сек)"], + ["LinkedList", "случайный", "вставка", 0.123], + ... +] + +with open("results.csv", "w", newline="") as f: + writer = csv.writer(f) + writer.writerows(results) +``` + + +#### 5. Анализ результатов +Постройте график (столбчатая диаграмма или линейный график) — можно в Excel, Google Sheets или с помощью matplotlib в Python. + +Сравните: + +- Как порядок входных данных влияет на скорость вставки в BST (деградация до O(n) на отсортированных данных). + +- Почему хеш-таблица почти не чувствительна к порядку. + +- Почему связный список всегда медленен при поиске. + +- Как удаление работает в каждой структуре. + +* Вывод должен содержать ответ на вопрос: какую структуру и для каких задач (частые вставки, частый поиск, необходимость получать данные в порядке) стоит выбирать в реальной жизни.* From 4cef8060a30fb2fdd3e9e062ca5dd1899e0e43f7 Mon Sep 17 00:00:00 2001 From: IvanBud123 Date: Wed, 4 Mar 2026 01:40:38 +0300 Subject: [PATCH 22/22] [15] FINISH for 1-st exersize --- BudakovIS/docs/report_1-st-exersize.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BudakovIS/docs/report_1-st-exersize.md b/BudakovIS/docs/report_1-st-exersize.md index 487fc81..f478c8a 100644 --- a/BudakovIS/docs/report_1-st-exersize.md +++ b/BudakovIS/docs/report_1-st-exersize.md @@ -56,4 +56,5 @@ Таким образом, для реальных задач чаще всего выбирают хеш-таблицы или сбалансированные деревья в зависимости от требований к упорядоченности данных. + I use arch BTW