268 lines
9.0 KiB
Python
268 lines
9.0 KiB
Python
"""
|
||
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: все самопроверки пройдены")
|