7

Примечания: Это проблема 4,3 из Крекинг кодирвоания Интервью 5th EditionБудет ли этот алгоритм работать в O (n)?

Проблема: Учитывая отсортированный (порядок возрастания) массив, написать алгоритм для создания бинарного дерева поиска с минимальной высотой

Вот мой алгоритм, написанный на Java, чтобы сделать эту проблему

public static IntTreeNode createBST(int[] array) { 
     return createBST(array, 0, array.length-1); 
    } 
    private static IntTreeNode createBST(int[] array, int left, int right) { 
     if(right >= left) { 
      int middle = array[(left + right)/2; 
      IntTreeNode root = new IntTreeNode(middle); 
      root.left = createBST(array, left, middle - 1); 
      root.right = createBST(array, middle + 1, right); 
      return root; 
     } else { 
      return null; 
     } 
    } 

Я проверил этот код против автора, и это почти идентичны.
Однако мне сложно анализировать временную сложность этого алгоритма.
Я знаю, что это не будет работать в O (logn) как Binary Search, потому что вы не выполняете столько же работы на каждом уровне рекурсии. E.G на первом уровне, 1 единица работы, 2-й уровень - 2 единицы работы, 3-й уровень - 4 единицы работы, вплоть до регистрации (n) уровень - n единиц работы.

Так основаны от того, количество шагов, этот алгоритм требует будет ограничена сверху этим математическим выражением

enter image description here

, который после просмотра Infinite geometric series, я оценил в enter image description here

или 2n, которые будет в O (n)

Вы согласны с моим работа здесь и что этот алгоритм будет работать в O (n) или я что-то пропустил или он действительно работает в O (nlogn) или какой-либо другой класс функций?

+0

Да, это O (n). Я бы сделал это ответом, если бы у меня было более четкое представление о том, что является хорошим доказательством сложности. – Beta

+0

@Beta Как вы смогли рассказать без доказательства? – committedandroider

+1

Я видел, что алгоритм вызывает себя дважды, каждый раз на n/2 элементах, с O (1) дополнительной работой и удалением одного элемента. Я не думаю, что это строгое доказательство (как в «Пусть' f (n) »и« g (n) »являются такими функциями, что ...»), но этого было достаточно, чтобы я мог видеть это в моей голове, как кусок струны, нарезанный кусочками и выложенный без перекрытия. – Beta

ответ

5

Иногда вы можете упростить расчеты, вычислив количество времени на элемент в результате, а не рекурсивные отношения. Этот трюк применяется здесь. Начните с изменения кода в этом, очевидно, эквивалентной форме:

private static IntTreeNode createBST(int[] array, int left, int right) { 
    int middle = array[(left + right)/2; 
    IntTreeNode root = new IntTreeNode(middle); 
    if (middle - 1 >= left) { 
     root.left = createBST(array, left, middle - 1); 
    } 
    if (right >= middle + 1) { 
     root.right = createBST(array, middle + 1, right); 
    } 
    return root; 
} 

Теперь каждый вызов createBST непосредственно создает 1 узел. Поскольку в конечном дереве есть n узлов, должно быть n общее количество звонков на createBST, и поскольку каждый вызов напрямую выполняет постоянный объем работы, общая временная сложность O (n).

+0

Побей меня. Я просто сел, чтобы написать тот же аргумент. –

+0

Спасибо, что эта часть аргумента имела смысл - «поскольку theres n узлов в конечном дереве, должно быть n общих вызовов для createBST». Однако как изменился этот код, показывают, что каждый вызов выполняет постоянную работу? Один вызов по-прежнему может выполнять два вызова, которые имеют одинаковый размер, но меньший, чем первоначальный вызов. Те вызовы одного размера становятся все меньше, каждый раз, чтобы каждый вызов не выполнял постоянный объем работы из-за уменьшения размера размера звонка? – committedandroider

+0

@committedandroider Я изменил код так, чтобы вызовы createBST и узлы результата были взаимно однозначными. В исходном коде некоторые вызовы createBST возвращают null. Аргумент может быть адаптирован к исходному коду, но он немного более беспорядочен: например, вы можете утверждать, что существует не более N вызовов createBST, которые создают узлы и не более 2N вызовов, которые возвращают null. –

1

Если и когда вы запутались в рекурсии, замените рекурсивный вызов (мысленно, конечно) как цикл. Например, в вашей вышеперечисленной функции вы можете представить, что рекурсивные вызовы должны находиться внутри цикла while. Так как теперь цикл while выполняется до тех пор, пока не пройдут все n узлов, сложность O (n).

+0

Но это не сработает с чем-то с quicksort. Вы посещаете все элементы, но время выполнения будет O (n log n), а не O (n). Как бы вы перефразировали свой метод для этой ситуации? Мне нравится то, что вы сделали с циклом while. – committedandroider

+0

Да, я допускаю, что его трудно представить циклы для алгоритмов вроде быстрой сортировки, которые логарифмически увеличиваются. Однако идея отлично работает для всех экспоненциально возрастающих функций, таких как O (n^2), O (n^3) и т. Д. Нам просто нужно использовать вложенные циклы. –