Назад | Начало урока | Вперед
Содержание

Глава 02

Связные списки


Очереди и стеки имеют общие черты.
Во первых как очереди, так и стеки имеют очень строгие правила ссылок на хранящиеся в них данные, а именно доступ к очереди определяется правилом FIFO, а для стека правилом LIFO.
Во вторых в обоих случаях операции извлечения данных по своей природе являются разрушающими.
Иными словами, для того, чтобы получить доступ к элементу, хранящемуся в очереди или стеке, этот элемент необходимо удалить. Если элемент больше нигде не хранится, он будет удален.

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

Вверх

Построение параметризированного списка с двойными связями

Для построения параметризированного списка с двойными связями используем
простую иерархию классов. Один из классов, называемый listob, определяет
природу объектов, которые будут храниться в списке. Этот класс затем
наследуется другим классом, dllist, который фактически и реализует
механизм списка с двойными связями.
Класс listob показан ниже:


template <class DataT> class listob
{
public:
DataT info ; //информация
listob<DataT> *next;//указатель на следующий объект
listob<DataT> *prior;//указатель на предыдущий объект
listob()
{
info=0;
next=NULL;
prior=NULL;
}
listob<DataT> *getnext() {return next;}
listob<DataT> *getprior() {return prior;}
void getinfo(DataT &c) {c=info;} //извлечение элемента
void change (DataT c) {info=c;}//изменение элемента
friend ostream &operator << (ostream &stream,listob<DataT> o);
friend ostream &operator << (ostream &stream,listob<DataT> *o);
friend istream &operator >> (istream &stream,listob<DataT> &o);
};

//перегрузка << для объекта типа listob
template <class DataT>
ostream &operator << (ostream &stream,listob<DataT> o)
{
stream << o.info << endl;
return stream;
}

//перегрузка << для указателя на объект типа listob
template <class DataT>
ostream &operator << (ostream &stream,listob<DataT> *o)
{

stream << o->info << endl;
return stream;
}

//перегрузка >> для ссылки на объект типа listob
template <class DataT>
istream &operator >> (istream &stream,listob<DataT> &o)
{

// cout<<"Введите информацию: ";
cout << "Vvedite informaciyu: ";
stream >> o.info;
return stream;
}

Как видно из этого листинга, listob имеет три члена.
Член info содержит информацию, хранящуюся в списке, определяемую родовым типом DataT.
Указатель next будет указывать на следующий элемент списка,
а указатель prior -на предыдущий.

DataT info ; //информация
listob<DataT> *next;//указатель на следующий объект
listob<DataT> *prior;//указатель на предыдущий объект

Обратите внимание на то, что в этом примере
все члены объекта listob декларируются как public. Это сделано только в целях
наглядности для упрощения демонстрации всех аспектов связного списка.
При разработке собственных приложений, программист может сделать их
закрытыми(private) или защищенными (protected).

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

void getinfo(DataT &c) {c=info;} //извлечение элемента

и модифицировать информацию,ассоциированную с объектом,

void change (DataT c) {info=c;}//изменение элемента

а так же получить указатели на следующий и предыдущий элементы.

listob<DataT> *getnext() {return next;}
listob<DataT> *getprior() {return prior;}

Помимо этого, объекты типа listob могут вводиться и выводиться с помощью
перегруженных операторов << и >>.

friend ostream &operator << (ostream &stream,listob<DataT> o);
friend ostream &operator << (ostream &stream,listob<DataT> *o);
friend istream &operator >> (istream &stream,listob<DataT> &o);

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

При построении каждого из объектов, поля prior и next инициализируются
значением NULL. Эти указатели сохранят значение NULL до тех пор, пока
в список не будет введен первый элемент. Если включен инициализатор,
то его значение будет скопировано в поле info, в противном случае
значение info инициализируется нулем.

listob()
{

info=0;
next=NULL;
prior=NULL;
}

Функция getnext() возвращает указатель на следующий элемент списка.
Если достигнут конец списка, то это значение будет NULL.
Функция getprior() возвращает указатель на предыдущий элемент списка
в том случае, если он существует, в противном случае - значение NULL.

listob<DataT> *getnext() {return next;}
listob<DataT> *getprior() {return prior;}

В данном примере эти функции технически не являются необходимыми,
так как указатели next и prior декларировались как public. Однако
они понадобятся, если декларировать next и prior закрытыми или
защищенными.

Обратите внимание что оператор << перегружается как для объектов
типа listob так и для указателей на объекты этого типа.
Причина этого в том ,что при использовании связных списков,
широко распространена практика получения доступа к элементам списка
через указатель. Поэтому оператор << полезно перегружать, с тем чтобы
он мог оперировать с переданным ему указателем на объект. Однако,
поскольку нет причин полагать ,что будет использоваться только
этот метод, включена и вторая форма доступа, оперирующая непосредственно
с самим объектом. Как альтернативу можно использовать и перегрузку <<
для использования ссылок на объект типа listob.

Хотя listob определяет природу списка с двойными ссылками, он сам не создает
этого списка. Механизм построения связного списка реализуется классом
dllist, приведенным ниже. Как можно видеть из приведенного листинга,
он наследует listob и оперирует с объектами этого типа.


template <class DataT> class dllist : public listob<DataT>
{

listob *start,*end;// указатели на объекты типа listob
public:
dllist() {start=end=NULL;} //конструктор
~dllist();//деструктор для списка

void store(DataT c); //добавление нового элемента в список
void remove(listob *ob);//удаление элемента
void frwdlist();//отображение списка сначала
void bkwdlist();//отображение списка с конца

listob *find(DataT c);//указатель на найденное совпадение
listob *getstart() { return start;}
listob *getend() {return end;}
};

Класс dllist поддерживает два указателя: один на начало списка, а другой
на его конец. Оба эти указателя являются указателями на объекты типа listob.

listob *start,*end;// указатели на объекты типа listob

При создании списка оба указателя инициализируются значением NULL.

dllist() {start=end=NULL;} //конструктор

Класс dllist поддерживает целый ряд операций над списком с двойными
ссылками, в том числе:

Далее подробно рассмотрим эти процедуры.

Вверх

Функция store()

Добавление новых элементов в список производится с помощью функции store().
Посмотрите пример реализации этой функции:


//добавление нового элемента
template <class DataT> void dllist<DataT>::store(DataT c)
{
listob<DataT> *p;//
p=new listob<DataT>

if(!p)
{

cout<<" Oshibka vydeleniya pamyaty\n";
exit(1);
}
p ->info=c;

if(start==NULL)
{
//первый элемент списка
end=start=p;
}
else
{
p->prior=end;
end->next=p;
end=p;
}
}

Для того, чтобы добавить в список новый элемент, необходимо создать новый
объект типа listob. Поскольку связные списки представляют собой
динамические структуры, для функции store() логично будет получать объект
динамически, используя new.

listob *p;//
p=new listob;

После выделения памяти для объекта listob
функция store() присваивает информацию, передаваемую в переменной с
члену info нового объекта,

p ->info=c;

после чего добавляет объект в конец списка.

if(start==NULL)
{

//первый элемент списка
end=start=p;
}
else
{
//не первый элемент списка, добавим элемент(объект) к списку
p->prior=end;
end->next=p;
end=p;
}

Обратите внимание на то, что указатели start и end обновляются по мере
надобности. Таким образом, start и end всегда будут указывать на
начало и конец списка.

Поскольку объекты всегда добавляются в конец списка, список будет
неотсортированным. Однако при желании вы можете модифицировать
функцию store() таким образом, чтобы она поддерживала отсортированный
список.
Связный список, управляемый классом dllist, поддерживает список объектов
типа listob. Тип данных, сохраняемых в объекте типа listob, не зависит
от функции store().

Вверх

Функция remove()

Функция remove() удаляет объект из списка.
Посмотрите программу:

//Удаление элемента из списка с обновлением указателей
//на начало и конец


template <class DataT> void dllist<DataT>::remove(listob<DataT> *ob)
{
if(ob->prior) //не первый элемент
{
ob->prior->next=ob->next;

if(ob->next) //не последний элемент

ob->next->prior=ob->prior;
else //в противном случае удаляется последний элемент
end=ob->prior //обновление указателя на конец списка
}
else //удаляется первый элемент списка
{
if(ob->next) //список не пуст
{
ob->next->prior=NULL;
start=ob->next;
}
else //теперь список пуст
start = end =NULL;
}
}

Функция remove() удаляет из списка объект, на который указывает ее параметр
ob (при этом параметр функции ob должен представлять собой допустимый
указатель на объект типа listob). Удаляемый из списка объект может занимать
одну из трех характерный позиций в списке, - а именно в начале, в конце или
в середине списка. Функция remove() обрабатывает все три ситуации.
Следует иметь в виду, что хотя функция remove() и удаляет объект из списка,
этот объект физически не разрушается. Уничтожаются только ссылки,
и объект просто выпадает из цепочки. Разумеется объект при необходимости
можно и уничтожить. Для этого предназначается функция delete().

Как и функция store(), функция remove() никак не зависит от типа данных,
фактически хранящихся в списке.

Вверх

Отображение списка

Функции frwdlist() и bkwdlist() отображают содержимое списка в направлении
от начала к концу и обратно. Эти функции включены чтобы нагляднее
проиллюстрировать работу класса dllist. Кроме того эти функции представляют
собой довольно удобные средства отладки.


//просмотр списка от начала к концу

template <class DataT> void dllist<DataT>::frwdlist()
{

listob<DataT> *temp;

temp=start;
while(temp)
{
cout << temp->info << " ";
temp=temp->getnext();
}

}

//просмотр списка в обратном направлении

template <class DataT> void dllist<DataT>::bkwdlist()
{

listob<DataT> *temp;

temp = end;
while(temp)
{
cout << temp->info << " ";
temp=temp->getprior();
}
cout<

}

Вверх

Поиск объекта в списке

Ниже приведена функция find(), которая возвращает указатель на объект
списка, который содержит информацию, совпадающую с информацией, указанной
параметром функции. Если совпадений не найдено, функция возвращает NULL.


//Поиск объекта, содержащего информацию, совпадающую с указанной
template <class DataT> lictob<Data> *dllist<DataT>::find(DataT c)

{

listob<DataT> *temp;

temp=start;
while(temp)
{

if(c==temp->info)return temp;//найдено совпадение
temp=temp->getnext();
}
return NULL;//совпадений не найдено
}


Вверх

Полный листинг параметризованного класса
связного списка с двойными ссылками

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


//Параметризованный класс связного списка с двойными ссылками

#include<iostream.h>
#include<string.h>
#include<stdlib.h>

template <class DataT> class listob
{

//перегрузка << для объекта типа listob
template <class DataT>
ostream &operator << (ostream &stream,listob<DataT> o)
{
stream << o.info << endl;
return stream;
}

//перегрузка << для указателя на объект типа listob
template <class DataT>
ostream &operator << (ostream &stream,listob *o)
{

stream << o->info << endl;
return stream;
}

//перегрузка >> для ссылки на объект типа listob
template <class DataT>
istream &operator << (istream &stream,listob<DataT> &o)
{
// cout<<"Введите информацию: ";
cout<<"Vvedite informaciyu: ";
stream << o.info << endl;
return stream;
}

//параметризованный класс объекта списка

template <class DataT> class listob
{

DataT info ; //информация
listob<DataT> *next;//указатель на следующий объект
listob<DataT> *prior;//указатель на предыдущий объект
listob()
{
info=0;
next=NULL;
prior=NULL;
}
listob<DataT> *getnext() { return next;}
listob<DataT> *getprior() {return prior;}
void getinfo(DataT &c) {c=info;}
void change (DataT c) {info=c;}//изменение элемента
friend ostream &operator << (ostream &stream,listob<DataT> o);
friend ostream &operator << (ostream &stream,listob<DataT> *o);
friend istream &operator << (istream &stream,listob<DataT> &o);
};

//параметризованный класс списка

template <class DataT> class dllist:public listob<DataT>
{

listob<DataT> *start,*end;//
public:
dllist() {start=end=NULL;} ~dllist();//деструктор для списка

void store(DataT c);
void remove(listob<DataT> *ob);//удаление элемента
void frwdlist();//отображение списка сначала
void bkwdlist();//отображение списка с конца

listob<DataT> *find(DataT c);//указатель на найденное совпадение
listob<DataT> *getstart() {return start;} listob<DataT> *getend() {return end;}
};

//деструтоор dllist

template <class DataT> dllist<DataT>::~dllist()
{

listob<DataT> *p, *p1;

//освобождаем все элементы списка

p=start;
while(p)
{
p1=p->next;
delete p;
p=p1;
}
}

//добавление нового элемента

template <class DataT> void dllist<DataT>::store(DataT c)
{

listob<DataT> *p;//

p=new listob<DataT>
if(!p)
{

cout<<" Oshibka vydeleniya pamyaty\n";
exit(1);
}
p ->info=c;

if(start==NULL)
{
//первый элемент списка
end=start=p;
}
else //put on end
{
p->prior=end;
end->next=p;
end=p;
}
}


//Удаление элемента из списка с обновлением указателей
//на начало и конец

template <class DataT> void
dllist::remove(listob<DataT> *ob)
{

if(ob->prior) //не первый элемент
{
ob->prior->next=ob->next;
if(ob->next) //не последний элемент
ob->next->prior=ob->prior;
else //в противном случае удаляется последний элемент
end=ob->prior //обновление указателя на конец списка
}
else //удаляется первый элемент списка
{
if(ob->next) //список не пуст
{
ob->next->prior=NULL;
start=ob->next;
}
else //теперь список пуст
start = end =NULL;
}
}

//просмотр списка от начала к концу

template <class DataT> void dllist<DataT>::frwdlist()
{

listob<DataT> *temp;

temp=start;
while(temp)
{
cout << temp->info << " ";
temp=temp->getnext();
}
cout<
}

//просмотр списка в обратном направлении

template <class DataT> void dllist<DataT>::bkwdlist()
{

listob<DataT> *temp;

temp = end;
while(temp)
{

cout << temp->info << " ";
temp=temp->getprior();
}
cout<

}

//Поиск объекта, содержащего информацию, совпадающую с указанной

template <class DataT> lictob<Data> *dllist<DataT>::find(DataT c)

{

listob *temp;

temp=start;

while(temp)
{

if(c==temp->info)return temp;//найдено совпадение
temp=temp->getnext();
}

return NULL;//совпадений не найдено

}


main()
{

//демонстрация списка из чисел типа int
dllist<int> list;
int i;
listob<int> *p;
list.store(1);
list.store(2);
list.store(3);
//использование функций-членов для отображения списка
cout << "Список целых от начала к концу:";
list.frwdlist();
cout << "Список целых от конца к началу: ";
list.bkwlist();
cout << endl;

//Ручной просмотр списка

cout << "Ручной просмотр списка: ";
p=list.getstart();
while(p)
{

p->getinfo(i);
cout << i << " ";
p=p->getnext();//следующий элемент

}

cout << endl << endl;

//удаление элемента

p->getinfo(i);
cout << "Udalenie eleventa " << i << "\n";
list.remove(p);
cout << "Spisok posle udaleniya:";
list.frwdlist();

cout << endl;

//добавление нового элемента

cout << "Dobavlenie novogo eleventa \n";
list.store(4);
cout << "Spisok posle dobavleniya: ";
list.frwdlist();
cout << endl;

//изменение информации

p=list.find(1);
if(!p)
{

cout<<"Oshibra,element ne nayden. \n";
return 1;
}

p->getinfo(i);
cout << "Izmeneniye "<< i << "na 5. \n";
p->change(5);
cout << "Spisok posle izmeneniya : ";
list.frwdlist();
cout << "Spisok v obratnom poryadke : ";
list.bkwdlist();

cout << endl;

//Демонстрация << и >>

cin >> *p;
cout << "Tepery element soderghit :" << *p;

cout << Spisok ot nachala k kontsu :";
list.frwdlist();
cout << endl;

//удалим начальный элемент списка

cout << " Posle udaleniya nachalnogo elementa spiska :';
p=list.getstart();
list.remove(p);
list.frwdlist();

cout << endl;

//удалим конечный элемент списка

cout << "Posle udaleniya konechnogo elementa spiska :";
p=list.getend();
list.remove(p);
list.frwdlist();

cout< //построение списка типа double

dllist<double>dlist;
dlist.store(98.6);
dlist.store(212.0);
dlist.store(88.9);

//использование функций-членов для отображения списка

cout << "Spisok elementov tipa double ot nachala k kontsu:";
cout << "i ot kontsa r nachalu :";
dlist.frwdlist();
dlist.bkwdlist();

return 0;

}


Результат:

Spisok tselyh ot nachala k kontsu:1 2 3
Spisok tselyh ot kontsa k nachalu: 3 2 1

Ruchnoy prosmotr spiska: 1 2 3

Poisk 2
Nayden element 2

Udalenie eleventa 2
Spisok posle udaleniya:1 3

Dobavlenie novogo eleventa
Spisok posle dobavleniya: 1 3 4

Izmeneniye 1na 5.
Spisok posle izmeneniya : 5 3 4
Spisok v obratnom poryadke : 4 3 5

Vvedite informaciyu: 3
Tepery element soderghit :3
Spisok ot nachala k kontsu :3 3 4

Posle udaleniya nachalnogo elementa spiska :3 4

Posle udaleniya konechnogo elementa spiska :3

Spisok elementov tipa double ot nachala k kontsu:
98.6 212 88.9
i ot kontsa k nachalu:
88.9 212 98.6

Анализ:

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


Назад | Начало урока | Вверх | Вперед
Содержание

Hosted by uCoz