Rope — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
Строка 1: Строка 1:
 
'''Rope''' (Веревка) {{---}} структура данных для хранения строки, представляющая из себя двоичное сбалансированное дерево и позволяющая делать операции вставки, удаления и конкатенации с логарифмической асимптотикой.
 
'''Rope''' (Веревка) {{---}} структура данных для хранения строки, представляющая из себя двоичное сбалансированное дерево и позволяющая делать операции вставки, удаления и конкатенации с логарифмической асимптотикой.
  
Заведем двоичное сбалансированное дерево поиска. В каждом листе будем хранить последовательную часть строки. Изначально дерево состоит из одной вершины - сама строка.
+
Заведем [[Дерево поиска, наивная реализация|двоичное дерево поиска]]. В каждом листе будем хранить последовательную часть строки. Изначально дерево состоит из одной вершины - сама строка.
 
В вершинах дерева будем хранить строку, если это лист, а также вес {{---}} суммарную длину все строк в поддереве, или, если это лист, длину строки, которая в нем хранится.
 
В вершинах дерева будем хранить строку, если это лист, а также вес {{---}} суммарную длину все строк в поддереве, или, если это лист, длину строки, которая в нем хранится.
  
Строка 10: Строка 10:
 
[[file:String_concat.png|800px|Результат конкатенации двух строк.]]
 
[[file:String_concat.png|800px|Результат конкатенации двух строк.]]
  
'''Псевдокод:'''
+
===Псевдокод===
<pre>
+
merge('''Node''' T1, '''Node''' T2) = '''Node'''(T1, T2, T1.w + T2.w)
merge(T1, T2) = Node(T1, T2, T1.w + T2.w)
+
 
</pre>
 
  
 
Асимптотика выполнения операции конкатенации двух строк очевидно <tex>O(1)</tex>.
 
Асимптотика выполнения операции конкатенации двух строк очевидно <tex>O(1)</tex>.
Строка 26: Строка 25:
 
* Текущая вершина {{---}} лист, тогда в этом листе хранится ответ, необходимо взять символ с соответствующим номером у строки которая там хранится.
 
* Текущая вершина {{---}} лист, тогда в этом листе хранится ответ, необходимо взять символ с соответствующим номером у строки которая там хранится.
  
'''Псевдокод:'''
+
===Псевдокод===
<pre>
+
'''char''' get('''int''' i,'''Node''' node):
char get(i, node):
+
    '''if''' ('''not''' isNil(node):
    if (!isNil(node):
+
        '''if''' (node.left >= i):
        if (node.left >= i):
+
            '''return''' get(i, node.left)
            return get(i, node.left)
+
        '''else''':
        else:
+
            '''return''' get(i - node.left.w, node.right)
            return get(i - node.left.w, node.right)
+
    '''else''':
    else:
+
        '''return''' node.s[i]
        return node.s[i]
 
</pre>
 
  
 
Асимптотика выполнения одного такого запроса очевидно <tex>O(h)</tex>, где <tex>h</tex> {{---}} высота дерева.
 
Асимптотика выполнения одного такого запроса очевидно <tex>O(h)</tex>, где <tex>h</tex> {{---}} высота дерева.
Строка 56: Строка 53:
 
[[file:Split3.png|800px|Результат выполнения операции split.]]
 
[[file:Split3.png|800px|Результат выполнения операции split.]]
  
'''Псевдокод:'''
+
===Псевдокод===
<pre>
+
('''Node''', '''Node''') split('''Node''' node, '''int''' i):
(node, node) split(node, i):
+
    '''Node''' t1, t2
    Node t1, t2
+
    t1.w = i
    t1.w = i
+
    t2.w = node.w - i
    t2.w = node.w - i
+
    '''Pair''' res
    Pair res
+
    '''if''' ('''not''' isNil(node)):
    if (!isNil(node)):
+
        '''if''' (node.left.w >= i):
        if (node.left >= i):
+
            res = split(node.left, i)
            res = split(node.left, i)
+
            t1 = res.first
            t1 = res.first
+
            t2.left = res.second
            t2.left = res.second
+
            t2.right = node.right
            t2.right = node.right
+
        '''else''':
        else:
+
            res = split(node.right, i - node.left.w)     
            res = split(node.right, i - node.left.w)     
+
            t1.left = node.left
            t1.left = node.left
+
            t1.right = res.first
            t1.right = res.first
+
            t2 = res.second
            t2 = res.second
+
    '''else''':
    else:
+
        t1.s = node.s.substr(0, i)
        t1.s = node.s.substr(0, i)
+
        t2.s = node.s.substr(i, s.len)
        t2.s = node.s.substr(i, s.len)
+
    '''return''' pair(t1, t2)
    return pair(t1, t2)
 
</pre>
 
  
 
Нетрудно заметить что асимптотическая сложность выполнения данной операции <tex>O(h)</tex>, где <tex>h</tex> {{---}} высота дерева.
 
Нетрудно заметить что асимптотическая сложность выполнения данной операции <tex>O(h)</tex>, где <tex>h</tex> {{---}} высота дерева.
Строка 88: Строка 83:
 
Операция <tex>delete</tex> удаляет из строки подстроку начиная с индекса <tex>beginIndex</tex> и заканчивая (не включая) индексом <tex>endIndex</tex>.
 
Операция <tex>delete</tex> удаляет из строки подстроку начиная с индекса <tex>beginIndex</tex> и заканчивая (не включая) индексом <tex>endIndex</tex>.
  
'''Псевдокод:'''
+
===Псевдокод===
<pre>
+
'''Node''' delete('''Node''' node, '''int''' beginIndex, '''int''' endIndex):
node delete(node, beginIndex, endIndex):
+
    '''Node''' t1, t2, t3
    Node t1, t2, t3
+
    (t1, t2) = split(node, beginIndex)
    (t1, t2) = split(node, beginIndex)
+
    t3 = split(t2, endIndex).second
    t3 = split(t2, endIndex).second
+
    '''return''' merge(t1, t3)
    return merge(t1, t3)
 
</pre>
 
  
 
Операция <tex>insert</tex> вставляет данную строку <tex>s</tex> в исходную начиная с позиции <tex>insertIndex</tex>.
 
Операция <tex>insert</tex> вставляет данную строку <tex>s</tex> в исходную начиная с позиции <tex>insertIndex</tex>.
  
'''Псевдокод:'''
+
===Псевдокод===
<pre>
+
'''Node''' insert('''Node''' node, '''int''' insertIndex, '''string''' s):
node insert(node, insertIndex, s):
+
    '''Node''' t1, t2
    Node t1, t2
+
    (t1, t2) = split(node, insertIndex)
    (t1, t2) = split(node, insertIndex)
+
    '''Node''' t3 = node(s)
    Node t3 = node(s)
+
    t1 = merge(t1, t3)
    t1 = merge(t1, t3)
+
    '''return''' merge(t1, t2)
    return merge(t1, t2)
 
</pre>
 
  
 
Так как данные операции используют только <tex>split</tex> и <tex>merge</tex> то асимптотика времени их работы <tex>O(h)</tex>, где <tex>h</tex> - высота дерева.
 
Так как данные операции используют только <tex>split</tex> и <tex>merge</tex> то асимптотика времени их работы <tex>O(h)</tex>, где <tex>h</tex> - высота дерева.
Строка 117: Строка 108:
 
[[file:Bamboo.png|800px|Пример вырождения дерева в бамбук.]]
 
[[file:Bamboo.png|800px|Пример вырождения дерева в бамбук.]]
  
Предлагается хранить его как [[АВЛ-дерево]] и делать соответствующие балансировки. Тогда, так как высота АВЛ-дерева <tex>h = \log{n}</tex>, то асимптотика операций <tex> get, split, delete, insert, merge</tex> будет равна <tex>O(\log{n})</tex>.
+
Предлагается хранить его как [[АВЛ-дерево]] и делать соответствующие балансировки. Тогда, так как высота АВЛ-дерева <tex>h = \log{n}</tex>, то асимптотика операций <tex> get, split, delete, insert, merge</tex> будет равна <tex>O(\log{n})</tex>, где tex>n</tex> - количество сконкатенированных строк.
  
 
==Оптимизации==
 
==Оптимизации==
Строка 125: Строка 116:
 
* Кэширование. Так как зачастую нужен последовательный доступ к индексам (например <tex>i</tex> и <tex>i + 1</tex>), то можно запоминать лист, а также его границы, в который мы пришли после очередного запроса <tex>get</tex> и на следующем запросе сначала искать в сохраненном листе.
 
* Кэширование. Так как зачастую нужен последовательный доступ к индексам (например <tex>i</tex> и <tex>i + 1</tex>), то можно запоминать лист, а также его границы, в который мы пришли после очередного запроса <tex>get</tex> и на следующем запросе сначала искать в сохраненном листе.
  
==Литература==
+
==Источники информации==
  
*[[wikipedia:en:Rope_(data_structure)|Википедия - Rope]]
+
*[[wikipedia:en:Rope_(data_structure)|Википедия {{---}} Rope]]
*[http://habrahabr.ru/post/144736/ Ropes быстрые строки]
+
*[http://habrahabr.ru/post/144736/ Ropes {{---}} быстрые строки]
 
*[http://citeseer.ist.psu.edu/viewdoc/download?doi=10.1.1.14.9450&rep=rep1&type=pdf Ropes: an Alternative to Strings]
 
*[http://citeseer.ist.psu.edu/viewdoc/download?doi=10.1.1.14.9450&rep=rep1&type=pdf Ropes: an Alternative to Strings]
 +
 +
==См. также==
 +
 +
*[[АВЛ-дерево]]
 +
*[[Splay-дерево]]
 +
*[[Декартово дерево по неявному ключу]]

Версия 18:53, 4 июня 2014

Rope (Веревка) — структура данных для хранения строки, представляющая из себя двоичное сбалансированное дерево и позволяющая делать операции вставки, удаления и конкатенации с логарифмической асимптотикой.

Заведем двоичное дерево поиска. В каждом листе будем хранить последовательную часть строки. Изначально дерево состоит из одной вершины - сама строка. В вершинах дерева будем хранить строку, если это лист, а также вес — суммарную длину все строк в поддереве, или, если это лист, длину строки, которая в нем хранится.

Merge

Когда приходит запрос на конкатенацию с другой строкой мы объединяем оба дерева, создав новый корень и подвесив к нему обе строки. Пример результата конкатенации двух строк:

Результат конкатенации двух строк.

Псевдокод

merge(Node T1, Node T2) = Node(T1, T2, T1.w + T2.w)


Асимптотика выполнения операции конкатенации двух строк очевидно [math]O(1)[/math].

Получение символа по индексу

Для того что реализовать операцию получения символа по индексу будем в вершинах дерева хранить суммарную длину всех строк в его поддереве, или, если это лист, длину строки которая в нем хранится. Тогда, если нам пришел запрос — получить символ с индексом [math]i[/math], то будем спускаться по дереву из корня следующим образом:

  • Текущая вершина — не лист, тогда возможно два варианта:
    • Вес левого поддерева больше либо равен [math]i[/math], тогда идем в левое поддерево.
    • Иначе идем в правое поддерево и ищем там [math]i - w[/math] символ, где [math]w[/math] вес левого поддерева.
  • Текущая вершина — лист, тогда в этом листе хранится ответ, необходимо взять символ с соответствующим номером у строки которая там хранится.

Псевдокод

char get(int i,Node node):
    if (not isNil(node):
        if (node.left >= i):
            return get(i, node.left)
        else:
            return get(i - node.left.w, node.right)
    else:
        return node.s[i]

Асимптотика выполнения одного такого запроса очевидно [math]O(h)[/math], где [math]h[/math] — высота дерева.

Split

Чтобы разбить строку на две по некоторому индексу [math]i[/math] необходимо спускаясь по дереву (аналогично операции [math]get[/math]), каждую вершину на пути поделить на две, каждая из которых будет соответствовать одно из половинок строк, при этом необходимо после деления пересчитать вес этих вершин.

Пускай дано дерево:

Перед операцией split.

Тогда результатом выполнения операции [math]split[/math] по индексу 16 будет:

Результат выполнения операции split.

Заметим, что появляются лишние вершины, у которых есть только один потомок. От них можно легко избавится, просто заменив их на этого потомка. Тогда результатом той же операции [math]split[/math] будет:

Результат выполнения операции split.

Псевдокод

(Node, Node) split(Node node, int i):
    Node t1, t2
    t1.w = i
    t2.w = node.w - i
    Pair res
    if (not isNil(node)):
        if (node.left.w >= i):
            res = split(node.left, i)
            t1 = res.first
            t2.left = res.second
            t2.right = node.right
        else:
            res = split(node.right, i - node.left.w)     
            t1.left = node.left
            t1.right = res.first
            t2 = res.second
    else:
        t1.s = node.s.substr(0, i)
        t2.s = node.s.substr(i, s.len)
    return pair(t1, t2)

Нетрудно заметить что асимптотическая сложность выполнения данной операции [math]O(h)[/math], где [math]h[/math] — высота дерева.

Операции удаления и вставки

Нетрудно понять, что имея операция [math]merge[/math] и [math]split[/math], можно легко через них выразить операции [math]delete[/math] и [math]insert[/math] по аналогии с другими деревьями поиска.

Операция [math]delete[/math] удаляет из строки подстроку начиная с индекса [math]beginIndex[/math] и заканчивая (не включая) индексом [math]endIndex[/math].

Псевдокод

Node delete(Node node, int beginIndex, int endIndex):
    Node t1, t2, t3
    (t1, t2) = split(node, beginIndex)
    t3 = split(t2, endIndex).second
    return merge(t1, t3)

Операция [math]insert[/math] вставляет данную строку [math]s[/math] в исходную начиная с позиции [math]insertIndex[/math].

Псевдокод

Node insert(Node node, int insertIndex, string s):
    Node t1, t2
    (t1, t2) = split(node, insertIndex)
    Node t3 = node(s)
    t1 = merge(t1, t3)
    return merge(t1, t2)

Так как данные операции используют только [math]split[/math] и [math]merge[/math] то асимптотика времени их работы [math]O(h)[/math], где [math]h[/math] - высота дерева.

Балансировка

Для того чтобы дерево не превращалось в бамбук:

Пример вырождения дерева в бамбук.

Предлагается хранить его как АВЛ-дерево и делать соответствующие балансировки. Тогда, так как высота АВЛ-дерева [math]h = \log{n}[/math], то асимптотика операций [math] get, split, delete, insert, merge[/math] будет равна [math]O(\log{n})[/math], где tex>n</tex> - количество сконкатенированных строк.

Оптимизации

  • Для уменьшения объема памяти занимаемой деревом и уменьшения высоты дерево, предлагается при конкатенации с маленькой строкой делать конкатенацию классическим способом.
  • Кэширование. Так как зачастую нужен последовательный доступ к индексам (например [math]i[/math] и [math]i + 1[/math]), то можно запоминать лист, а также его границы, в который мы пришли после очередного запроса [math]get[/math] и на следующем запросе сначала искать в сохраненном листе.

Источники информации

См. также