Демистификация отсортированных наборов деревьев: руководство для начинающих по пониманию и реализации сортировки в древовидных структурах данных

В мире структур данных наборы деревьев играют решающую роль в поддержании отсортированных коллекций элементов. Но задумывались ли вы когда-нибудь, почему множество деревьев по своей сути отсортировано? В этой статье блога мы углубимся во внутреннюю работу наборов деревьев и рассмотрим различные методы и примеры кода, иллюстрирующие, как достигается сортировка. Итак, давайте углубимся и разгадаем тайну наборов отсортированных деревьев!

Понимание наборов деревьев:

Прежде чем мы углубимся в аспект сортировки, важно понять основы наборов деревьев. Древовидный набор — это структура данных, которая хранит набор элементов в двоичном дереве поиска (BST). Каждый элемент в наборе дерева уникален, а узлы дерева расположены в определенном порядке, что упрощает операции поиска, вставки и удаления.

  1. Двоичные деревья поиска (BST):

В основе набора деревьев лежит двоичное дерево поиска (BST). BST — это структура данных, в которой каждый узел имеет не более двух дочерних узлов, обычно называемых левым дочерним элементом и правым дочерним элементом. Узлы в BST расположены в определенном порядке, так что левый дочерний элемент меньше родительского, а правый дочерний больше родительского. Это свойство позволяет эффективно выполнять операции поиска и сортировки.

Давайте рассмотрим следующий пример, чтобы лучше понять, как работает BST:

class Node:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None
def insert(root, value):
    if root is None:
        return Node(value)
    if value < root.value:
        root.left = insert(root.left, value)
    elif value > root.value:
        root.right = insert(root.right, value)
    return root
def inorder_traversal(root):
    if root:
        inorder_traversal(root.left)
        print(root.value)
        inorder_traversal(root.right)
# Creating a BST and inserting elements
root = None
root = insert(root, 50)
insert(root, 30)
insert(root, 20)
insert(root, 40)
insert(root, 70)
insert(root, 60)
insert(root, 80)
# Inorder traversal to retrieve sorted elements
inorder_traversal(root)

Выход:

20
30
40
50
60
70
80

В приведенном выше фрагменте кода мы создаем BST и вставляем в него элементы. Функция inorder_traversalвыполняет обход дерева по порядку, в результате чего элементы печатаются в отсортированном порядке.

  1. Сбалансированные деревья:

Хотя базовый BST может поддерживать сортировку, он не всегда может быть эффективным, особенно если дерево перекошено. Чтобы преодолеть это ограничение, используются сбалансированные деревья. Сбалансированные деревья гарантируют, что высота левого и правого поддеревьев любого узла отличается не более чем на единицу, что обеспечивает эффективность операций поиска и сортировки.

Существуют различные типы сбалансированных деревьев, например деревья AVL и красно-черные деревья. Эти деревья обладают дополнительными свойствами и алгоритмами, которые автоматически балансируют дерево во время вставок и удалений, обеспечивая оптимальную производительность.

Давайте рассмотрим пример дерева AVL, которое представляет собой самобалансирующееся двоичное дерево поиска:

class Node:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None
        self.height = 1
def insert(root, value):
    if root is None:
        return Node(value)
    elif value < root.value:
        root.left = insert(root.left, value)
    else:
        root.right = insert(root.right, value)

    root.height = 1 + max(get_height(root.left), get_height(root.right))

    balance_factor = get_balance(root)

    if balance_factor > 1 and value < root.left.value:
        return right_rotate(root)

    if balance_factor < -1 and value > root.right.value:
        return left_rotate(root)

    if balance_factor > 1 and value > root.left.value:
        root.left = left_rotate(root.left)
        return right_rotate(root)

    if balance_factor < -1 and value < root.right.value:
        root.right = right_rotate(root.right)
        return left_rotate(root)

    return root
def get_height(node):
    if node is None:
        return 0
    return node.height
def get_balance(node):
    if node is None:
        return 0
    return get_height(node.left) - get_height(node.right)
def right_rotate(z):
    y = z.left
    T3 = y.right

    y.right = z
    z.left = T3

    z.height = 1 + max(get_height(z.left), get_height(z.right))
    y.height =1 + max(get_height(y.left), get_height(y.right))

    return y
def left_rotate(z):
    y = z.right
    T2 = y.left

    y.left = z
    z.right = T2

    z.height = 1 + max(get_height(z.left), get_height(z.right))
    y.height = 1 + max(get_height(y.left), get_height(y.right))

    return y
# Creating an AVL tree and inserting elements
root = None
root = insert(root, 50)
root = insert(root, 30)
root = insert(root, 20)
root = insert(root, 40)
root = insert(root, 70)
root = insert(root, 60)
root = insert(root, 80)
# Inorder traversal to retrieve sorted elements
inorder_traversal(root)

Выход:

20
30
40
50
60
70
80

В этом фрагменте кода мы реализуем дерево AVL, которое представляет собой самобалансирующееся двоичное дерево поиска. Функция insertвставляет элементы в дерево, сохраняя при этом свойства дерева AVL. Функция inorder_traversalизвлекает элементы в отсортированном порядке.

В заключение отметим, что набор деревьев сортируется, поскольку он использует присущие свойства двоичных деревьев поиска. Поддерживая порядок элементов на основе их значений, наборы деревьев обеспечивают эффективные операции поиска и сортировки. Более того, использование сбалансированных деревьев, таких как деревья AVL или красно-черные деревья, обеспечивает оптимальную производительность даже в сценариях, где элементы часто вставляются или удаляются.

Поняв основные принципы и реализовав предоставленные примеры кода, вы теперь имеете прочную основу для понимания и эффективного использования наборов отсортированных деревьев.