forked from UNN/2026-rff_mp
253 lines
6.0 KiB
Python
253 lines
6.0 KiB
Python
from MP_records import records
|
||
import random as rd
|
||
import time
|
||
import csv
|
||
import codecs
|
||
import sys
|
||
|
||
sys.setrecursionlimit(15000)
|
||
|
||
|
||
# ---------- Binary Search Tree ----------
|
||
# Узел:
|
||
# {
|
||
# "name": name,
|
||
# "phone": phone,
|
||
# "left": None,
|
||
# "right": None
|
||
# }
|
||
|
||
|
||
def bst_insert(root, name, phone):
|
||
"""
|
||
Вставляет новую запись или обновляет телефон по имени.
|
||
Возвращает корень дерева.
|
||
"""
|
||
if root is None:
|
||
return {
|
||
"name": name,
|
||
"phone": phone,
|
||
"left": None,
|
||
"right": None
|
||
}
|
||
|
||
if name == root["name"]:
|
||
root["phone"] = phone
|
||
|
||
elif name < root["name"]:
|
||
root["left"] = bst_insert(root["left"], name, phone)
|
||
|
||
else:
|
||
root["right"] = bst_insert(root["right"], name, phone)
|
||
|
||
return root
|
||
|
||
|
||
def bst_find(root, name):
|
||
"""
|
||
Поиск телефона по имени.
|
||
"""
|
||
if root is None:
|
||
return None
|
||
|
||
if name == root["name"]:
|
||
return root["phone"]
|
||
|
||
if name < root["name"]:
|
||
return bst_find(root["left"], name)
|
||
|
||
return bst_find(root["right"], name)
|
||
|
||
|
||
def bst_find_min(node):
|
||
"""
|
||
Возвращает узел с минимальным именем.
|
||
"""
|
||
current = node
|
||
|
||
while current["left"] is not None:
|
||
current = current["left"]
|
||
|
||
return current
|
||
|
||
|
||
def bst_delete(root, name):
|
||
"""
|
||
Удаляет запись по имени.
|
||
Возвращает новый корень дерева.
|
||
"""
|
||
if root is None:
|
||
return None
|
||
|
||
if name < root["name"]:
|
||
root["left"] = bst_delete(root["left"], name)
|
||
|
||
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["left"]
|
||
|
||
# Узел с двумя потомками
|
||
successor = bst_find_min(root["right"])
|
||
|
||
root["name"] = successor["name"]
|
||
root["phone"] = successor["phone"]
|
||
|
||
root["right"] = bst_delete(root["right"], successor["name"])
|
||
|
||
return root
|
||
|
||
|
||
def bst_inorder(root, result):
|
||
"""
|
||
Центрированный обход дерева.
|
||
"""
|
||
if root is None:
|
||
return
|
||
|
||
bst_inorder(root["left"], result)
|
||
|
||
result.append((root["name"], root["phone"]))
|
||
|
||
bst_inorder(root["right"], result)
|
||
|
||
|
||
def bst_list_all(root):
|
||
"""
|
||
Возвращает список записей в отсортированном порядке.
|
||
"""
|
||
result = []
|
||
bst_inorder(root, result)
|
||
return result
|
||
|
||
|
||
# ---------- Benchmark helpers ----------
|
||
|
||
def build_bst(records_list):
|
||
root = None
|
||
|
||
for name, phone in records_list:
|
||
root = bst_insert(root, name, phone)
|
||
|
||
return root
|
||
|
||
|
||
def measure_bst(records_list, mode_name, repeats=5):
|
||
rows = []
|
||
|
||
insertion_times = []
|
||
finding_times = []
|
||
deletion_times = []
|
||
|
||
for run_number in range(1, repeats + 1):
|
||
data = records_list[:]
|
||
|
||
if mode_name == "случайный":
|
||
rd.shuffle(data)
|
||
|
||
# А. Вставка
|
||
root = None
|
||
|
||
start = time.perf_counter()
|
||
|
||
for name, phone in data:
|
||
root = bst_insert(root, name, phone)
|
||
|
||
end = time.perf_counter()
|
||
|
||
insertion_time = end - start
|
||
insertion_times.append(insertion_time)
|
||
|
||
# Б. Поиск
|
||
existing_names = [name for name, phone in rd.sample(data, 100)]
|
||
missing_names = [f"None_{i}" for i in range(10)]
|
||
|
||
search_names = existing_names + missing_names
|
||
rd.shuffle(search_names)
|
||
|
||
start = time.perf_counter()
|
||
|
||
for name in search_names:
|
||
bst_find(root, name)
|
||
|
||
end = time.perf_counter()
|
||
|
||
finding_time = end - start
|
||
finding_times.append(finding_time)
|
||
|
||
# В. Удаление
|
||
delete_names = rd.sample(existing_names, 50)
|
||
|
||
start = time.perf_counter()
|
||
|
||
for name in delete_names:
|
||
root = bst_delete(root, name)
|
||
|
||
end = time.perf_counter()
|
||
|
||
deletion_time = end - start
|
||
deletion_times.append(deletion_time)
|
||
|
||
rows.append(["BinarySearchTree", mode_name, "вставка", run_number, insertion_time])
|
||
rows.append(["BinarySearchTree", mode_name, "поиск", run_number, finding_time])
|
||
rows.append(["BinarySearchTree", mode_name, "удаление", run_number, deletion_time])
|
||
|
||
rows.append(["BinarySearchTree", mode_name, "вставка", "среднее", sum(insertion_times) / repeats])
|
||
rows.append(["BinarySearchTree", mode_name, "поиск", "среднее", sum(finding_times) / repeats])
|
||
rows.append(["BinarySearchTree", mode_name, "удаление", "среднее", sum(deletion_times) / repeats])
|
||
|
||
return rows
|
||
|
||
|
||
def save_results(rows, filename="results.csv"):
|
||
with codecs.open(filename, "a+", "utf-16") as file:
|
||
writer = csv.writer(file)
|
||
writer.writerows(rows)
|
||
|
||
|
||
def run_shuffled(records_shuffled):
|
||
rows = measure_bst(records_shuffled, "случайный")
|
||
save_results(rows)
|
||
return rows
|
||
|
||
|
||
def run_sorted(records_sorted):
|
||
rows = measure_bst(records_sorted, "отсортированный")
|
||
save_results(rows)
|
||
return rows
|
||
|
||
|
||
# ---------- Manual tests ----------
|
||
|
||
def test():
|
||
root = None
|
||
|
||
root = bst_insert(root, "Ivan", "111")
|
||
root = bst_insert(root, "Anna", "222")
|
||
root = bst_insert(root, "Petr", "333")
|
||
root = bst_insert(root, "Maria", "444")
|
||
|
||
print(bst_find(root, "Anna")) # 222
|
||
print(bst_find(root, "Unknown")) # None
|
||
|
||
root = bst_insert(root, "Anna", "999")
|
||
print(bst_find(root, "Anna")) # 999
|
||
|
||
root = bst_delete(root, "Ivan")
|
||
|
||
print(bst_list_all(root))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
records_shuffled, records_sorted = records()
|
||
|
||
run_shuffled(records_shuffled)
|
||
run_sorted(records_sorted)
|