Немного сложно ответить на вопрос о сложности этого кода, когда он написан на высоком уровне, который абстрагирует детали реализации. Java-документация , похоже, не дает никаких гарантий в отношении сложности функции append
. Как отмечали другие, класс StringBuffer
можно (и нужно) записывать так, чтобы сложность добавления строк не зависела от текущей длины строки, содержащейся в StringBuffer
.
Тем не менее, я подозреваю, что человеку, задающему этот вопрос, не очень полезно просто сказать: «Ваша книга неверна!» - вместо этого давайте посмотрим, какие допущения делаются, и поясним, что автор пытался сказать.
Вы можете сделать следующие предположения:
- Создание
new StringBuffer
- это O (1)
- Получение следующей строки
w
в words
равно O (1)
- Возвращение
sentence.toString
- самое большее O (n).
Вопрос в том, каков порядок sentence.append(w)
, и это зависит от того, как это происходит внутри StringBuffer
. Наивный способ сделать это, как Шлемиэль Художник .
Глупый путь
Предположим, вы используете строку с нулем в конце в стиле C для содержимого StringBuffer
. Вы находите конец такой строки, читая каждый символ, один за другим, пока не найдете нулевой символ - затем, чтобы добавить новую строку S, вы можете начать копирование символов из S в строку StringBuffer
(заканчивая с другим нулевым символом). Если вы пишете append
таким образом, то это O ( a + b ), где a - количество символов, которое в данный момент находится в StringBuffer
, b - количество символов в новом слове. Если вы перебираете массив слов, и каждый раз, когда вам нужно прочитать все символы, которые вы только что добавили, прежде чем добавлять новое слово, то сложность цикла составляет O (n ^ 2), где n - общее количество символов во всех словах (также количество символов в последнем предложении).
лучший способ
С другой стороны, предположим, что содержимое StringBuffer
по-прежнему является массивом символов, но мы также храним целое число size
, которое сообщает нам, какова длина строки (количество символов). Теперь нам больше не нужно читать каждый символ в StringBuffer
, чтобы найти конец строки; мы можем просто посмотреть индекс size
в массиве, который является O (1) вместо O ( a ). Тогда функция append
теперь зависит только от количества добавляемых символов, O ( b ). В этом случае сложность цикла составляет O (n), где n - общее количество символов во всех словах.
... Мы еще не закончили!
Наконец, есть еще один аспект реализации, который еще не был рассмотрен, и это тот факт, который был затронут в ответе из учебника - распределение памяти. Каждый раз, когда вы хотите записать больше символов в ваш StringBuffer
, вам не гарантируется, что в вашем массиве символов будет достаточно места для фактического размещения нового слова. Если места недостаточно, ваш компьютер должен сначала выделить несколько больше места в чистом разделе памяти, а затем скопируйте всю информацию в старом массиве StringBuffer
, и затем она может продолжиться, как и раньше Копирование таких данных займет время O ( a ) (где a - количество символов для копирования).
В худшем случае вам нужно выделять больше памяти каждый раз, когда вы добавляете новое слово. Это в основном возвращает нас к исходной точке, где цикл имеет O (n ^ 2) сложность, и это то, что книга предлагает. Если вы предполагаете, что ничего сумасшедшего не происходит (слова не становятся длиннее с экспоненциальной скоростью !), То вы, вероятно, можете уменьшить количество выделений памяти до чего-то более похожего на O (log (n)) Распределенная память растет экспоненциально. Если это количество распределений памяти и общее выделение памяти O ( a ), то общая сложность, связанная только с управлением памятью в цикле, составляет O (n log (n)). Поскольку добавляемая работа равна O (n) и меньше, чем сложность управления памятью, общая сложность функции составляет O (n log (n)).
Опять же, документация по Java не помогает нам с точки зрения увеличения емкости StringBuffer
, она просто говорит: «Если внутренний буфер переполняется, он автоматически увеличивается». В зависимости от того, как это происходит, вы можете получить либо O (n ^ 2), либо O (n log (n)).
В качестве упражнения, оставленного читателю: найдите простой способ изменить функцию, чтобы общая сложность составляла O (n), устраняя проблемы с перераспределением памяти.