Продолжение темы 4. Понятие классов и методов (функции) языка C#. Организация данных в виде строк. Лекция 21 СТЕКИ 21.1 Понятие списочных структур Работа с любой информационной, справочной системой требует обращение к некоторым массивам данных. Обычно данные хранятся в виде файлов на магнитных дисках. Однако если к данным файла необходимо непрерывно обращаться и выполнять в них различные поисковые операции, то желательно размещать эти файлы данных в памяти компьютера, так как время поиска данных в файле на магнитном диске в тысячи раз больше времени поиска данных в файле памяти компьютера. Возникает задача по размещению в памяти компьютера больших файлов справочной информации. Существует множество алгоритмов размещения в памяти компьютера данных из файла на магнитном диске. Например, файлы данных можно представлять в виде различных таблиц, матриц. Табличная форма представления информации предполагает использование традиционных алгоритмов обработки данных в массивах. Одним из вариантов размещения данных в памяти компьютера являются различные списочные структуры. Организация списка записей в памяти компьютера предполагает, что в структуру каждого элемента списка (записи) включается дополнительное поле – указатель на следующий элемент списка (обычно это поле называют next), которое позволяет «связывать» элементы списка в единое целое. Такая структура обязательно должна иметь точку входа в список – заголовок или начало списка и признак окончания списка (значение поля next последнего элемента списка равно NULL) или, что переменная, определяющая количество элементов списка равна нулю. … zagolovok Зп 1 Зп 2 Зп 3 Зп N next next next next NULL Рисунок 21.1 – Структура простого списка Минимальный набор действий (алгоритмов), которые обычно выполняются со списочными структурами, включает следующий перечень: – создание списка из файла записей; – просмотр списка; – добавление нового элемента в список (в начало, середину и конец списка); – удаление элемента из списка (из начала, середины и конца списка); – запись списка в файл; – объединение нескольких списков в один; – деление одного списка на два или несколько независимых списков; – сортировка элементов списка и т.д. По способу добавления и удаления элемента из простого списка все списочные структуры можно разделить на четыре группы: – стеки; – очереди; – деки; – простые списки. Стек – простой список, в котором добавление и удаление элемента осуществляется в одном конце списка. Часто принцип работы стека поясняют фразой «Вошедший первым выходит последним, а вошедший последним выходит первым». Очередь – простой список, в котором добавление элемента осуществляется в одном конце списка (конец очереди), а удаление или обслуживание элемента в другом конце списка (начало очереди). Дек – простой список, в котором добавление и удаление элемента может осуществляться в любом конце списка. В простом списке добавление и удаление элемента может осуществляться в любом месте списка, в том числе и в середину списка. 21.2 Основные характеристики структурой типа стек Структура типа стек широко используется в различных вычислительных процессах. Процессор компьютера использует стек при обработке прерываний. Многие алгоритмы используют стеки для реализации «возвращения» алгоритма к предыдущему состоянию, например, при прохождении лабиринта или «обходе» дерева или графа. Алгоритмы решения многих транспортных задач используют стек для нахождения оптимальных маршрутов и т.д. Естественно, в дисциплине «Алгоритмизация и основы программирования», мы не можем обойти стороной такую нужную для алгоритмизации структуру как стек, тем более что в языке C# ей выделен специальный класс (Stack) с набором методов. Основными алгоритмы при работе со стеком являются алгоритмы добавления элемента в стек, исключения элемента из стека и просмотра содержимого стека. Доступ в структуру типа стек возможен только в одном его конце, который обычно называют вершиной стека. Класс стек представляет собой динамическую коллекцию – объединение однотипных данных. При расширении стека (во время добавления элемента в «заполненный» стек) емкость стека динамически удваивается. В классе Stack определены следующие три конструктор: Public Stack(); Public Stack(int capacity); Public Stack(ICollection n); Первый конструктор создает «пустой» стек на 10 элементов. Второй конструктор создает «пустой» стек с начальной емкостью на capacity элементов. Третий конструктор создает стек на n элементов, который инициируется элементами ICollection. 21.3 Организация работ со структурой типа стек Основные методы класса Stack представлены в таблице 21.1. Таблица 21.1 Основные методы класса Stack. Меод Описание public virtual bool Возвращает значение true, если объект v содержится в Contains(object v) стеке, иначе false public virtual void Очищает стек (свойство Count – число элементов стека устанавливается в нуль). Clear() Возвращает элемент вершины стека, но не удаляет его public virtual object Peek() Возвращает элемент вершины стека и удаляет его public virtual object Pop() public virtual void Добавляет элемент v в стек – в вершину стека Push(object v) Для просмотра содержимого стека часто используется операция in – определение принадлежности, например: Console.Write(" Содержимое стека = "); foreach (int i in vst) Console.Write(i + " "); Console.WriteLine(); Приведенные строки кода позволяют просмотреть стек состоящий из целых чисел и вершиной vst. В качестве примера использования алгоритмов работы со стеком рассмотрим решение следующей задачи: Задача 21.1 Для стека на 5 целых чисел случайным образом формируется вероятность включения и исключения числа. При этом вероятность включения числа в стек равна 70%, а исключения 30%. Числа так же формируются случайным образом и находятся в диапазоне минус 50 до 50. При заполнении стека и попытке добавить новое число в стек происходит «переключение» вероятностей включения и исключения на противоположные значения. Окончание работы программы определяется попыткой исключить число из «пустого» стека. Исходный код программы имеет следующий вид: using System; using System.Collections; namespace ConsoleApplication1 { class Program { static void vkl(Stack vst, int n) { vst.Push(n); Console.Write("В стек помещен элемент {0}", n); Console.Write(" Содержимое стека = "); foreach (int i in vst) Console.Write(i + " "); Console.WriteLine(); } static void iskl(Stack vst) { if (vst == null) Console.WriteLine("Стек пуст !"); else { int n = (int) vst.Pop(); Console.Write("Из стека удален элемент {0}", n); Console.Write(" Содержимое стека = "); foreach (int i in vst) Console.Write(i + " "); Console.WriteLine(); }} static void Main() { Stack vstek = new Stack(); int i, k, n; Random rnd = new Random(); i=0; while (i < 5) { k = rnd.Next() % 101; if (k <= 70) { i++; n = rnd.Next() % 101 - 50; vkl(vstek, n); } else if (i > 0) { i--; iskl(vstek); } } Console.WriteLine(" Стек переполнен !"); while (i > 0) { k = rnd.Next() % 101; if (k <= 30) { i++; n = rnd.Next() % 101 - 50; vkl(vstek, n); } else if (i > 0) { i--; iskl(vstek); } } Console.WriteLine(" Стек пуст !"); Console.WriteLine("Для продолжения нажмите клавишу Enter"); Console.ReadLine(); } } } Работа программы: В стек помещен элемент 24 Содержимое стека = 24 Из стека удален элемент 24 Содержимое стека = В стек помещен элемент -31 Содержимое стека = -31 Из стека удален элемент -31 Содержимое стека = В стек помещен элемент -35 Содержимое стека = -35 В стек помещен элемент -1 Содержимое стека = -1 -35 В стек помещен элемент -1 Содержимое стека = -1 -1 -35 Из стека удален элемент -1 Содержимое стека = -1 -35 В стек помещен элемент 9 Содержимое стека = 9 -1 -35 В стек помещен элемент 11 Содержимое стека = 11 9 -1 -35 В стек помещен элемент 29 Содержимое стека = 29 11 9 -1 -35 Стек переполнен ! Из стека удален элемент 29 Содержимое стека = 11 9 -1 -35 Из стека удален элемент 11 Содержимое стека = 9 -1 -35 Из стека удален элемент 9 Содержимое стека = -1 -35 Из стека удален элемент -1 Содержимое стека = -35 В стек помещен элемент -41 Содержимое стека = -41 -35 В стек помещен элемент 7 Содержимое стека = 7 -41 -35 Из стека удален элемент 7 Содержимое стека = -41 -35 В стек помещен элемент -30 Содержимое стека = -30 -41 -35 В стек помещен элемент -28 Содержимое стека = -28 -30 -41 -35 Из стека удален элемент -28 Содержимое стека = -30 -41 -35 Из стека удален элемент -30 Содержимое стека = -41 -35 Из стека удален элемент -41 Содержимое стека = -35 Из стека удален элемент -35 Содержимое стека = Стек пуст ! Для продолжения нажмите клавишу Enter В программу включены комментарии каждой операции включения и исключения элемента из стека. 21.4 Вопросы для проверки 21.4.1 Как называется простой список, в котором все включения и исключения элементов выполняются в одном конце списка? 21.4.2 Как называется начало стека? 21.4.3 Что означает запись Public Stack(ICollection n);? 21.4.4 Что делает следующий фрагмент программы int n = (int) vst.Pop();, где vst – вершина стека? 21.4.5 Что делает следующий фрагмент программы: i = 0; while (i < 5) { i++; n = rnd.Next() % 101 - 50; vstek.Push(n); }, где vstek – вершина стека? Продолжение темы 4. Понятие классов и методов (функции) языка C#. Организация данных в виде строк. Лекция 22 ОЧЕРЕДИ, СПИСКИ 22.1 Организация работ со структурой типа очередь Структуры типа очередь широко используется в алгоритмах «поиска с возвращением». Алгоритмы обход всех вершин графов, деревьев используют либо стеки – при реализации алгоритма «обхода в глубину», либо очереди – при реализации алгоритма «обхода в ширину». Многие задачи теории массового обслуживания используют очереди для нахождения оптимальных вариантов решения и т.д. Структуры типа очередь может иметь два заголовка – начала очереди, с помощью которого осуществляется обслуживание элемента очереди (удаление элемента) и конца очереди, который используется для постановки новых элементов в очередь на обслуживание (включение элемента). Так же как и для структуры типа стек в языке C# ей выделен специальный класс (Queue) для работы с очередями. Этот класс снабжен данными и набором методов. В классе Queue определены следующие три конструктор: Public Queue (); Public Queue (int capacity); Public Queue (ICollection c); Первый конструктор создает «пустой» стек на 10 элементов. Второй конструктор создает «пустой» стек с начальной емкостью на capacity элементов. Третий конструктор создает стек на n элементов, который инициируется элементами ICollection. Основные методы класса Queue представлены в таблице 22.1. Таблица 22.1 Основные методы класса Queue Метод Описание public virtual bool Возвращает значение true, если объект v содержится в Contains(object v) очереди, иначе false public virtual void Очищает очередь (свойство Count – число элементов очереди устанавливается в нуль). Clear() public virtual Возвращает объект из начала очереди, но не удаляет его object Peek() public virtual Возвращает объект из начала очереди и удаляет его object Dequeue() public virtual void Добавляет объект v в конец очереди Enqueue(object v) В качестве примера, поясняющего работу методов очереди, рассмотрим решение следующей задачи. Задача 22.1 Магазин за 1 день посещает 250 человек. Обслуживание покупателей предполагает продажу им : хлеба – вероятность 25%; сыра – вероятность 20%; печенья – вероятность 20%; пиво – вероятность 10% и мороженного – вероятность 25%. Считаем, что каждый покупатель приобретает только один продукт. Все события равновероятны на диапазоне чисел от 0 – 100. Организовать очередь покупателей, а при ее обслуживании определить и напечатать: количество проданных продуктов каждого вида, и какой продукт покупается чаще. Из условия задачи известно, что все события равновероятны, следовательно, если формировать случайные числа от 0 до 100, то вероятность свершения события можно определять попаданием числа в заданный диапазон. Например, вероятность покупки хлеба можно определить диапазоном от 1 до 25, сыра – от 26 до 45, печенья – от 46 до 65, пива – от 66 до 75 и мороженного – от 76 до 100. Каждый вид продукта можно представить целым числом от 0 до 4. Таким образом, можно очередь покупателей заменить очередью покупаемых ими продуктов, которые представлены целыми числами. С учетом предложенных допущений программный код имеет следующий вид: using using using using System; System.Collections; System.Linq; System.Text; namespace ConsoleApplication1 { class Program { public static int n; static void vkl(Queue ocer, int n) { ocer.Enqueue(n); } static void iskl(Queue ocer) { n = (int)ocer.Dequeue(); } public static void Main() { Queue zagol = new Queue(); int[] masi = new int[5]; char[] masc = new char[5] {'Х', 'С','П','Н','М'}; string[] mass = new string[5] {"Хлеб","Сыр","Печенье","Напиток пиво","Мороженное"}; int i, j, p, k; Random rnd = new Random(); for (i = 0; i < 4; i++) masi[i] = 0; for (i = 0; i < 250; i++) { p = rnd.Next() % 100 + 1; if (p <= 25) vkl(zagol, 0); if if if if (25 < p && p <= 45) vkl(zagol, 1); (45 < p && p <= 65) vkl(zagol, 2); (65 < p && p <= 75) vkl(zagol, 3); (p > 75) vkl(zagol, 4); }; for (i = 0; i < 250; i++) { iskl(zagol); masi[n]++; Console.Write(masc[n]); if ((i+1)%25==0) Console.WriteLine(); }; k = 0; j = 0; for (i = 0; i < 5; i++) { Console.WriteLine(" {0} = {1}", mass[i], masi[i]); if (k < masi[i]) { k = masi[i]; j = i; } } Console.WriteLine("Больше всего продано {0} = {1}", mass[j], masi[j]); Console.WriteLine("Для продолжения нажмите клавишу Enter"); Console.ReadLine(); } } } Работа программы: МНХСМХСХПССНХМХМССХСММХММ ННСПМХМХМПССНПМСМСПСМХМХП ХССМСППСПММПХПММХХМНППНСП СМХХСНМХХММХНПСМХХМСММНММ ХМПММПНММСНМПНПХССХПНСНММ ПМСПНСХССХМНПХХПСМПМСМПМС СХММХМПМХСХММПМСНСХПМХХММ ПХПХПХППХХМНСПМММПМПММНХС НХМХМНСПМНСХХХПХННСПННХСС ХХХСПХСМХСПППМХСНПММНХХМП Хлеб = 57 Сыр = 48 Печенье = 46 Напиток пиво = 29 Мороженное = 70 Больше всего продано Мороженное = 70 Для продолжения нажмите клавишу Enter 22.2 Организация работ со структурой типа список Списочные структуры широко используются в организации данных на компьютерах, например, для устранения коллизий при хешировании данных. Существует несколько различных структур типа список, для которых в языке C# разработаны специальные классы: List<T>, ArrayList, LinkedList<T> и т.д. Наиболее часто используются классы List<T> и ArrayList, представляющие собой динамические массивы объектов. Обозначение <T> называется обобщенным типом и позволяет использовать в качестве элемента списка практически любые типы. Основные свойства класса ArrayList представлены в таблице 22.2 Таблица 22.2 Основные свойства класса ArrayList Свойство класса Описание public virtuai int Capacity Это свойство, предназначенное только для {get;} чтения, хранит текущую емкость коллекции public virtal int Count (get;}; Это свойство, предназначенное только для чтения, хранит текущую длину коллекции public virtual Object this[int Это свойство позволяет обратиться к Indx]{set; get;} элементам по индексу Основные методы класса ArrayList представлены в таблице 22.3. Таблица 22.3 Основные методы класса ArrayList Метод класса Описание public static ArrayList (IList: На основе коллекции List создает объект List); ArrayList public virtual int Add(Oblect Добавляет в конец списка новый объект и Value); возвращает его индекс public virtual viod Добавляет в конец списка несколько AddRange (ICollection coll)l объектов public virtual int Отыскивает объект Value в отсортированном BinsrySearch(Qbject Value); списке и возвращает его индекс или отрицательно число, если объект не найден public virtual void Clean(); Удаляет все элементы коллекции public virtual bool Containse Возвращает true, если коллекция содержит (Object Value); элемент Value public static ArrayList Метод возвращает объект, элементы FixedSize(ArrayList AL); которого можно изменять, но нельзя добавлять или удалять public virtual IEnumerator Возвращает итератор для объекта GetEnumerator(); public virtual ArrayList Возвращает диапазон элементов GetRange(int Indx, int Count); public virtual int IndexOf(Object Value); public virtual void Insert (int Indx, Object Value); public virtual void InsertRange(int Indx, IColliction col); public virtual bool IsFixedSize{get;} public virtual bool IsReadOnly{get;} Возвращает индекс элемента со значением Value Вставляет элемент Value на нужное место Indx коллекции Вставляет диапазон элементов Возвращает true, если объект имеет фиксированный размер Возвращает true, если объект имеет элементы, предназначенные только для чтения Возвращает индекс последнего вхождения в коллекцию значения Valiue Устанавливает для элементов коллекции режим только чтение Удаляет из коллекции первое вхождение элемента со значением Value Удаляет из коллекции элемент с индексом Indx Удаляет из коллекции count элементов, начиная с элемента с индексом Indх public virtual int LastIndexOf(Object Value); public static ArrayList ReadOnly(ArrayList AL); public virtual void Remove (Object Value); public virtual void RemoveAt (int Indx); public virtual void RemoveRange(int Indx, int count); public static ArrayList Возвращает коллекцию, в которой элемент Repeat(Object Value, int Value повторен count раз count); public virtual void Reverse(); Изменяет порядок следования элементов на обратный public virtual void SetRange Вставляет коллекцию col, начиная с индекса (int Indx, ICollection col); Indx public virtual void Sort(); Сортирует коллекцию public virtual void Устанавливает емкость коллекции равной TrimToSize(); количеству ее элементов По умолчанию начальная емкость коллекции ArrayList составляет 16 элементов. При расширении коллекции ее емкость удваивается, составляя 32, 64, 128 и т. д. элементов. Метод TrimToSize() отсекает неиспользуемые элементы. Рассмотрим учебный пример организации записей типа «Avto» в списке «Garaj». using using using using System; System.Collections; System.Linq; System.Text; namespace ConsoleApplication1 { struct Avto { public String Marka; public String Vodilo; public int Ctoim; public int God; } // Марка автомобиля //ФИО водителя // Стоимость автомобиля // Год выпуска class Program { public static int kol = 0; public static Avto avto = new Avto(); static ArrayList Garaj = new ArrayList(); public static void addcpi() { int b; int kol; string buf; Console.WriteLine("Количество авто в гараже"); buf = Console.ReadLine(); kol = Convert.ToInt32(buf); for (int i = 0; i < kol; i++) { Console.WriteLine("Введите марку {0} авто", i + 1); buf = Console.ReadLine(); avto.Marka = buf; Console.WriteLine("Введите ФИО водителя {0} авто",i+1); buf = Console.ReadLine(); avto.Vodilo = buf; Console.WriteLine("Введите стоимость {0} авто",i+1); buf = Console.ReadLine(); b = Convert.ToInt32(buf); avto.Ctoim = b; Console.WriteLine("Введите год выпуска {0} авто", i + 1); buf = Console.ReadLine(); b = Convert.ToInt32(buf); avto.God = b; Garaj.Add(avto); } } public static void printcpi() { Console.WriteLine("{0,20}, {1, 20}, {2, 15}, {3, 15}", "Марка авто", "ФИО водителя", "Стоим.авто", "Год вып. авто"); foreach (Avto T in Garaj) Console.WriteLine("{0,20}, {1, 20}, {2, 15}, {3, 15}", T.Marka, T.Vodilo, T.Ctoim, T.God); } public static void ydalelemcpi() { int n; string buf; Console.WriteLine("Введите номер удаляемой записи ?"); buf = Console.ReadLine(); n = Convert.ToInt32(buf); // Удаляем элемент по номеру Garaj.RemoveAt(n); } static void Main() { // Создаем и наполняем список addcpi(); // Печатаем список printcpi(); // Удаляем элемент списка по номеру ydalelemcpi(); // Печатаем список printcpi(); Console.ReadLine(); } } } Работа программы: Количество авто в гараже 3 Введите марку 1 авто Лада Введите ФИО водителя 1 авто Петров Введите стоимость 1 авто 2000 Введите год выпуска 1 авто 2007 Введите марку 2 авто Фиат Введите ФИО водителя 2 авто Иванов Введите стоимость 2 авто 3000 Введите год выпуска 2 авто 2008 Введите марку 3 авто БМВ Введите ФИО водителя 3 авто Сидоров Введите стоимость 3 авто 4000 Введите год выпуска 3 авто 2006 Марка авто, ФИО водителя, Лада, Петров, Фиат, Иванов, БМВ, Сидоров, Введите номер удаляемой записи ? 1 Марка авто, ФИО водителя, Лада, Петров, БМВ, Сидоров, Стоим.авто, Год вып. авто 2000, 2007 3000, 2008 4000, 2006 Стоим.авто, Год вып. авто 2000, 2007 4000, 2006 22.3 Списки с расширенными поисковыми возможностями Для расширения поисковых возможностей однонаправленных списков в C# были включены двунаправленные списки, например, LinkedList<T>. Двунаправленные списки это цепочки узлов, в которых имеются ссылки как на следующий узел (обычно поле Next), так и на предыдущий узел (обычно поле Old). Дополнительно каждый узел имеет ссылку на элемент списка (объект, помещенный в список). Расширение поисковых возможностей связано с возможность перемещения по списку в любом направлении. Дополнительным достоинством двунаправленных списков является простота операций вставки и удаления элементов списка. Эта процедура сводится к обновлению нескольких ссылок элементов списка, между которыми выполняется операция. Следующей модификацией двунаправленного списка является создание циклических списков. В циклическом списке последний элемент списка с помощью указателя Next соединен с его первым элементом, а первый элемент списка с помощью указателя Old соединен с его последним элементов. Особенностью циклического списка является полная равнозначность элементов (если не учитывать организацию информации внутри самих элементов списка), т.е. заголовок может находиться на любом элементе списка. Например, после выполнения некоторой поисковой операции мы можем установить заголовок на найденный элемент. При следующей поисковой операции заголовок может быть перемещен на следующий найденный элемент и т.д. С учетом возможности перемещения по списку в двух направлениях можно улучшить алгоритм поиска в списке. Однако многие стандартные поисковые алгоритмы основаны на обработке списков, информация элементов которых располагается в определенном порядке. При этом требуется фиксированное расположение заголовка списка. Для этих целей было предложено устанавливать заголовок на элемент, поля которого не содержат смысловой информации (информация в этот элемент не записывается), а сам элемент необходим как некоторая начальная точка в циклическом списке. Такая организация списка получила название списка с выделенным заголовком. Для реализации алгоритма двоичного поиска применяются древовидные структуры, элементы которой имеют два указателя (левый и правый). 22.4 Вопросы для проверки 22.4.1 Как называется простой список, в котором все включения элементов выполняются в одном конце списка, а все исключения – на другом его конце? 22.4.2 Как называется простой список, в котором включения и исключения элементов могут выполняться в любом месте списка? 22.4.3 Что означает запись Public Queue()? 22.4.4 Что делает следующий фрагмент программы: foreach (int i in ocer) Console.Write(i + " "); Console.WriteLine();, где ocer – заголовок очереди? 22.4.5 Что делает следующий фрагмент программы: ocer.Clear();, где ocer – заголовок очереди?