<< Предыдущая Оглавление Следующая >>


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

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

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

4.5. Графическая интерпретация связных списков

Здесь *prev и *next – указатели на предыдущий и следующий объекты соответственно; *head и *tail – указатели на первый и последний объекты; *current – указатель на текущий объект, с которым идет работа. Если предыдущего или последующего объекта не существует, то указатели *prev и *next принимают значение NULL. Указатели *head и *tail являются вспомогательными и служат для быстрого перемещения к первому и последнему объекту соответственно. Рассмотрим работу связных списков на примере представления следующей информации.

Строки данной таблицы можно описать с помощью структуры:

typedef struct tag_lib {
char title[100];
char author[100];
int year;
} LIB;

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

typedef struct tag_obj {
LIB lib;
typedef struct tag_obj* prev, *next;
} OBJ;

Здесь *prev и *next – указатели на предыдущую и следующую строки соответственно.

По умолчанию указатели head и tail равны NULL:

OBJ* head = NULL, *tail = NULL;

При добавлении записей выполняется инициализация этих указателей, а также prev и next внутри объектов:

OBJ* add_obj(char* title, char* author, int year)
{
OBJ* current = (OBJ *)malloc(sizeof(OBJ));
strcpy(current->lib.title, title);
strcpy(current->lib.author, author);
current->lib.year = year;
current->prev = tail;
current->next = NULL;
if(tail != NULL) tail->next = current;
if(head == NULL) head = current;
tail = current;
return current;
}

Данная функция использует три параметра для ввода данных в структуру LIB. В первой строке функции создается новая структура типа OBJ. Во второй, третьей и четвертой строках осуществляется запись информации в структуру LIB. Затем, инициализируются указатели prev и next добавленного объекта. Учитывая, что добавление осуществляется в конец списка, то указатель next должен быть равен NULL, а указатель prev указывать на предыдущий объект, т.е. быть равен указателю tail. В свою очередь, объект, на который указывает указатель tail, становится предпоследним и его указатель next должен указывать на последний объект, т.е. быть равным указателю current. Затем проверяется, является ли добавляемый объект первым (head == NULL), и если это так, то указатель head приравнивается указателю current. Наконец, указатель tail инициализируется на последний объект. Последняя строка функции возвращает указатель на созданный объект.

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

OBJ* del_obj(OBJ* current)
{
if(current == head)
if(current->prev != NULL) head = current->prev;
else head = current->next;
if(current == tail)
if(current->next != NULL) tail = current->next;
else tail = current->prev;
if(current->prev != NULL)
current->prev->next = current->next;
if(current->next != NULL)
current->next->prev = current->prev;
free(current);
return head;
}

Функция del_obj() в качестве аргумента использует указатель на объект, который следует удалить. Сначала выполняется проверка для инициализации указателя head, в том случае, если удаляется первый объект, на который он указывает. Аналогичная проверка осуществляется для tail. Затем осуществляется проверка: если предыдущий объект относительно текущего существует, то его указатель на следующий объект следует переместить. Аналогичная проверка выполняется и для следующего объекта относительно удаляемого. После настройки всех указателей вызывается функция free() для удаления объекта из памяти и возвращается указатель на первый объект.

Введенные функции в программе можно использовать следующим образом:

int main()
{
OBJ *current = NULL;
int year;
char title[100], author[100];
do
{
printf("Введите название книги: ");
scanf("%s",title);
printf("Введите автора: ");
scanf("%s",author);
printf("Введите год издания: ");
scanf("%d",&year);
current = add_obj(title,author,year);
printf("Для выхода введите 'q'");
} while(scanf("%d",&year) == 1);
current = head;
while(current != NULL)
{
printf("Title: %s, author %s, year = %d\n",
current->lib.title, current->author.old, current->lib.year);
current = current->next;
}
while(head != NULL)
del_obj(head);
return 0;
}

Функция main() осуществляет ввод названия книги, автора и года издания в цикле do while(). Там же вызывается функция add_obj() с соответствующими параметрами, которая формирует связанный список на основе введенных данных. Пользователь выполняет ввод до тех пор, пока не введет какой либо символ на последний запрос. В результате цикл завершится, и указатель current передвигается на первый объект. Затем, в цикле while осуществляется вывод информации текущего объекта на экран, а указатель current передвигается на следующий объект. Данная процедура выполняется до тех пор, пока указатель не станет равным NULL, что означает достижение конца списка. Перед выходом из программы связный список удаляется с помощью функции del_obj(), у которой в качестве аргумента всегда используется указатель head. Если данный указатель принимает значение NULL, то это означает, что связный список пуст и не содержит ни одного объекта. После этого программа завершает свою работу.


<< Предыдущая Оглавление Следующая >>