В этом разделе мы будем думать о стеке, как о частном случае односвязного списка. А именно, пустой стек - это пустой односвязный список, операция push - это добавление элемента в конец списка, pop - это удаление элемента из конца списка, а top - это получение элемента из конца списка.

Stack as forward list

Реализация
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Node {
    int value;
    Node *next;
};

struct Stack {
    Node *top;

    Stack() : top(nullptr) {}

    void push(int x) {
        top = new Node{x, top};
    }
    void pop() {
        top = top->prev;
    }
    int top() {
        return top->value;
    }
};

Теперь сделаем такой стек персистентным. Мы начнем делать стек частично персистентным, однако в конце он окажется полностью персистентным.

Операция push

Теперь при операции push мы будем создавать новый узел, как и раньше, но добавится лишь одно отличие - мы пометим эту вершину как версию стека $V_{k+1}$, если сейчас у нас есть $k$ версий.

enumerated 4 versions

Добавим новую вершину:

enumerated 5 versions

При этом мы можем заглянуть в любую версию стека по индексу $k$ и узнать её top - то есть для операций push и top мы уже реализовали частичную персистентность.

Операция pop

Обычно в стеке при операции pop мы просто удаляем элемент из конца стека - его последнюю вершину в списке, однако теперь в нашем случае мы не можем просто взять и удалить элемент из памяти насовсем, ведь нам нужно так или иначе иметь доступ ко всем версиям стека, в том числе и к версии, в которой есть этот элемент. Поэтому мы лишь сдвинем указатель на top стека на один элемент назад, не удаляя его из памяти, не забыв пометить при этом текущую вершину как версию $V_{k+1}$. На картинке ниже поментим top зеленым цветом, чтобы не потерять его.

remove one item

Но тут у нас сломается push - ведь раньше мы добавляли элемент в конец стека, создавая новый узел в односвязном списке, а теперь новый узел уже может быть “занят” какой-то (удаленной) вершиной, а нашем случае вершиной $E$.

Поэтому мы перейдем от “линейной” структуры - односвязного списка, к древовидному, а именно теперь каждая вершина может иметь несколько “следующих” элементов в стеке - из разных версий, - но всё ещё каждая вершина будет указывать только на одну предыдущую вершину.

tree structure

Удивительно, но это всё, что нам нужно сделать - то есть персистентный стек - это всего лишь корневое дерево. Более того, мы получили fully persistent stack, то есть никто нам не мешает откатиться в произвольную версию стека и делать push и pop из неё - добавится лишь новая вершина в дереве.

Реализация

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct Node {
    int value;
    Node *prev;
}

struct PersistentStack {
    Node *top;
    vector<Node*> versions;

    PersistentStack() : top(nullptr) {}
    void push(int x) {
        top = new Node{x, top};
        versions.push_back(top);
    }
    void pop() {
        top = top->prev;
        versions.push_back(top);
    }
    int top() {
        return top->value;
    }
    void rollback(int k) {
        top = versions[k];
    }
};