Техника частичного каскадирования — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Построение)
(К псевдокоду добавлены некоторые комментарии, исправлен пункт 11)
Строка 65: Строка 65:
 
  M[k] = C[k]
 
  M[k] = C[k]
 
  '''for''' i = k - 1 '''to''' 1
 
  '''for''' i = k - 1 '''to''' 1
     pointer_in_C = 1
+
     pointer_in_C = 1                     <font color=green>// указатель на самый левый элемент каталога C[i], который еще не рассмотрели </font>
     pointer_in_next_M = 1
+
     pointer_in_next_M = 1               <font color=green>// указатель на самый левый элемент каталога M[i + 1], который еще не рассмотрели </font>
     pointer_in_M = 1
+
     pointer_in_M = 1                     <font color=green>// указатель на самый левый элемент каталога M[i], в который будем добавлять элемент </font>
     last_non_alien = NULL
+
     node last_non_alien = NULL           <font color=green>// указатель на последний ''неподставной элемент'' для текущей позиции </font>
     last_alien = NULL
+
     node last_alien = NULL               <font color=green>// указатель на последний ''подставной элемент'' для текущей позиции</font>
 
     '''while'''('''true''')
 
     '''while'''('''true''')
 
         '''if''' (pointer_in_next_M > M[i + 1].size && pointer_in_C > C[i].size)  
 
         '''if''' (pointer_in_next_M > M[i + 1].size && pointer_in_C > C[i].size)  
Строка 86: Строка 86:
 
             pointer_in_next_M += 2
 
             pointer_in_next_M += 2
 
         pointer_in_M++
 
         pointer_in_M++
     last_non_alien = (!M[i][M[i].size].is_alien ? M[i][M[i].size] : NULL)   
+
     last_non_alien = (!M[i][M[i].size].is_alien ? M[i][M[i].size] : NULL)  <font color=green>// теперь это указатель на первый справа ''неподставной элемент'' для текущей позиции</font>
 
     '''for''' j = M[i].size - 1 '''to''' 1       
 
     '''for''' j = M[i].size - 1 '''to''' 1       
         if (M[i][j].is_alien)
+
         '''if''' (M[i][j].is_alien)
 
             M[i][j].right = last_non_alien
 
             M[i][j].right = last_non_alien
         else
+
         '''else'''
 
             M[i][j].right = M[i][j + 1]
 
             M[i][j].right = M[i][j + 1]
 
             last_non_alien = M[i][j]
 
             last_non_alien = M[i][j]
Строка 106: Строка 106:
 
  '''for''' i = 2 '''to''' k
 
  '''for''' i = 2 '''to''' k
 
     cell = cell.left.down
 
     cell = cell.left.down
     if (cell.right <= x)
+
     '''if''' (cell.right <= x)       <font color=green>// Попытка сдвинуться к большему элементу </font>
           cell = cell.right
+
           cell = cell.right    
     if (cell.right <= x)
+
     '''if''' (cell.right <= x)       <font color=green>// Попытка сдвинуться к большему элементу </font>
           cell = cell.right
+
           cell = cell.right     <font color=green>// Замечание: по построению, если мы стоим в ''неподставном элементе'', то при сдвиге вправо мы можем оказаться в элементе любого типа</font>
     if (cell.is_alien)
+
     '''if''' (cell.is_alien)         <font color=green>// Для этого есть проверка </font>
 
           cell = cell.left
 
           cell = cell.left
 
     ans[i] = cell.key
 
     ans[i] = cell.key

Версия 00:23, 8 июня 2017

Определение:
Каталог (англ. catalog)— упорядоченный массив из элементов, на которых введено отношение порядка. В данной статье предполагается, что массив упорядочен по неубыванию.


Определение:
Техника частичного каскадирования (англ. fractional cascading technique) — это способ организации структуры данных, который предназначен для быстрого итеративного поиска в [math] k [/math] каталогах.


Задача:
Дано [math] k [/math] каталогов [math] C_i [/math], каталог [math]C_i[/math] имеет размер [math] n_i [/math]. Поступают запросы, которые представляют собой один элемент [math] x [/math]. Требуется для каждого запроса определить в каждом каталоге максимальный элемент меньше либо равный [math] x [/math].


Различные подходы к решению

Пример ответа на запрос

Пусть [math] n = \sum\limits_{i = 1}^k n_i [/math].
1) Для ответа на запрос последовательно посетим все каталоги. Пусть мы находимся в [math] i[/math]-ом каталоге, тогда мы можем ответить на запрос для данного каталога за [math] O(\log n_i) [/math], используя бинарный поиск. Так как каталогов [math] k [/math] штук, то для ответа на запрос понадобится [math] O(k \log n) [/math] времени. Для хранения всех каталогов понадобится [math] \Theta(n) [/math] памяти.
2) Для второго способа построим сбалансированное бинарное дерево поиска из всех элементов всех каталогов. В каждой вершине дерева будет хранится дополнительно кортеж из [math] k [/math] элементов — максимальных представителей каталогов меньше либо равных ключу вершины. Таким образом такая структура будет занимать [math] O(n) [/math] на дерево поиска и [math] O(kn) [/math] на дополнительные кортежи.Тогда для ответа на запрос найдем в дереве поиска максимальный ключ меньше либо равный [math] x [/math] и выведем [math] k [/math] элементов соответствующего кортежа, итого ответ на запрос производится за [math] O(\log n + k) [/math].

Итого имеем:

Тип подхода к решению Необходимая память Время ответа на один запрос
[math] k [/math] бинарных поисков
[math] \Theta(n) [/math]
[math] O(k \log n) [/math]
Построение бинарного дерева поиска с кортежами
[math] O(kn) [/math]
[math] O(\log n + k) [/math]

Решение с помощью техники частичного каскадирования

Как будет показано далее, эта техника берет лучшее от подходов к решению этой задачи, что были рассмотрены выше, а именно она требует [math] O(n) [/math] памяти и [math] O(\log n + k) [/math] времени для ответа на запрос.
Идея данной техники построена на следующем:

  • 1) Мы можем проводить ссылки из каталога номер [math] i [/math] в [math] (i + 1) [/math]-ый каталог таким образом, что разница между элементами, соединенными ссылками минимальна, что, очевидно, в некоторых случаях уменьшит время поиска.
  • 2) Мы можем для оптимизации пункта 1 создать модифицированные каталоги [math] M_i [/math], где [math] i [/math]-ый каталог будет представлять каталог [math] C_i [/math] слитый с [math] M_{i + 1} [/math]

Построение

Введем определения:

Определение:
Подставной элемент — элемент каталога [math] M_i [/math], который пришел из каталога [math] M_{i + 1} [/math]. А также каталоги [math] M_i [/math] будем называть модифицированными каталогами.
Построение модифицированных каталогов


Первый этап построения:

  • [math] i = k [/math] : Данный каталог не имеет никаких ссылок и равен [math] C_i [/math].
  • [math] i \lt k [/math] : Для построения данного каталога будем сливать каталог [math] C_i [/math] с каждым вторым элементом каталога [math] M_{i + 1} [/math]. Каждый элемент из каталога [math] M_{i + 1} [/math] оснастим ссылкой на позицию, откуда мы его взяли, такие ссылки будет называть ссылками вниз.


Второй этап построения:
В каждом модифицированном каталоге для каждого элемента заведем две ссылки. Для неподставных элементов это будут ссылки на максимальныйподставной элемент меньше текущего и на минимальный любого типа больше текущего. Если элемент подставной, то ссылки будут на минимальный неподставной элемент" больше текущего и на максимальный неподставной элемент меньше текущего. Назовем их ссылками влево и вправо.


Рассмотрим на процесс построения на примере.
Пусть дано [math] k = 5 [/math] каталогов:
[math] C_1 = \{1, 3, 6, 7, 11, 12\} [/math]
[math] C_2 = \{4, 9, 10\} [/math]
[math] C_3 = \{1, 2, 7, 8, 11, 12\} [/math]
[math] C_4 = \{3, 4, 8, 10, 12\} [/math]
[math] C_5 = \{1, 2, 4, 6, 9\} [/math]
Для наглядности заведем таблицу, где в [math]i[/math]-ой строке [math] j [/math]-ая ячейка будет окрашена в зеленый цвет, если она присутствует в каталоге [math] C_i [/math]. Тогда результатом построения будет таблица, которая представлена на рисунке.
Из-за необходимости хранения ссылок будет удобно завести структуру для хранения элементов в модифицированных каталогах:

struct node :  
    element key 
    node left, right, down 
    bool is_alien

Псевдокод построения модифицированных каталогов:

M[k] = C[k]
for i = k - 1 to 1
    pointer_in_C = 1                     // указатель на самый левый элемент каталога C[i], который еще не рассмотрели 
    pointer_in_next_M = 1                // указатель на самый левый элемент каталога M[i + 1], который еще не рассмотрели 
    pointer_in_M = 1                     // указатель на самый левый элемент каталога M[i], в который будем добавлять элемент  
    node last_non_alien = NULL           // указатель на последний неподставной элемент для текущей позиции 
    node last_alien = NULL               // указатель на последний подставной элемент для текущей позиции
    while(true)
        if (pointer_in_next_M > M[i + 1].size && pointer_in_C > C[i].size) 
            break 
        if (pointer_in_next_M > M[i + 1].size || M[i + 1][pointer_in_next_M] >= C[i][pointer_in_C])
            M[i][pointer_in_M].key = C[i][pointer_in_C].key
            M[i][pointer_in_M].key = last_alien
            last_non_alien = M[i][pointer_in_M]
            pointer_in_C++
        else
            M[i][pointer_in_M].key = M[i + 1][pointer_in_next_M].key
            M[i][pointer_in_M].down = M[i + 1][pointer_in_next_M]
            M[i][pointer_in_M].is_alien = true
            M[i][pointer_in_M].left = last_non_alien
            lst_alien = M[i][pointer_in_M]
            pointer_in_next_M += 2
        pointer_in_M++
    last_non_alien = (!M[i][M[i].size].is_alien ? M[i][M[i].size] : NULL)   // теперь это указатель на первый справа неподставной элемент для текущей позиции
    for j = M[i].size - 1 to 1       
        if (M[i][j].is_alien)
            M[i][j].right = last_non_alien
        else
            M[i][j].right = M[i][j + 1]
            last_non_alien = M[i][j]

Из построения понятно, что мы тратим [math] O(n_k) [/math] на построение последнего каталога, [math] O(n_{k-1} + n_k / 2) [/math] на построение предпоследнего и т.д. Пусть [math] p = 2^{\lfloor log n \rfloor} [/math]. Тогда получаем оценку [math] O(n_k (1 + 1/2 + 1/4 + ... + 1/p) + n_{k - 1} (1 + 1/2 + 1/4 + ... + 1/(p/2)) + ... + n_1 ) [/math] [math] = O(2 n_k + 2 n_{k -1} + ... n_1) = O(n) [/math] памяти. По алгоритму понятно, что такая же оценка верна и для времени на предподсчет.

Ответ на запрос

В первом каталоге ответ на запрос найдем с помощью бинарного поиска по [math] C_1 [/math]. Пусть ответом для этого каталога будет ячейка [math] cell [/math]. Далее проитерируемся по оставшимся каталогам. Для того, чтобы перейти в новый модифицированный каталог мы перейдем из [math] cell [/math] по ссылке влево, чтобы попасть в подставную вершину, а потом из нее перейдем по ссылке вниз, чтобы попасть в следующий каталог. Если теперь [math] cell [/math]неподставная вершина, то нам достаточно рассмотреть двух ее соседей справа в [math] C_i [/math], так как [math] cell.key \lt x [/math], а каждая вторая ячейка из [math] M_i [/math] попадает в [math] M_{i - 1} [/math], т.е. мы бы встретили ее ранее и перешли мы вниз по ней, но это не случилось. Обновив cell максимальным из подходящих значений нужно проверить, является ли она подставным элементом, если да, то перейдем по ссылке влево, попав в ответ для текущего каталога, иначе это и будет ответ.

node cell = binary_search(M[1], x)
if (cell.is_alien) 
    cell = cell.left
ans[1] = cell.key;
for i = 2 to k
    cell = cell.left.down
    if (cell.right <= x)        // Попытка сдвинуться к большему элементу 
         cell = cell.right      
    if (cell.right <= x)        // Попытка сдвинуться к большему элементу 
         cell = cell.right      // Замечание: по построению, если мы стоим в неподставном элементе, то при сдвиге вправо мы можем оказаться в элементе любого типа
    if (cell.is_alien)          // Для этого есть проверка 
         cell = cell.left
    ans[i] = cell.key

Как можно видеть, для того, чтобы найти ответ для первого каталога необходимо сделать один бинарный поиск, что требует [math] O(\log n) [/math] времени, после чего необходимо [math] O(k) [/math] времени, чтобы ответить на запрос для всех остальных каталогов. Суммарное время работы [math] O(\log n + k) [/math].

Ссылки