
3 семестр / Методические материалы / metodicheskie-ukazaniia-po-teme-19
.pdfНАЦИОНАЛЬНЫЙ ИССЛЕДОВАТЕЛЬСКИЙ ЯДЕРНЫЙ УНИВЕРСИТЕТ «МИФИ» |
|
Кафедра информатики и процессов управления (№17) |
|
Дисциплина «Информатика» (основной уровень), 2-й курс, 3-й семестр. |
|
Методические указания |
|
Тематическое занятие 19 |
|
Линейные списки, очереди, стеки. |
|
Содержание |
|
Связанные динамические данные .............................................................. |
1 |
Основные определения......................................................................................... |
1 |
Организация связей............................................................................................... |
2 |
Определение синонимов типов с помощью typedef.................................... |
3 |
Синонимы структур............................................................................................. |
3 |
Очередь ........................................................................................................... |
4 |
Указатели очереди ................................................................................................ |
4 |
Создание очереди................................................................................................... |
4 |
Добавление элемента в очередь........................................................................ |
5 |
Удаление элемента из очереди .......................................................................... |
6 |
Стек................................................................................................................... |
7 |
Указатели стека .................................................................................................... |
7 |
Создание стека ...................................................................................................... |
7 |
Добавление элемента в стек ............................................................................. |
8 |
Удаление элемента из стека .............................................................................. |
8 |
Пример работы с очередью ......................................................................... |
9 |
Действия с очередью ........................................................................................... |
9 |
Функции для работы с очередью..................................................................... |
10 |
Функция добавления элемента ........................................................................ |
11 |
Связанные динамические данные
Основные определения
Линейный список (list) – это динамическая структура данных, которые представляют собой совокупность линейно связанных однородных элементов.
Линейный список называется односвязным, если каждый его элемент (кроме последнего) с помощью указателя связывается с одним (следующим) элементом.
В кольцевом списке имеется связь между последним и первым элементами.
1

Очередь (queue) – частный случай линейного односвязного списка, организованного по принципу “first in, first out” (FIFO, «первым пришел, первым ушел»). Для очереди разрешено только два действия:
добавление элемента в конец (хвост) очереди,
удаление элемента из начала (головы) очереди.
Стек (stack) – частный случай линейного односвязного списка, организованного по принципу “last in, first out” (LIFO, «последним пришел,
первым ушел»). Для стека разрешено добавлять и удалять элементы только с одного конца списка, который называется вершиной (головой) стека.
Организация связей
Высокая гибкость структур, основанных на связанных динамических данных, достигается за счет реализации двух возможностей:
динамическое выделение и освобождение памяти под элементы в любой момент работы программы;
установление связей между любыми двумя элементами с помощью указателей.
Для организации связей между элементами динамических структур данных
требуется, чтобы каждый элемент содержал кроме информационных значений хотя бы один указатель. Поэтому в качестве элементов таких структур следует использовать структуры, которые могут объединять в единое целое разнородные данные.
В простейшем случае элемент динамической структуры данных должен состоять из двух полей: информационного (inf) и указательного (link).
Схематичное изображение такой структуры данных в списке:
inf |
inf |
link |
link |
inf |
link |
inf |
NULL |
Соответствующее ей объявление: |
|
||
struct elem { |
/* Структура с меткой (тегом) elem:*/ |
||
int inf; |
/* |
inf - информационное поле, */ |
|
struct elem *link; |
/* |
link - указательное поле. |
*/ |
}; |
|
|
|
Здесь типом указательного поля является указатель на саму структуру целиком
– тип данных «struct elem *». Такой способ описания структуры называется
рекурсивным.
При объявлении структуры не выделяется память – по сути объявление структуры является описанием нового типа данных. Для удобства использования этого типа данных можно описать его синоним (псевдоним) с помощью ключевого слова typedef:
typedef struct elem Elem; /* Elem – синоним типа struct elem.*/
Теперь можно объявлять и использовать переменные:
Elem e1, e2, array[10], *p;
Такое описание эквивалентно:
struct elem e1, e2, array[10], *p;
2
Определение синонимов типов с помощью typedef
В языке Си для создания синонимов (псевдонимов) определенных ранее типов данных используется ключевое слово typedef, синтаксис:
typedef СуществующийТип ИмяНовогоТипа;
Например, можно создать синонимы для стандартных типов данных:
typedef int Integer; /* Integer – синоним типа int. */ typedef char *String; /* String – синоним типа char *. */
и объявлять переменные этих новых типов:
Integer a, i, *pa, *pi; /* Эквивалентно: String str, name, s; /* Эквивалентно:
int a, i, *pa, *pi; */ char *str, *name, *s; */
Имена синонимов типов, созданных с помощью typedef, принято начинать с прописной буквы (или полностью набирать прописными буквами: INTEGER, STRING) для того, чтобы выделить их в тексте программы.
Хотя typedef является оператором, он не создает новых типов, а всего лишь предоставляет удобные для использования синонимы (псевдонимы) имеющихся типов. Это означает, что эти синонимы можно использовать там, где ожидается исходный существующий тип. Например, в параметрах функции:
void func(char *); |
/* Ожидается аргумент типа char *. */ |
int main(void) |
|
{ |
|
String str; |
/* Эквивалентно: char *str; */ |
... |
|
func(str); |
/* Передается аргумент str типа String. */ |
... |
|
}
void func(char *s) { /* Формальный параметр s типа char *. */
...
}
Синонимы структур
Удобно использовать typedef для определения синонимов типов, заданных структурами. Например, описать синоним типа для комплексных чисел:
struct complex { /* Структура с меткой (тегом) complex. */ double Re;
double Im;
};
typedef struct complex Complex; /* Complex – синоним для структуры */ /* с меткой (тегом) complex. */
Можно описать структуру непосредственно в операторе typedef:
typedef struct complex { /* Структура с меткой (тегом) complex. */ double Re;
double Im;
} Complex; /* Complex – синоним типа для этой структуры. */
В каждом их этих случаев можно объявлять переменные:
Complex z1, z2, *pz;
3

В последнем примере метку (тег) структуры можно не указывать:
typedef struct { /* Структура без метки (тега). */ double Re;
double Im;
} Complex; /* Complex – синоним типа для этой структуры. */
Но метку (тег) необходимо указать, если структура описана рекурсивно и ссылается сама на себя. Например, при описании структуры элементов списка:
typedef |
struct elem { |
|
|
|
|
int |
inf; |
/* |
inf - |
информационное поле |
*/ |
struct elem *link; |
/* |
link - |
указательное поле |
*/ |
|
} Elem; |
|
|
|
|
|
Очередь
Указатели очереди
Для создания очереди (queue) и работы с ней необходимо иметь как минимум два указателя:
на начало очереди (назовем его BegQ, от begin of queue),
на конец очереди (назовем EndQ – end of queue).
Кроме того, для освобождения памяти удаляемых элементов потребуется дополнительный указатель (назовем его p), он часто используется и в других ситуациях для удобства работы с очередью.
Elem *BegQ;
Elem *EndQ; Elem *p;
Создание очереди
1. Исходное состояние. |
|
|
||||
BegQ |
|
EndQ |
|
|
p |
BegQ = NULL; |
|
|
|
|
|
|
|
NULL |
|
NULL |
|
? |
|
EndQ = NULL; |
|
|
|
|
|
|
|
?
4

2. Выделение памяти под первый элемент очереди.
BegQ |
|
EndQ |
p |
||
|
|
|
|
|
p = (Elem *) malloc(sizeof(Elem)); |
NULL |
|
NULL |
|
|
|
|
|
|
|
||
|
|
|
inf |
|
|
|
|
|
|
|
|
|
|
|
? |
|
|
|
|
|
link |
|
|
|
|
|
|
? |
|
3. Занесение данных в первый элемент очереди. |
|||||
BegQ |
|
EndQ |
p |
||
|
|
|
|
|
p->inf = 3; |
NULL |
|
NULL |
|
|
|
|
|
|
p->link = NULL; |
||
|
|
|
|
|
|
|
|
|
inf |
|
|
|
|
|
3 |
|
|
|
|
|
link |
NULL |
|
4. Установка указателей BegQ и EndQ на созданный первый элемент.
BegQ |
|
EndQ |
p |
||
|
|
|
|
|
BegQ = p; |
|
|
|
|
|
|
|
|
|
|
|
EndQ = p; |
inf 3
link NULL
Добавление элемента в очередь
1. Исходное состояние. |
|
|
||||
BegQ |
|
|
|
EndQ |
|
p |
|
|
|
|
|
|
|
|
|
|
|
|
|
? |
|
|
|
|
|
? |
|
2 |
4 |
|
3 |
|||
|
|
|
NULL
2. Выделение памяти под новый элемент и занесение в него данных.
BegQ |
|
EndQ |
|
p |
p=(Elem*)malloc(sizeof(Elem)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
p->inf = 5; |
|
|
|
|
|
p->link = NULL; |
2 |
4 |
3 |
5 |
|
|
NULL |
NULL |
5

3. Установка связи между последним элементом очереди и новым, а также перемещение указателя EndQ на новый элемент.
BegQ |
|
EndQ |
|
|
|
|
|
|
2 |
|
4 |
|
3 |
|
|
|
|
|
p
EndQ->link = p; EndQ = p;
5 |
NULL |
Удаление элемента из очереди
1. Исходное состояние.
p |
BegQ |
EndQ |
?
? |
2 |
4 |
3 |
5 |
|
||||
|
|
|
|
NULL |
2. Извлечение информации из удаляемого элемента в переменную val и установка на него вспомогательного указателя p.
p |
BegQ |
|
EndQ |
|
|
|
|
|
|
|
|
|
|
|
val |
|
|
|
|
2 |
2 |
4 |
3 |
5 |
|
|
|
NULL |
|
|
|
|
|
int val;
...
val = BegQ->inf; p = BegQ;
3. Перестановка указателя BegQ на следующий элемент, используя значение поля link удаляемого элемента. Освобождение памяти удаляемого элемента p.
p |
BegQ |
|
EndQ |
|
|
|
|
|
|
|
|
|
|
|
val |
|
|
|
|
2 |
2 |
4 |
3 |
5 |
|
|
|
NULL |
|
|
|
|
|
BegQ = p->link; free(p);
6

Стек
Указатели стека
Для работы со стеком (stack) необходимо иметь один указатель на вершину стека (назовем его Top). Также потребуется один дополнительный указатель (p), который используется для выделения и освобождения памяти элементов стека.
Elem *Top; Elem *p;
Создание стека
1. Исходное состояние. |
|
|
Top |
p |
Top = NULL; |
|
|
NULL ?
?
2.Выделение памяти под первый элемент стека.
|
|
Top |
p |
|||
|
|
|
|
|
|
p = (Elem *) malloc(sizeof(Elem)); |
|
|
NULL |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inf |
|
|
|
|
|
|
|
|
|
|
|
|
|
? |
|
|
|
|
|
|
link |
|
|
|
|
|
|
|
? |
|
3. Занесение данных в первый элемент стека. |
||||||
|
|
Top |
p |
|||
|
|
|
|
|
|
p->inf = 5; |
|
|
NULL |
|
|
|
|
|
|
|
|
|
p->link = NULL; |
|
|
|
|
|
|
|
|
|
|
|
|
inf |
|
|
|
|
|
|
5 |
|
|
|
|
|
|
link |
NULL |
|
4. Установка указателя Top на созданный первый элемент. |
||||||
|
|
Top |
p |
|||
|
|
|
|
inf |
|
Top = p; |
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
|
|
|
|
|
|
link |
NULL |
|

Добавление элемента в стек
1. Исходное состояние. p Top
?
? |
3 |
1 |
5 |
|
NULL
2. Выделение памяти под новый элемент и занесение в него данных.
p |
Top |
||
|
|
|
p=(Elem*)malloc(sizeof(Elem)); |
|
|
|
|
|
|
|
p->inf = 8; |
|
|
|
p->link = NULL; |
8 |
3 |
1 |
5 |
NULL |
|
|
NULL |
3. Установка связи между новым элементом и первым элементом стека, а также перемещение указателя Top на новый элемент.
p Top
p->link = Top; Top = p;
8 |
3 |
1 |
5 |
NULL
Удаление элемента из стека
|
1. Исходное состояние. |
|
|
|
|||||
p |
|
|
Top |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
? |
|
|
|
|
|
|
|
|
|
? |
|
|
|
|
|
|
|
|
|
|
|
8 |
|
3 |
|
1 |
|
5 |
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
NULL |
|
|
|
|
|
|
|
|
|
|
8

2. Извлечение информации из удаляемого элемента в переменную val и установка на него вспомогательного указателя p.
p |
Top |
|
|
|
|
|
|
int |
val; |
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
... |
|
||
|
|
|
|
|
|
|
|
|
val |
= Top->inf; |
val |
|
|
|
|
|
|
|
p = |
Top; |
|
8 |
|
3 |
|
1 |
5 |
|||||
|
|
|
|
|
|
|
||||
8 |
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NULL |
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3. Перестановка указателя Top на следующий элемент, используя значение поля link удаляемого элемента. Освобождение памяти удаляемого элемента p.
p Top
Top = p->link; free(p);
val |
|
|
|
|
|
|
|
|
8 |
|
3 |
|
1 |
|
5 |
||
|
|
|
|
|
||||
8 |
|
|
|
|
||||
|
|
|
|
|
|
|
NULL |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Пример работы с очередью
Действия с очередью
Используем описанный ранее тип рекурсивной структуры Elem для хранения элементов очереди:
typedef |
struct elem { |
|
|
|
|
int |
inf; |
/* |
inf - |
информационное поле |
*/ |
struct elem *link; |
/* |
link - |
указательное поле |
*/ |
|
} Elem; |
|
|
|
|
|
Для работы с очередью потребуется реализовать два основных действия, соответствующих принципу FIFO, которые принято называть так:
push – добавление элемента в конец (хвост) очереди;
pop – удаление элемента из начала (головы) очереди .
Каждое из этих двух действий следует вынести в отдельную функцию, которая будет выполнять отдельное единое законченное действие.
Используя две эти функции push и pop, можно реализовать все остальные действия с очередью:
создание очереди – добавление элемента в пустую очередь;
удаление очереди – удаление всех элементов очереди.
9
При реализации функций push и pop следует обратить внимание на то, как изменяются значения указателей на начало BegQ и конец EndQ очереди в разных условиях:
Действие |
Указатель |
||
на начало, BegQ |
на конец, EndQ |
||
|
|||
добавление элемента в непустую очередь |
изменяется |
не изменяется |
|
добавление элемента в пустую очередь |
изменяется |
изменяется |
|
удаление элемента из очереди, |
не изменяется |
изменяется |
|
содержащей два и более элементов |
|||
|
|
||
удаление элемента из очереди, |
изменяется |
изменяется |
|
содержащей только один элемент |
|||
|
|
||
удаление элемента из пустой очереди |
не изменяется |
не изменяется |
|
операция некорректна |
|||
|
Оба указателя на начало и конец очереди могут изменяться как при добавлении, таки при удалении элементов. Поэтому каждая из двух функций push и pop должна получать в качестве входных параметров оба указателя BegQ и EndQ.
Функции для работы с очередью
На основе этих рассуждений составим заголовки функций push и pop:
void push(Elem *beg, Elem *end, int val) {
/* beg – указатель на начало очереди */ /* end – указатель на конец очереди */
/* val – значение поля inf добавляемого элемента */
...
}
void pop(Elem *beg, Elem *end) {
...
}
При вызове в эти функции передаются указателя на начало и конец очереди:
Elem *BegQ = NULL; /* В начале очередь пуста - */
Elem *EndQ = NULL; /* - не содержит ни одного элемента. */
...
push(BegQ, EndQ, 123); /* Добавление элемента. */
...
pop(BegQ, EndQ); /* Удаление элемента. */
Получается, что при выполнении любого действия с очередью, необходимо передавать оба указателя. Поэтому удобнее работать с очередью, если объединить указатели на начало beg и конец end очереди в отдельной структуре Queue:
typedef struct queue { |
|
||
Elem *beg; |
/* |
beg – начало (голова) очереди */ |
|
Elem *end; |
/* |
end |
– конец (хвост) очереди */ |
} Queue; |
|
|
|
Заметим, что в этом описании метка (тег) структуры queue – необязательна и может быть опущена.
Теперь заголовки функций push и pop выглядят проще:
10