5.2.2. Структуры данных
Структуры данных – это абстрактные структуры или классы, которые используются для организации данных и предоставляют пользователям различные операции над этими данными.
Наиболее распространенной и общеизвестной структурой данных является массив (array), который содержит непрерывную совокупность элементов данных, к которым можно получить доступ.
В любом языке программирования массивы имеют несколько общих свойств:
- Содержимое массива хранится в непрерывной области памяти.
- Все элементы массива имеют одинаковый тип; поэтому массивы называют однородными (homogeneous) структурами данных.
- Существует пряммой доступ к элементам массива. (В случае других структур данных это необязательно так. Например, в структуре данных скип-список (SkipList) для доступа к конкретному элементу скип-списка нужно предварительно осуществить поиск среди других элементов скип-списка, пока не найдется требуемый элемент. В случае с массивами, если необходимо получить доступ к i-му элементу массива, можно сделать это одной операцией: arrayName[i].)
Типичные операции для массивов включают:
- Выделение элемента(Allocation).
- Доступ к элементу (Accessing).
- Изменение размеров массива (Redimensioning).
Когда в C# объявляется массив, его значение равняется null. Например, следующая строчка кода просто создает переменную с именем booleanArray , которая равна null: bool [ ] booleanArray.
Прежде чем начать работу с массивом, следует выделить определенное количество элементов. Синтаксически это выглядит так:
booleanArray = new bool[10],
или более обобщенно:
arrayName = new arrayType[allocationSize].
Массивы являются превосходной структурой данных, если требуется хранить коллекцию однородных типов, к которым нужно иметь прямой доступ. Поиск в неотсортированном массиве занимает линейное от размера массива время. Это приемлемо, если мы работаем с маленькими массивами, или когда нам нужно лишь изредка производить поиск внутри массива. Если же приложение хранит большие массивы, в которых часто производится поиск, то существуют структуры данных гораздо лучше приспособленные к таким ситуациям.
Многомерные массивы, как и одномерные требуют постоянного времени для доступа к своим элементам. Время поиска в n-элементном массиве равно O(n). Для двумерного массива размером nxn время поиска будет равно O(n2), потому что процедура поиска должна будет просмотреть n2 элементов. В более общем случае время поиска в k-мерном массиве есть O(nk).
Несмотря на то, что обычные массивы, безусловно, находят широкое применение, они накладывают определенные ограничения на программиста, поскольку каждый массив может хранить данные только одного типа (однородность) и вдобавок перед использованием массива требуется выделять (allocate) определенное количество элементов.
Массивы позволяют хранить непрерывный блок однородных типов. Основное их преимущество состоит в исключительно малом времени доступа к своим элементам для чтения и записи. Слабое место массивов – дороговизна поиска в них, т.к. при поиске в неотсортированном массиве требуется потенциально посетить каждый элемент массива.
Структура данных 'стек'. Стеком будем называть последовательность элементов, упорядоченных по времени их поступления. Эта последовательность доступна только с одного конца (вершины стека). Для работы со стеком необходим указатель вершины стека. Основные операции над стеком следующие: "запомнить в стеке" и "извлечь из стека" (причем извлекается последний из занесенных в стек элементов - то есть элемент с вершины стека). Поэтому говорят, что стек – это структура типа LIFO - "Last In - First Out" - "последним зашел - первым вышел".
Для представления стека в программе обычно используется одномерный массив (назовем его B), нумерация элементов которого начинается с нуля. В этом нулевом элементе массива хранится индекс первого свободного места в массиве (т.е. увеличенный на 1 индекс вершины стека). Если массив пуст, то указатель равен 1 (B[0]=1).
Добавление элемента X в стек реализуется очень просто - на первое свободное место (индекс которого хранится в B[0]) помещается X, после чего индекс первого свободного места увеличивается на 1:
B[B[0]]:=x; {Занести в стек} B[0]:=B[0]+1; { Увеличить указатель }
Если необходимо извлечь элемент x из стека, то берется последний из занесенных элементов (естественно, только в том случае, если стек непуст), и указатель на первое свободное место уменьшается на 1:
if B[0]<>1 {Если стек не пуст} then begin x:=B[B[0]]; {Взять элемент} B[0]:=B[0]-1; {Уменьшить указатель} end;
Структура данных 'список'. Каждый элемент списка имеет указатель на следующий за ним элемент (другими словами – хранит информацию о расположении следующего элемента), а если список двусвязный (двунаправленный), то имеет также указатель и на предшествующий элемент. Кроме того есть переменная, указывающая на первый элемент списка. Например, двунаправленный список может быть реализован с помощью двумерного массива A[1..3, 1..N], где содержимое A[2,i] определяет позицию (индекс, адрес) элемента, предшествующего элементу A[1,i], а содержимое A[3,i] определяет позицию элемента, следующего за A[1,i].
Основные операции над списком следующие: "удалить из списка" и "поместить в список".
Например, помещение в список элемента х после элемента A[1,i] может выглядеть так:
A[1,j]:=x; { Занести в массив } A[2,j]:=i; { Изменить указатели } A[3,j]:=A[3,i]; A[3,i]:=j;
Здесь j - адрес свободной позиции в массиве А.
Структура данных 'очередь'. Очередь в программировании, как и очередь в магазине, имеет начало и конец. Если приходит новый элемент, то он становится в конец очереди, если необходимо взять элемент из очереди, то он берется из ее начала. Очередь будем представлять в виде массива. Пусть у нас есть индекс первого - BEG и последнего - FIN элементов очереди (если очередь пуста, то BEG=FIN+1; сначала же BEG=1, FIN=0).
Очередь опишем так: var Queue=array[1..MaxQ] of element.
Тут MaxQ - максимальное число элементов в очереди, element какой-то тип данных. В качестве element можно взять структуру следующего вида:
type element = record x: byte; y: byte; end;
где element - это запись, состоящая из x-овой и y-овой координаты.
С очередью можно проводить операции: вставка в очередь InQueue, удаление из очереди OutQueue.
Procedure InQueue (x : element);
begin
FIN:=FIN+1; { на первое свободное место}
Queue[FIN]:=x; { ставим элемент x }
end;
Procedure OutQueue (var x : element);
begin
x:=Queue[BEG]; { берем первый элемент }
BEG:=BEG+1; { и изменяем указатель }
{ на следующий за ним }
end;
|