Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Пособие часть 1.doc
Скачиваний:
60
Добавлен:
24.09.2019
Размер:
6.98 Mб
Скачать

2.5.2. Двусвязные списки

Схематическое изображение двусвязного списка на основе указателей показано на рис.2.

Рис.2.14. Двусвязный список

При работе с двусвязным списком уже не нужен дополнительный указатель на элемент, предшествующий текущему, т. к. все необходимые указатели есть в каждом элементе, а значит, для выполнения вставок и удалений достаточно только указателя на текущий элемент. Однако для реализации все-таки опять потребуется три дополнительных указателя — на первый, последний и текущий элементы (например, first, last, cur).

Структуры, описывающие двунаправленный спиоск, могут иметь, например, такой вид:

struct item //один элемент списка

{type_of_data data;

item *next, *prev;

};

struct list_l2 // структура списка

{ item *first, *last; *cur;//указатели на первый, последний и текущий элементы.

//прототипы функций

...

};

Также, как и для списков Л1, можно выделить три особых состояния:

  • head=last=cur=NULL — список пуст, в этом состоянии нельзя удалять элементы, первый добавленный элемент будет одновременно и началом и концом списка;

  • head ≠ NULL; last ≠ NULL;cur=head — «начало списка»; в этом состоянии новый элемент вставляется перед первым;

  • head ≠ NULL; last ≠ NULL; cur=NULL;  — «конец списка», в этом состоянии новый элемент вставляется после последнего, а удалить текущий элемент нельзя.

Обратим внимание, что в двунаправленном списке размер каждого элемента списка больше, чем в однонаправленном, поскольку указующая часть содержит не один, а два указателя. Фактически каждый указатель хранится два раза, поскольку каждый элемент, кроме первого и последнего, имеют и предыдущий, и следующий. Интересно отметить, что в случае ссылочной реализации с использованием массива можно хранить не два индекса (next и prev), а одно значение, равное разности next-prev. Нетрудно убедиться, что имея такие разности и зная индексы первого и последнего элемента списка, можно без труда восстановить цепочки индексов как при движении в прямом, так и в обратном направлении. В этом случае мы даже не увеличиваем расход памяти под элементы списка по сравнению с Л1, правда немного проигрываем в быстродействии при обходе списков за счет необходимости вычисления индексов.

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

2.5.3. Кольцевые списки

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

Рис.2.15. Кольцевой односвязный список

Реализация операций с кольцевыми списками отличается от линейных списков только деталями, связанными с крайними элементами.

2.5.4. Примеры программ, использующих списки Очередь с приоритетами на основе линейного списка

Очередь с приоритетами на основе линейного списка — простой, но далеко не самый эффективный способ реализации. В главе 4 будет рассмотрен другой вариант на основе специальной древовидной структуры, которая называется пирамидой.

#include "list.cpp"

#include <stdlib.h>

#include <time.h>

struct sorted_list

{ list_l1 list;

type_of_data getdata() { list.first(); return list.getdata(); }

void ins(type_of_data x);

void del() { list.first(); list.del(); }

bool isnull() { return list.isnull(); }

void makenull() { list.makenull(); }

};

void sorted_list::ins(type_of_data x)

{ if ((list.eolist())||(list.getdata()>x)) list.first();

while ((!list.eolist())&&(list.getdata()<x)) list.next();

list.ins(x);

}

int main()

{ int n,k,i; sorted_list l;

cout<<" "; cin>>n; cin.get();

randomize();

for(int i=0; i<n; i++)

l.ins(random(100));

cout<<" : "<<endl;

for(int i=0; i<n; i++)

{ cout<<l.getdata()<<" "; l.del();

}

cout<<endl; cin.get();return 0;

}