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

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

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

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

 

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