Опубликован: 27.09.2006 | Уровень: для всех | Доступ: платный | ВУЗ: Московский государственный индустриальный университет
Лекция 10:

Основы объектно-ориентированного программирования

Основная идея, используемая в этой реализации — "сворачивание вектора в кольцо", что реализуется с помощью метода forward, вычисляющего индекс элемента, следующего за данным. При этом следующим за последним элементом массива считается его самый первый элемент (с индексом 0).

Реализация ограниченной очереди

Рис. 10.6. Реализация ограниченной очереди

Графическая иллюстрация этой реализации приведена на рис. 10.6. Переменная size здесь содержит количество элементов в очереди, headиндекс самого первого элемента в (непустой) очереди, а tail — последнего ее элемента. Первая из этих трех переменных необходима для того, чтобы отличить пустую очередь от полностью заполненной.

Если не "сворачивать" вектор, то после выполнения n (где nдлина вектора) последовательных пар операций push и pop над изначально пустой очередью она по-прежнему будет пуста, но положение указателей head и tail сделает невозможным добавление в нее новых элементов.

Интерфейс Queue был изменен так, чтобы попытка добавить элемент в полностью заполненную очередь также приводила к возникновению исключительной ситуации. Реализация на базе "циклического вектора" не приводит к автоматическому возникновению исключительной ситуации, связанной с выходом за границу массива, как это было в предыдущем примере. Поэтому здесь приходится возбуждать исключительную ситуацию явным образом с помощью оператора throw.

Также как и для стека, реализация очереди является достаточно эффективной — все методы имеют константную сложность \Theta(1).

Непрерывно можно реализовать практически любую структуру данных. Ряд упражнений на это приведен в конце параграфа. Обратите, в частности, внимание на две реализации ограниченного множества на базе вектора, основанные на последовательном и двоичном поиске элементов.

Любая непрерывная реализация на базе вектора множества или, скажем, трех стеков, имеет, однако, существенный недостаток — некоторые из методов имеют линейную сложность ( \Theta(n) ), ибо требуют массовых операций — сдвига части элементов вектора. Как правило, это неприемлемо — высокая временная эффективность реализации обычно является обязательным условием.

Существенно повысить эффективность можно, используя ссылочные реализации. Особенно изящно это удается сделать в языках программирования, допускающих указатели (например C++), но и язык Java позволяет применять данный прием. Следующее определение относится именно к языкам, не допускающим использования указателей.

Определение 10.10. Реализация на базе вектора называется ссылочной, если все элементы контейнера хранятся в одном векторе, а другой векторвектор ссылок, определяет реально содержащиеся в имитируемой структуре элементы и их порядок (если он существует).

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

Идея ссылочной реализации односвязного списка

Рис. 10.7. Идея ссылочной реализации односвязного списка

Типичным примером ссылочной реализации является реализация односвязного списка.

Задача 10.4. Постройте ссылочную реализацию ограниченного односвязного списка целых чисел на базе вектора с применением двух векторов ссылок и оцените эффективность полученной программы.

Элементы списка будут размещаться в массиве array, а второй массивnext будем использовать для ссылок. Лучше всего представлять себе элементы списка в виде бусинок на ниточке. При этом все элементы массива, не содержащиеся в списке, нанизаны на другую нитку. Для того чтобы упростить работу с пустым и полностью заполненным списком, полезно завести два фиктивных элемента, к которым прикрепляются концы этих двух нитей (см. рис. 10.7). По историческим причинам будем называть эти элементы "нилом списка" и "нилом свободного места" соответственно, разместив их в двух дополнительных элементах массива ссылок next.

Добавление элемента в список при этом сводится к тому, что элемент (бусинку) надо снять с одной нити и переместить на другую. Удаление из списка производится точно также, только нити меняются местами.

Для реализация этой идеи в i -м элементе массива ссылок необходимо хранить индекс элемента, следующего за ним (собственно в списке или в списке свободных элементов). В частности, пустому списку соответствует ситуация, изображенная на рис. 10.8.

Ссылочная реализация односвязного списка

Рис. 10.8. Ссылочная реализация односвязного списка

В приведенной ниже программе конструктор списка L1ListTest заносит необходимые значения в массив next, используя метод link, обеспечивающий "связывание" двух элементов. Все остальные методы класса имеют константную сложность \Theta(1).

Текст программы

interface L1List {
    boolean empty();
    void    clear();
    void    toFront();
    boolean end();
    void    forward() throws Exception;
    int     after() throws Exception;
    void    insert(int val) throws Exception;
    int     erase() throws Exception;
}
class L1ListTest implements L1List {
    // массив элементов.
    private int[] array;
    // массив ссылок.
    private int[] next;
    // "нил" списка.
    private int nilList;
    // "нил" свободного места.
    private int nilFree;
    // индексы элементов до и после указателя.
    private int before, after;
    // связать два элемента, заданные индексами.
    private void link(int first, int second) {
        next[first] = second;
    }
    // захватить место.
    private int mallocIndex() {
        int index = next[nilFree];
        link(nilFree, next[index]);
        return index;
    }
    // освободить место.
    private void freeIndex(int index) {
        link(index, next[nilFree]);
        link(nilFree, index);
    }

    public L1ListTest(int size) {
        array = new int[size];
        next  = new int[size + 2];
        nilList = size;
        nilFree = size + 1;

        link(nilList, nilList);

        link(nilFree, 0);
        for (int i=0; i<size-1; i++)
            link(i, i+1);
        link(size-1, nilFree);

        before = after = nilList;
    }
    public boolean empty() {
        return next[nilList] == nilList;
    }
    public void clear() {
        try {
            toFront();
            while (true)
               erase();
        } catch(Exception e) {
            ;
        }
    }
    public void toFront() {
        before = nilList;
        after  = next[nilList];
    }
    public boolean end() {
        return after == nilList;
    }
    public void forward() throws Exception {
        if(after == nilList) throw new Exception();
        before = after;
        after  = next[after];
    }
    public int after() throws Exception {
        return array[after];
    }
    public void insert(int val) throws Exception {
        int index = mallocIndex();
        link(before, index);
        link(index, after);
        after = index;
        array[index] = val;
    }
    public int erase() throws Exception {
        int val   = array[after];
        int index = after;
        after = next[index];
        link(before, after);
        freeIndex(index);
        return val;
    }
    public static void main(String[] args) throws Exception {
        L1ListTest l = new L1ListTest(5);
        while (true) {
            switch (Xterm.inputInt("Action [01234567] -> ")) {
              case 0:	
                System.out.println("List is " + (l.empty() ?
                                                 "empty" :
                                                 "not empty"));
                break;
              case 1:
                l.clear(); break;
              case 2:
                l.toFront(); break;
              case 3:	
                System.out.println("Pointer " + (l.end() ?
                                                 "is" :
                                                 "is not")+
                                   " in the end of the list");
                break;
              case 4:
                l.forward(); break;
              case 5:
                System.out.println("elem = " + l.after()); break;
              case 6:
                l.insert(Xterm.inputInt("insert: ")); break;
              case 7:
                l.erase(); break;
              default:
                System.out.println("Wrong action, ignore");
            }
        }
    }
}
Анастасия Халудорова
Анастасия Халудорова
екатерина яковлева
екатерина яковлева
Ирина Тютрина
Ирина Тютрина
Россия, Усолье-Сибирское
Глеб Бочкарёв
Глеб Бочкарёв
Россия