Занятие 9
Занятие 9: Связные списки и двоичные деревья.¶
Цели работы¶
- Связные списки.
- Деревья.
- Двоичные деревья поиска.
Связный список¶
Связный список – это структура данных, которая состоит из ячеек (узлов), содержащих хранимую в списке информацию и ссылку на следующую ячейку.
Удобство связного списка заключается в постоянном времени операции вставки и удаления отдельного элемента (узла): асимптотика этих операций равна $O(1)$ (для структуры list длины n асимптотика данных операций равна $O(n)$).
Первый узел в связном списке называется головой списка.
Однонаправленным связным (односвязным) списоком называется список, каждый узел которого связан только со следующим узлом: хранит ссылку только на следующий узел.
В двунаправленных списках ссылки ячеек указывают на следующий и предыдущий узлы.
На языке Python односвязный список можно реализовать с помощью словаря:
L = {
'head': None,
'size': 0,
}
headсодержит головной узел спискаLsizeхранит текущий размер списка.
Также на основе словаря можно создать узел списка:
node = {
'value': x,
'next': None
}
valueсодержит произвольного типа значение узлаnextсодержит ссылку на следующий узел списка илиNone, если данный узел является концевым.
У определенного выше пустого списка L голова является концом, а размер равен 0.
Задание 1 – Для определенных выше списка L и узла node создайте:
list_create()- конструктор списка, который создаёт и возвращает пустой списокlist_add(L, value)- функция, добавляет узел со значениемvalueв начало спискаL
Деревья¶
Дерево состоит из вершин (узлов), которые содержат данные (ключи) и соединяются ветвями. Ветви изображают в виде стрелок, ведущих от родительской вершины к дочерней.
Корневой называется вершина, не имеющая родителя.
У каждой вершины, за исключением корневой, есть одна родительская вершина. Дочерние вершины дочерних вершин называют потомками, а родительские вершины родительских вершин — предками.
Вершины, не имеющие потомков называются листьями.
Деревья предназначены, в первую очередь, для представления иерархических структур.
Двоичные деревья поиска¶
Дерево называется двоичным, если каждый его узел имеет не более двух прямых потомков: левый и правый.
Двоичное дерево называется деревом двоичного поиска (binary search tree), если для каждого узла x все ключи в левом поддереве, чем ключ в x, а все ключи в правом поддереве больше, чем ключ в x. Это свойство упорядоченности отличает дерево двоичного поиска от любого другого типа двоичных деревьев.
Упорядоченность ключей позволяет реализовать в двоичном дереве поиска упорядоченный обход: посещение всех узлов в ветви левого прямого потомка до посещения корня, а затем – все узлы в ветви правого прямого потомка после посещения корня. Результатом является обход узлов в отсортированном порядке в соответствии с упорядоченностью ключей.
Реализовать структуру данных "двоичное дерево" можно на основе классов: нам их понадобится два:
- класс Node для представления узла дерева
- класс Tree для представления двоичного дерева
Ниже приведено описание класса Node:
class Node:
def __init__(self, data):
self.data = data # Данные, хранящиеся в узле
self.left = None # Левый потомок. None, если ребенка нет
self.right = None # Правый потомок. None, если ребенка нет
Для создания экземпляра node класса Node, хранящего значение 2 необходимо выпонить код в ячейке ниже:
class Node:
def __init__(self, data):
self.data = data # Данные, хранящиеся в узле
self.left = None # Левый потомок. None, если ребенка нет
self.right = None # Правый потомок. None, если ребенка нет
node = Node(2)
Результат (в памяти) выполнеения кода выше приведен на рисунке:
Ниже код определения класса Tree:
class Tree:
def __init__(self):
self.root = None # Корневой узел. None, если корень не определен
Данное определение класса очевидно бесполезно, так как отсутствуют методы (функции) класса для построения и отображения дерева.
Дополним его определив метод add(data), который позволит добавить в дерево узел, хранящий данные data:
class Tree:
def __init__(self):
self.root = None # Корневой узел. None, если корень не определен
def add_at(self, node, data):
if node.data == data: # Узел с таким значением уже существует: ничего не делаем
return
if data < node.data: # Спускаемся влево
if node.left is None:
node.left = Node(data)
else:
self.add_at(node.left, data)
else: # Спускаемся вправо
if node.right is None:
node.right = Node(data)
else:
self.add_at(node.right, data)
def add(self, data):
if self.root is None:
self.root = Node(data)
return
self.add_at(self.root, data)
Поясним код. Бинарное дерево достраивается спуском по дереву; на каждом шаге спуска выбирается соотвествующая левая или правая ветка (в зависимости от того, меньше или больше значение data нового узла значения data, хранящегося в текущем узле дерева), пока не найдется место для вставки новой вершины (т.е ссылка на нужную ветвь равна None). Новая вершина добавляется как лист дерева, т.е. указатели на дочерние вершины left и right равны None. Если элемент уже существует в дереве, добавлять его не надо. Спуск по дереву реализован с помощью рекурсивного метода add_at().
Создадим дерево с узлами, хранящеми значения 3, 2, 1, 9, 5, 4, 6, 8 (выполните код в ячейке ниже):
class Node:
def __init__(self, data):
self.data = data # Данные, хранящиеся в узле
self.left = None # Левый потомок. None, если ребенка нет
self.right = None # Правый потомок. None, если ребенка нет
class Tree:
def __init__(self):
self.root = None # Корневой узел. None, если корень не определен
def add_at(self, node, data):
if node.data == data: # Узел с таким значением уже существует: ничего не делаем
return
if data < node.data: # Спускаемся влево
if node.left is None:
node.left = Node(data)
else:
self.add_at(node.left, data)
else: # Спускаемся вправо
if node.right is None:
node.right = Node(data)
else:
self.add_at(node.right, data)
def add(self, data):
if self.root is None:
self.root = Node(data)
return
self.add_at(self.root, data)
tree = Tree()
for x in [3, 2, 1, 9, 5, 4, 6, 8]:
tree.add(x)
Результат (в памяти) выполнеения кода выше приведен на рисунке:
Для того, чтобы иметь возможность выводить дерево на экран реализукем метод print(). Код метода print() реализует обход дерева в глубину слева направо, печатая значения data, хранящиеся в узлах, через пробел. Вызывает функцию печати левого поддерева, печатает значение в узле, вызывает функцию печати правого поддерева.
Определение класса примет следующий вид:
class Tree:
def __init__(self):
self.root = None # Корневой узел. None, если корень не определен
def add_at(self, node, data):
if node.data == data: # Узел с таким значением уже существует: ничего не делаем
return
if data < node.data: # Спускаемся влево
if node.left is None:
node.left = Node(data)
else:
self.add_at(node.left, data)
else: # Спускаемся вправо
if node.right is None:
node.right = Node(data)
else:
self.add_at(node.right, data)
def add(self, data):
if self.root is None:
self.root = Node(data)
return
self.add_at(self.root, data)
def print(self, node=None):
if node is None:
if self.root is None:
return
node = self.root
if node.left is not None:
self.print(node.left)
print(' ', end='')
print(node.data, end='')
if node.right is not None:
print(' ', end='')
self.print(node.right)
Код в ячейке ниже создает и выводит на экран двоичное дерево с ключами 7, 3, 2, 1, 9, 5, 4, 6, 8 при помощи методов add() и print(). Выполните его.
class Node:
def __init__(self, data):
self.data = data # Данные, хранящиеся в узле
self.left = None # Левый потомок. None, если ребенка нет
self.right = None # Правый потомок. None, если ребенка нет
class Tree:
def __init__(self):
self.root = None # Корневой узел. None, если корень не определен
def add_at(self, node, data):
if node.data == data: # Узел с таким значением уже существует: ничего не делаем
return
if data < node.data: # Спускаемся влево
if node.left is None:
node.left = Node(data)
else:
self.add_at(node.left, data)
else: # Спускаемся вправо
if node.right is None:
node.right = Node(data)
else:
self.add_at(node.right, data)
def add(self, data):
if self.root is None:
self.root = Node(data)
return
self.add_at(self.root, data)
def print(self, node=None):
if node is None:
if self.root is None:
return
node = self.root
if node.left is not None:
self.print(node.left)
print(' ', end='')
print(node.data, end='')
if node.right is not None:
print(' ', end='')
self.print(node.right)
tree = Tree()
for x in [7, 3, 2, 1, 9, 5, 4, 6, 8]:
tree.add(x)
tree.print()