add ll, ht and bst

This commit is contained in:
SobolevNS 2026-05-22 12:18:09 +03:00
parent 82e988c965
commit 666bc0af37

View File

@ -0,0 +1,267 @@
"""
phonebook.py
Три структуры данных для хранения телефонного справочника,
реализованные в процедурной парадигме (без классов).
Узлы и контейнеры представляются обычными словарями и списками.
"""
import sys
# Увеличиваем лимит рекурсии - нужно для BST в худшем случае
sys.setrecursionlimit(200_000)
# ============================================================
# 1. СВЯЗНЫЙ СПИСОК
# Узел: {'name': str, 'phone': str, 'next': dict|None}
# Голова списка - это либо узел, либо None (пустой список)
# ============================================================
def ll_create():
"""Создаёт пустой связный список."""
return None
def ll_insert(head, name, phone):
"""Вставляет или обновляет запись. Возвращает новую голову списка."""
# Если списка нет - создаём первый узел
if head is None:
return {'name': name, 'phone': phone, 'next': None}
# Если совпадение в голове - просто обновляем телефон
node = head
while node is not None:
if node['name'] == name:
node['phone'] = phone
return head
if node['next'] is None:
break
node = node['next']
# node - последний узел, добавляем после него
node['next'] = {'name': name, 'phone': phone, 'next': None}
return head
def ll_find(head, name):
"""Возвращает phone или None."""
node = head
while node is not None:
if node['name'] == name:
return node['phone']
node = node['next']
return None
def ll_delete(head, name):
"""Удаляет узел по имени. Возвращает новую голову (она могла измениться)."""
if head is None:
return None
# Удаление головы
if head['name'] == name:
return head['next']
prev = head
cur = head['next']
while cur is not None:
if cur['name'] == name:
prev['next'] = cur['next']
return head
prev = cur
cur = cur['next']
# Не нашли - игнорируем
return head
def ll_collect(head):
"""Возвращает несортированный список (name, phone) - служебная функция."""
out = []
node = head
while node is not None:
out.append((node['name'], node['phone']))
node = node['next']
return out
def ll_list_all(head):
"""Возвращает все записи, отсортированные по имени."""
items = ll_collect(head)
items.sort(key=lambda x: x[0])
return items
# ============================================================
# 2. ХЕШ-ТАБЛИЦА
# buckets - список фиксированной длины из голов связных списков
# ============================================================
def ht_create(size=1024):
"""Создаёт пустую хеш-таблицу с заданным числом бакетов."""
return {
'size': size,
'buckets': [None] * size,
}
def _ht_index(name, size):
"""Хеш-функция: встроенный hash + остаток от деления."""
return hash(name) % size
def ht_insert(ht, name, phone):
"""Вставляет или обновляет запись в нужном бакете."""
idx = _ht_index(name, ht['size'])
ht['buckets'][idx] = ll_insert(ht['buckets'][idx], name, phone)
def ht_find(ht, name):
"""Возвращает phone или None."""
idx = _ht_index(name, ht['size'])
return ll_find(ht['buckets'][idx], name)
def ht_delete(ht, name):
"""Удаляет запись (если она есть)."""
idx = _ht_index(name, ht['size'])
ht['buckets'][idx] = ll_delete(ht['buckets'][idx], name)
def ht_list_all(ht):
"""Собирает все записи из всех бакетов и сортирует по имени."""
out = []
for head in ht['buckets']:
out.extend(ll_collect(head))
out.sort(key=lambda x: x[0])
return out
# ============================================================
# 3. ДВОИЧНОЕ ДЕРЕВО ПОИСКА
# Узел: {'name': str, 'phone': str, 'left': dict|None, 'right': dict|None}
# Корень - узел или None
# ============================================================
def bst_create():
"""Создаёт пустое BST."""
return None
def bst_insert(root, name, phone):
"""Вставляет/обновляет. Итеративная реализация, чтобы не упереться в рекурсию
при отсортированном входе. Возвращает новый корень."""
new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}
if root is None:
return new_node
cur = root
while True:
if name == cur['name']:
cur['phone'] = phone
return root
if name < cur['name']:
if cur['left'] is None:
cur['left'] = new_node
return root
cur = cur['left']
else:
if cur['right'] is None:
cur['right'] = new_node
return root
cur = cur['right']
def bst_find(root, name):
"""Возвращает phone или None. Итеративный поиск."""
cur = root
while cur is not None:
if name == cur['name']:
return cur['phone']
cur = cur['left'] if name < cur['name'] else cur['right']
return None
def _bst_min_node(node):
"""Возвращает узел с минимальным ключом в поддереве."""
while node['left'] is not None:
node = node['left']
return node
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_min_node(root['right'])
root['name'] = successor['name']
root['phone'] = successor['phone']
root['right'] = bst_delete(root['right'], successor['name'])
return root
def bst_list_all(root):
"""Центрированный обход (in-order) - сразу даёт отсортированный по имени список.
Итеративный, чтобы не упасть на вырожденном дереве."""
out = []
stack = []
cur = root
while cur is not None or stack:
while cur is not None:
stack.append(cur)
cur = cur['left']
cur = stack.pop()
out.append((cur['name'], cur['phone']))
cur = cur['right']
return out
# ============================================================
# Маленький self-test, запускается только при прямом вызове
# ============================================================
if __name__ == "__main__":
data = [("Alice", "111"), ("Bob", "222"), ("Charlie", "333"),
("Dave", "444"), ("Eve", "555")]
# LinkedList
head = ll_create()
for n, p in data:
head = ll_insert(head, n, p)
assert ll_find(head, "Bob") == "222"
assert ll_find(head, "Nope") is None
head = ll_delete(head, "Bob")
assert ll_find(head, "Bob") is None
assert ll_list_all(head) == sorted([(n, p) for n, p in data if n != "Bob"])
# HashTable
ht = ht_create(size=8)
for n, p in data:
ht_insert(ht, n, p)
assert ht_find(ht, "Charlie") == "333"
ht_delete(ht, "Charlie")
assert ht_find(ht, "Charlie") is None
assert ht_list_all(ht) == sorted([(n, p) for n, p in data if n != "Charlie"])
# BST
root = bst_create()
for n, p in data:
root = bst_insert(root, n, p)
assert bst_find(root, "Dave") == "444"
root = bst_delete(root, "Dave")
assert bst_find(root, "Dave") is None
assert bst_list_all(root) == sorted([(n, p) for n, p in data if n != "Dave"])
print("phonebook.py: все самопроверки пройдены")