Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

3 семестр / Методические материалы / metodicheskie-ukazaniia-po-teme-19

.pdf
Скачиваний:
0
Добавлен:
20.05.2025
Размер:
604.93 Кб
Скачать

НАЦИОНАЛЬНЫЙ ИССЛЕДОВАТЕЛЬСКИЙ ЯДЕРНЫЙ УНИВЕРСИТЕТ «МИФИ»

 

Кафедра информатики и процессов управления (№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

7

Стек

Указатели стека

Для работы со стеком (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