Структура даних для карти по інтервалах


11

Нехай - ціле число, а позначає набір усіх цілих чисел. Нехай позначає інтервал цілих чисел .nZ[a,b]{a,a+1,a+2,,b}

Я шукаю структуру даних, щоб представити карту . Я хочу, щоб структура даних підтримувала такі операції:f:[1,n]Z

  • get(i) повинен повернути .f(i)

  • set([a,b],y) повинен оновити так, що , тобто оновити f на нову карту f ' такий, що f' (i) = y для i \ in [a, b] і f '(i) = f (i) для i \ notin [a, b] .ff(a)=f(a+1)==f(b)=yfff(i)=yi[a,b]f(i)=f(i)i[a,b]

  • stab(i) повинен повернути найбільший інтервал [a,b] такий, що i[a,b] і f є постійним на [a,b] (тобто f(a)=f(a+1)==f(b) ).

  • add([a,b],δ) повинен оновити f до нової карти f таким, що f(i)=f(i)+δ для i[a,b] і f(i)=f(i) для i[a,b] .

Я хочу, щоб кожна з цих операцій була ефективною. Я б вважав час O(1) або O(lgn) ефективним, але час O(n) занадто повільний. Це нормально, якщо час роботи амортизується. Чи існує структура даних, яка одночасно робить всі ці операції ефективними?

(Я помітив подібну схему в декількох завданнях програмування. Це узагальнення, якого вистачить для всіх цих проблемних проблем.)


Я думаю, що відхідні дерева - це відправна точка. addбуло б лінійним за кількістю підінтервалів хоча; Ви думали про хитромудре дерево з додатковими одинарними вузлами " ", ліниво ущільнені? + δ[a,b]+δ
Жил "ТАК - перестань бути злим"

Розглянемо таким, що для всіх , . Тоді ви повинні десь зберігати значень. Виконуючи , потрібно якось позбутися цих значень (переписавши їх або викинувши - можна відкласти з GC, але вам доведеться робити операції в деякій точці). Таким чином, операція буде . ff(i)f(j)j n безліч ( [ a , b ] , y ) O ( n ) O ( n )ijnset([a,b],y)O(n)O(n)
avakar

@avakar, я був би задоволений рішенням, яке GC вважає ефективно "безкоштовним". Загалом, я був би задоволений рішенням, коли тривалість часу амортизується тривалістю (таким чином, вартість GC може бути амортизована в першу чергу на вартість створення вартості).
DW

Ви зазначили, що постійний і логарифмічний час ефективні, а лінійний час повільний. Чи буде час занадто повільним для ваших потреб? O(nlgn)
jbapple

@jbapple, ей, це початок! Я думаю, що це варто документувати як відповідь.
DW

Відповіді:


4

Я вважаю, що логарифмічний час для всіх запитів досяжний. Основна ідея полягає у використанні інтервального дерева, де кожному вузлу в дереві відповідає інтервал індексів. Я буду створювати основні ідеї, починаючи з більш простої версії структури даних (яка може підтримувати отримання та встановлення, але не інші операції), а потім додаю функції для підтримки інших функцій.

Проста схема (підтримує отримати і встановити, але не додавати і не встановлювати)

Скажіть, що інтервал є рівним, якщо функція постійна на , тобто якщо .f [ a , b ] f ( a ) = f ( a + 1 ) = = f ( b )[a,b]f[a,b]f(a)=f(a+1)==f(b)

Наша проста структура даних буде інтервальним деревом. Іншими словами, у нас є двійкове дерево, де кожному вузлу відповідає інтервал (з індексів). Ми збережемо відповідний інтервал у кожному вузлі дерева. Кожен аркуш буде відповідати плоскому проміжку, і вони будуть розташовані так, що зчитування листя зліва направо дає нам послідовність послідовних плоских інтервалів, які непересічні і чиє об'єднання є . Інтервал для внутрішнього вузла буде об'єднанням інтервалів двох його дітей. Також у кожному вузлі аркуша ми будемо зберігати значення функції на інтерваліv [ 1 , n ] V ( ) f I ( )I(v)v[1,n]V()fI()відповідний цьому вузлу (зауважте, що цей інтервал плоский, тому постійний на проміжку, тому ми просто зберігаємо одне значення у кожному вузлі аркуша).fff

Еквівалентно можна уявити, що ми розділимо на плоскі інтервали, і тоді структура даних є двійковим деревом пошуку, де клавішами є ліві кінцеві точки цих інтервалів. Листя містять значення за деяким діапазоном показників, де постійний.f f[1,n]ff

Використовуйте стандартні методи для того, щоб двійкове дерево залишалося врівноваженим, тобто його глибина дорівнює (де підраховує поточну кількість листя на дереві). Звичайно, , тому глибина завжди становить максимум . Це буде корисно нижче.m m n O ( lg n )O(lgm)mmnO(lgn)

Тепер ми можемо підтримувати операції get і set наступним чином:

  • get(i) легко: ми обходимо дерево, щоб знайти листок, інтервал якого містить . Це в основному просто обхід бінарного дерева пошуку. Оскільки глибина , час роботи .O ( lg n ) O ( lg n )iO(lgn)O(lgn)

  • set([a,b],y) складніше. Це працює так:

    1. Спочатку знаходимо інтервал листка що містить ; якщо , то ми розділимо цей інтервал листочків на два інтервали та (таким чином перетворивши цей вузол листів у внутрішній вузол та ввівши двох дітей).a a 0 < a [ a 0 , a - 1 ] [ a , b 0 ][a0,b0]aa0<a[a0,a1][a,b0]

    2. Далі знаходимо інтервал листя що містить ; якщо , ми розділимо цей інтервал листочків на два інтервали та (таким чином перетворивши цей вузол листів у внутрішній вузол та ввівши двох дітей).b b < b 1 [ a 1 , b ] [ b + 1 , b 1 ][a1,b1]bb<b1[a1,b][b+1,b1]

    3. У цей момент я стверджую, що інтервал можна виразити як неперервне об'єднання інтервалів відповідають деякій підмножині вузлів у дереві. Отже, видаліть усі нащадки цих вузлів (перетворюючи їх на листя) і встановіть значення, збережене в цих вузлах, .O ( lg n ) O ( lg n ) y[a,b]O(lgn)O(lgn)y

    4. Нарешті, оскільки ми змінили форму дерева, ми здійснимо будь-які необхідні обертання, щоб відновити баланс дерева (використовуючи будь-яку стандартну техніку для підтримки балансу дерева).

    Оскільки ця операція включає декілька простих операцій на вузлах (а цей набір вузлів легко знайти за час ), загальний час для цієї операції становить .O ( lg n ) O ( lg n )O(lgn)O(lgn)O(lgn)

Це показує, що ми можемо підтримувати операції отримання та встановлення в час за операцію. Насправді час роботи може бути показано як , де - кількість встановлених операцій, виконаних до цього часу.O ( lg min ( n , s ) ) sO(lgn)O(lgmin(n,s))s

Додавання підтримки для add

Ми можемо змінити вищевказану структуру даних, щоб вона також підтримувала операцію додавання. Зокрема, замість того, щоб зберігати значення функції у листках, вона буде представлена ​​як сума чисел, що зберігаються у наборі вузлів.

Точніше, значення функції на вході буде відновлено як сума значень, що зберігаються у вузлах на шляху від кореня дерева до листа, інтервал якого містить . У кожному вузлі ми збережемо значення ; якщо представляють предків листа (включаючи сам лист), то значення функції в буде .i i v V ( v ) v 0 , v 1 , , v k v k I ( v k ) V ( v 0 ) + + V ( v k )f(i)iivV(v)v0,v1,,vkvkI(vk)V(v0)++V(vk)

Підтримувати операції отримання та встановлення легко, використовуючи варіант описаних вище методів. В основному, коли ми переходимо по дереву вниз, ми відстежуємо поточну суму значень, так що для кожного вузла який проходить обхід, ми знатимемо суму значень вузлів на шляху від кореня до . Як тільки ми це зробимо, простих коригувань реалізації описаних вище get і set буде достатньо.хxx

І тепер ми можемо ефективно підтримувати . Спочатку ми виражаємо інтервал як об'єднання інтервалів відповідають деякому набору дерев (розділяючи вузол на ліву кінцеву точку та праву кінцеву точку, якщо потрібно ) точно так, як це зроблено на кроках 1-3 встановленої операції. Тепер ми просто додаємо до значення, що зберігається у кожному з цих вузлів. (Ми не видаляємо їхніх нащадків.)[ a , b ] O ( lg n ) O ( lg n ) δ O ( lg n )add([a,b],δ)[a,b]O(lgn)O(lgn)δO(lgn)

Це забезпечує спосіб підтримки отримати, встановити та додати за час за операцію. Насправді час роботи на одну операцію становить де підраховує кількість заданих операцій плюс кількість операцій додавання.O ( lg min ( n , s ) ) sO(lgn)O(lgmin(n,s))s

Підтримка колючої операції

Запит на колотування є найбільш складним для підтримки. Основною ідеєю буде модифікація вищевказаної структури даних, щоб зберегти наступний додатковий інваріант:

(*) Інтервал відповідний кожному листу - це максимальний плоский інтервал.I()

Тут я кажу, що інтервал - максимальний плоский інтервал, якщо (i) є плоским, і (ii) жоден інтервал, що містить є рівним (іншими словами, для всіх задовольняє , або або не є плоским).[ a , b ] [ a , b ] a , b 1 a a b b n [ a , b ] = [ a , b ] [ a , b ' ][a,b][a,b][a,b]a,b1aabbn[a,b]=[a,b][a,b]

Це робить операцію наносування легко виконати:

  • istab(i) знаходить аркуш, інтервал якого містить , і повертає цей інтервал.i

Однак зараз нам потрібно змінити набір і додати операції для підтримки інваріанта (*). Кожен раз, коли ми розділяємо лист на два, ми можемо порушити інваріант, якщо деякі сусідні пари інтервалів листків мають однакове значення функції . На щастя, кожна операція встановлення / додавання додає максимум 4 нові інтервали аркуша. Крім того, для кожного нового інтервалу легко знайти інтервал листка відразу зліва та справа від нього. Тому ми можемо сказати, чи був порушений інваріант; якщо це було, то ми зливаємо суміжні інтервали, де має однакове значення. На щастя, об'єднання двох суміжних інтервалів не викликає каскадні зміни (тому нам не потрібно перевіряти, чи може злиття ввело додаткові порушення інваріанта). Загалом, це включає вивченняf 12 = O ( 1 )ff12=O(1)пари інтервалів і, можливо, їх об'єднання. Нарешті, оскільки злиття змінює форму дерева, якщо це порушує інваріанти балансу, виконайте будь-які необхідні обертання, щоб зберегти дерево збалансованим (дотримуючись стандартних методик для збалансованості бінарних дерев). Загалом це додає не більше додаткових робіт до операцій set / add.O(lgn)

Таким чином, ця кінцева структура даних підтримує всі чотири операції, а час виконання для кожної операції становить . Більш точна оцінка - час на операцію, де підраховує кількість операцій встановлення та додавання.O ( lg min ( n , s ) ) sO(lgn)O(lgmin(n,s))s

Розставання думок

Фу, це була досить складна схема. Сподіваюся, я не помилився. Будь ласка, уважно перевірте мою роботу, перш ніж покладатися на це рішення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.