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)
|