diff --git a/KuznetsovMA/lab1/docs/data/graph_delete.png b/KuznetsovMA/lab1/docs/data/graph_delete.png new file mode 100644 index 00000000..542ee285 Binary files /dev/null and b/KuznetsovMA/lab1/docs/data/graph_delete.png differ diff --git a/KuznetsovMA/lab1/docs/data/graph_find.png b/KuznetsovMA/lab1/docs/data/graph_find.png new file mode 100644 index 00000000..accc3e04 Binary files /dev/null and b/KuznetsovMA/lab1/docs/data/graph_find.png differ diff --git a/KuznetsovMA/lab1/docs/data/graph_insert.png b/KuznetsovMA/lab1/docs/data/graph_insert.png new file mode 100644 index 00000000..03e6b561 Binary files /dev/null and b/KuznetsovMA/lab1/docs/data/graph_insert.png differ diff --git a/KuznetsovMA/lab1/docs/data/results.csv b/KuznetsovMA/lab1/docs/data/results.csv new file mode 100644 index 00000000..2a5b7132 --- /dev/null +++ b/KuznetsovMA/lab1/docs/data/results.csv @@ -0,0 +1,19 @@ +structure,order,operation,run1,run2,run3,run4,run5,average +LinkedList,random,insert,3.000600399999712,3.022712899999533,2.9421689999999217,2.9075659000000087,3.0319512999994913,2.980999899999733 +LinkedList,random,find,0.031094500000108383,0.02800200000001496,0.034349299999121286,0.029372199999670556,0.03242119999958959,0.031047839999700955 +LinkedList,random,delete,0.017322699999567703,0.0368361000000732,0.04029200000059063,0.03775789999963308,0.03554420000000391,0.033550579999973705 +HashTable,random,insert,0.011551699999472476,0.012756400000398571,0.011765299999751733,0.011679000000185624,0.011983400000644906,0.011947160000090662 +HashTable,random,find,0.00012409999999363208,0.00011009999980160501,0.0001415999995515449,0.00010400000064691994,0.00010089999977935804,0.000116139999954612 +HashTable,random,delete,6.38999999864609e-05,6.779999966965988e-05,6.0600000324484427e-05,6.070000017643906e-05,6.0600000324484427e-05,6.272000009630574e-05 +BST,random,insert,0.014788199999202334,0.014159299999846553,0.013975800000480376,0.014118900000539725,0.013331299999663315,0.01407469999994646 +BST,random,find,0.00013829999988956843,0.00011389999963284936,0.00011369999992894009,0.00011379999978089472,0.00011439999980211724,0.00011881999980687397 +BST,random,delete,8.690000049682567e-05,6.450000000768341e-05,6.2199999774748e-05,6.209999992279336e-05,6.229999962670263e-05,6.759999996575061e-05 +LinkedList,sorted,insert,2.4411346000006233,2.36463619999995,2.2797248999995645,2.2860746000005747,2.2526011999998445,2.3248343000001115 +LinkedList,sorted,find,0.024703000000044995,0.02455259999987902,0.02468479999970441,0.02444869999999355,0.02606350000041857,0.02489052000000811 +LinkedList,sorted,delete,0.012835599999561964,0.027673999999933585,0.027570299999752024,0.02708100000018021,0.02999909999925876,0.02503199999973731 +HashTable,sorted,insert,0.011780100000578386,0.010850699999537028,0.010314100000869075,0.010621500000524975,0.011015500000212342,0.010916380000344362 +HashTable,sorted,find,0.0001464000006308197,0.00017980000029638177,0.00016909999976633117,0.00012620000052265823,0.00023630000032426324,0.0001715600003080908 +HashTable,sorted,delete,0.00016370000048482325,0.00018089999957737746,0.0001443999999537482,7.579999964946182e-05,6.469999971159268e-05,0.0001258999998754007 +BST,sorted,insert,3.5400651999998445,3.5145174999997835,3.5583661999999094,3.5149656000003233,3.481246600000304,3.521832220000033 +BST,sorted,find,0.03275260000009439,0.030442500000390282,0.02994349999971746,0.030269500000031258,0.030329999999594293,0.030747619999965538 +BST,sorted,delete,0.012705400000413647,0.01333390000036161,0.013192000000344706,0.013699000000087835,0.013079800000014075,0.013202020000244374 diff --git a/KuznetsovMA/lab1/docs/report.md b/KuznetsovMA/lab1/docs/report.md new file mode 100644 index 00000000..1e7e3d47 --- /dev/null +++ b/KuznetsovMA/lab1/docs/report.md @@ -0,0 +1,34 @@ +Лабораторная работа 1 + + + +Цель работы + + + +Нужно было сделать три структуры данных и проверить как они работают на телефонном справочнике. + + + +Ход работы + + + +Сделал связный список хеш таблицу и двоичное дерево поиска. Для всех структур сделал добавление поиск удаление и вывод записей. Для проверки создал 10000 записей с именами User\_00000 и т.д. Потом проверил работу со случайным порядком и с отсортированным порядком. Каждый эксперимент повторял 5 раз. + + + +Результаты + + + +Результаты сохранились в results.csv. Также сделал графики для добавления поиска и удаления. По результатам видно что связный список медленно ищет записи потому что нужно идти по элементам. Хеш таблица работает примерно одинаково при разном порядке записей. У двоичного дерева порядок записей влияет намного сильнее. Если добавлять записи по порядку дерево становится похожим на обычный список и работает медленнее. + + + +Вывод + + + +В работе я сделал три структуры данных и проверил их работу. Самой удобной для телефонного справочника получилась хеш таблица. Связный список проще но поиск медленный. Двоичное дерево может работать быстро но сильно зависит от порядка добавления данных. + diff --git a/KuznetsovMA/lab1/experiments.py b/KuznetsovMA/lab1/experiments.py new file mode 100644 index 00000000..fab1645d --- /dev/null +++ b/KuznetsovMA/lab1/experiments.py @@ -0,0 +1,185 @@ +import random +import time +import csv +import os + +from phonebook import * + +N = 10000 +REPEATS = 5 + +def generate_test_data(): + records = [ + (f"User_{i:05d}", f"+7900000{i:04d}") + for i in range(N) + ] + + records_shuffled = records.copy() + random.shuffle(records_shuffled) + + records_sorted = records.copy() + + return records_shuffled, records_sorted + +def measure_experiment(insert_function, find_function, delete_function, records): + insert_times = [] + find_times = [] + delete_times = [] + + for _ in range(REPEATS): + structure = None + + start = time.perf_counter() + + for name, phone in records: + structure = insert_function(structure, name, phone) + + insert_times.append(time.perf_counter() - start) + + structure_for_find = structure + + names = [name for name, phone in records] + search_names = random.sample(names, 100) + [ + "NotFound_001", + "NotFound_002", + "NotFound_003", + "NotFound_004", + "NotFound_005", + "NotFound_006", + "NotFound_007", + "NotFound_008", + "NotFound_009", + "NotFound_010" + ] + + for _ in range(REPEATS): + start = time.perf_counter() + + for name in search_names: + find_function(structure_for_find, name) + + find_times.append(time.perf_counter() - start) + + delete_names = random.sample(names, 50) + + for _ in range(REPEATS): + structure = structure_for_find + + start = time.perf_counter() + + for name in delete_names: + structure = delete_function(structure, name) + + delete_times.append(time.perf_counter() - start) + + return insert_times, find_times, delete_times + +def measure_hash(records): + insert_times = [] + find_times = [] + delete_times = [] + + names = [name for name, phone in records] + search_names = random.sample(names, 100) + [ + f"NotFound_{i:03d}" for i in range(10) + ] + delete_names = random.sample(names, 50) + + for _ in range(REPEATS): + buckets = ht_create() + + start = time.perf_counter() + + for name, phone in records: + ht_insert(buckets, name, phone) + + insert_times.append(time.perf_counter() - start) + + structure_for_find = buckets + + for _ in range(REPEATS): + start = time.perf_counter() + + for name in search_names: + ht_find(structure_for_find, name) + + find_times.append(time.perf_counter() - start) + + for _ in range(REPEATS): + buckets = structure_for_find.copy() + + start = time.perf_counter() + + for name in delete_names: + ht_delete(buckets, name) + + delete_times.append(time.perf_counter() - start) + + return insert_times, find_times, delete_times + +def average(values): + return sum(values) / len(values) + +def run(): + records_shuffled, records_sorted = generate_test_data() + + results = [] + + for order_name, records in [ + ("random", records_shuffled), + ("sorted", records_sorted) + ]: + print("Order:", order_name) + + ll = measure_experiment( + ll_insert, + ll_find, + ll_delete, + records + ) + + results.append(["LinkedList", order_name, "insert", *ll[0]]) + results.append(["LinkedList", order_name, "find", *ll[1]]) + results.append(["LinkedList", order_name, "delete", *ll[2]]) + + ht = measure_hash(records) + + results.append(["HashTable", order_name, "insert", *ht[0]]) + results.append(["HashTable", order_name, "find", *ht[1]]) + results.append(["HashTable", order_name, "delete", *ht[2]]) + + bst = measure_experiment( + bst_insert, + bst_find, + bst_delete, + records + ) + + results.append(["BST", order_name, "insert", *bst[0]]) + results.append(["BST", order_name, "find", *bst[1]]) + results.append(["BST", order_name, "delete", *bst[2]]) + + os.makedirs("docs/data", exist_ok=True) + + with open("docs/data/results.csv", "w", newline="", encoding="utf-8") as file: + writer = csv.writer(file) + + writer.writerow([ + "structure", + "order", + "operation", + "run1", + "run2", + "run3", + "run4", + "run5", + "average" + ]) + + for row in results: + writer.writerow(row + [average(row[3:])]) + + print("Results saved to docs/data/results.csv") + +if __name__ == "__main__": + run() \ No newline at end of file diff --git a/KuznetsovMA/lab1/graphs.py b/KuznetsovMA/lab1/graphs.py new file mode 100644 index 00000000..d6598544 --- /dev/null +++ b/KuznetsovMA/lab1/graphs.py @@ -0,0 +1,56 @@ +import csv +import os +import matplotlib.pyplot as plt + +data = [] + +with open("docs/data/results.csv", "r", encoding="utf-8") as file: + reader = csv.DictReader(file) + + for row in reader: + data.append(row) + +def get_average(structure, order, operation): + for row in data: + if ( + row["structure"] == structure + and row["order"] == order + and row["operation"] == operation + ): + return float(row["average"]) + + return 0 + +structures = ["LinkedList", "HashTable", "BST"] +orders = ["random", "sorted"] + +os.makedirs("docs/data", exist_ok=True) + +for operation in ["insert", "find", "delete"]: + random_values = [ + get_average(s, "random", operation) + for s in structures + ] + + sorted_values = [ + get_average(s, "sorted", operation) + for s in structures + ] + + x = range(len(structures)) + + plt.figure() + plt.bar([i - 0.2 for i in x], random_values, width=0.4, label="random") + plt.bar([i + 0.2 for i in x], sorted_values, width=0.4, label="sorted") + + plt.xticks(list(x), structures) + plt.ylabel("Time, seconds") + plt.title(operation.capitalize() + " time") + plt.yscale("log") + plt.legend() + plt.tight_layout() + + plt.savefig("docs/data/graph_" + operation + ".png") + plt.close() + +print("Graphs saved to docs/data/") \ No newline at end of file diff --git a/KuznetsovMA/lab1/phonebook.py b/KuznetsovMA/lab1/phonebook.py new file mode 100644 index 00000000..31163af3 --- /dev/null +++ b/KuznetsovMA/lab1/phonebook.py @@ -0,0 +1,211 @@ +def ll_insert(head, name, phone): + new_node = { + 'name': name, + 'phone': phone, + 'next': None + } + + if head is None: + return new_node + + current = head + + while current['next'] is not None: + if current['name'] == name: + current['phone'] = phone + return head + current = current['next'] + + if current['name'] == name: + current['phone'] = phone + else: + current['next'] = new_node + + return head + +def ll_find(head, name): + current = head + + while current is not None: + if current['name'] == name: + return current['phone'] + current = current['next'] + + return None + +def ll_delete(head, name): + if head is None: + return None + + if head['name'] == name: + return head['next'] + + current = head + + while current['next'] is not None: + if current['next']['name'] == name: + current['next'] = current['next']['next'] + return head + + current = current['next'] + + return head + +def ll_list_all(head): + records = [] + current = head + + while current is not None: + records.append((current['name'], current['phone'])) + current = current['next'] + + records.sort(key=lambda x: x[0]) + return records + +def hash_function(name, table_size): + total = 0 + + for ch in name: + total = (total * 31 + ord(ch)) % table_size + + return total + +def ht_create(size=1000): + return [None] * size + +def ht_insert(buckets, name, phone): + index = hash_function(name, len(buckets)) + buckets[index] = ll_insert(buckets[index], name, phone) + return buckets + +def ht_find(buckets, name): + index = hash_function(name, len(buckets)) + return ll_find(buckets[index], name) + +def ht_delete(buckets, name): + index = hash_function(name, len(buckets)) + buckets[index] = ll_delete(buckets[index], name) + return buckets + +def ht_list_all(buckets): + records = [] + + for bucket in buckets: + current = bucket + + while current is not None: + records.append((current['name'], current['phone'])) + current = current['next'] + + records.sort(key=lambda x: x[0]) + return records + +def bst_insert(root, name, phone): + new_node = { + 'name': name, + 'phone': phone, + 'left': None, + 'right': None + } + + if root is None: + return new_node + + current = root + + while True: + if name < current['name']: + if current['left'] is None: + current['left'] = new_node + break + current = current['left'] + + elif name > current['name']: + if current['right'] is None: + current['right'] = new_node + break + current = current['right'] + + else: + current['phone'] = phone + break + + return root + +def bst_find(root, name): + current = root + + while current is not None: + if name == current['name']: + return current['phone'] + + if name < current['name']: + current = current['left'] + else: + current = current['right'] + + return None + +def bst_delete(root, name): + parent = None + current = root + + while current is not None and current['name'] != name: + parent = current + + if name < current['name']: + current = current['left'] + else: + current = current['right'] + + if current is None: + return root + + if current['left'] is None: + child = current['right'] + + elif current['right'] is None: + child = current['left'] + + else: + successor_parent = current + successor = current['right'] + + while successor['left'] is not None: + successor_parent = successor + successor = successor['left'] + + current['name'] = successor['name'] + current['phone'] = successor['phone'] + + if successor_parent['left'] == successor: + successor_parent['left'] = successor['right'] + else: + successor_parent['right'] = successor['right'] + + return root + + if parent is None: + return child + + if parent['left'] == current: + parent['left'] = child + else: + parent['right'] = child + + return root + +def bst_list_all(root): + records = [] + + def inorder(node): + if node is None: + return + + inorder(node['left']) + records.append((node['name'], node['phone'])) + inorder(node['right']) + + inorder(root) + + return records \ No newline at end of file