существуетИнтенсивное чтение "Принцип DOM diff"В статье мы упомянули, что Vue использует жадный алгоритм + деление пополам для нахождения самой длинной восходящей подпоследовательности, но не углублялись в принцип этого алгоритма, поэтому для подробного объяснения была открыта специальная глава.
Кроме того, как проблема алгоритма, самая длинная восходящая подпоследовательность является очень классической, и она практична и сложна в отрасли, поэтому я надеюсь, что каждый должен ее освоить.
интенсивное чтение
Какая самая длинная восходящая подпоследовательность? Он заключается в том, чтобы найти самую длинную непрерывно возрастающую часть массива, как показано на следующем рисунке:
Если сама последовательность является возрастающей, вернуть себя; если ни один сегмент последовательности не является возрастающим, вернуть любое число. Как видно на рисунке, хотя3, 7, 22
также повышается, но поскольку22
После этого его нельзя продолжить, поэтому его длина равна 3, что совпадает с3, 7, 8, 9, 11, 12
Для сравнения, он, конечно, не самый длинный, поэтому найти его непросто.
В конкретном сценарии DOM diff, чтобы убедиться, что перемещается как можно меньше DOM, нам нужносохранить самый длинный восходящий подпорядокНе двигайтесь, просто перемещайте другие элементы. Зачем? Поскольку самая длинная восходящая подпоследовательность сама по себе относительно упорядочена, пока другие элементы перемещаются, ответ будет получен. Еще этот пример, предполагая, что исходный DOM находится в таком порядке возрастания (конечно, это должно быть 1 2 3 4 последовательных индекса, но не имеет значения, является ли алгоритм непрерывным или нет, пока он увеличивается):
Если вы сохраните самый длинный восходящий подпорядок без изменений, вам нужно будет переместиться всего три раза, чтобы восстановить:
Никакое другое движение не меньше трех шагов,Потому что мы сохранили детали, которые уже в порядке, насколько это возможно..
Итак, вопрос в том, как найти эту самую длинную восходящую подпоследовательность? Решения, о которых легче думать, это: насилие и динамическое программирование.
решение грубой силы
Временная сложность: O(2ⁿ)
Наконец, мы хотим сгенерировать самую длинную подпоследовательность, поэтому давайте смоделируем процесс создания этой подпоследовательности, но этот процесс насильственный.
Как создать подпоследовательность путем моделирования грубой силы? Это попытка выбрать или не выбрать текущий номер каждый раз из диапазона [0,n], при условии, что номер, выбранный после, больше, чем предыдущий номер. Так как длина массива равна n, каждое число может быть выбрано или нет, то есть у каждого числа есть два варианта, поэтому будет сгенерировано не более 2ⁿ результатов, и из него можно найти наибольшую длину, что и является ответом:
Если вы попробуете это тупо, вы обязательно сможете попробовать самый длинный сегмент, и вы можете записать самый длинный сегмент во время прохождения процесса.
Так как этот метод слишком неэффективен, его не рекомендуют, но этот вид насильственного мышления все же нужно освоить.
динамическое программирование
Временная сложность: O(n²)
Если рассматривать эту задачу с точки зрения динамического программирования, то определение DP(i) эмпирическое: длина самой длинной подпоследовательности, когда она заканчивается i-й строкой.
Здесь есть опыт, то есть общее возвращаемое значение DP датчика движения является ответом, а задача строки часто заканчивается на i-й строке, поэтому просканируйте ее один раз. При этом самая длинная подпоследовательность имеет повторяющиеся подзадачи, то есть i-я операция ответа включает в себя часть предыдущих вычислений.Чтобы не повторять вычисление, используется динамическое программирование.
Тогда это зависит от соотношения между результатом пункта i и предыдущими результатами, как показано на рисунке для удобства понимания:
Предположим, мы смотрим на число 8, которое и есть DP(4). Поскольку предыдущие DP (0), DP (1) ... DP (3) были рассчитаны в это время, давайте посмотрим, как DP (4) связан с предыдущими результатами расчета.
Простое наблюдение показывает, что еслиnums[i] > nums[i-1]
, то DP(i) равноDP(i-1) + 1
, это очевидно, то есть если 8 больше 4, то ответ на позиции 8 равен длине ответа на позиции 4 + 1, если значение на позиции 8 равно 3, что меньше 4, то ответ 1, потому что предыдущий Чтобы встретить восходящие отношения, вы можете бороться только в одиночку с числом 3.
Но если хорошенько подумать, то обнаружится, что эта подпоследовательность не обязательно должна быть непрерывной: если i-й элемент сочетается с i-2, i-3 элементом, то она может быть длиннее, чем комбинация с i-м элементом. -1 предмет? Возьмем встречный пример:
очевидно,1, 2, 3, 4
Комбинированная — это самая длинная восходящая подпоследовательность, если вы просто посмотрите на5, 4
, то ответ может быть только4
.
Именно из-за этой особенности прерывности нам необходимо сравнивать i-й предмет с j-м по очереди, гдеj=[0,i-1]
, только сравнив его со всеми предыдущими элементами, мы можем быть уверены, что результат, найденный i-м элементом, действительно является самым длинным:
Итак, как рассчитать временную сложность? В решении динамического программирования мы сначала выполняем цикл от 0 до n, а затем делаем это для каждого из этих i.[0,i-1]
, поэтому число вычислений равно1 + 2 + ... + n = n * (n + 1) / 2
, после исключения констант порядок величины равен O(n²).
Жадность + два очка
Временная сложность: O(nlogn)
Честно говоря, обычно хорошо иметь возможность придумать решение для динамического программирования, но очень сложно дополнительно оптимизировать временную сложность. Если вы еще не ответили на этот вопрос и хотите оспорить его, прекратите читать здесь.
Что ж, ответ озвучен.Честно говоря,этот метод не похож на то,что придумало нормальное человеческое мышление.У него много мысленных скачков,поэтому я не могу привести процесс вывода мышления.Скажем только вывод: жадность + дихотомия.
Если нам нужно сказать, что мы думаем, мы можем посмотреть на временную сложность постфактум.Как правило, временная сложность n² станет nlogn после оптимизации, а временная сложность бинарного поиска равна logn, поэтому мы отчаянно пытаемся найти способ объединить их Бар.
Конкретный план состоит всего из одного предложения:Используя структуру стека, если значение больше, чем все значения в стеке, оно помещается в стек, в противном случае заменяется наименьшее число, большее, чем оно, и длина конечного стека является ответом:
Сначала объясните временную сложность.По причинам, связанным с работой, числа, хранящиеся в стеке, расположены в порядке возрастания, поэтому для сравнения и вставки можно использовать двоичный метод.Сложность logn, а внешний слой-n петель, поэтому общая временная сложность O(nlogn) . Другая проблема с этой схемой заключается в том, что длина ответа является точной, но массив в стеке может быть неправильным. Если вы хотите полностью понять это предложение, вы должны полностью понять принцип этого алгоритма, и только после понимания принципа вы сможете узнать, как его улучшить, чтобы получить правильную подпоследовательность.
Далее, чтобы объяснить принцип,Начальное мышление не сложное, его можно посмотреть за чаем. Прежде всего, мы должны иметь интуитивное понимание, то есть, чтобы сделать самую длинную восходящую подпоследовательность максимально длинной, мы должны изо всех сил стараться, чтобы скорость роста выбранных чисел была как можно более медленной, иначе, это максимально быстро. Например, если число, которое мы выбираем, равно0, 1, 2, 3, 4
Тогда этот вид жадности относительно стабилен, потому что он рос максимально медленно, и можно заложить высокую вероятность столкнуться позже. Но если мы выберем0, 1, 100
выбери это100
Вы должны паниковать, потому что она увеличивается до100
,Позади100
Разве вы не отказываетесь от всех чисел внутри? на этот раз100
Это не обязательно мудрый выбор, но его выбрасывание может иметь больше места в будущем.Это на самом деле жадное мышление.Так называемое локальное оптимальное решение является глобальным оптимальным решением.
Но вышеприведенная мысль явно неполна, продолжаем думать, если читаем0, 1, 100
, если сзади нет числа, то100
Вы все еще можете поставить его, хотя100
Он огромен, но он последний и все еще полезен.Поэтому при переходе слева направо, если вы встретите большее число, вы должны сначала ввести его., дело в том, что если вы продолжите перечитывать, вы прочитаете больше, чем100
Число все еще мало, что мне делать?
Если вы не можете сделать скачок в мышлении здесь, анализ может остановиться только там. Вы можете почувствовать, что можете продолжать анализировать, например, столкнувшись с5
, очевидно,100
Выжимай, потому что0, 1, 5
а также0, 1, 100
длина 3, но0, 1, 5
«Потенциал» значительно выше, чем0, 1, 100
Большой, поэтому длина осталась прежней, у одного больший потенциал и его необходимо заменить! Эта идея верна, но в другом сценарии, если вы столкнетесь с3, 7, 11, 15
, вы столкнулись9
, как изменить? Если рассматривать потенциал,3, 7, 9
Потенциал лучший, но длина приносится в жертву с 4 на 3, и не знаешь, если нет сравнения9
Если он больше, если его больше нет, эта длина не так хороша, как исходная 4; если это считается по длине, оставьте ее.3, 7, 11, 15
, в случае нескольких последовательных10, 12, 13, 14
Также ошеломлен, немного чувство близорукости.
Вот и вопрос, как поступить с очередным номером, чтобы не быть недальновидным в будущем, и "ухватиться за устойчивое счастье".Прыгающее мышление начинается здесь, ответ "если значение больше всех значений в стеке, то протолкнуть стек, в противном случае заменить наименьшее число большее, чем оно", упомянутое в приведенной выше схеме. Это отражает скачкообразное мышление, и суть реализации настоящего и будущего заключается в следующем:Жертвуя правильностью содержимого стека и гарантируя правильную общую длину, каждый шаг может использовать наилучшую возможность в будущем.Только когда общая длина верна, можно гарантировать самую длинную последовательность.Что касается принесения в жертву правильности содержимого стека, это стоит дорого, но в обмен на возможность в будущем, по крайней мере, длина может получить правильный результат, если содержание также будет корректным, то можно добавить некоторые вспомогательные средства для его решения, о которых речь пойдет позже. Так что в целом эта жертва очень оправдана.На следующем рисунке показано, почему жертвование правильностью содержимого стека может привести к правильной длине и использовать будущие возможности.
Возьмем крайний пример:3, 7, 11, 15, 9, 11, 12
, если вы будете придерживаться того, что нашли в начале3, 7, 11, 15
, это всего 4 в длину, но если вы сдадитесь11, 15
,Пучок3, 7, 9, 11, 12
Подключено, длина лучше. Согласно жадному алгоритму, мы сначала встретим3
7
11
15
, так как каждое число больше предыдущего, тут и думать нечего, просто запихиваем в стек:
встретить9
это прекрасное время,В настоящее время9
Не самый большой, чтобы уловить устойчивое счастье, мы просто сравниваем9
немного больше11
заменили, что в результате?
Во-первых, длина массива не изменится, потому что операция замены не изменит длину массива.9
Позже значения нет, и мы не растерялись.На данный момент выходная длина 4 по-прежнему является лучшим ответом. мы продолжаем, следующая встреча11
, ставим еще чуть больше15
заменять:
В этот момент мы заменяем последнюю цифру и находим3, 7, 9, 11
Наконец разумный порядок, и длина и3, 7, 11, 15
Такой же,но больше потенциала, следующий12
Поставил в конце как надо, и получил окончательный ответ: 5.
На самом деле суть этого алгоритма здесь не разъяснена внятно, вернемся к3, 7, 9, 15
Этот шаг, выяснить9
Почему его можно заменить11
.
Предположение9
Позади большой99
, то следующий шаг99
будет добавлено непосредственно в конец:
То, что мы получаем в этот момент,3, 7, 9, 15, 99
, но если вы присмотритесь, то обнаружите, что в исходной последовательности9
существует15
позже, потому что наша вставка приводит к9
помещать15
раньше, так что это явно не правильный ответ, но длина правильная, потому что этот ответ эквивалентен нашему выбору3, 7, 11, 15, 99
! Почему это так понятно? потому чтоПока последнее число не заменено, очередь в нашем сердце на самом деле является исходной очередью.
То есть, пока стек не полностью заменен, вновь вставленное значение всегда будет служить только заполнителем. Цель состоит в том, чтобы позволить легко вставлять новое значение, но если действительно нет нового значения для вставки, то хотя содержимое стека Нет, но хотя бы длина правильная, т.к.9
Это не совсем так, когда его не заменили9
, это просто заполнитель, значение по-прежнему11
. Поэтому, как бы вы его ни меняли, пока последний не заменяется, операция замены недействительна.Возьмем другой пример:
видимый,1, 2, 3, 4
не могу поставить7, 8, 9, 10, 11
заменены, поэтому окончательный результат1, 2, 3, 4, 11
, но это не беда, пока замена не сделана, ответ7, 8, 9, 10, 11
, просто мы его не записывали, но если посмотреть на длину, между ними нет никакой разницы, так что это не проблема. что если1, 2, 3, 4, 5, 6
Шерстяная ткань? Давайте посмотрим, что произойдет, когда мы сможем заменить его:
виден при замене на5
, порядок последовательности правильный, потому что1, 2, 3, 4, 5
был полностью заменен7, 8, 9, 10, 11
, а потенциал больше его, мы нашли оптимальное локальное решение. так1, 2, 3, 4, 11
здесь1, 2, 3, 4
как под прикрытием, в11
Когда он был еще там, он сказал7, 8, 9, 10, 11
для босса (на самом деле1
сказать7
для босса,2
сказать8
для начальника и так далее), но когда5
Когда вы входите,1, 2, 3, 4, 5
ты можешь и7, 8, 9, 10, 11
Он повернулся лицом, потому что его сила превысила силу первоначального босса.
Затем, казалось бы, незначительные замены, которые мы сделали перед нами, на самом деле, на самом деле, чтобы продолжать искать наилучшее возможное решение в будущем до дня первых дней. Если ранних дней нет, это хорошо быть младшим братом , В первые дни максимальная длина была обновлена, поэтому такая жадность может учитывать как правильность, так и эффективность.
Наконец, давайте посмотрим, как мы можем найти правильную последовательность при поиске ответа?
найти правильную последовательность
Найти правильную последовательность непросто, давайте рассмотрим такую ситуацию:
После завершения жадного алгоритма общая длина верна, но очевидно, что порядок по-прежнему неправильный. Для того, чтобы облегчить вычисление, преобразуем его в индекс при сохранении:
И используйте хранилище двумерных массивов, чтобы можно было сохранить замененные числа. Когда вычисление завершено, мы начинаем с последнего бита и ищем вперед,Как только найдено значение, которое не монотонно убывает, поиск продолжается вверх по массиву до первого узла.
Таким образом, в приведенном выше примере последний последовательный индекс равен[0, 1, 2, 3, 4, 5, 9]
, соответствующие цифры[10, 20, 30, 40, 50, 60, 61]
,И это число является самой длинной подпоследовательностью с наибольшим потенциалом.
Суммировать
Итак, сколько Vue, наконец, использовал жадность для вычисления самой длинной восходящей подпоследовательности? На самом деле это соотношение между O(n) и O(nlogn) Давайте посмотрим на картинку:
Видно, что тенденция роста временной сложности O(nlogn) едва ли приемлема, особенно в инженерных сценариях, когда количество дочерних узлов родительского узла не может быть слишком большим, это не займет слишком много времени на анализ. Преимущество заключается в минимальном движении DOM. Это идеальное сочетание алгоритма и инженерной практики.
Адрес обсуждения:Интенсивное чтение «Самая длинная восходящая последовательность DOM diff», выпуск № 310 dt-fe / еженедельно
Если вы хотите принять участие в обсуждении, пожалуйста,кликните сюда, с новыми темами каждую неделю, выходящими по выходным или понедельникам. Интерфейс интенсивного чтения — поможет вам отфильтровать надежный контент.
Сфокусируйся наАккаунт WeChat для интенсивного чтения в интерфейсе
Заявление об авторских правах: Бесплатная перепечатка - некоммерческая - не производная - сохранить авторство (Лицензия Creative Commons 3.0)