Partially Persistent Array
Оглавление
В этом разделе мы сделаем массив частично персистентным. Иначе говоря, мы сможем узнавать значения элементов массива в любой версии, но изменять только последнюю версию.
Операция set
Чтобы изменить элемент массива $a_k = x$ мы как обычно не можем просто перезаписать $a_k$, ведь нам нужно сохранить все предыдущие версии $k$-й ячейки, поэтому в ячейке $a_k$ мы будем хранить список из всех версий $a_k$. Таким образом, при операции set
мы всего лишь добавляем новый элемент в конец списка версий $a_k$. Такой подход называется fat node - то есть в вершине структуры мы храним все версии элемента.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Node {
int value;
int version;
};
struct PersistentArray {
vector<vector<Node>> versions;
int version = 0;
PersistentArray(int n) {
versions.resize(n);
}
void set(int x) {
versions[x].push_back({x, version});
version++;
}
}
Операция get
При операции get version i
мы пойти в ячейку $a_i$ и взять оттуда самую посленюю версию до $i$-й включительно, например если $a_i$ менялся на 2-й и 5-й версиях, то при запросе 3-й версии мы вернем значение после 2-й. Чтобы найти подходящую версию нужно лишь запустить бинарный поиск по списку. Таким образом, мы сможем за логарифмическое время находить нужную версию.
1
2
3
4
5
6
7
8
int PersistentArray::get(int i, int version) {
auto it = lower_bound(versions[i].begin(), versions[i].end(), version, [](Node a, int b) {
return a.version < b;
});
if (it == versions[i].begin()) return -1;
it--;
return it->value;
}
Время работы и память
Мы потратили $O(q)$ дополнительной памяти на хранение всех версий массива, где $q$ - количество версий массива, а так же теперь get
будет работать за $O(\log q)$. При этом set
будет работать за $O(1)$ в среднем, так как мы просто добавляем элемент в конец списка версий. Но, к сожалению, данный подход не позволяет нам сделать массив полностью персистентным, однако позже мы всё-таки придумаем как это сделать.