269 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			269 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| 
								 | 
							
								---
							 | 
						|||
| 
								 | 
							
								title: Binary Search Trees
							 | 
						|||
| 
								 | 
							
								localeTitle: Деревья двоичного поиска
							 | 
						|||
| 
								 | 
							
								---
							 | 
						|||
| 
								 | 
							
								## Деревья двоичного поиска
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Дерево представляет собой структуру данных, состоящую из узлов, которые имеют следующие характеристики:
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								1.  Каждое дерево имеет корневой узел (вверху), имеющий некоторое значение.
							 | 
						|||
| 
								 | 
							
								2.  Корневой узел имеет ноль или более дочерних узлов.
							 | 
						|||
| 
								 | 
							
								3.  Каждый дочерний узел имеет ноль или более дочерних узлов и т. Д. Это создает поддерево в дереве. Каждый узел имеет свое собственное поддерево, состоящее из его детей и их детей и т. Д. Это означает, что каждый узел сам по себе может быть деревом.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Двоичное дерево поиска (BST) добавляет эти две характеристики:
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								1.  Каждый узел имеет максимум до двух детей.
							 | 
						|||
| 
								 | 
							
								2.  Для каждого узла значения его левых узлов-потомков меньше, чем у текущего узла, который, в свою очередь, меньше, чем правые узлы-потомки (если они есть).
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								BST построен на идее алгоритма [бинарного поиска](https://guide.freecodecamp.org/algorithms/search-algorithms/binary-search) , который позволяет быстро находить, вставлять и удалять узлы. Способ их настройки означает, что в среднем каждое сравнение позволяет операциям пропускать около половины дерева, так что каждый поиск, вставка или удаление занимает время, пропорциональное логарифму количества элементов, хранящихся в дереве, `O(log n)` . Однако иногда может произойти худший случай, когда дерево не сбалансировано, а временная сложность `O(n)` для всех трех этих функций. Вот почему самобалансирующиеся деревья (AVL, red-black и т. Д.) Намного эффективнее базового BST.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								**Пример худшего сценария:** это может произойти, когда вы добавляете узлы, которые _всегда_ больше, чем узел (родительский), то же самое может произойти, когда вы всегда добавляете узлы со значениями ниже своих родителей.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								### Основные операции на BST
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								*   Create: создает пустое дерево.
							 | 
						|||
| 
								 | 
							
								*   Вставить: вставить узел в дерево.
							 | 
						|||
| 
								 | 
							
								*   Поиск: поиск узла в дереве.
							 | 
						|||
| 
								 | 
							
								*   Удалить: удаляет узел из дерева.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								#### Создайте
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Сначала создается пустое дерево без каких-либо узлов. Переменная / идентификатор, который должен указывать на корневой узел, инициализируется значением `NULL` .
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								#### Поиск
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Вы всегда начинаете искать дерево в корневом узле и спускаетесь оттуда. Вы сравниваете данные в каждом узле с тем, который вы ищете. Если сравниваемый узел не совпадает, вы либо переходите к правильному ребенку, либо к левому ребенку, что зависит от результата следующего сравнения: если узел, который вы ищете, меньше, чем тот, с которым вы сравнивали его, вы переходите к левому ребенку, иначе (если он больше) вы переходите к правильному ребенку. Зачем? Поскольку BST структурирован (согласно его определению), что правильный ребенок всегда больше родителя, а левый ребенок всегда меньше.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								#### Вставить
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Он очень похож на функцию поиска. Вы снова начинаете с корня дерева и возвращаетесь рекурсивно, ища подходящее место для вставки нашего нового узла так же, как описано в функции поиска. Если узел с тем же значением уже находится в дереве, вы можете выбрать либо вставить дубликат, либо нет. Некоторые деревья допускают дубликаты, некоторые - нет. Это зависит от определенной реализации.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								#### делеция
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Есть три случая, которые могут произойти, когда вы пытаетесь удалить узел. Если это так,
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								1.  Нет поддерева (без детей): этот самый простой. Вы можете просто удалить узел без каких-либо дополнительных действий.
							 | 
						|||
| 
								 | 
							
								2.  Одно поддерево (один ребенок): вы должны убедиться, что после удаления узла его дочерний элемент затем подключается к родительскому элементу удаленного узла.
							 | 
						|||
| 
								 | 
							
								3.  Два поддерева (двое детей): вы должны найти и заменить узел, который хотите удалить, с его преемником (letfmost node в правом поддереве).
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Сложность времени для создания дерева - `O(1)` . Сложность времени для поиска, вставки или удаления узла зависит от высоты дерева `h` , поэтому худшим случаем является `O(h)` .
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								#### Предшественник узла
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Предшественники можно охарактеризовать как узел, который появится прямо перед узлом, в котором вы сейчас находитесь. Чтобы найти предшественника текущего узла, посмотрите на правый / самый большой листовой узел в левом поддереве.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								#### Преемник узла
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Преемники можно охарактеризовать как узел, который появится сразу после узла, в котором вы сейчас находитесь. Чтобы найти преемника текущего узла, посмотрите на самый левый или самый маленький листовой узел в правом поддереве.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								### Специальные типы BT
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								*   отвал
							 | 
						|||
| 
								 | 
							
								*   Красно-черное дерево
							 | 
						|||
| 
								 | 
							
								*   В-дерево
							 | 
						|||
| 
								 | 
							
								*   Splay tree
							 | 
						|||
| 
								 | 
							
								*   N-арное дерево
							 | 
						|||
| 
								 | 
							
								*   Trie (дерево Radix)
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								### время выполнения
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								**Структура данных: массив**
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								*   Наихудшая производительность: `O(log n)`
							 | 
						|||
| 
								 | 
							
								*   Лучшая производительность: `O(1)`
							 | 
						|||
| 
								 | 
							
								*   Средняя производительность: `O(log n)`
							 | 
						|||
| 
								 | 
							
								*   Сложная сложность пространства: `O(1)`
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Где `n` - количество узлов в BST.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								### Внедрение BST
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Вот определение для узла BST, имеющего некоторые данные, ссылающиеся на его левый и правый дочерние узлы.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								```c
							 | 
						|||
| 
								 | 
							
								struct node { 
							 | 
						|||
| 
								 | 
							
								   int data; 
							 | 
						|||
| 
								 | 
							
								   struct node *leftChild; 
							 | 
						|||
| 
								 | 
							
								   struct node *rightChild; 
							 | 
						|||
| 
								 | 
							
								 }; 
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								#### Операция поиска
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Всякий раз, когда элемент нужно искать, начните поиск с корневого узла. Затем, если данные меньше значения ключа, выполните поиск элемента в левом поддереве. В противном случае выполните поиск элемента в правом поддереве. Следуйте одному и тому же алгоритму для каждого узла.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								```c
							 | 
						|||
| 
								 | 
							
								struct node* search(int data){ 
							 | 
						|||
| 
								 | 
							
								   struct node *current = root; 
							 | 
						|||
| 
								 | 
							
								   printf("Visiting elements: "); 
							 | 
						|||
| 
								 | 
							
								 
							 | 
						|||
| 
								 | 
							
								   while(current->data != data){ 
							 | 
						|||
| 
								 | 
							
								 
							 | 
						|||
| 
								 | 
							
								      if(current != NULL) { 
							 | 
						|||
| 
								 | 
							
								         printf("%d ",current->data); 
							 | 
						|||
| 
								 | 
							
								 
							 | 
						|||
| 
								 | 
							
								         //go to left tree 
							 | 
						|||
| 
								 | 
							
								         if(current->data > data){ 
							 | 
						|||
| 
								 | 
							
								            current = current->leftChild; 
							 | 
						|||
| 
								 | 
							
								         }//else go to right tree 
							 | 
						|||
| 
								 | 
							
								         else { 
							 | 
						|||
| 
								 | 
							
								            current = current->rightChild; 
							 | 
						|||
| 
								 | 
							
								         } 
							 | 
						|||
| 
								 | 
							
								 
							 | 
						|||
| 
								 | 
							
								         //not found 
							 | 
						|||
| 
								 | 
							
								         if(current == NULL){ 
							 | 
						|||
| 
								 | 
							
								            return NULL; 
							 | 
						|||
| 
								 | 
							
								         } 
							 | 
						|||
| 
								 | 
							
								      } 
							 | 
						|||
| 
								 | 
							
								   } 
							 | 
						|||
| 
								 | 
							
								   return current; 
							 | 
						|||
| 
								 | 
							
								 } 
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								#### Вставить операцию
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Всякий раз, когда элемент должен быть вставлен, сначала найдите его правильное местоположение. Начните поиск с корневого узла, затем, если данные меньше значения ключа, найдите пустое место в левом поддереве и вставьте данные. В противном случае найдите пустое место в правом поддереве и вставьте данные.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								```c
							 | 
						|||
| 
								 | 
							
								void insert(int data) { 
							 | 
						|||
| 
								 | 
							
								   struct node *tempNode = (struct node*) malloc(sizeof(struct node)); 
							 | 
						|||
| 
								 | 
							
								   struct node *current; 
							 | 
						|||
| 
								 | 
							
								   struct node *parent; 
							 | 
						|||
| 
								 | 
							
								 
							 | 
						|||
| 
								 | 
							
								   tempNode->data = data; 
							 | 
						|||
| 
								 | 
							
								   tempNode->leftChild = NULL; 
							 | 
						|||
| 
								 | 
							
								   tempNode->rightChild = NULL; 
							 | 
						|||
| 
								 | 
							
								 
							 | 
						|||
| 
								 | 
							
								   //if tree is empty 
							 | 
						|||
| 
								 | 
							
								   if(root == NULL) { 
							 | 
						|||
| 
								 | 
							
								      root = tempNode; 
							 | 
						|||
| 
								 | 
							
								   } else { 
							 | 
						|||
| 
								 | 
							
								      current = root; 
							 | 
						|||
| 
								 | 
							
								      parent = NULL; 
							 | 
						|||
| 
								 | 
							
								 
							 | 
						|||
| 
								 | 
							
								      while(1) { 
							 | 
						|||
| 
								 | 
							
								         parent = current; 
							 | 
						|||
| 
								 | 
							
								 
							 | 
						|||
| 
								 | 
							
								         //go to left of the tree 
							 | 
						|||
| 
								 | 
							
								         if(data < parent->data) { 
							 | 
						|||
| 
								 | 
							
								            current = current->leftChild; 
							 | 
						|||
| 
								 | 
							
								            //insert to the left 
							 | 
						|||
| 
								 | 
							
								 
							 | 
						|||
| 
								 | 
							
								            if(current == NULL) { 
							 | 
						|||
| 
								 | 
							
								               parent->leftChild = tempNode; 
							 | 
						|||
| 
								 | 
							
								               return; 
							 | 
						|||
| 
								 | 
							
								            } 
							 | 
						|||
| 
								 | 
							
								         }//go to right of the tree 
							 | 
						|||
| 
								 | 
							
								         else { 
							 | 
						|||
| 
								 | 
							
								            current = current->rightChild; 
							 | 
						|||
| 
								 | 
							
								 
							 | 
						|||
| 
								 | 
							
								            //insert to the right 
							 | 
						|||
| 
								 | 
							
								            if(current == NULL) { 
							 | 
						|||
| 
								 | 
							
								               parent->rightChild = tempNode; 
							 | 
						|||
| 
								 | 
							
								               return; 
							 | 
						|||
| 
								 | 
							
								            } 
							 | 
						|||
| 
								 | 
							
								         } 
							 | 
						|||
| 
								 | 
							
								      } 
							 | 
						|||
| 
								 | 
							
								   } 
							 | 
						|||
| 
								 | 
							
								 } 
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Двоичные деревья поиска (BST) также дают нам быстрый доступ к предшественникам и преемникам. Предшественники можно охарактеризовать как узел, который появится прямо перед узлом, в котором вы сейчас находитесь.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								*   Чтобы найти предшественника текущего узла, посмотрите на самый правый / самый большой листовой узел в левом поддереве. Преемники можно охарактеризовать как узел, который появится сразу после узла, в котором вы сейчас находитесь.
							 | 
						|||
| 
								 | 
							
								*   Чтобы найти преемника текущего узла, посмотрите на самый левый / самый маленький листовой узел в правом поддереве.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								### Давайте посмотрим на пару процедур, работающих на деревьях.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Поскольку деревья рекурсивно определены, очень часто приходится писать процедуры, которые работают на деревьях, которые сами являются рекурсивными.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Например, если мы хотим рассчитать высоту дерева, то есть высоту корневого узла, мы можем идти вперед и рекурсивно делать это, проходя через дерево. Поэтому мы можем сказать:
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								*   Например, если у нас есть дерево nil, то его высота равна 0.
							 | 
						|||
| 
								 | 
							
								*   В противном случае мы достигнем 1 плюс максимум левого дочернего дерева и правого дочернего дерева.
							 | 
						|||
| 
								 | 
							
								*   Поэтому, если мы посмотрим на лист, например, эта высота будет равна 1, так как высота левого дочернего элемента равна нулю, равно 0, а высота нулевого правильного ребенка равна 0. Таким образом, максимальная величина равна 0, затем 1 плюс 0.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								#### Алгоритм высоты (дерева)
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								if tree = nil: 
							 | 
						|||
| 
								 | 
							
								    return 0 
							 | 
						|||
| 
								 | 
							
								 return 1 + Max(Height(tree.left),Height(tree.right)) 
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								#### Вот код в C ++
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								int maxDepth(struct node* node) 
							 | 
						|||
| 
								 | 
							
								 { 
							 | 
						|||
| 
								 | 
							
								    if (node==NULL) 
							 | 
						|||
| 
								 | 
							
								        return 0; 
							 | 
						|||
| 
								 | 
							
								   else 
							 | 
						|||
| 
								 | 
							
								   { 
							 | 
						|||
| 
								 | 
							
								       int rDepth = maxDepth(node->right); 
							 | 
						|||
| 
								 | 
							
								       int lDepth = maxDepth(node->left); 
							 | 
						|||
| 
								 | 
							
								 
							 | 
						|||
| 
								 | 
							
								       if (lDepth > rDepth) 
							 | 
						|||
| 
								 | 
							
								       { 
							 | 
						|||
| 
								 | 
							
								           return(lDepth+1); 
							 | 
						|||
| 
								 | 
							
								       } 
							 | 
						|||
| 
								 | 
							
								       else 
							 | 
						|||
| 
								 | 
							
								       { 
							 | 
						|||
| 
								 | 
							
								            return(rDepth+1); 
							 | 
						|||
| 
								 | 
							
								       } 
							 | 
						|||
| 
								 | 
							
								   } 
							 | 
						|||
| 
								 | 
							
								 } 
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Мы могли бы также посмотреть на вычисление размера дерева, которое является числом узлов.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								*   Опять же, если у нас есть дерево nil, у нас есть нулевые узлы.
							 | 
						|||
| 
								 | 
							
								*   В противном случае мы имеем число узлов в левом дочернем элементе плюс 1 для себя плюс число узлов в правом дочернем элементе. Итак, 1 плюс размер левого дерева плюс размер правильного дерева.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								#### Алгоритм размера (дерева)
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								if tree = nil 
							 | 
						|||
| 
								 | 
							
								    return 0 
							 | 
						|||
| 
								 | 
							
								 return 1 + Size(tree.left) + Size(tree.right) 
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								#### Вот код в C ++
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								int treeSize(struct node* node) 
							 | 
						|||
| 
								 | 
							
								 { 
							 | 
						|||
| 
								 | 
							
								    if (node==NULL) 
							 | 
						|||
| 
								 | 
							
								        return 0; 
							 | 
						|||
| 
								 | 
							
								    else 
							 | 
						|||
| 
								 | 
							
								        return 1+(treeSize(node->left) + treeSize(node->right)); 
							 | 
						|||
| 
								 | 
							
								 } 
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								### Соответствующие видео на канале freeCodeCamp YouTube
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								*   [Двоичное дерево поиска](https://youtu.be/5cU1ILGy6dM)
							 | 
						|||
| 
								 | 
							
								*   [Двоичное дерево поиска: обход и высота](https://youtu.be/Aagf3RyK3Lw)
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								### Ниже приведены общие типы двоичных деревьев:
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Полное двоичное дерево / строковое двоичное дерево: двоичное дерево является полным или строгим, если каждый узел имеет ровно 0 или 2 детей.
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								           18 
							 | 
						|||
| 
								 | 
							
								       /       \ 
							 | 
						|||
| 
								 | 
							
								     15         30 
							 | 
						|||
| 
								 | 
							
								    /  \        /  \ 
							 | 
						|||
| 
								 | 
							
								  40    50    100   40 
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								В полном двоичном дереве количество листовых узлов равно числу внутренних узлов плюс один.
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Полное двоичное дерево: двоичное дерево является полным двоичным деревом, если все уровни полностью заполнены, за исключением, возможно, последнего уровня, а последний уровень имеет все ключи как можно дальше
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								           18 
							 | 
						|||
| 
								 | 
							
								       /       \ 
							 | 
						|||
| 
								 | 
							
								     15         30 
							 | 
						|||
| 
								 | 
							
								    /  \        /  \ 
							 | 
						|||
| 
								 | 
							
								  40    50    100   40 
							 | 
						|||
| 
								 | 
							
								 /  \   / 
							 | 
						|||
| 
								 | 
							
								 8   7  9 
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								```
							 |