6.7. Демонстрация - Топ лучших (бинарное дерево поиска)

Шаг 1

Итак, возвращаемся к нашей предыдущей задаче — поиску топовых игроков. Так как построение деревьев поиска с нуля является довольно сложной алгоритмической задачкой, построим небольшое дерево «руками». Просто продемонстрируем технологам, что единоразовый перевод данных в более подходящий формат делает задачу куда проще и предотвращает дальнейшие проблемы.


import {log} from "./logger";

const data = [
  {
    "login": "DreamLess",
    "leaguePoints": 956
  },
  {
    "login": "cavernous",
    "leaguePoints": 1056
  },
  {
    "login": "SaiyanBroadway",
    "leaguePoints": 1432
  },
  {
    "login": "Mountaintrid",
    "leaguePoints": 1130
  },
  {
    "login": "cathead",
    "leaguePoints": 930
  },
  {
    "login": "Goldenelox",
    "leaguePoints": 932
  },
  {
    "login": "BoostScooby",
    "leaguePoints": 1476
  },
  {
    "login": "JoshChase",
    "leaguePoints": 931
  }
];

function topThree(data) {

}

log(topThree(data)); // Должен вывести игроков BoostScooby, SaiyanBroadway, Mountaintrid

Шаг 2

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


import {log} from "./logger";

const data = [
  {
    "login": "DreamLess",
    "leaguePoints": 956
  },
  {
    "login": "cavernous",
    "leaguePoints": 1056
  },
  {
    "login": "SaiyanBroadway",
    "leaguePoints": 1432
  },
  {
    "login": "Mountaintrid",
    "leaguePoints": 1130
  },
  {
    "login": "cathead",
    "leaguePoints": 930
  },
  {
    "login": "Goldenelox",
    "leaguePoints": 932
  },
  {
    "login": "BoostScooby",
    "leaguePoints": 1476
  },
  {
    "login": "JoshChase",
    "leaguePoints": 931
  }
];

// BST = Binary Search Tree
class BSTNode {
  constructor(value, left, right) {
    this.value = value;
    this.left = left;
    this.right = right;
  }
}

// просто для наглядности буду называть ноды по их пути в нашем новом дереве
const leftLeftLeft = new BSTNode(data[4], null, null);
const leftLeft = new BSTNode(data[7], leftLeftLeft, null);
const leftRight = new BSTNode(data[0], null, null);
const left = new BSTNode(data[5], leftLeft, leftRight);

const rightLeft = new BSTNode(data[3], null, null);
const rightRight = new BSTNode(data[6], null, null);
const right = new BSTNode(data[2], rightLeft, rightRight);

// "Корень" нашего дерева, будем обходить начиная с него
const root = new BSTNode(data[1], left, right);


function topThree(data) {

}

log(topThree(root)); // Должен вывести игроков BoostScooby, SaiyanBroadway, Mountaintrid

Шаг 3

Теперь наша задача довольно проста: нужно лишь найти самую правую часть дерева, у которой есть два потомка. Так как наше дерево самобалансируется, предположим, что у каждой правой ноды есть либо ноль, либо оба ребёнка.


import {log} from "./logger";

const data = [
  {
    "login": "DreamLess",
    "leaguePoints": 956
  },
  {
    "login": "cavernous",
    "leaguePoints": 1056
  },
  {
    "login": "SaiyanBroadway",
    "leaguePoints": 1432
  },
  {
    "login": "Mountaintrid",
    "leaguePoints": 1130
  },
  {
    "login": "cathead",
    "leaguePoints": 930
  },
  {
    "login": "Goldenelox",
    "leaguePoints": 932
  },
  {
    "login": "BoostScooby",
    "leaguePoints": 1476
  },
  {
    "login": "JoshChase",
    "leaguePoints": 931
  }
];

Шаг 4

Мы точно знаем, что у полученного нами корня лучшей тройки слева точно идёт самый маленький счет, справа — самый большой, а в самом корне — топ-2. Остается лишь вернуть их в нужной очерёдности.

import {log} from "./logger";

const data = [
  {
    "login": "DreamLess",
    "leaguePoints": 956
  },
  {
    "login": "cavernous",
    "leaguePoints": 1056
  },
  {
    "login": "SaiyanBroadway",
    "leaguePoints": 1432
  },
  {
    "login": "Mountaintrid",
    "leaguePoints": 1130
  },
  {
    "login": "cathead",
    "leaguePoints": 930
  },
  {
    "login": "Goldenelox",
    "leaguePoints": 932
  },
  {
    "login": "BoostScooby",
    "leaguePoints": 1476
  },
  {
    "login": "JoshChase",
    "leaguePoints": 931
  }
];

// BST = Binary Search Tree
class BSTNode {
  constructor(value, left, right) {
    this.value = value;
    this.left = left;
    this.right = right;
  }
}

// просто для наглядности буду называть ноды по их пути в нашем новом дереве
const leftLeftLeft = new BSTNode(data[4], null, null);
const leftLeft = new BSTNode(data[7], leftLeftLeft, null);
const leftRight = new BSTNode(data[0], null, null);
const left = new BSTNode(data[5], leftLeft, leftRight);

const rightLeft = new BSTNode(data[3], null, null);
const rightRight = new BSTNode(data[6], null, null);
const right = new BSTNode(data[2], rightLeft, rightRight);

// "Корень" нашего дерева, будем обходить начиная с него
const root = new BSTNode(data[1], left, right);


function topThree(root) {
  let bestThreeRoot = root;

  // пока снизу справа есть как минимум один уровень...
  while(bestThreeRoot.right && bestThreeRoot.right.right) {
    // ... обновляем указатель на лучшую тройку
    bestThreeRoot = bestThreeRoot.right;
  }

  return [bestThreeRoot.right.value, bestThreeRoot.value, bestThreeRoot.left.value];
}

log(topThree(root)); // Должен вывести игроков BoostScooby, SaiyanBroadway, Mountaintrid

File logger

const results = document.getElementById('results')

export function log(players) {
  for (const {login, leaguePoints} of players) {
    const result = document.createElement('li');

    result.innerHTML = `${login} со счетом ${leaguePoints}`

    results.appendChild(result);
  }
}