Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТП лекции Раздел 4.doc
Скачиваний:
16
Добавлен:
28.09.2019
Размер:
2.56 Mб
Скачать

4.5.3. Списки.

Рассмотренные структуры — стек и очередь, являются специальными разно­видностями более общей структуры данных — обобщенного связанного списка. Существует множество способов его реализации. Рассмотрим линейные (однонаправленные) списки. Интерфейс для программ работы с такими списками приведен в листинге 5.7.

/* Интерфейс для работы со списком */.

itdefine LIST struct list

LIST

{

char aLastName[16];

char aFirstName[16];

char aTelephoneNumber[16];

LIST *pNext;

};

extern void insert(LIST **ppList, LIST *pItem);

extern void destroy(LIST **pList);

extern void display(LIST *pList);

extern int remove(LIST **ppList, LIST *pItem) ;

Чтобы сделать пример работы со списком более реалистичным и продемон­стрировать особенности работы со структурами, предположим, что каждый элемент списка (рис. 5.8) представляет собой символьную строку. В прин­ципе, возможности здесь безграничны, и вместо строки можно использовать любые достаточно сложные структуры и объединения.

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

  • insert — добавить новый элемент в список, сохраняя установленный по­рядок следования.

  • destroy — разрушить список.

  • display — вывести все элементы списка.

  • remove — удалить элемент списка.

Реализация этих функций представлена в листинге 5.8.

/* Реализация функций работы со списком */

#include <string.h>

#include <malloc.h>

#include <stdio.h>

#include "list.h"

// Статическая функция создания элемента списка

static LIST* create(LIST *pItem)

{

LIST *pNewItem = (LIST *)malloc(sizeof(LIST));

*pNewItem = *pItem;

return pNewItem; }

void insert(LIST **ppList, LIST *pItem)

{ char aKey[16];

LIST *pNewItem;

LIST *pCurItem = *ppList;

LIST *pPreItem = 0;

// В качестве ключевого поля в списке используется фамилия.

strcpy(aKey, pItem->aLastName);

// Ищем элемент, совпадаю!ций с ключевым

while(pCurItem != 0 &&

strcmp(pCurItem->aLastName, aKey) < 0)

{

pPreItem = pCurItem;

pCurItem = pCurItem->pNext;

}

// Здесь pCurItem либо 0, либо равен указателю на найденный элемент

// Создаем новый элемент и заполняем его поля

pNewItem = create(pItem);

pNewItem->pNext = pCurItem;

// Вставляем новый элемент в список

if (pPreltem = = 0) // либо в начало,

*ppList = pNewItem;

else // либо после ключевого pPreItem->pNext = pNewItem;

}

int remove(LIST **ppList, LIST *pItem)

{

LIST *pCur!tem = *ppList;

LIST *pPreItem = 0;

// Ищем заданный элемент

while(pCurItem != 0 &&

strcmp(pCurItem->aLastName, pItem->aLastName) != 0)

{

pPreItem = pCurItem;

pCurItem = pCurItem->pNext;

}

// Если такого элемента нет, то и удалять нечего

if (pCurItem != 0)

return 0;

if (pPreItem ==0)

// Удаляем первый элемент списка

*ppList = pCurItem->pNext;

else

// Удаляем элемент списка, начиная со второго

pPreItem->pNext = pCurItem->pNext;

free(pCurltem); // обязательно освобождаем память

return 1;

}

void destroy(LIST **ppList)

{

LIST *pCurItem = *ppList;

LIST *pPreItem = 0;

// Проходим весь список

while(pCurItem != 0)

{

pPreItem = pCurItem;

pCurItem = pCurItem->pNext;

// и удаляем каждый текущий элемент

free(pPreltem) ;

}

// Нулевой указатель на начало списка говорит, что список пустой

*ppList = 0;

}

void display(LIST *pList)

{

LIST *pCurItem = pList;

// Последовательно проходим весь список

while(pCurltem)

{

// и выводим информацию о каждом элементе

printf("\n%s, %s", pCurItem->aLastName, pCurItem->aFirstName);

printf(" \t%s", pCurItem->aTelephoneNumber);

pCur-Item = pCurltem->pNext;

}

printf("\n\n"); }

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

*pNewItem = *pItem;

для пересылки всей информации, расположенной по адресу pItem, в область памяти по адресу pNewItem.

Она введена исключительно для демонстрации работы со статическими функциями: ее можно вызвать только из перечисленных выше функций ра­боты со списком; непосредственный вызов из другого модуля запрещен. Это один из приемов ограничения доступа — новый элемент списка можно создавать только при помощи интерфейсных функций.

Остановимся также на некоторых аспектах функций insert и remove, по­скольку реализация функций display и destroy достаточно тривиальна и уже, в том или ином виде, рассматривалась.

Работа функции insert проиллюстрирована на рис. 5.9.

Таким образом, чтобы вставить элемент в список, необходимо сначала най­ти элемент со значением ключевого поля, совпадающим с заданным. Это осуществляется путем прохода по списку.

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

В некоторых случаях может возникнуть, задача вставки элемента перед клю­чевым. Решите ее самостоятельно в качестве упраж­нения — принцип при этом не изменяется.

Операция удаления элемента из списка также довольно проста (рис. 5.10).

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

Если же такой элемент есть, то либо устанавливаем новое значение указате­ля на список (если это первый элемент), либо полю next предыдущего эле­мента присваиваем значение адреса следующего элемента. В обоих случаях не забываем освободить занимаемую элементом память:

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

  • Циклический список — поле next последнего элемента содержит указатель назад на первый элемент (рис. 5.12). Такой список не имеет первого и последнего элементов. Однако в некоторых случаях удобно использовать внешний указатель на последний элемент, что автоматически делает пер­вым следующий за ним элемент. Альтернативный вариант предполагает использование указателя на первый элемент.

  • Двунаправленные списки — каждый элемент такого списка содержит два указателя: один указывает на предшествующий элемент, а другой — на последующий. Такие списки могут быть линейными и циклическими (рис. 5.13).

  • Мультисписки — структура данных, состоящая из элементов, которые могут принадлежать нескольким спискам, не повторяясь в нескольких элементах дня каждого списка. Такие элементы имеют указатели на каждый список, которому они принадлежат (рис. 5.14). Списки в мультисписке могут быть линейными и циклическими, однонаправленными и двунаправленными.