Поиск k-ой порядковой статистики в двух массивах — различия между версиями
Анна (обсуждение | вклад) (→Совсем не наивное решение) |
Анна (обсуждение | вклад) |
||
Строка 4: | Строка 4: | ||
== Варианты решения == | == Варианты решения == | ||
=== Наивное решение === | === Наивное решение === | ||
− | Сольем два массива и просто возьмем элемент с индексом <tex>k - 1</tex>. Сливание будет выполнено за <tex>O(n + m)</tex> c использованием дополнительной памяти, что является существенным недостатком. | + | Сольем два массива и просто возьмем элемент с индексом <tex>k</tex> {{---}} <tex>1</tex>. Сливание будет выполнено за <tex>O(n + m)</tex> c использованием дополнительной памяти, что является существенным недостатком. |
=== Чуть менее наивное решение === | === Чуть менее наивное решение === | ||
− | Будем использовать два указателя, с помощью которых сможем обойти массивы не сливая их. Поставим указатели на начало каждого из массивов. Будем увеличивать на единицу тот из них, который указывает на меньший элемент. После <tex>(k - 1)</tex>-ого добавления сравним элементы, на которых стоят указатели. Меньший из них и будет ответом. Таким образом, мы получим <tex>k</tex>-ый элемент за <tex>O(k)</tex> шагов. | + | Будем использовать два указателя, с помощью которых сможем обойти массивы не сливая их. Поставим указатели на начало каждого из массивов. Будем увеличивать на единицу тот из них, который указывает на меньший элемент. После <tex>(k</tex> {{---}} <tex>1)</tex>-ого добавления сравним элементы, на которых стоят указатели. Меньший из них и будет ответом. Таким образом, мы получим <tex>k</tex>-ый элемент за <tex>O(k)</tex> шагов. |
=== Совсем не наивное решение === | === Совсем не наивное решение === | ||
Оба решения, приведенные выше, работают за линейное время, то есть приемлемы только при небольших значениях <tex>k</tex>. Следующее решение работает за <tex>O(log(n) + log(m))</tex>. | Оба решения, приведенные выше, работают за линейное время, то есть приемлемы только при небольших значениях <tex>k</tex>. Следующее решение работает за <tex>O(log(n) + log(m))</tex>. | ||
Строка 12: | Строка 12: | ||
Чтобы получить логарифмическую сложность, будем использовать [[Целочисленный двоичный поиск|бинарный поиск]], который сокращает область поиска с каждой итерацией. То есть для достижения нужной сложности мы должны на каждой итерации сокращать круг поиска в каждом из массивов. | Чтобы получить логарифмическую сложность, будем использовать [[Целочисленный двоичный поиск|бинарный поиск]], который сокращает область поиска с каждой итерацией. То есть для достижения нужной сложности мы должны на каждой итерации сокращать круг поиска в каждом из массивов. | ||
− | Рассмотрим следующую ситуацию: пусть у нас есть элемент <tex>a[i]</tex> из массива <tex>A</tex> и элемент <tex>b[j]</tex> из массива <tex>B</tex> и они связаны неравенством <tex>b[j - 1] < a[i] < b[j]</tex>. Тогда <tex>a[i]</tex> есть <tex>(j + i + 1)</tex>-ый порядковый элемент после слияния массивов. Это объясняется тем, что до <tex>a[i]</tex>-ого элемента идут <tex>(j - 1)</tex> элемент из массива <tex>B</tex>, <tex>i</tex> элементов из массива <tex>A</tex> (включая сам элемент <tex>a[i]</tex>). В итоге получаем <tex>(j - 1) + i + 2 = j + i + 1</tex>. Принимая это во внимание, будем выбирать <tex>i</tex> и <tex>j</tex> таким образом, чтобы <tex>j + i + 1 = k</tex>. | + | Рассмотрим следующую ситуацию: пусть у нас есть элемент <tex>a[i]</tex> из массива <tex>A</tex> и элемент <tex>b[j]</tex> из массива <tex>B</tex> и они связаны неравенством <tex>b[j</tex> {{---}} <tex>1] < a[i] < b[j]</tex>. Тогда <tex>a[i]</tex> есть <tex>(j + i + 1)</tex>-ый порядковый элемент после слияния массивов. Это объясняется тем, что до <tex>a[i]</tex>-ого элемента идут <tex>(j</tex> {{---}} <tex>1)</tex> элемент из массива <tex>B</tex>, <tex>i</tex> элементов из массива <tex>A</tex> (включая сам элемент <tex>a[i]</tex>). В итоге получаем <tex>(j</tex> {{---}} <tex>1) + i + 2 = j + i + 1</tex>. Принимая это во внимание, будем выбирать <tex>i</tex> и <tex>j</tex> таким образом, чтобы <tex>j + i + 1 = k</tex>. |
Подведем промежуточный итог: | Подведем промежуточный итог: | ||
− | # Инвариант <tex>j + i = k - 1</tex> | + | # Инвариант <tex>j + i = k</tex> {{---}} <tex>1</tex> |
− | # Если <tex>b[j - 1] < a[i] < b[j]</tex>, то <tex>a[i]</tex> и есть <tex>k</tex>-ая порядковая статистика | + | # Если <tex>b[j</tex> {{---}} <tex>1] < a[i] < b[j]</tex>, то <tex>a[i]</tex> и есть <tex>k</tex>-ая порядковая статистика |
− | # Если <tex>a[i - 1] < b[j] < a[i]</tex>, то <tex>b[j]</tex> и есть <tex>k</tex>-ая порядковая статистика | + | # Если <tex>a[i</tex> {{---}} <tex>1] < b[j] < a[i]</tex>, то <tex>b[j]</tex> и есть <tex>k</tex>-ая порядковая статистика |
Итак, если одно из двух последних условий выполняется, то мы нашли нужный элемент. Иначе нам нужно сократить область поиска, как задумывалось в начале. | Итак, если одно из двух последних условий выполняется, то мы нашли нужный элемент. Иначе нам нужно сократить область поиска, как задумывалось в начале. | ||
− | Будем использовать <tex>i</tex> и <tex>j</tex> как опорные точки для разделения массивов. Заметим, что если <tex>a[i] < b[j]</tex>, то <tex>a[i] < b[j - 1]</tex> (иначе второе условие бы выполнялось). В таком случае на месте <tex>i</tex>-го элемента может стоять максимум <tex>i + (j - 2) + 2 = (i + j)</tex>-ый порядковый элемент после слияния массивов (так произойдет в случае, когда <tex>a[i] > b[j - 2]</tex>), а значит элемент с номером <tex>i</tex> и все до него в массиве <tex>A</tex> никогда не будут <tex>k</tex>-ой порядковой статистикой. Аналогично элемент с индексом <tex>j</tex> и все элементы, стоящие после него, в массиве <tex>B</tex> никогда не будут ответом, так как на позиции <tex>j</tex> будет стоять <tex>(i + j + 2)</tex>-ой порядковый элемент после слияния, порядковые номера остальных же будут еще больше. Таким образом, далее мы можем продолжать поиск в массиве <tex>A</tex> только в диапазоне индексов <tex>[i + 1, n - 1]</tex>, а в массиве <tex>B</tex> - <tex>[0, j - 1]</tex>. Также, если <tex>b[j] < a[i]</tex>, то <tex>b[j] < a[i - 1]</tex>. Аналогичными рассуждениями приходим к тому, что в таком случае дальнейший поиск нужно осуществлять в массиве <tex>A</tex> в диапазоне <tex>[0, i - 1]</tex>, в массиве <tex>B</tex> - <tex>[j + 1, m - 1]</tex>. | + | Будем использовать <tex>i</tex> и <tex>j</tex> как опорные точки для разделения массивов. Заметим, что если <tex>a[i] < b[j]</tex>, то <tex>a[i] < b[j</tex> {{---}} <tex>1]</tex> (иначе второе условие бы выполнялось). В таком случае на месте <tex>i</tex>-го элемента может стоять максимум <tex>i + (j</tex> {{---}} <tex>2) + 2 = (i + j)</tex>-ый порядковый элемент после слияния массивов (так произойдет в случае, когда <tex>a[i] > b[j</tex> {{---}} <tex>2]</tex>), а значит элемент с номером <tex>i</tex> и все до него в массиве <tex>A</tex> никогда не будут <tex>k</tex>-ой порядковой статистикой. Аналогично элемент с индексом <tex>j</tex> и все элементы, стоящие после него, в массиве <tex>B</tex> никогда не будут ответом, так как на позиции <tex>j</tex> будет стоять <tex>(i + j + 2)</tex>-ой порядковый элемент после слияния, порядковые номера остальных же будут еще больше. Таким образом, далее мы можем продолжать поиск в массиве <tex>A</tex> только в диапазоне индексов <tex>[i + 1, n</tex> {{---}} <tex>1]</tex>, а в массиве <tex>B</tex> {{---}} <tex>[0, j</tex> {{---}} <tex>1]</tex>. Также, если <tex>b[j] < a[i]</tex>, то <tex>b[j] < a[i</tex> {{---}} <tex>1]</tex>. Аналогичными рассуждениями приходим к тому, что в таком случае дальнейший поиск нужно осуществлять в массиве <tex>A</tex> в диапазоне <tex>[0, i</tex> {{---}} <tex>1]</tex>, в массиве <tex>B</tex> {{---}} <tex>[j + 1, m</tex> {{---}} <tex>1]</tex>. |
'''int''' findKthOrderStatistic('''int[]''' A, '''int''' n, '''int[]''' B, '''int''' m, '''int''' k): | '''int''' findKthOrderStatistic('''int[]''' A, '''int''' n, '''int[]''' B, '''int''' m, '''int''' k): | ||
Строка 32: | Строка 32: | ||
'''int''' Bj_left = ((j == 0) ? INT_MIN : B[j-1]) | '''int''' Bj_left = ((j == 0) ? INT_MIN : B[j-1]) | ||
'''int''' Bj = ((j == m) ? INT_MAX : B[j]) | '''int''' Bj = ((j == m) ? INT_MAX : B[j]) | ||
− | '''if''' (Bj_left < Ai | + | '''if''' (Bj_left < Ai and Ai < Bj): |
− | + | '''return''' Ai | |
− | '''else if''' (Ai_left < Bj | + | '''else if''' (Ai_left < Bj and Bj < Ai): |
− | + | '''return''' Bj | |
'''if''' (Ai < Bj): | '''if''' (Ai < Bj): | ||
− | + | '''return''' findKthOrderStatistic(A + i + 1, n - i - 1, B, j, k- i - 1) | |
'''else''' | '''else''' | ||
− | + | '''return''' findKthOrderStatistic(A, i, B + j + 1, m - j - 1, k - j - 1) | |
==См. также== | ==См. также== |
Версия 18:48, 16 апреля 2015
Задача: |
Пусть даны два отсортированных массива найти после их слияния. Будем считать, что все элементы в массивах различны и нумеруются с нуля. -ый порядковый элемент | и размерами и соответственно. Требуется
Содержание
Варианты решения
Наивное решение
Сольем два массива и просто возьмем элемент с индексом
— . Сливание будет выполнено за c использованием дополнительной памяти, что является существенным недостатком.Чуть менее наивное решение
Будем использовать два указателя, с помощью которых сможем обойти массивы не сливая их. Поставим указатели на начало каждого из массивов. Будем увеличивать на единицу тот из них, который указывает на меньший элемент. После
— -ого добавления сравним элементы, на которых стоят указатели. Меньший из них и будет ответом. Таким образом, мы получим -ый элемент за шагов.Совсем не наивное решение
Оба решения, приведенные выше, работают за линейное время, то есть приемлемы только при небольших значениях
. Следующее решение работает за .Чтобы получить логарифмическую сложность, будем использовать бинарный поиск, который сокращает область поиска с каждой итерацией. То есть для достижения нужной сложности мы должны на каждой итерации сокращать круг поиска в каждом из массивов.
Рассмотрим следующую ситуацию: пусть у нас есть элемент
из массива и элемент из массива и они связаны неравенством — . Тогда есть -ый порядковый элемент после слияния массивов. Это объясняется тем, что до -ого элемента идут — элемент из массива , элементов из массива (включая сам элемент ). В итоге получаем — . Принимая это во внимание, будем выбирать и таким образом, чтобы .Подведем промежуточный итог:
- Инвариант —
- Если — , то и есть -ая порядковая статистика
- Если — , то и есть -ая порядковая статистика
Итак, если одно из двух последних условий выполняется, то мы нашли нужный элемент. Иначе нам нужно сократить область поиска, как задумывалось в начале.
Будем использовать
и как опорные точки для разделения массивов. Заметим, что если , то — (иначе второе условие бы выполнялось). В таком случае на месте -го элемента может стоять максимум — -ый порядковый элемент после слияния массивов (так произойдет в случае, когда — ), а значит элемент с номером и все до него в массиве никогда не будут -ой порядковой статистикой. Аналогично элемент с индексом и все элементы, стоящие после него, в массиве никогда не будут ответом, так как на позиции будет стоять -ой порядковый элемент после слияния, порядковые номера остальных же будут еще больше. Таким образом, далее мы можем продолжать поиск в массиве только в диапазоне индексов — , а в массиве — — . Также, если , то — . Аналогичными рассуждениями приходим к тому, что в таком случае дальнейший поиск нужно осуществлять в массиве в диапазоне — , в массиве — — .int findKthOrderStatistic(int[] A, int n, int[] B, int m, int k): int i = random(0 .. n - 1) int j = (k - 1) - i // чтобы сохранить инвариант сделаем A[-1] = -INF и A[n] = +INF B[-1] = -INF и B[m] = +INF int Ai_left = ((i == 0) ? INT_MIN : A[i-1]) int Ai = ((i == n) ? INT_MAX : A[i]) int Bj_left = ((j == 0) ? INT_MIN : B[j-1]) int Bj = ((j == m) ? INT_MAX : B[j]) if (Bj_left < Ai and Ai < Bj): return Ai else if (Ai_left < Bj and Bj < Ai): return Bj if (Ai < Bj): return findKthOrderStatistic(A + i + 1, n - i - 1, B, j, k- i - 1) else return findKthOrderStatistic(A, i, B + j + 1, m - j - 1, k - j - 1)