МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РФ ГОСУДАРСТВЕННОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ «ВОЛГОГРАДСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ» КАМЫШИНСКИЙ ТЕХНОЛОГИЧЕСКИЙ ИНСТИТУТ (ФИЛИАЛ) ГОУ ВПО «ВОЛГОГРАДСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ» КАФЕДРА «АВТОМАТИЗИРОВАННЫЕ СИСТЕМЫ ОБРАБОТКИ ИНФОРМАЦИИ И УПРАВЛЕНИЯ» ЛИНЕЙНЫЕ СТРУКТУРЫ ДАННЫХ: СПИСКИ, СТЕК, ОЧЕРЕДЬ Методические указания к лабораторным работам по дисциплине «Структуры и алгоритмы обработки данных» Волгоград 2011 УДК 004. 42(07) Л 59 ЛИНЕЙНЫЕ СТРУКТУРЫ ДАННЫХ: СПИСКИ, СТЕК, ОЧЕРЕДЬ: методические указания к лабораторным работам по дисциплине «Структуры и алгоритмы обработки данных» / Сост. А. Э. Панфилов; Волгоград. гос. техн. ун-т. – Волгоград, 2011. – 31 с. Содержат необходимые материалы для поддержки лабораторных работ по изучению и применению линейных структур данных – линейных списков, стека, очереди. Предназначены для студентов, обучающихся по направлению 654600 «Информатика и вычислительная техника». Ил. 10. Табл. 4. Библиогр.: 6 назв. Рецензент: В. И. Кручинин Печатается по решению редакционно-издательского совета Волгоградского государственного технического университета 2 Волгоградский государственный технический университет, 2011 ВВЕДЕНИЕ Организация данных для обработки является важным этапом разработки программ. Для реализации многих приложений выбор структуры данных – единственное важное решение: когда выбор сделан, разработка алгоритмов не вызывает затруднений. Для одних и тех же данных различные структуры будут занимать неодинаковое дисковое пространство. Одни и те же операции с различными структурами данных создают алгоритмы неодинаковой эффективности. Выбор алгоритмов и структур данных тесно взаимосвязан [1]. Связные линейные списки, стеки и очереди относятся к классическим структурам данных. Они имеют широкое применение как сами по себе, так и в качестве базового набора средств для составления сложных алгоритмов. Лабораторная работа №1. ЛИНЕЙНЫЕ СТРУКТУРЫ ДАННЫХ: СПИСКИ Цель работы: Освоить и закрепить приемы работы с основными динамическими структурами данных – одно- и двунаправленными линейными списками. Ключевые понятия, которые необходимо знать: динамические структуры данных, линейный список, однонаправленный список, двунаправленный список. Время выполнения лабораторного занятия: 4 часа. Количество баллов (минимальное, максимальное): 6 – 9 баллов. 1 ТЕОРЕТИЧЕСКАЯ ЧАСТЬ Списком называется упорядоченное множество, состоящее из переменного числа элементов, к которым применимы операции включения и исключения. Список, отражающий отношения соседства между элементами, называется линейным. Если ограничения на длину списка не установлены, то список представляется в памяти в виде связной структуры. Линейные связные списки являются простейшими динамическими структурами данных. 1.1 Машинное представление связных линейных списков На рисунке 1.1 приведена структура однонаправленного списка, где поле INFO - информационное поле, данные, NEXT - указатель на следу3 ющий элемент списка. Каждый список должен иметь особый элемент, называемый началом списка или головой списка, который обычно по формату отличен от остальных элементов. В поле указателя последнего элемента списка находится специальный признак NULL, свидетельствующий о конце списка. Рисунок 1.1 - Структура однонаправленного списка Обработка однонаправленного списка не всегда удобна, так как отсутствует возможность продвижения по списку в сторону противоположной направлению имеющимся связям. Такую возможность обеспечивает двунаправленный список, каждый элемент которого содержит два указателя: на следующий и предыдущий элементы списка. Структура линейного двунаправленного списка приведена на рисунке 1.2, где поле NEXT - указатель на следующий элемент, поле PREV - указатель на предыдущий элемент. В крайних элементах соответствующие указатели должны содержать NULL, как и показано на рисунке 1.2. Рисунок 1.2 - Структура двунаправленного списка Для удобства обработки списка добавляют еще один особый элемент - указатель конца списка. Наличие двух указателей в каждом элементе усложняет список и приводит к дополнительным затратам памяти, но в то же время обеспечивает более эффективное выполнение некоторых операций над списком (перемещение, добавление и т.п.). Ниже приведен проект ListL1 программной реализации однонаправленного списка на языке С#. //Класс ElementL1List - отдельный элемент (узел) Л1-списка class ElementL1List { public int info; //информационная часть public ElementL1List next; //указательная часть }; 4 //Класс - Л1-список class L1 { ElementL1List head; //начало (голова) списка //Добавить элемент в начало списка public void ins_begin(int value) { //создать узел списка и задать ему значения ElementL1List elem = new ElementL1List(); elem.info = value; elem.next = head; //следующий элемент после созданного текущая "голова" списка head = elem; //сделать новый элемент "головой" } //Вставить элемент в заданную позицию public void insert_node(int value, int pos) { //проверить значение pos на соответствие размеру списка if (pos < 2) ins_begin(value); else { if (pos > number_node()) pos = number_node() + 1; ElementL1List elem = head; //elem-временная переменная (то же самое что и head) //перемещаем elem до вставляемой позиции for (int i = 0; i < pos - 2; i++) elem = elem.next; //после цикла elem = элемент списка, после которого надо добавить новый элемент ElementL1List v = new ElementL1List(); //v-новый элемент, вставляемый в список v.info = value; v.next = elem.next; elem.next = v; //следующим элементом за elem будет v } } //Подсчитать количество элементов списка public int number_node() { int k = 0; //счетчик элементов списка ElementL1List elem=head; //пока не достигнут конец списка while(elem!=null) { k++; elem = elem.next; //переходить к следующему элементу } return k; } 5 //Получить значение элемента в value из заданной позиции pos. //Если элемент по позиции pos существует, функция вернет true, не существует - false public bool out_node(int pos, out int value) { //проверка на допустимость позиции if (pos > number_node() || pos < 1 || number_node() == 0) { value = 0; return false; } else { ElementL1List elem = head; //перемещение от "головы" списка до элемента по позиции pos for (int i = 0; i < pos - 1; i++) elem = elem.next; value = elem.info; //записать запрошенное значение return true; } } //Удалить элемент заданного позицией pos из списка. //Если элемент по позиции pos существует, функция вернет true, не существует - false public bool delete_node(int pos) { //проверка на допустимость позиции if (pos > number_node() || pos < 1 || number_node() == 0) return false; if (pos == 1) //переместить "голову" списка на следующий элемент //(прежняя "голова" будет потеряна, т.е. удалена) head = head.next; else { ElementL1List elem = head; //перемещение от "головы" списка до элемента по позиции pos-1 for (int i = 0; i < pos - 2; i++) elem = elem.next; //связать элемент предшествующий позиции pos с элементом - следующим за pos elem.next = elem.next.next; } return true; } } Демонстрация работы с методами класса L1 //Вспомогательная функция - вывод элементов списка на экран static void show(L1 list) { 6 int value; for (int i = 1; i <= list.number_node(); i++) { list.out_node(i, out value); Console.WriteLine(value); } } //Главная функция консольного приложения static void Main(string[] args) { int value, positon, code; L1 list = new L1(); //Л1-список do{ //вывод меню работы с Л1-списком Console.WriteLine(" Работа с Л1-списком:"); Console.WriteLine("1. Добавить элемент в начало списка"); Console.WriteLine("2. Добавить элемент в заданную позицию списка (нумерация с 1)"); Console.WriteLine("3. Удалить элемент из заданной позиции списка (нумерация с 1)"); Console.WriteLine("4. Вывести значение элемента списка по его номеру"); Console.WriteLine("5. Вывести весь список на экран"); Console.WriteLine("0. Завершить работу"); Console.WriteLine("\tКоличество элементов в Л1-списке = {0}", list.number_node()); Console.Write(" Ваш выбор: "); try { code = Convert.ToInt32(Console.ReadLine()); } catch { code = 9; } switch (code) //разбор вариантов действий { case 1: Console.Write("Введите значение добавляемого элемента: "); value = Convert.ToInt32(Console.ReadLine()); list.ins_begin(value); break; case 2: Console.Write("Введите значение позиции: "); positon= Convert.ToInt32(Console.ReadLine()); Console.Write("Введите значение нового элемента: "); value = Convert.ToInt32(Console.ReadLine()); list.insert_node(value,positon); break; case 3: Console.Write("Введите значение позиции: "); positon = Convert.ToInt32(Console.ReadLine()); if(list.delete_node(positon)) Console.WriteLine("Элемент по позиции {0} удален.", positon); break; case 4: Console.Write("Введите значение позиции: "); positon= Convert.ToInt32(Console.ReadLine()); if(list.out_node(positon, out value)) 7 Console.WriteLine("Элемент по позиции {0} равен {1}", positon,value); else Console.WriteLine("Элемента по позиции {0} не существует", positon); break; case 5: show(list); break; case 0: break; default:Console.WriteLine("Неверный выбор пункта меню"); break; } Console.WriteLine(); } while (code != 0); } } 1.2 Реализация линейных списков в различных библиотеках В связи с тем, что линейные списки являются одними из самых распространенных структур данных, используемыми программистами, большинство программных библиотек содержат их реализацию. В среде Microsoft Visual Studio 2005, в частности, доступны следующие классысписки: - класс list в библиотеке STL (стандартной библиотеке шаблонов); - классы CList, CPtrList, CObList, CStringList в библиотеке MFC; - классы ArrayList, SortedList в пространстве имен System.Collections библиотеки .NET - классы-шаблоны List, LinkedList в пространстве имен System.Collections.Generic библиотеки .NET В таблице 1.1 приведены основные свойства и методы класса ArrayList из пространстве имен System.Collections библиотеки .NET. Таблица 1.1 – Основные свойства и методы класса ArrayList. Имя свойства/ Описание свойства/метода метода Количество элементов, для которых списку выделена память к текущеCapacity му моменту Count Фактическое количество элементов, находящихся в списке Add() Добавляет новый элемент в конец списка Clear() Удаляет все элементы списка Contains() Проверяет наличие элемента с заданным значением в списке IndexOf() Возвращает позицию элемента с заданным значением в списке Insert() Вставляет новый элемент с заданным значением в заданную позицию Remove() Удаляет первый найденный элемент с заданным значением из списка RemoveAt() Удаляет элемент с заданной позицией из списка Reverse() Переставляет все элементы списка в обратном порядке Sort() Сортирует элементы списка в порядке возрастания 8 Ниже приведен проект DemoArrayList на языке С# использующий класс ArrayList из библиотеки .NET. //Подключение пространств имен using System; using System.Collections; // пространство, в котором находится класс ArrayList namespace DemoArrayList { class Program { //Вспомогательная функция - вывод элементов списка на экран static void show(ArrayList list) { for (int i = 0; i < list.Count; i++) Console.WriteLine(list[i]); } //Главная функция консольного приложения static void Main(string[] args) { int value, positon, code; ArrayList arlist = new ArrayList(); //Л1-список do{ //вывод меню работы с Л1-списком Console.WriteLine(" Работа с Л1-списком, используя класс ArrayList:"); Console.WriteLine("1. Добавить элемент в начало списка"); Console.WriteLine("2. Добавить элемент в заданную позицию списка (нумерация с 1)"); Console.WriteLine("3. Удалить элемент из заданной позиции списка (нумерация с 1)"); Console.WriteLine("4. Вывести значение элемента списка по его номеру"); Console.WriteLine("5. Вывести весь список на экран"); Console.WriteLine("6. Вставить элемент в конец списка"); Console.WriteLine("7. Проверить список на пустоту "); Console.WriteLine("8. Сортировать элементы списка по возрастанию"); Console.WriteLine("0. Завершить работу"); Console.WriteLine("\tКоличество элементов в Л1-списке = {0}", arlist.Count); Console.WriteLine("\tКоличество мест для элементов в Л1-списке = {0}", arlist.Capacity); Console.Write(" Ваш выбор: "); try { code = Convert.ToInt32(Console.ReadLine()); } catch { code = 9; } switch (code) //разбор вариантов действий { case 1: Console.Write("Введите значение добавляемого элемента: "); 9 value = Convert.ToInt32(Console.ReadLine()); arlist.Insert(0,value); break; case 2: Console.Write("Введите значение позиции: "); positon= Convert.ToInt32(Console.ReadLine()); Console.Write("Введите значение добавляемого элемента: "); value = Convert.ToInt32(Console.ReadLine()); arlist.Insert(positon-1,value); break; case 3: Console.Write("Введите значение позиции: "); positon = Convert.ToInt32(Console.ReadLine()); try{ arlist.RemoveAt(positon-1); Console.WriteLine("Элемент по позиции {0} удален.", positon); } catch{} break; case 4: Console.Write("Введите значение позиции: "); positon= Convert.ToInt32(Console.ReadLine()); try{ Console.WriteLine("Элемент по позиции {0} равен {1}", positon, arlist[positon-1]); } catch(ArgumentOutOfRangeException) { Console.WriteLine("Элемента по позиции {0} не существует", positon); } break; case 5: show(arlist); break; case 6: Console.Write("Введите значение добавляемого элемента: "); value = Convert.ToInt32(Console.ReadLine()); arlist.Insert(arlist.Count,value); break; case 7: if(arlist.Count==0) Console.WriteLine("Список пуст"); else Console.WriteLine("Список не пуст"); break; case 8: arlist.Sort(); show(arlist); break; case 0: break; default: Console.WriteLine("Неверный выбор меню"); break; } Console.WriteLine(); } while (code != 0); 10 } } } 2 ЗАДАНИЯ НА ЛАБОРАТОРНУЮ РАБОТУ Задание 1 (5 баллов). Разобраться с примерами использования линейного однонаправленного списка приведенными в проектах: ListL1 (пример реализации однонаправленного списка) и DemoArratList (использование класса ArrayList из библиотеки .NET). Задание 2 (+1 балл). Используя файлы с примером реализации однонаправленного списка (проект ListL1), дополнить класс ListL1 следующими функциями: - Добавить элемент в конец списка (не используя функцию insert_node); - Проверить список на пустоту. Изменить главную функцию Main(), включив в неё вызовы добавленных функций для проверки правильности их работы. Задание 3 (+2 балла). Используя файлы с примером реализации однонаправленного списка (проект ListL1), дополнить класс ListL1 функцией сортировки элементов списка по возрастанию (без использования дополнительных массивов). Изменить главную функцию Main(), включив в неё вызов добавленной функции сортировки. Задание 4 (+2 балла). Преобразовать однонаправленный список из проекта ListL1 в двунаправленный. Внести соответствующие изменения в функции работы со списком. Проверить работу всех функций. Примечание. По определению двунаправленного списка, каждый элемент списка должен иметь связь с предыдущим и последующим элементами. Для этого структура элемента двунаправленного списка может иметь следующий вид: struct ElementL2List { public int info; //информационная часть элемента списка public ElementL2List left; // указатель на предыдущий (левый) элемент public ElementL2List right; // указатель на следующий (правый) элемент }; Задание 5 (+ 3 балла). Разработать программу для работы с базой данных, содержащей информацию о студентах. Для описания каждого студента используется следующая информация: - фамилия студента; - имя студента; - отчество студента; - год рождения студента; - обозначение группы; 11 - средний балл в сессию. Необходимо реализовать следующие операции работы с базой данных о студентах: - ввод данных о студентах из файла; - ввод данных о студенте с консоли; - вывод данных обо всех студентах в текстовый файл; - вывод данных о студенте на консоль; - сравнение студентов по среднему баллу. Замечания к программной реализации. 1) Тип файла с данными о студентах – любой, по Вашему выбору. 2) При реализации данной задачи для хранения информации о студентах в оперативной памяти использовать структуру данных – «Список». Пример такого представления приведен на рисунке 1.3. КВТ-051 КВТ-052 КВТ-043 узел списка узел списка узел списка Айрапетов Вершинина Гончаров Бащенко Осадший Камилов NULL Сафронов Горбунова NULL NULL NULL Рисунок 1.3 – Представление данных о студентах в виде списка 3 КОНТРОЛЬНЫЕ ВОПРОСЫ 1. Что такое динамический линейный однонаправленный список? 2. Что такое динамический линейный двунаправленный список? 3. Что такое голова линейного списка и для чего она используется? 4. Напишите команды для перехода к следующему элементу в списке. 5. Какова структура отдельного элемента двунаправленного списка? 6. Какие программные классы линейных списков доступны в среде Microsoft Visual Studio 2005? 12 Лабораторная работа №2 ЛИНЕЙНЫЕ СТРУКТУРЫ ДАННЫХ: СТЕК И ОЧЕРЕДЬ Цель работы: Освоить и закрепить приемы работы с данными различного типа, организованными в виде стека и очереди. Получить навыки реализации структур стека и очереди на базе массива, линейного списка. Ключевые понятия, которые необходимо знать: динамические структуры данных, линейный список, однонаправленный список, двунаправленный список, стек, очередь, вершина стека, начало и конец очереди, реализация одной структуры на базе другой. Время выполнения лабораторного занятия: 4 часа. Количество баллов (минимальное, максимальное): 6 – 9 баллов. 1 ТЕОРЕТИЧЕСКАЯ ЧАСТЬ 1.1 Структура данных – СТЕК Стек - такой последовательный список с переменной длиной, включение и исключение элементов из которого выполняются только с одной стороны списка, называемого вершиной стека. Применяются и другие названия стека - магазин и очередь, функционирующая по принципу LIFO (Last In – First Out – «последним пришел - первым исключается»). Примеры стека в повседневной жизни: пистолетный патронный магазин, тупиковый железнодорожный разъезд для сортировки вагонов. Вершина стека Добавить элемент в стек Извлечь элемент из стека Рисунок 2.1 – Представление стека 1.2 Программная реализация стека Программно стек можно реализовать двумя основными способами: 1) на базе массива. При этом способе количество элементов ограничено размером массива. Дно стека соответствует элементу массива с индексом 0, а вершина стека – значению массива с индексом равному <количество элементов в стеке – 1>. 13 Стек 2 5 1 Представление стека массивом Вершина стека Вершина стека 2 5 1 0 1 2 Индексы массива Рисунок 2.2 – Представление стека на базе массива 2) на базе связного списка. Количество элементов ограничено размером свободной оперативной памяти. Каждый элемент связного списка содержит два поля – информационное (содержит значение элемента) и адресное (содержит адрес следующего элемента стека). Поле адреса последнего элемента стека содержит значение NULL. Стек 2 5 1 Представление стека связным списком Вершина стека Вершина стека 2 1 "Голова" списка 5 1 Адресное поле Информационное поле Рисунок 2.3 – Представление стека на базе связного однонаправленного списка 1.3 Рекомендуемый состав методов класса «Стек» // тип элементов, хранимых в стеке – MyType void InitStack(); // инициализировать стек void PushStack(MyType Elem); // положить в стек MyType PopStack(); // прочитать с удалением MyType TopStack(); // прочитать без удаления bool IsEmptyStack(); // проверить на пустоту bool IsFullStack (); // проверить на заполнение Тип MyType описывает тип значений элементов стека. Для обеспечения возможности хранения в стеке значений выбранного типов достаточно изменить MyType на выбранный тип. Метод InitStack выполняет инициализацию стека; метод должен быть выполнен перед началом работы со стеком. Метод PushStack используется для записи в стек нового значения. 14 Метод PopStack обеспечивает получение значения, записанного в стек последним; выполнение метода выполняется с удалением полученного значения из стека. Метод TopStack также используется для получения значения, записанного в стек последним; в отличие от предыдущего метода, полученное значение из стека не удаляется. Метод IsEmptyStack проверяет стеке на пустоту, возвращает значение true в случае, когда в стеке элементов нет. Метод IsFullStack проверяет стек на полное заполнение; возвращает значение true когда в стеке нет свободного места для нового элемента. Ниже приведен проект StackOnArray программной реализации стека на базе массива на языке С#. //*** Реализация стека на базе одномерного массива *** class Stack { const int SIZE=10; //максимальное количество элементов в стеке (массиве) int[] mas=new int [SIZE]; // массив для элементов стека int count; //количество элементов хранящихся в массиве //Конструктор класса public Stack() { mas = new int[SIZE];//массив для хранения элементов стека InitStack(); } //Конструктор класса с параметром – размером стека public Stack(int size) { mas = new int[size];//массив для хранения элементов стека InitStack(); } //Инициализация стека public void InitStack() { count=0; } //обнуление количества элементов стека //Добавление элемента Elem в стек public bool PushStack(int elem) { if(count < SIZE) //если есть свободное место в массиве { mas[count]=elem; //добавление в массив нового элемента count++; //увеличить количество элементов стека return true; } return false; } //Извлечение элемента из стека 15 public int PopStack() { if (count >0) //если есть элементы в стеке { count--; //уменьшить количество элементов стека return mas[count]; //вернуть вершину стека } return -1; } //Проверка на пустоту public bool IsEmptyStack() { if(count==0) //если количество элементов = 0 return true; //то, вернуть - истина (стек пуст) else return false; //иначе, вернуть - ложь (стек не пуст) } Демонстрация работы с методами класса Stack //Главная функция консольного приложения static void Main(string[] args) { int value, code; Stack st = new Stack(); //объект-стек do { //вывод меню работы со стеком Console.WriteLine(" Работа со стеком:"); Console.WriteLine("1. Добавить элемент в стек"); Console.WriteLine("2. Удалить элемент из стека"); Console.WriteLine("3. Проверка на пустоту"); Console.WriteLine("0. Завершить работу"); Console.Write(" Ваш выбор: "); try { code = Convert.ToInt32(Console.ReadLine()); } catch { code = 9; } switch (code) //разбор вариантов действий { case 1: Console.Write("Введите значение добавляемого элемента: "); value = Convert.ToInt32(Console.ReadLine()); st.PushStack(value); break; case 2: if (!st.IsEmptyStack()) Console.WriteLine("Элемент {0} из вершины стека удален.", st.PopStack ()); else Console.WriteLine("Стек пуст."); break; case 3: if (st.IsEmptyStack()) Console.WriteLine("Стек пуст."); 16 else Console.WriteLine("Стек НЕ пуст."); break; case 0: break; default: Console.WriteLine("Неверный выбор меню"); break; } Console.WriteLine(); } while (code != 0); } 1.4 Структура данных – ОЧЕРЕДЬ Очередью FIFO (First In - First Out – «первым пришел - первым исключается») называется такой последовательный список с переменной длиной, в котором включение элементов выполняется только с одной стороны списка (называемой концом или хвостом очереди), а исключение - с другой стороны (начало или голова очереди). Очереди к прилавкам и к кассам являются типичным бытовым примером очереди FIFO. Добавить элемент в конец очереди Начало очереди Конец очереди Извлечь элемент из начала очереди Рисунок 2.4 – Представление очереди 1.5 Программная реализация очереди Программно очередь можно реализовать тремя основными способами: 1) на базе массива. При этом способе количество элементов ограничено размером массива. Начало очереди соответствует элементу массива с индексом 0, а конец очереди – значению массива с индексом равному <количество элементов в очереди – 1>. Очередь 2 Начало очереди 5 Представление очереди массивом Начало очереди 1 Конец очереди Конец очереди 2 5 1 0 1 2 Индексы массива Рисунок 2.5 – Представление очереди на базе массива 17 2) на базе циклического массива. Этот способ похож на предыдущий. Отличие в том, что, если при добавлении элемента в конец очереди достигается верхняя граница массива, то производится попытка добавить элемент в начало массива (если оно не занято). При этом массив как бы сворачивается в кольцо (считают что за последним элементом массива следует первый элемент). Аналогично происходит и с извлечением элемента из очереди. Очередь Перед добавлением элемента «3» После добавления элемента «3» Конец очереди 2 5 1 Конец очереди 2 5 1 3 3 Начало очереди Начало очереди Представление очереди циклическим массивом Начало очереди 2 0 1 Начало очереди Конец очереди 5 3 1 N Конец очереди Индексы массива 0 2 1 5 1 N Индексы массива Рисунок 2.6 – Представление очереди на базе циклического массива 3) на базе связного списка. Способ аналогичен представлению стека на базе связного списка. Начало очереди соответствует первому элементу связного списка, конец очереди – последнему элементу списка (возможно и представление наоборот: конец очереди соответствует началу списка, а начало очереди – концу списка). 18 Очередь Представление очереди связным списком Начало очереди «Голова» списка 2 1 2 Начало очереди Конец очереди 5 1 5 Конец очереди 1 Адресное поле Информационное поле Рисунок 2.7 – Представление очереди на базе связного однонаправленного списка 1.6 Рекомендуемый состав методов класса «Очередь» // тип элементов, хранимых в очереди – MyType void InitQueue(); // инициализировать очередь void PushQueue (MyType Elem); // добавить в конец очереди MyType PopQueue (); // прочитать с удалением из начала очереди MyType TopQueue (); // прочитать без удаления из начала очереди bool IsEmptyQueue (); // проверить на пустоту bool IsFullQueue (); // проверить на переполнение Тип MyType описывает тип значений элементов очереди. Для обеспечения возможности хранения в очереди значений выбранного типов достаточно изменить MyType на выбранный тип. Метод InitQueue выполняет инициализацию очереди; метод должен быть выполнен перед началом работы с очередью. Метод PushQueue используется для записи в конец очереди нового значения. Метод PopQueue обеспечивает получение значения, записанного в очередь первым (из начала очереди); выполнение операции выполняется с удалением возвращаемого значения из очереди. Метод TopQueue также используется для получения значения из начала очереди; в отличие от предыдущей функции, возвращаемое значение из очереди не удаляется. Метод IsEmptyQueue проверяет очередь на пустоту, возвращает значение true в случае, когда в очереди элементов нет. Метод IsFullQueue проверяет очередь на полное заполнение; возвращает значение true когда в очереди нет свободного места для нового элемента. Ниже приведен проект QueueOnList программной реализации очереди на базе линейного списка на языке С#. 19 //Тип элементов хранимых в очереди class MyClass { public string str; //строковое поле public int value; //целочисленное поле } //*** Реализация очереди на базе линейного списка // (начало списка = начало очереди) *** class QueueOnList { List<MyClass> list; //линейный список элементов типа MyClass public int Count //свойство - количество элементов в очереди (только чтение) { get { return list.Count; } } //Конструктор класса-очередь public QueueOnList() { list= new List<MyClass>(); //список для хранения элементов очереди } //Добавление элемента Elem в очередь public bool PushQueue(MyClass Elem) { list.Add(Elem); //вставить элемент в конец списка return true; } //Извлечение элемента из стека public MyClass PopQueue() { MyClass temp=new MyClass(); if (list.Count>0) //если есть элементы в очереди { temp=list[0]; //сохранить элемент в начале списка (начале очереди) list.RemoveAt(0); // удалить начало очереди } return temp; //вернуть элемент из начала очереди } 1.7 Реализация стека и очереди в различных библиотеках В связи с тем, что стек и очередь довольно широко применяются программистами, большинство программных библиотек содержат их реализацию. В Microsoft Visual Studio 2005 доступны следующие классы, реализующие стек и очередь: 20 - классы stack и queue в библиотеке STL (стандартной библиотеке шаблонов); - классы Stack и Queue в пространстве имен System.Collections библиотеки .NET - классы-шаблоны Stack<T> и Queue<T> в пространстве имен System.Collections.Generic библиотеки .NET В таблице 2.1 приведены основные свойства и методы классашаблона Stack<T>, а в таблице 2.2 - основные свойства и методы классашаблона Queue<T>. Таблица 2.1 Основные свойства и методы класса-шаблона Stack<T> Имя свойства/метода Описание свойства/метода Count Фактическое количество элементов, находящихся в стеке Clear() Удаляет все элементы из стека Contains() Проверяет наличие элемента с заданным значением в стеке Push() Вставляет новый элемент в вершину стека Pop() Удаляет элемент из вершины стека и возвращает его значение Возвращает значение элемента из вершины стека, без его удалеPeek() ния Таблица 2.2 Основные свойства и методы класса-шаблона Queue<T> Имя свойства/метода Описание свойства/метода Count Фактическое количество элементов, находящихся в очереди Clear() Удаляет все элементы из очереди Contains() Проверяет наличие элемента с заданным значением в очереди Enqueue() Вставляет новый элемент в конец очереди Dequeue() Удаляет элемент из начала очереди и возвращает его значение Возвращает значение элемента из начала очереди, без его удалеPeek() ния Ниже приведен проект DemoStackAndQueue на языке С#, использующий классы-шаблоны Stack<T> и Queue <T> из библиотеки .NET. //Подключение пространств имен using System; using System.Collections.Generic; //пространство для использования классов Stack и Queue namespace DemoStackAndQueue { class Program { //Работа со стеком целых чисел static void DemoStack() { int value, code; Stack<int> st = new Stack<int>(); //стек целых чисел do { //вывод меню работы со стеком Console.WriteLine(" Работа со стеком целых чисел:"); 21 Console.WriteLine("1. Добавить элемент в стек"); Console.WriteLine("2. Удалить элемент из стека"); Console.WriteLine("3. Прочитать элемент из вершины стека (без удаления)"); Console.WriteLine("4. Очистить весь стек"); Console.WriteLine("5. Проверка на пустоту"); Console.WriteLine("0. Завершить работу"); Console.WriteLine("\tВ стеке находится {0} элементов", st.Count); Console.Write(" Ваш выбор: "); try { code = Convert.ToInt32(Console.ReadLine()); } catch { code = 9; } switch (code) //разбор вариантов действий { case 1: Console.Write("Введите значение добавляемого элемента: "); value = Convert.ToInt32(Console.ReadLine()); st.Push(value); break; case 2: if (st.Count > 0) Console.WriteLine("Элемент {0} из вершины стека удален", st.Pop()); else Console.WriteLine("Стек пуст"); break; case 3: if (st.Count > 0) Console.WriteLine("Элемент в вершине стека равен = {0}", st.Peek()); else Console.WriteLine("Стек пуст"); break; case 4: st.Clear(); Console.WriteLine("Стек очищен"); break; case 5: if (st.Count == 0) Console.WriteLine("Стек пуст"); else Console.WriteLine("Стек НЕ пуст"); break; case 0: break; default: Console.WriteLine("Неверный выбор меню"); break; } Console.WriteLine(); } while (code != 0); } //Работа с очередью строк static void DemoQueue() { 22 int code; string value; Queue<string> q = new Queue<string>(); // очередь строк do { //вывод меню работы с очередью Console.WriteLine(" Работа с очередью строк:"); Console.WriteLine("1. Добавить элемент в конец очереди"); Console.WriteLine("2. Удалить элемент из начала очереди"); Console.WriteLine("3. Прочитать элемент из начала очереди(без удаления)"); Console.WriteLine("4. Очистить всю очередь"); Console.WriteLine("5. Проверка на пустоту"); Console.WriteLine("0. Завершить работу"); Console.WriteLine("\tВ очереди находится {0} элементов", q.Count); Console.Write(" Ваш выбор: "); try { code = Convert.ToInt32(Console.ReadLine()); } catch { code = 9; } switch (code) //разбор вариантов действий { case 1: Console.Write("Введите значение добавляемого элемента: "); value = Console.ReadLine(); q.Enqueue (value); break; case 2: if (q.Count > 0) Console.WriteLine("Элемент {0} из начала очереди удален", q.Dequeue()); else Console.WriteLine("Очередь пуста"); break; case 3: if (q.Count > 0) Console.WriteLine("Элемент в начале очереди равен = {0}", q.Peek()); else Console.WriteLine("Очередь пуста"); break; case 4: q.Clear(); Console.WriteLine("Очередь очищена"); break; case 5: if (q.Count == 0) Console.WriteLine("Очередь пуста"); else Console.WriteLine("Очередь НЕ пуста"); break; case 0: break; default: Console.WriteLine("Неверный выбор меню"); break; 23 } Console.WriteLine(); } while (code != 0); } //Главная функция консольного приложения static void Main(string[] args) { int code; //вывод меню Console.WriteLine("1. Работа со стеком"); Console.WriteLine("2. Работа с очередью"); Console.Write(" Ваш выбор: "); try { code = Convert.ToInt32(Console.ReadLine()); } catch { code = 9; } switch(code) { case 1: DemoStack(); break; case 2: DemoQueue(); break; } } } } 2 ЗАДАНИЕ НА ЛАБОРАТОРНУЮ РАБОТУ Задание 1 (5 баллов). Разобраться с примерами использования стека и очереди приведенными в проектах: StackOnArray (пример реализации стека на базе массива), QueueOnList (пример реализации очереди на базе линейного списка) и DemoStackAndQueue (использование классов Stack и Queue из библиотеки .NET). Задание 2 (+2 балла). Разработать программы на языке С#, реализующие структуры данных – «стек» и «очередь», в соответствии с вариантом (см. таблицу 2.3) и составом методов стека и очереди приведенных в п.п. 1.3, 1.6. Номер варианта совпадает с номером варианта по семестровому заданию. № 1. 2. 3. 4. 5. 6. 7. Таблица 2.3 – Варианты заданий Представление стека Представление очереди на базе массива на базе циклического массива на базе связного списка на базе циклического массива на базе массива на базе связного списка (начало очереди = начало списка) на базе связного списка на базе связного списка (начало очереди = начало списка) на базе массива на базе связного списка (начало очереди = конец списка) на базе связного списка на базе связного списка (начало очереди = конец списка) на базе массива на базе циклического массива 24 8. 9. 10. 11. 12. 13. 14. 15. на базе связного списка на базе массива на базе связного списка на базе массива на базе связного списка на базе массива на базе связного списка на базе массива на базе циклического массива на базе связного списка (начало очереди = начало списка) на базе связного списка (начало очереди = начало списка) на базе связного списка (начало очереди = конец списка) на базе связного списка (начало очереди = конец списка) на базе связного списка (начало очереди = начало списка) на базе связного списка (начало очереди = начало списка) на базе связного списка (начало очереди = конец списка) Задание 3 (+2 балл). Используя разработанный, при выполнении задания 1, класс «стек» разработать программу для решения следующей задачи: Требуется составить программу синтаксического анализа соответствия открывающих и закрывающих круглых скобок в арифметическом выражении алгоритмического языка. Программа должна напечатать таблицу соответствия скобок, причем в таблице должно быть указано, для каких скобок отсутствуют парные им. Для идентификации скобок могут быть использованы их порядковые номера в выражении. Например, для арифметического выражения ( a + b1 ) / 2 + 6.5 ) * ( 4.8 + sin ( x ) 1 2 3 4 5 6 должна быть напечатана таблица вида СКОБКИ открывающая закрывающая 1 2 3 5 6 4 Прочерки в таблице обозначают отсутствие соответствующей скобки. При полном отсутствии скобок в выражении программа должна выдать соответствующее сообщение. Идея алгоритма решения задачи Выражение просматривается посимвольно слева направо. Все символы, кроме скобок, игнорируются (т.е. просто производится переход к просмотру следующего символа). Если очередной символ - открывающая скобка, то ее порядковый номер помещается в стек. Если очередной символ - закрывающая скобка, то производится выталкивание из стека номера открывающей скобки и печать этого номера в паре с номером закрывающей скобки. Если в этой ситуации стек оказывается пустым, то вместо номера открывающей скобки печатается прочерк. 25 Если после просмотра всего выражения стек оказывается не пустым, то выталкиваются все оставшиеся номера открывающих скобок и печатаются в паре с прочерком на месте номера закрывающей скобки. Задание 4 (+2 балл). Используя класс «стек» из библиотеки .NET разработать программу для решения следующей задачи: Реализовать перевод арифметического выражения в символьном виде, в форму польской инверсной записи. По полученной польской инверсной записи произвести численное вычисление значения арифметического выражения. Арифметическое выражение в обычном символьном виде должно вводится с клавиатуры. Считать что элементы (операнды) арифметического выражения являются числами. Алгоритм ПОЛИЗ Существует достаточно много алгоритмов разбора математического выражения (формулы) хранящегося в виде символьной строки. Одним из них является алгоритм ПОЛИЗ (ПОЛьская Инверсная Запись), который разбирает исходное математическое выражение и преобразует его к постфиксному виду. Пример. Математическое выражение: « (a + b / c) * (d – e / f) » Это же выражение в постфиксном виде: « a b c / + d e f / – * » В формуле, в привычной для нас форме, знак операции записывается между операндами, к которым она применяется. В формуле, в формате ПОЛИЗ, знак операции записывается после обоих операндов, к которым она применяется. Запись формулы в формате ПОЛИЗ предпочтительнее при реализации на ЭВМ, так как: - вычисление по таким формулам производится строго слева на право (без учета приоритетов операций) - в ней уже нет скобок - возможно применение стека для хранения операндов и операций Вычисление по формуле в формате ПОЛИЗ производится по следующему правилу: - начиная просматривать формулу с левого конца, ищется первый знак операции; - найденная операция применяется к операндам (двум), которые находятся непосредственно перед знаком этой операции; - операция и участвующие в ней операнды исключаются из формулы, а на их место записывается результат выполнения исключенной операции; - и так продолжается до тех пор, пока в формуле не останется ни одной операции (останется лишь ответ) Далее на языке исполнителя приведена программа для преобразования математической формулы в обычном виде, к формуле в формате ПОЛИЗ. В формуле допускаются применение: - знаков операций « + », « – », « * », « / » - скобок « ( », « ) » - букв в качестве переменных ПРОГРАММА Полиз 26 ДАНО:| в строке S находится математическая формула в обычном виде ПОЛУЧИТЬ:| в строке S1 эта же математическая формула в польской инверсной записи ИСПОЛЬЗУЕМЫЕ ПЕРЕМЕННЫЕ . ST – СТЕК ЭЛЕМЕНТОВ ТИПА СИМВОЛ .С – СИМВОЛ .I, LEN, K –ЧИСЛО ЦЕЛОГО ТИПА .ST.Начать работу .LEN = Длина_Строки(S) .K = 0 . ST. Добавить элемент в вершину стека <вх: ‘(’> . ЦИКЛ ДЛЯ I = 0 ДО LEN -1 ВЫПОЛНЯТЬ . . ЕСЛИ S[I] = буква ТО . . . S1[K] = S[I] . . .K=K+1 . . . ИНАЧЕ . . . . ЕСЛИ S[I] = ‘(’ ТО . . . . . ST.Добавить элемент в вершину стека <вх: ‘(’> . . . . . ИНАЧЕ . . . . . . ЕСЛИ S[I] = ‘)’ ТО . . . . . . . С = ST.Взять элемент из вершины стека . . . . . . . ЦИКЛ ПОКА НЕ С = ‘(’ ВЫПОЛНЯТЬ . . . . . . . . S1[K] = С . . . . . . . .K=K+1 . . . . . . . . С = ST.Взять элемент из вершины стека . . . . . . . КОНЕЦ ЦИКЛА . . . . . . . ИНАЧЕ . . . . . . . . МНОЖЕСТВЕННЫЙ ВЫБОР ПО S[I] . . . . . . . . . СЛУЧАЙ ‘+’, ‘–‘ . . . . . . . . . . С = ST.Прочитать элемент из вершины стека . . . . . . . . . . ЦИКЛ ПОКА НЕ С = ‘(’ ВЫПОЛНЯТЬ . . . . . . . . . . . S1[K] = ST.Взять элемент из стека . . . . . . . . . . .K=K+1 . . . . . . . . . . . С = ST.Прочитать элемент из вершины стека . . . . . . . . . . КОНЕЦ ЦИКЛА . . . . . . . . . . ST.Добавить элемент в вершину стека <вх: S[I]> . . . . . . . . . СЛУЧАЙ ‘*’, ‘/‘ . . . . . . . . . . С = ST.Прочитать элемент из вершины стека . . . . . . . . . . ЦИКЛ ПОКА НЕ С = ‘(’ ВЫПОЛНЯТЬ . . . . . . . . . . . ЕСЛИ НЕ ( С = ‘*’ ИЛИ С = ‘/’) ТО . . . . . . . . . . . . ПРЕРВАТЬ ЦИКЛ . . . . . . . . . . . КОНЕЦ ЕСЛИ . . . . . . . . . . . S1[K] = ST.Взять элемент из стека . . . . . . . . . . .K=K+1 . . . . . . . . . . . С = ST.Прочитать элемент из вершины стека . . . . . . . . . . КОНЕЦ ЦИКЛА 27 . . . . . . . . . . ST.Добавить элемент в вершину стека <вх: S[I]> . . . . . . . . . ДРУГИЕ СЛУЧАИ . . . . . . . . . . <Вывести сообщение “Ошибочный символ S[I]”> . . . . . . . . . . ЗАВЕРШИТЬ ПРОГРАММУ . . . . . . . . КОНЕЦ МНОЖЕСТВЕННОГО ВЫБОРА . . . . . . КОНЕЦ ЕСЛИ . . . . КОНЕЦ ЕСЛИ . . КОНЕЦ ЕСЛИ . КОНЕЦ ЦИКЛА . С = ST.Взять элемент из вершины стека . ЦИКЛ ПОКА НЕ С = ‘(’ ВЫПОЛНЯТЬ . . S1[K] = С . .K=K+1 . . С = ST.Взять элемент из вершины стека . КОНЕЦ ЦИКЛА КОНЕЦ ПРОГРАММЫ 3 КОНТРОЛЬНЫЕ ВОПРОСЫ 1. 2. 3. 4. 5. 6. 7. 8. Что такое стек? Что такое очередь? Отличия стека и очереди. Как представляется стек на базе массива. Как представляется стек на базе связного списка. Как представляется очередь на базе массива. Как представляется очередь на базе циклического массива. Как представляется стек на базе связного списка. 28 Литература 1. Седжвик, Р. Фундаментальные алгоритмы на С++. Анализ. Структуры данных. Сортировка. Поиск: Пер. с англ. / Р. Седжвик. – К.: Издательство «ДиаСофт», 2001. – 688 с. 2. Красиков, И. В. Алгоритмы. Просто как дважды два / И. В. Красиков, И. Е. Красикова. – М.: Эксмо, 2006. – 256 с. 3. Костин, А.Е. Организация и обработка структур данных в вычислительных системах: учеб. пособ. для вузов / А. Е. Костин, В. Ф. Шаньгин. – М.:Высш. шк., 1987.– 248 с. 4. Кнут, Д. Искусство программирования. В 3 т. Т. 1. Основные алгоритмы /Д. Кнут; 3-е изд.; пер. с англ. – М.: Издательский дом «Вильямс», 2000. – 687 с. 5. Структуры и организация данных [Электронный ресурс]: национальный технический университет Украины, кафедра информатики и интеллектуальной собственности. – 2000. – Режим доступа: http://khpi-iip.mipk.kharkiv.edu/library/datastr. 6. Портал «Информационно-коммуникационные технологии в образовании»: раздел «Структуры и алгоритмы обработки данных» [Электронный ресурс]. – 2010. –http:// www.ict.edu.ru/lib/index.php?a=elib&c=getForm&r=resNode&d=mod&id_node=220. 29 СОДЕРЖАНИЕ ВВЕДЕНИЕ ...........................................................................................3 Лабораторная работа №1. ЛИНЕЙНЫЕ СТРУКТУРЫ ДАННЫХ: СПИСКИ ............................................................................................3 Лабораторная работа №2. ЛИНЕЙНЫЕ СТРУКТУРЫ ДАННЫХ: СТЕК И ОЧЕРЕДЬ ..........................................................................13 Литература........................................................................................... 29 30 Составитель: Александр Эдуардович Панфилов ЛИНЕЙНЫЕ СТРУКТУРЫ ДАННЫХ: СПИСКИ, СТЕК, ОЧЕРЕДЬ Методические указания к лабораторным работам по дисциплине «Структуры и алгоритмы обработки данных» Под редакцией автора Темплан 2011 г., поз. № 21К. Подписано в печать 27. 12. 2010 г. Формат 60×84 1/16. Бумага листовая. Печать офсетная. Усл. печ. л. 1,86. Уч.-изд. л. 2,17. Тираж 50 экз. Заказ № Волгоградский государственный технический университет 400131, г. Волгоград, пр. Ленина, 28, корп. 1. Отпечатано в КТИ 403874, г. Камышин, ул. Ленина, 5, каб. 4.5 31