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