Рубрики
Без рубрики

Структуры данных 101: двоичное поиск деревьев

Структурой данных CEVIN Turnee Data 101: Двоичное поисковое TreeShow для объединения эффективности вставки связанного списка и быстрого поиска упорядоченного массива. «Безлистые дерево на холме» на Вилларде Fabrice на unsplashwhat – это двоичное дерево поиска? Начнем с базовой терминологии так мы

Автор оригинала: FreeCodeCamp Community Member.

Кевин Тюреей

Как объединить эффективность вставки связанного списка и быстрый поиск заказанного массива.

Что такое двоичное поиск?

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

* Отсюда, я буду использовать «BST» для краткости

BST считается структурой данных, состоящей из узлы , как Связанные списки Отказ Эти узлы либо NULL, либо имеют ссылки (ссылки) к другим узлам. Эти «другие» узлы – дочерние узлы, называемые левым узлом и правым узлом. У узлов есть Значения Отказ Эти значения определяют, где они размещены в BST.

Аналогично связано с подключенным списком, каждый узел ссылается Только Еще один узел, его родитель (кроме корневого узла). Таким образом, можно сказать, что каждый узел в BST находится в себе BST. Потому что дальше вниз по дереву, мы достигаем другого узла, и этот узел имеет левый и правый. Затем в зависимости от того, какой путь мы идем, этот узел имеет левый и правый и так далее.

1. Левый узел всегда меньше, чем его родитель.

2. Правильный узел всегда больше, чем его родитель.

3. BST считается сбалансированным, если каждый уровень дерева полностью заполнен исключением последнего уровня. На последнем уровне дерево заполнено слева направо.

4. Идеальный BST – это тот, в котором он оба полный, так и полный (все дочерние узлы находятся на одном уровне, и каждый узел имеет левый и правый узел для детей).

Почему мы использовали это?

Каковы некоторые реальные примеры BST? Деревья часто используются в поисках, логике игры, автозаполненные задачи и графики.

Скорость. Как упоминалось ранее, BST является упорядоченной структурой данных. При вставке узлы помещаются в упорядоченную моду. Этот присущий порядок делает поиск быстро. Подобно бинарному поиску (с отсортированным массивом), мы сокращаем количество данных для сортировки по половине на каждом проходе. Например, предположим, что мы ищем небольшое значение узла. На каждом проходе мы продолжаем двигаться по левому узеру. Это автоматически исключает половину большего значения!

Также, в отличие от массива, данные хранятся посредством ссылки. Как мы добавляем к структуре данных, мы создаем новый кусок в памяти и ссылку на него. Это быстрее, чем создание нового массива с большим количеством пространства, а затем вставляя данные из меньшего массива на новый, большей.

Короче, вставка, удаление и поиск – это все звезды для BST

Теперь, когда мы понимаем принципы, преимущества и основные компоненты BST, давайте реализуем один в JavaScript.

API для BST состоит из следующих: Вставка, содержит, получить мин, получить максимум, удалить узл, проверьте, если полностью, сбалансирован и типы поиска – Глубина сначала (предварительно задержет, внешний заказ, почтальдер), первое поиск по широту и, наконец, Получить высоту Отказ Это большая API, просто возьми его один раздел за раз.

Реализация

Конструктор

BST состоит из узлов, и каждый узел имеет значение.

function Node(value){  this.value = value;  this.left = null;  this.right = null;}

Конструктор BST состоит из корневого узла.

function BinarySearchTree() { this.root = null;}
let bst = new BST();let node = new Node();
console.log(node, bst); // Node { value: undefined, left: null, right: null } BST { root: null }

… Все идет нормально.

Вставка

BinarySearchTree.prototype.insert = function(value){ let node = new Node(value); if(!this.root) this.root = node; else{    let current = this.root;    while(!!current){       if(node.value < current.value){       if(!current.left){           current.left = node;           break;         }         current = current.left;         }        else if(node.value > current.value){         if(!current.right){            current.right = node;            break;           }          current = current.right;          }         else {          break;           }         }        }    return this; };
let bst = new BST();bst.insert(25); // BST { root: Node { value: 25, left: null, right: null } }

Давайте добавим еще несколько ценностей.

bst.insert(40).insert(20).insert(9).insert(32).insert(15).insert(8).insert(27);
BST { root:  Node { value: 25, left: Node { value: 20, left: [Object], right: null }, right: Node { value: 40, left: [Object], right: null } } }

Для прохладной визуализации Иди сюда !

Давайте распадаем это.

  1. Во-первых, мы передаем значение и создаем новый узел
  2. Проверьте, есть ли корня, если нет, установите этот вновь созданный узел в корневой узел
  3. Если есть корневой узел, мы создаем переменную объявленную «току» и установите его значение в корневой узел
  4. Если вновь созданный Node.Value меньше, чем узел корневого узла, мы будем двигаться влево
  5. Мы продолжаем сравнивать этот узел. Value к левому узлам.
  6. Если значение достаточно мало, и мы достигаем точки, где нет больше левых узлов, мы помещаем этот предмет здесь.
  7. Если Node.Value больше, мы повторяем одни и те же шаги, что и выше, за исключением того, что мы движемся по праву.
  8. Нам нужны операторы разрыва, потому что нет шага подсчета, чтобы завершить цикл While.

Содержит

Это довольно простой подход.

BinarySearchTree.prototype.contains = function(value){ let current = this.root; while(current){ if(value === current.value) return true; if(value < current.value) current = current.left; if(value > current.value) current = current.right; } return false;};

Получите мин и получите максимум.

Продолжайте проходить влево на наименьшее значение или вправо для крупнейшего.

BinarySearchTree.prototype.getMin = function(node){ if(!node) node = this.root; while(node.left) { node = node.left; } return node.value};
BinarySearchTree.prototype.getMax = function(node){ if(!node) node = this.root; while(node.right) { node = node.right; } return node.value;};

Удаление

Удаление узла – самая сложная операция, потому что узлы должны быть переупорядочить, чтобы поддерживать свойства BST. Есть случай, если узел имеет только один ребенок и случай, если есть левый, так и правый узел. Мы используем большую функцию помощника, чтобы сделать тяжелый подъем.

BinarySearchTree.prototype.removeNode = function(node, value){ if(!node){   return null; } if(value === node.value){ // no children if(!node.left && !node.right) return null; // one child and it's the right if(!node.left) node.right;// one child and it's the left if(!node.right) node.left;  // two kids const temp = this.getMin(node.right); node.value = temp; node.right = this.removeNode(node.right, temp); return node; } else if(value < node.value) {     node.left = this.removeNode(node.left, value);     return node; } else  {     node.right = this.removeNode(node.right, value);     return node;   }};
BinarySearchTree.prototype.remove = function(value){ this.root = this.removeNode(this.root, value);};

Это работает так …

В отличие от делитемина и делитемакса, где мы можем просто пересечь весь путь влево или вправо и полностью поднять последнее значение, мы должны взять узел, а затем заменить его чем-то. Это решение было разработано в 1962 году Т. Хиббардом. Мы учитываем случай, когда мы можем удалить узел только одним ребенком или никто, это незначительное. Если нет детей, нет проблем. Если ребенок присутствует, этот ребенок просто поднимается.

Но с узлом, запланированным, чтобы быть удаленным, у которого двое детей, который ребенок занимает свое место? Конечно, мы не можем переместить больший узел вниз. Так что мы делаем, это заменить его наследником, следующим жгуком. Мы должны найти наименьшего правильного ребенка справа, которое больше левого ребенка.

  1. Создайте значение TEMP и сохраните наименьшего узла в правом направлении. Что это делает, удовлетворяет свойство, что значения слева все еще меньше, а значения справа все еще больше.
  2. Сбросьте значение узла к этой переменме TEMP
  3. Снимите правый узел.
  4. Затем мы сравним значения слева и вправо и определите назначенное значение.

Это лучше всего объяснено с изображением:

Searching

Есть два типа поиска, первая глубина и ширина первыми. Ширина сначала просто останавливается на каждом уровне на пути вниз. Похоже, что мы начинаем в корне, а затем левый ребенок, затем правый ребенок. Перейти к следующему уровню, левый ребенок, а затем правильный ребенок. Думайте об этом, как движение по горизонтали. Мы используем, я должен сказать, что имитация, очередь, чтобы помочь заказать процесс. Мы передаем функцию, потому что много раз мы хотим работать на ценности.

BinarySearchTree.prototype.traverseBreadthFirst = function(fn) { let queue = []; queue.push(this.root); while(!!queue.length) {   let node = queue.shift();   fn(node);   node.left && queue.push(node.left);   node.right && queue.push(node.right);  }}

Первый поиск глубины включает в себя движение вниз по BST указанным образом, либо предварительно задержать, внешность или почтовый заказ. Я объясню различия в ближайшее время.

В духе краткого кода у нас есть базовая функция TraversedePthFirst, и мы передаем функцию и метод. Опять же функция подразумевает, что мы хотим сделать что-то с ценностями по пути, в то время как метод – это тип поиска, который мы хотим выполнить. В TraversEdfs у нас есть ошибка: поиск на месте.

Теперь, как каждый другой другой? Во-первых, давайте отправлять внешность. Это должно быть самоуверенно, но это не так. Развиваем ли мы в порядке вставки, по порядку наивысшего до самого низкого или самого низкого до самого высокого? Я просто хотел, чтобы вы рассмотрели эти вещи заранее. В этом случае да, это означает наименьшее до самого высокого.

Предоставляет можно думать как Родитель, левый ребенок, затем правый ребенок .

почтовый заказ как Левый ребенок, правый ребенок, родитель Отказ

BinarySearchTree.prototype.traverseDFS = function(fn, method){ let current = this.root; if(!!method) this[method](current, fn); else this._preOrder(current, fn);};
BinarySearchTree.prototype._inOrder = function(node, fn){ if(!!node){ this._inOrder(node.left, fn); if(!!fn) fn(node); this._inOrder(node.right, fn); }};
BinarySearchTree.prototype._preOrder = function(node, fn){ if(node){ if(fn) fn(node); this._preOrder(node.left, fn); this._preOrder(node.right, fn); }};
BinarySearchTree.prototype._postOrder = function(node, fn){ if(!!node){ this._postOrder(node.left, fn); this._postOrder(node.right, fn); if(!!fn) fn(node); }};

Проверьте, заполнен ли BST

Помните с ранее, BST заполнен, если каждый узел имеет ноль или два детей.

// a BST is full if every node has zero two children (no nodes have one child)
BinarySearchTree.prototype.checkIfFull = function(fn){ let result = true; this.traverseBFS = (node) => {   if(!node.left && !node.right) result = false;   else if(node.left && !node.right) result = false;  } return result;};

Получить высоту BST

Что значит получить высоту дерева? Почему это важно? Это где Сложность времени (AKA Big O) входит в игру. Основные операции пропорциональны высоте дерева. Поэтому, как мы намереваемся ранее, если мы ищем определенную ценность, количество операций, которые мы должны сделать, сокращается на каждом этапе.

Это означает, что если у нас есть буханка хлеба и порезать его пополам, затем разрезать это наполовину пополам и продолжай делать то, пока мы не получим точный кусок хлеба, который мы хотим.

В информатике это называется O (log n). Мы начинаем с входного размера каких-либо своего рода, и со временем, когда размер становится меньше (вид выравнивания). Прямой линейный поиск обозначается как O (N), поскольку размер ввода увеличивается, поэтому требуется время, необходимое для запуска операций. O (n) концептуально представляет собой 45-градусную линию, начиная с нуля происхождения на диаграмме и движущимся правым. Горизонтальная шкала представляет собой размер ввода, а вертикальная шкала представляет время, необходимое для завершения.

Постоянное время – это o (1). Независимо от того, насколько большой или маленький размер входного размера используется, операция происходит в одинаковом количестве времени. Например, Push () и POP () выкл из массива являются постоянным временем. Глядя вверх по цене в хэш-хешмере – это постоянное время.

Я объясню больше об этом в будущей статье, но я хотел бы ворвать вам эти знания сейчас.

Вернуться к высоте.

У нас есть рекурсивная функция, а наш базовый случай: «Если у нас нет узла, то мы начнем с этого .oot ‘ Отказ Это подразумевает, что мы можем начать с ценностями ниже в дереве и получить под высоты деревьев.

Поэтому, если мы пройдем в этом. Воскреситься начать, мы рекурсивно двигаемся вниз по дереву и добавляем функцию вызовы в стек выполнения (здесь другие статьи). Когда мы доберемся дона, стек заполнен. Затем вызовы выполняются, и мы сравниваем высоты левой и высоты правого и приращения на одну.

BinarySearchTree.prototype._getHeights = function(node){ if(!node) return -1; let left = this._getHeights(node.left); let right = this._getHeights(node.right); return Math.max(left, right) + 1;};
BinarySearchTree.prototype.getHeight = function(node){ if(!node) node = this.root; return this._getHeights(node);};

Наконец, сбалансирован

То, что мы делаем, проверяет, заполнено ли дерево на каждом уровне, и на последнем уровне, если он заполнен слева направо.

BinarySearchTree.prototype._isBalanced = function(node){ if(!node) return true; let heightLeft = this._getHeights(node.left); let heightRight = this._getHeights(node.right); let diff = Math.abs(heightLeft — heightRight); if(diff > 1) return false; else return this._isBalanced(node.left) &&    this._isBalanced(node.right);};
BinarySearchTree.prototype.isBalanced = function(node){ if(!node) node = this.root; return this._isBalanced(node);};

Распечатать

Используйте это для визуализации всех методов, которые вы видите, особенно глубину первой и шириной первых обходов.

BinarySearchTree.prototype.print = function() { if(!this.root) {   return console.log('No root node found'); } let newline = new Node('|'); let queue = [this.root, newline]; let string = ''; while(queue.length) {   let node = queue.shift();   string += node.value.toString() + ' ';   if(node === newline && queue.length) queue.push(newline);    if(node.left) queue.push(node.left);   if(node.right) queue.push(node.right);  } console.log(string.slice(0, -2).trim());};

Наш друг Coundole.log !! Играть вокруг и экспериментировать.

const binarySearchTree = new BinarySearchTree();binarySearchTree.insert(5);binarySearchTree.insert(3);
binarySearchTree.insert(7);binarySearchTree.insert(2);binarySearchTree.insert(4);binarySearchTree.insert(4);binarySearchTree.insert(6);binarySearchTree.insert(8);binarySearchTree.print(); // => 5 | 3 7 | 2 4 6 8
binarySearchTree.contains(4);
//binarySearchTree.printByLevel(); // => 5 \n 3 7 \n 2 4 6 8console.log('--- DFS inOrder');
binarySearchTree.traverseDFS(function(node) { console.log(node.value); }, '_inOrder'); // => 2 3 4 5 6 7 8
console.log('--- DFS preOrder');
binarySearchTree.traverseDFS(function(node) { console.log(node.value); }, '_preOrder'); // => 5 3 2 4 7 6 8
console.log('--- DFS postOrder');
binarySearchTree.traverseDFS(function(node) { console.log(node.value); }, '_postOrder'); // => 2 4 3 6 8 7 5
console.log('--- BFS');
binarySearchTree.traverseBFS(function(node) { console.log(node.value); }); // => 5 3 7 2 4 6 8
console.log('min is 2:', binarySearchTree.getMin()); // => 2
console.log('max is 8:', binarySearchTree.getMax()); // => 8
console.log('tree contains 3 is true:', binarySearchTree.contains(3)); // => true
console.log('tree contains 9 is false:', binarySearchTree.contains(9)); // => false
// console.log('tree height is 2:', binarySearchTree.getHeight()); // => 2
console.log('tree is balanced is true:', binarySearchTree.isBalanced(),'line 220'); // => true
binarySearchTree. remove(11); // remove non existing node
binarySearchTree.print(); // => 5 | 3 7 | 2 4 6 8
binarySearchTree.remove(5); // remove 5, 6 goes up
binarySearchTree.print(); // => 6 | 3 7 | 2 4 8
console.log(binarySearchTree.checkIfFull(), 'should be true');
var fullBSTree = new BinarySearchTree(10);
fullBSTree.insert(5).insert(20).insert(15).insert(21).insert(16).insert(13);
console.log(fullBSTree.checkIfFull(), 'should be true');
binarySearchTree.remove(7); // remove 7, 8 goes up
binarySearchTree.print(); // => 6 | 3 8 | 2 4
binarySearchTree.remove(8); // remove 8, the tree becomes unbalanced
binarySearchTree.print(); // => 6 | 3 | 2 4
console.log('tree is balanced is false:', binarySearchTree.isBalanced()); // => true
console.log(binarySearchTree.getHeight(),'height is 2')
binarySearchTree.remove(4);
binarySearchTree.remove(2);
binarySearchTree.remove(3);
binarySearchTree.remove(6);
binarySearchTree.print(); // => 'No root node found'
//binarySearchTree.printByLevel(); // => 'No root node found'
console.log('tree height is -1:', binarySearchTree.getHeight()); // => -1
console.log('tree is balanced is true:', binarySearchTree.isBalanced()); // => true
console.log('---');
binarySearchTree.insert(10);
console.log('tree height is 0:', binarySearchTree.getHeight()); // => 0
console.log('tree is balanced is true:', binarySearchTree.isBalanced()); // => true
binarySearchTree.insert(6);
binarySearchTree.insert(14);
binarySearchTree.insert(4);
binarySearchTree.insert(8);
binarySearchTree.insert(12);
binarySearchTree.insert(16);
binarySearchTree.insert(3);
binarySearchTree.insert(5);
binarySearchTree.insert(7);
binarySearchTree.insert(9);
binarySearchTree.insert(11);
binarySearchTree.insert(13);
binarySearchTree.insert(15);
binarySearchTree.insert(17);
binarySearchTree.print(); // => 10 | 6 14 | 4 8 12 16 | 3 5 7 9 11 13 15 17
binarySearchTree.remove(10); // remove 10, 11 goes up
binarySearchTree.print(); // => 11 | 6 14 | 4 8 12 16 | 3 5 7 9 x 13 15 17
binarySearchTree.remove(12); // remove 12; 13 goes up
binarySearchTree.print(); // => 11 | 6 14 | 4 8 13 16 | 3 5 7 9 x x 15 17
console.log('tree is balanced is true:', binarySearchTree.isBalanced()); // => true
//console.log('tree is balanced optimized is true:', binarySearchTree.isBalancedOptimized()); // => true
binarySearchTree.remove(13); // remove 13, 13 has no children so nothing changes
binarySearchTree.print(); // => 11 | 6 14 | 4 8 x 16 | 3 5 7 9 x x 15 17
console.log('tree is balanced is false:', binarySearchTree.isBalanced()); // => false
// yields ...5 | 3 7 | 2 4 6 8--- DFS inOrder2345678--- DFS preOrder5324768--- DFS postOrder2436875--- BFS5372468min is 2: 2max is 8: 8tree contains 3 is true: truetree contains 9 is false: falsetree is balanced is true: true line 2205 | 3 7 | 2 4 6 86 | 3 7 | 2 4 8true 'should be true'true 'should be true'6 | 3 8 | 2 46 | 3 | 2 4tree is balanced is false: false2 'height is 2'No root node foundtree height is -1: -1tree is balanced is true: true---tree height is 0: 0tree is balanced is true: true10 | 6 14 | 4 8 12 16 | 3 5 7 9 11 13 15 1711 | 6 14 | 4 8 12 16 | 3 5 7 9 13 15 1711 | 6 14 | 4 8 13 16 | 3 5 7 9 15 17tree is balanced is true: true11 | 6 14 | 4 8 16 | 3 5 7 9 15 17tree is balanced is false: false

Сложность времени

1. Вставка o (log n) 2. Снятие o (log n) 3. Поиск o (log n)

Вау, это действительно много информации. Я надеюсь, что объяснения были такими же ясными и как можно вводными. Опять же, писать помогает мне укрепить концепции и как сказал Ричард Фейнман: «Когда один человек учит, два учится».

Ресурсы

Вероятно, лучший ресурс для визуализации, определенно использовать его:

Визуализация структуры данных Дэвид Галлас Компьютерный университет Сан-Франциско www.cs.usfca.edu Binarytreevisualiser – двоичное поиск деревьев Описание сайта здесь BTV.Melezinek.cz Visualgo – двоичное поиск, дерево AVL Двоичное дерево поиска (BST) – это двоичное дерево, в котором каждая вершина имеет только до 2 детей, которые удовлетворяют свойство BST … Visualgo.net Сложность Big-O Cheat Complectity (знайте, что сложности твои!) @ericdrowell Всем привет! Эта веб-страница покрывает пространство и время Big-O сложности общих алгоритмов, используемых в информатике. Когда … www.bigocheatsheet.com Алгоритмы, 4-е издание Роберта Седвик и Кевин Уэйн Алгоритмы учебника, 4-е издание Роберта Седвик и Кевин Уэйн опросы самые важные алгоритмы и данные … algs4.cs.princeton.edu Двоичное поиск – Википедия В информатике, двоичные поиски деревья (BST), иногда называемые заказанными или отсортированными двоичными деревьями, являются конкретным типом … en.wikipedia.org

Оригинал: “https://www.freecodecamp.org/news/data-structures-101-binary-search-tree-398267b6bff0/”