forked from UNN/2026-rff_mp
211 lines
7.3 KiB
Python
211 lines
7.3 KiB
Python
|
|
import random
|
|||
|
|
import time
|
|||
|
|
import csv
|
|||
|
|
import os
|
|||
|
|
|
|||
|
|
# --------------------- Реализация бинарного дерева поиска (итеративная) ---------------------
|
|||
|
|
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
|
|||
|
|
else:
|
|||
|
|
current = current['left']
|
|||
|
|
elif name > current['name']:
|
|||
|
|
if current['right'] is None:
|
|||
|
|
current['right'] = new_node
|
|||
|
|
break
|
|||
|
|
else:
|
|||
|
|
current = current['right']
|
|||
|
|
else: # имя уже существует — обновляем телефон
|
|||
|
|
current['phone'] = phone
|
|||
|
|
break
|
|||
|
|
return root
|
|||
|
|
|
|||
|
|
def bst_find(root, name):
|
|||
|
|
#Итеративный поиск. Возвращает phone или None.
|
|||
|
|
current = root
|
|||
|
|
while current:
|
|||
|
|
if name == current['name']:
|
|||
|
|
return current['phone']
|
|||
|
|
elif name < current['name']:
|
|||
|
|
current = current['left']
|
|||
|
|
else:
|
|||
|
|
current = current['right']
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def bst_find_min(node):
|
|||
|
|
#Возвращает узел с минимальным ключом в поддереве.
|
|||
|
|
while node['left']:
|
|||
|
|
node = node['left']
|
|||
|
|
return node
|
|||
|
|
|
|||
|
|
def bst_delete(root, name):
|
|||
|
|
#Итеративное удаление. Возвращает новый корень.
|
|||
|
|
# Сначала найдём удаляемый узел и его родителя
|
|||
|
|
parent = None
|
|||
|
|
current = root
|
|||
|
|
while current and current['name'] != name:
|
|||
|
|
parent = current
|
|||
|
|
if name < current['name']:
|
|||
|
|
current = current['left']
|
|||
|
|
else:
|
|||
|
|
current = current['right']
|
|||
|
|
if current is None: # узел не найден
|
|||
|
|
return root
|
|||
|
|
|
|||
|
|
# Случай 1: нет левого потомка
|
|||
|
|
if current['left'] is None:
|
|||
|
|
child = current['right']
|
|||
|
|
# Случай 2: нет правого потомка
|
|||
|
|
elif current['right'] is None:
|
|||
|
|
child = current['left']
|
|||
|
|
# Случай 3: два потомка
|
|||
|
|
else:
|
|||
|
|
# Находим минимальный узел в правом поддереве (преемник)
|
|||
|
|
min_parent = current
|
|||
|
|
min_node = current['right']
|
|||
|
|
while min_node['left']:
|
|||
|
|
min_parent = min_node
|
|||
|
|
min_node = min_node['left']
|
|||
|
|
# Копируем данные из min_node в current
|
|||
|
|
current['name'], current['phone'] = min_node['name'], min_node['phone']
|
|||
|
|
# Удаляем min_node (у него нет левого потомка)
|
|||
|
|
if min_parent['left'] == min_node:
|
|||
|
|
min_parent['left'] = min_node['right']
|
|||
|
|
else:
|
|||
|
|
min_parent['right'] = min_node['right']
|
|||
|
|
return root
|
|||
|
|
|
|||
|
|
# Подсоединяем child к parent
|
|||
|
|
if parent is None:
|
|||
|
|
return child
|
|||
|
|
if parent['left'] == current:
|
|||
|
|
parent['left'] = child
|
|||
|
|
else:
|
|||
|
|
parent['right'] = child
|
|||
|
|
return root
|
|||
|
|
|
|||
|
|
def bst_list_all(root):
|
|||
|
|
#Итеративный симметричный обход (inorder) без рекурсии, используя стек.
|
|||
|
|
result = []
|
|||
|
|
stack = []
|
|||
|
|
current = root
|
|||
|
|
while stack or current:
|
|||
|
|
while current:
|
|||
|
|
stack.append(current)
|
|||
|
|
current = current['left']
|
|||
|
|
current = stack.pop()
|
|||
|
|
result.append((current['name'], current['phone']))
|
|||
|
|
current = current['right']
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
# --------------------- Функции измерений ---------------------
|
|||
|
|
def generate_data(N=10000):
|
|||
|
|
records = []
|
|||
|
|
for i in range(N):
|
|||
|
|
name = f"User_{i:05d}"
|
|||
|
|
phone = f"8{random.randint(9000000000, 9999999999)}"
|
|||
|
|
records.append((name, phone))
|
|||
|
|
return records
|
|||
|
|
|
|||
|
|
def measure_insert(records):
|
|||
|
|
root = None
|
|||
|
|
start = time.perf_counter()
|
|||
|
|
for name, phone in records:
|
|||
|
|
root = bst_insert(root, name, phone)
|
|||
|
|
end = time.perf_counter()
|
|||
|
|
return end - start
|
|||
|
|
|
|||
|
|
def measure_find(records, test_names):
|
|||
|
|
root = None
|
|||
|
|
for name, phone in records:
|
|||
|
|
root = bst_insert(root, name, phone)
|
|||
|
|
start = time.perf_counter()
|
|||
|
|
for name in test_names:
|
|||
|
|
bst_find(root, name)
|
|||
|
|
end = time.perf_counter()
|
|||
|
|
return end - start
|
|||
|
|
|
|||
|
|
def measure_delete(records, delete_names):
|
|||
|
|
times = []
|
|||
|
|
for name in delete_names:
|
|||
|
|
root = None
|
|||
|
|
for n, p in records:
|
|||
|
|
root = bst_insert(root, n, p)
|
|||
|
|
start = time.perf_counter()
|
|||
|
|
root = bst_delete(root, name)
|
|||
|
|
end = time.perf_counter()
|
|||
|
|
times.append(end - start)
|
|||
|
|
return sum(times) / len(times)
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
N = 10000
|
|||
|
|
records = generate_data(N)
|
|||
|
|
|
|||
|
|
records_shuffled = records.copy()
|
|||
|
|
random.shuffle(records_shuffled)
|
|||
|
|
records_sorted = sorted(records, key=lambda x: x[0])
|
|||
|
|
|
|||
|
|
existing_names = random.sample([rec[0] for rec in records], 100)
|
|||
|
|
non_existing = [f"None_{i}" for i in range(10)]
|
|||
|
|
test_names = existing_names + non_existing
|
|||
|
|
|
|||
|
|
delete_names = random.sample([rec[0] for rec in records], 50)
|
|||
|
|
|
|||
|
|
insert_shuffled_avg = 0.0
|
|||
|
|
insert_sorted_avg = 0.0
|
|||
|
|
find_avg = 0.0
|
|||
|
|
delete_avg = 0.0
|
|||
|
|
|
|||
|
|
repeats = 5
|
|||
|
|
for _ in range(repeats):
|
|||
|
|
insert_shuffled_avg += measure_insert(records_shuffled)
|
|||
|
|
insert_sorted_avg += measure_insert(records_sorted)
|
|||
|
|
find_avg += measure_find(records, test_names)
|
|||
|
|
delete_avg += measure_delete(records, delete_names)
|
|||
|
|
|
|||
|
|
insert_shuffled_avg /= repeats
|
|||
|
|
insert_sorted_avg /= repeats
|
|||
|
|
find_avg /= repeats
|
|||
|
|
delete_avg /= repeats
|
|||
|
|
|
|||
|
|
new_rows = [
|
|||
|
|
["Binary tree", "случайный", "вставка (корень)", insert_shuffled_avg],
|
|||
|
|
["Binary tree", "отсортированный", "вставка (корень)", insert_sorted_avg],
|
|||
|
|
["Binary tree", "любой", "поиск 110 записей", find_avg],
|
|||
|
|
["Binary tree", "любой", "удаление 50 записей (среднее)", delete_avg]
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
csv_filename = "results.csv"
|
|||
|
|
file_exists = os.path.isfile(csv_filename)
|
|||
|
|
need_header = False
|
|||
|
|
if file_exists:
|
|||
|
|
with open(csv_filename, 'r', encoding='utf-8-sig') as f:
|
|||
|
|
first_line = f.readline()
|
|||
|
|
if not first_line.startswith("Структура"):
|
|||
|
|
need_header = True
|
|||
|
|
else:
|
|||
|
|
need_header = True
|
|||
|
|
|
|||
|
|
with open(csv_filename, 'a', newline='', encoding='utf-8-sig') as f:
|
|||
|
|
writer = csv.writer(f, delimiter=';')
|
|||
|
|
if need_header:
|
|||
|
|
writer.writerow(["Структура", "Режим", "Операция", "Время (сек)"])
|
|||
|
|
writer.writerows(new_rows)
|
|||
|
|
|
|||
|
|
print("Результаты для двоичного дерева поиска добавлены в", csv_filename)
|
|||
|
|
print(f"Среднее время вставки (случ. порядок): {insert_shuffled_avg:.6f} сек")
|
|||
|
|
print(f"Среднее время вставки (отсорт.): {insert_sorted_avg:.6f} сек")
|
|||
|
|
print(f"Среднее время поиска 110 записей: {find_avg:.6f} сек")
|
|||
|
|
print(f"Среднее время удаления 50 записей: {delete_avg:.6f} сек")
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|