Двоичное дерево поиска - это структура данных, которая позволяет поддерживать отсортированный список чисел.
- Двоичным (бинарным) деревом называется, потому что каждый узел дерева имеет максимально два дочерних элементов.
- Деревом поиска, потому что его можно использовать для поиска числа в O(log(n)) time (алгоритм с временной сложностью T(n) = O(log(n))(прим.ред.)).
Свойства, которые отличают двоичное дерево поиска от обычного двоичного дерева:
- Все узлы левого поддерева меньше корневого узла.
- Все узлы правого поддерева больше корневого узла.
- Оба поддерева каждого узла также являются BST, т.е. они имеют два вышеуказанных свойства.
Двоичное дерево справа не является двоичным деревом поиска, потому что правое поддерево узла "3" содержит значение, которое меньше его.
Есть две основные операции, которые вы можете выполнять в двоичном дереве поиска:
1. Проверка, присутствует ли число в двоичном дереве поиска.
Алгоритм зависит от свойства BST так, что, если каждое левое поддерево имеет значения ниже корневого, то каждое правое поддерево имеет значения выше корневого.
Если значение ниже корневого, мы можем с уверенностью сказать, что значение находится не в правом поддереве, а значит нам стоит искать его в левом поддереве.
И наоборот, если значение выше корневого, наверняка, что значение находится не в левом поддереве, а значит нужно искать в правом.
Алгоритм:
If root == NULL return NULL; If number == root->data return root->data; If number < root->data return search(root->left) If number > root->data return search(root->right)
Давайте попробуем визуализировать алгоритм с помощью диаграммы.
Если значение найдено, мы возвращаем значение, чтобы оно распространялось на каждом шаге рекурсии, как показано на рисунке ниже.
Вы могли заметить, что мы вызывали функцию return search (struct node) четыре раза. Когда мы возвращаем либо новый узел, либо NULL, значение возвращается снова и снова, до тех пор, пока search (root) не вернет окончательный результат.
Если значение не найдено, мы в конечном итоге достигаем левого или правого потомка конечного узла, который равен NULL, и он распространяется и возвращается.
2. Вставка значения в двоичное дерево поиска (BST)
Вставка значения в правильную позицию аналогична поиску, потому что мы пытаемся соответствовать правилу, согласно которому левое поддерево меньше корневого, а правое поддерево больше корневого.
Мы продолжаем идти либо к правому поддереву, либо к левому в зависимости от значения, а когда мы достигаем точки, где левое или правое поддерево равно нулю, мы помещаем туда новый узел.
Алгоритм:
If node == NULL return createNode(data) if (data < node->data) node->left = insert(node->left, data); else if (data > node->data) node->right = insert(node->right, data); return node;
Алгоритм не так прост, как кажется. Давайте попробуем визуализировать, как мы добавляем число к существующему BST.
Мы подсоединили узел, но нам все еще нужно выйти из функции, не нанося вреда остальной части дерева. Вот где return node (возвратный узел) в конце пригодится. В этом случае NULL, вновь созданный узел возвращается и присоединяется к родительскому узлу, в противном случае тот же самый узел возвращается без каких-либо изменений, пока мы не вернемся к корневому узлу.
Это гарантирует, что когда мы вернемся вверх по дереву, соединения с другими узлами не изменятся.
Полный код для вставки и поиска в BST на языке программирования C приведен ниже:
#include<stdio.h> #include<stdlib.h> struct node { int data; struct node* left; struct node* right; }; struct node* createNode(value){ struct node* newNode = malloc(sizeof(struct node)); newNode->data = value; newNode->left = NULL; newNode->right = NULL; return newNode; } struct node* insert(struct node* root, int data) { if (root == NULL) return createNode(data); if (data < root->data) root->left = insert(root->left, data); else if (data > root->data) root->right = insert(root->right, data); return root; } void inorder(struct node* root){ if(root == NULL) return; inorder(root->left); printf("%d ->", root->data); inorder(root->right); } int main(){ struct node *root = NULL; root = insert(root, 8); insert(root, 3); insert(root, 1); insert(root, 6); insert(root, 7); insert(root, 10); insert(root, 14); insert(root, 4); inorder(root); }
Вывод программы
1 ->3 ->4 ->6 ->7 ->8 ->10 ->14 ->