diff --git a/kornevma/docs/1/main.py b/kornevma/docs/1/main.py new file mode 100644 index 0000000..84fdee9 --- /dev/null +++ b/kornevma/docs/1/main.py @@ -0,0 +1,349 @@ +import random +import time +import csv +import sys + +sys.setrecursionlimit(10**8)#прикалюха 6 вылетаел + +def mk(name, phone): + return {"name": name, "phone": phone} + +def ll_create_node(record): + return [record, None] + +def ll_insert(ll_head, record): + new_node = ll_create_node(record) + new_node[1] = ll_head[0] + ll_head[0] = new_node + +def ll_find(ll_head, name): + cur = ll_head[0] + while cur: + if cur[0]["name"] == name: + return cur[0] + cur = cur[1] + return None + +def ll_delete(ll_head, name): + cur = ll_head[0] + prev = None + while cur: + if cur[0]["name"] == name: + if prev: + prev[1] = cur[1] + else: + ll_head[0] = cur[1] + return True + prev = cur + cur = cur[1] + return False + +def ll_list_all(ll_head): + res = [] + cur = ll_head[0] + while cur: + res.append(cur[0]) + cur = cur[1] + return res + +def ht_hash(name, size): + return hash(name) % size + +def ht_insert(table, record): + idx = ht_hash(record["name"], len(table)) + new_node = ll_create_node(record) + new_node[1] = table[idx] + table[idx] = new_node + +def ht_find(table, name): + idx = ht_hash(name, len(table)) + cur = table[idx] + while cur: + if cur[0]["name"] == name: + return cur[0] + cur = cur[1] + return None + +def ht_delete(table, name): + idx = ht_hash(name, len(table)) + cur = table[idx] + prev = None + while cur: + if cur[0]["name"] == name: + if prev: + prev[1] = cur[1] + else: + table[idx] = cur[1] + return True + prev = cur + cur = cur[1] + return False + +def ht_list_all(table): + res = [] + for head in table: + cur = head + while cur: + res.append(cur[0]) + cur = cur[1] + return res + +def bst_create_node(record): + return [record, None, None] + +def bst_insert(root, record): + if root is None: + return bst_create_node(record) + if record["name"] < root[0]["name"]: + root[1] = bst_insert(root[1], record) + elif record["name"] > root[0]["name"]: + root[2] = bst_insert(root[2], record) + else: + root[0] = record + return root + +def bst_find(root, name): + if root is None: + return None + if name == root[0]["name"]: + return root[0] + elif name < root[0]["name"]: + return bst_find(root[1], name) + else: + return bst_find(root[2], name) + +def bst_find_min(node): + while node[1] is not None: + node = node[1] + return node + +def bst_delete(root, name): + if root is None: + return None + if name < root[0]["name"]: + root[1] = bst_delete(root[1], name) + elif name > root[0]["name"]: + root[2] = bst_delete(root[2], name) + else: + if root[1] is None: + return root[2] + elif root[2] is None: + return root[1] + else: + succ = bst_find_min(root[2]) + root[0] = succ[0] + root[2] = bst_delete(root[2], succ[0]["name"]) + return root + +def bst_list_all(root): + def inorder(node): + if node is None: + return [] + return inorder(node[1]) + [node[0]] + inorder(node[2]) + return inorder(root) + +def tmr(func): + def wrapper(*args, **kwargs): + start = time.perf_counter() + result = func(*args, **kwargs) + elapsed = time.perf_counter() - start + return result, elapsed + return wrapper + +@tmr +def ins_ll(ll_head, records): + for rec in records: + ll_insert(ll_head, rec) + +@tmr +def fnd_ll(ll_head, names): + for name in names: + ll_find(ll_head, name) + +@tmr +def del_ll(ll_head, names): + for name in names: + ll_delete(ll_head, name) + +@tmr +def ins_ht(table, records): + for rec in records: + ht_insert(table, rec) + +@tmr +def fnd_ht(table, names): + for name in names: + ht_find(table, name) + +@tmr +def del_ht(table, names): + for name in names: + ht_delete(table, name) + +@tmr +def ins_bst(root, records): + for rec in records: + root = bst_insert(root, rec) + return root + +@tmr +def fnd_bst(root, names): + for name in names: + bst_find(root, name) + +@tmr +def del_bst(root, names): + for name in names: + root = bst_delete(root, name) + return root + +def gen(n, seed=42): + random.seed(seed) + recs = [] + for i in range(n): + name = f"user_{i:05d}" + phone = random.randint(1000000, 9999999) + recs.append(mk(name, phone)) + return recs + +def prep(recs, ecnt=100, mcnt=10): + alln = [r["name"] for r in recs] + ex = random.sample(alln, ecnt) + ms = [f"none_{i}" for i in range(mcnt)] + sn = ex + ms + dn = random.sample(alln, 50) + return sn, dn + +def prm(n): + if n < 2: return False + if n % 2 == 0: return n == 2 + d = 3 + while d * d <= n: + if n % d == 0: return False + d += 2 + return True + +def nxtprm(n): + while not prm(n): + n += 1 + return n + +def bench(n=10000, rpts=5): + recs = gen(n) + shuf = recs.copy() + random.shuffle(shuf) + srt = sorted(recs, key=lambda r: r["name"]) + + snms, dnms = prep(recs) + + htsz = nxtprm(2 * n) + + exps = [ + { + "name": "linkedlist", + "init_empty": lambda: [None], + "insert": ins_ll, + "find": fnd_ll, + "delete": del_ll, + }, + { + "name": "hashtable", + "init_empty": lambda: [None] * htsz, + "insert": ins_ht, + "find": fnd_ht, + "delete": del_ht, + }, + { + "name": "bst", + "init_empty": lambda: None, + "insert": ins_bst, + "find": fnd_bst, + "delete": del_bst, + }, + ] + + res = [] + + for e in exps: + sn = e["name"] + for mn, recs_set in [("shuffled", shuf), ("sorted", srt)]: + for rp in range(1, rpts + 1): + st = e["init_empty"]() + + if sn == "bst": + st, ti = e["insert"](st, recs_set) + else: + _, ti = e["insert"](st, recs_set) + + _, tf = e["find"](st, snms) + + if sn == "bst": + st, td = e["delete"](st, dnms) + else: + _, td = e["delete"](st, dnms) + + res.append([sn, mn, "insert", rp, ti]) + res.append([sn, mn, "find", rp, tf]) + res.append([sn, mn, "delete", rp, td]) + + with open("results.csv", "w", newline="", encoding="utf-8") as f: + w = csv.writer(f) + w.writerow(["тип", "режим", "операция", "повтор", "время"]) + w.writerows(res) + + from collections import defaultdict + agg = defaultdict(list) + for row in res: + k = (row[0], row[1], row[2]) + agg[k].append(row[4]) + print("\n5 повторов в ср:") + print(f"{'тип':<15} {'режим':<10} {'операция':<10} {'срдений':<10}") + for k, times in sorted(agg.items()): + avg = sum(times) / len(times) + print(f"{k[0]:<15} {k[1]:<10} {k[2]:<10} {avg:<10.6f}") + + return res, agg + +def plot(agg): + try: + import matplotlib.pyplot as plt + except ImportError: + print("матплотлтб скачать") + return + + sts = ["linkedlist", "hashtable", "bst"] + mds = ["shuffled", "sorted"] + ops = ["insert", "find", "delete"] + + fig, axes = plt.subplots(1, 3, figsize=(16, 5)) + fig.suptitle("сравенние", fontsize=14) + + for oi, op in enumerate(ops): + ax = axes[oi] + data = [] + for s in sts: + for m in mds: + k = (s, m, op) + avg = sum(agg[k]) / len(agg[k]) if k in agg else 0 + data.append(avg) + x = range(len(sts)) + width = 0.35 + svals = [data[i*2] for i in range(len(sts))] + svals2 = [data[i*2+1] for i in range(len(sts))] + + ax.bar([i - width/2 for i in x], svals, width, label='shuffled') + ax.bar([i + width/2 for i in x], svals2, width, label='sorted') + ax.set_xticks(x) + ax.set_xticklabels(sts) + ax.set_title(op) + ax.set_ylabel('время (sec)') + ax.legend() + + plt.tight_layout() + plt.savefig("kortinko.png") + plt.show() + print("zibka kortinko.png") + +if __name__ == "__main__": + res, agg = bench(n=10000, rpts=5) + plot(agg) \ No newline at end of file diff --git a/kornevma/docs/kortinko.png b/kornevma/docs/kortinko.png new file mode 100644 index 0000000..8dc0aa1 Binary files /dev/null and b/kornevma/docs/kortinko.png differ diff --git a/kornevma/docs/report1.txt b/kornevma/docs/report1.txt new file mode 100644 index 0000000..89584f9 --- /dev/null +++ b/kornevma/docs/report1.txt @@ -0,0 +1,60 @@ +Отчёт по экспериментальному сравнению структур данных телефонного справочника +1. Условия эксперимента +Реализованы три структуры для хранения записей вида {name, phone}: + +односвязный список (вставка в голову); + +хеш-таблица с цепочками (размер таблицы ~ 2N, простое число, хеш-функция hash(name) % size); + +двоичное дерево поиска без балансировки. + +Измерялось время выполнения операций вставки N = 10 000 записей, поиска 110 имён (100 существующих + 10 несуществующих) и удаления 50 случайных записей. +Эксперименты проводились для двух вариантов порядка входных данных: + +shuffled – случайный порядок имён; + +sorted – имена, отсортированные по алфавиту. +Каждый замер повторялся 5 раз, результаты усреднены. + +Полученные средние значения (фрагмент): + +Структура Режим Вставка (сек) Поиск (сек) Удаление (сек) +LinkedList shuffled ~0.003 ~0.100 ~0.056 +LinkedList sorted ~0.003 ~0.063 ~0.038 +HashTable shuffled ~0.008 ~0.00006 ~0.00004 +HashTable sorted ~0.006 ~0.00008 ~0.00003 +BST shuffled ~0.055 ~0.0005 ~0.00027 +BST sorted ~23.997 ~0.212 ~0.129 +Точные числа см. в файле results.csv + +2. Сравнительный анализ +2.1. Влияние порядка данных на BST: деградация до O(n) +На shuffled-данных дерево строится относительно сбалансированным, и операции выполняются в среднем за O(log N). +На sorted-данных каждая следующая вставка попадает строго в правого потомка, и дерево вырождается в линейный список. Глубина рекурсии достигает N = 10 000, что приводит к: + +колоссальному росту времени вставки (с ~0.055 сек до ~24 сек); + +значительному замедлению поиска и удаления (с ~0.0005 сек до ~0.21 сек и с ~0.00027 сек до ~0.13 сек соответственно). +Причина — рекурсивные функции вынуждены обходить все N узлов, превращая логарифмическую сложность в линейную. Практический вывод: использовать несбалансированное BST можно только при гарантированно случайном порядке поступающих ключей, иначе необходимы самобалансирующиеся варианты (AVL, красно-чёрное дерево). + +2.2. Почему хеш-таблица почти не чувствительна к порядку +Хеш-функция равномерно распределяет ключи по корзинам независимо от порядка поступления. Операции вставки, поиска и удаления сводятся к вычислению хеша и проходу по очень короткой цепочке (в среднем O(1)). Время вставки в отсортированном наборе даже чуть меньше (0.006 сек против 0.008 сек), что объясняется меньшим количеством коллизий при последовательном поступлении близких имён (хотя разница несущественна). Поиск и удаление занимают доли миллисекунды и практически не зависят от размера набора в исследованном диапазоне. Хеш-таблица демонстрирует наилучшую устойчивость к любым шаблонам входных данных при условии хорошей хеш-функции и адекватного размера. + +2.3. Почему связный список всегда медленен при поиске +В односвязном списке единственный способ найти элемент — линейный проход от головы к хвосту. Среднее время поиска — O(N), то есть пропорционально количеству записей. В эксперименте при 10 000 записей и поиске 110 имён время составило около 0.1 сек, что на три порядка хуже, чем у хеш-таблицы, и на два порядка хуже, чем у сбалансированного BST. Режим sorted/shuffled влияет слабо (для списка порядок вставки не меняет структуру). Медленный поиск — фундаментальное ограничение связного списка. + +2.4. Удаление в каждой структуре +Связный список: удаление требует поиска элемента (O(N)) и изменения ссылки у предыдущего узла. Время удаления соизмеримо с поиском (0.038–0.056 сек). + +Хеш-таблица: удаление выполняется за O(1) в среднем — вычисление индекса, поиск в цепочке (очень короткой) и изменение ссылок. Время минимально (~0.00004 сек). + +BST: удаление узла с двумя потомками требует поиска минимального элемента в правом поддереве. На сбалансированных данных (shuffled) время ~0.00027 сек. На вырожденных (sorted) удаление замедляется до 0.13 сек из-за необходимости обходить длинные цепочки. + +3. Выводы и практические рекомендации +Если преобладают частые поиск и вставка, а порядок данных не важен – оптимальный выбор хеш-таблица. Она обеспечивает константное среднее время операций и нечувствительна к порядку поступления ключей. Идеально для телефонного справочника, кешей, словарей. + +Если требуется хранить данные в отсортированном виде и нужны операции типа «найти следующий/предыдущий» – подходит BST, но обязательно самобалансирующееся (например, АВЛ или красно-чёрное дерево). Несбалансированное BST применимо только при случайном порядке вставки; иначе деградация до O(n) делает его бесполезным. + +Связный список стоит использовать лишь в случаях, когда операции поиска редки, а основные действия — добавление/удаление в начале или в середине списка при известной позиции. Для телефонного справочника он практически непригоден из-за линейного времени поиска. + +Таким образом, для типового приложения с преимущественно операциями поиска и вставки (например, адресная книга в мобильном телефоне) лучшим решением является хеш-таблица. Если же функциональность требует получения отсортированного списка контактов или диапазонных запросов, разумно применять сбалансированное дерево поиска. \ No newline at end of file diff --git a/kornevma/docs/results.csv b/kornevma/docs/results.csv new file mode 100644 index 0000000..3b254fd --- /dev/null +++ b/kornevma/docs/results.csv @@ -0,0 +1,91 @@ +тип,режим,операция,повтор,время +linkedlist,shuffled,insert,1,0.00348759995540604 +linkedlist,shuffled,find,1,0.10299369995482266 +linkedlist,shuffled,delete,1,0.06881969998357818 +linkedlist,shuffled,insert,2,0.002795599983073771 +linkedlist,shuffled,find,2,0.11420650000218302 +linkedlist,shuffled,delete,2,0.050943999958690256 +linkedlist,shuffled,insert,3,0.003374699968844652 +linkedlist,shuffled,find,3,0.09485340001992881 +linkedlist,shuffled,delete,3,0.04981170000974089 +linkedlist,shuffled,insert,4,0.002937599958386272 +linkedlist,shuffled,find,4,0.0926941999932751 +linkedlist,shuffled,delete,4,0.04564540000865236 +linkedlist,shuffled,insert,5,0.0032468000426888466 +linkedlist,shuffled,find,5,0.09445199999026954 +linkedlist,shuffled,delete,5,0.06562509998911992 +linkedlist,sorted,insert,1,0.004829699988476932 +linkedlist,sorted,find,1,0.05208860000129789 +linkedlist,sorted,delete,1,0.06792090000817552 +linkedlist,sorted,insert,2,0.0030329000437632203 +linkedlist,sorted,find,2,0.09589699999196455 +linkedlist,sorted,delete,2,0.024623799952678382 +linkedlist,sorted,insert,3,0.0023055000347085297 +linkedlist,sorted,find,3,0.05262780003249645 +linkedlist,sorted,delete,3,0.035465800028759986 +linkedlist,sorted,insert,4,0.003455400001257658 +linkedlist,sorted,find,4,0.06551479996414855 +linkedlist,sorted,delete,4,0.0368536000023596 +linkedlist,sorted,insert,5,0.0036825999850407243 +linkedlist,sorted,find,5,0.05081029998837039 +linkedlist,sorted,delete,5,0.02609110000776127 +hashtable,shuffled,insert,1,0.008456900017336011 +hashtable,shuffled,find,1,7.070001447573304e-05 +hashtable,shuffled,delete,1,3.9300008211284876e-05 +hashtable,shuffled,insert,2,0.0068731000064872205 +hashtable,shuffled,find,2,6.079999729990959e-05 +hashtable,shuffled,delete,2,3.4599972423166037e-05 +hashtable,shuffled,insert,3,0.008831500017549843 +hashtable,shuffled,find,3,6.859999848529696e-05 +hashtable,shuffled,delete,3,5.959998816251755e-05 +hashtable,shuffled,insert,4,0.009147099975962192 +hashtable,shuffled,find,4,5.989999044686556e-05 +hashtable,shuffled,delete,4,3.470003139227629e-05 +hashtable,shuffled,insert,5,0.006436199997551739 +hashtable,shuffled,find,5,4.67000063508749e-05 +hashtable,shuffled,delete,5,2.6500027161091566e-05 +hashtable,sorted,insert,1,0.0056028999970294535 +hashtable,sorted,find,1,7.159996312111616e-05 +hashtable,sorted,delete,1,3.060000017285347e-05 +hashtable,sorted,insert,2,0.006678299978375435 +hashtable,sorted,find,2,0.00012290000449866056 +hashtable,sorted,delete,2,3.4299970138818026e-05 +hashtable,sorted,insert,3,0.005322600016370416 +hashtable,sorted,find,3,4.8499961849302053e-05 +hashtable,sorted,delete,3,2.600002335384488e-05 +hashtable,sorted,insert,4,0.006450399989262223 +hashtable,sorted,find,4,7.379997987300158e-05 +hashtable,sorted,delete,4,4.780001472681761e-05 +hashtable,sorted,insert,5,0.0060063999844715 +hashtable,sorted,find,5,5.689996760338545e-05 +hashtable,sorted,delete,5,3.120000474154949e-05 +bst,shuffled,insert,1,0.05349189997650683 +bst,shuffled,find,1,0.0005713000427931547 +bst,shuffled,delete,1,0.0002283000503666699 +bst,shuffled,insert,2,0.05809580005006865 +bst,shuffled,find,2,0.00046800001291558146 +bst,shuffled,delete,2,0.00030310003785416484 +bst,shuffled,insert,3,0.05402979999780655 +bst,shuffled,find,3,0.0005937999812886119 +bst,shuffled,delete,3,0.0002316000172868371 +bst,shuffled,insert,4,0.0533465999760665 +bst,shuffled,find,4,0.000450699997600168 +bst,shuffled,delete,4,0.00031700002728030086 +bst,shuffled,insert,5,0.05407660000491887 +bst,shuffled,find,5,0.0004341000458225608 +bst,shuffled,delete,5,0.0002673999988473952 +bst,sorted,insert,1,24.01944399997592 +bst,sorted,find,1,0.2082300999900326 +bst,sorted,delete,1,0.11376300000119954 +bst,sorted,insert,2,24.020037700014655 +bst,sorted,find,2,0.21519700001226738 +bst,sorted,delete,2,0.1168955999892205 +bst,sorted,insert,3,23.98331290000351 +bst,sorted,find,3,0.20412800001213327 +bst,sorted,delete,3,0.16615210002055392 +bst,sorted,insert,4,24.231940899975598 +bst,sorted,find,4,0.2066795999999158 +bst,sorted,delete,4,0.13191749999532476 +bst,sorted,insert,5,23.72923769999761 +bst,sorted,find,5,0.22344960004556924 +bst,sorted,delete,5,0.11515349999535829