Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
TA / 1.3 Структуры данных.doc
Скачиваний:
24
Добавлен:
14.04.2015
Размер:
198.14 Кб
Скачать

18

1.3. Структуры данных

Издавна и до наших дней люди обрабатывали числовые данные о своей деятельности и об окружающем их мире, вовлеченном в эту деятельность. Уже к моменту появления ЭВМ процессы обработки данных при решении инженерных и научных задач были достаточно хорошо приспособлены для реализации на машине. С иным положением столкнулись экономисты, лингвисты и др. специалисты, для которых применение ЭВМ стало необходимостью. При постановке задач для решения ее на ЭВМ потребовалось существенно повысить требования к обобщению представления данных. В ходе развития автоматизации программирования было установлено, что и сами программы следует рассматривать как объекты сложной структуры, надлежащие обработке на ЭВМ. Наконец, переход к интегрированной обработке данных значительно повысил интерес к методам размещения и поиска данных в памяти большого объема.

В результате были определены объекты, отвечающие понятию структуры данных, процессы, в которых эти объекты преобразуются, и этим объектам были сопоставлены классы структур реальных данных. Так возникла реальная дисциплина структуры данных. Она пользуется методами дискретной математики(теории множеств, математической логики, теории графов). Эти методы применяются для описания общих свойств и структур совокупностей данных. Такое описание строится, чтобы создавать эффективные алгоритмы обработки данных, упорядочить сбор и хранение данных, и в конечном счете оптимизировать потоки и ресурсы информации.

Правильный подход к разработке эффективного алгоритма для данной задачи - изучить ее сущность. Часто задачу можно сформулировать на языке основных математических понятий, таких, как множество, и тогда алгоритм для нее можно изложить в терминах основных операций над основными объектами. Далее нужно проанализировать несколько структур данных и выбрать ту из них, которая лучше всего подходит для задачи в целом. Таким образом, разработка эффективного алгоритма предполагает выбор подходящей структуры данных. Не случайно возникла формула (11):

Алгоритмы + структуры данных = программа

В предыдущем разделе были рассмотрены основные модели вычислений, среди которых РАМ достаточно полно отражает вычислительные процессы реальной машины. Однако алгоритмы, написанные в терминах подобного устройства являются громоздкими, и потому мы ввели в рассмотрение язык упрощенный Алгол. Но и он является слишком примитивным, если не ввести в рассмотрение структуры данных, более сложные, чем массивы. Мы познакомимся с такими структурами данных, как списки и стеки, с помощью которых можно представлять множества, графы и деревья.

1.3.1. Списки, очереди, стеки

С математической точки зрения список - конечная последовательность элементов, взятых из некоторого подходящего множества. Описание алгоритма часто будет включать в себя некоторый список, к которому добавляются и из которого удаляются элементы. В частности может потребоваться добавление или удаление элемента где-то в середине списка.

Рассмотрим список EL1, EL2, EL3, EL4. (1)

Простейшей его реализацией будет структура последовательно связанных компонент, изображенная на рис.1. Каждая компонента в этой структуре состоит из двух ячеек памяти. Первая ячейка содержит сам элемент, вторая - указатель следующего элемента. Это можно реализовать в виду двух массивов, которые на рис.2 названы NAME и NEXT. Если COMP - компонента в рассматриваемом массиве, то NAME[COMP] - сам элемент хранящийся там, а NEXT[COMP] - индекс следующего элемента в списке( если такой элемент существует). Если COMP - индекс последнего элемента в этом списке, то NEXT[COMP]=0.

EL1

EL2

EL3

EL4

FIRST

Рис.3.1 Список со связями

NAME

NEXT

0

__

1

1

EL1

3

2

EL4

0

3

EL2

4

4

EL3

2

5

__

__

На рис.2 NEXT[0] означает постоянный указатель на первую компоненту в списке. Заметим, что порядок элементов в массиве NAME не совпадает с их порядком в списке. Тем не менее рис.2. дает верное представление списка, изображенного на рис.1, так как массив NEXT располагает элементы в той же последовательности, в какой они расположены в списке (1).

Опишем процедуру, которая вставляет новую компоненту в список. В ней предполагается, что FREE - номер незанятой ячейки в массивах NAME и NEXT, а POS - индекс той компоненты в списке, после которой следует вставить элемент NEL. Процедура ВСТАВИТЬ имеет вид:

Рис.3.2. Представление списка из четырех элементов.

procedure Вставить (NEL, FREE, POS);

begin

NAME[FREE]=NEL;

NEXT[FREE]=NEXT[POS];

NEXT[POS]=FREE;

end

Любой разумный перевод в команды РАМ приведет к тому, что время выполнения процедуры ВСТАВИТЬ не будет зависеть от размера списка.

Пример 1. Пусть мы хотим вставить в список (1) элемент NEL после элемента EL2 и получить список EL1, EL2, NEL, EL3, EL4. Если пятая ячейка в каждом массиве на рис.3.2 пуста, можно вставить NEL после EL2 ( позиция 3), вызвав процедуру ВСТАВИТЬ(NEL,5,3).В результате выполнения трех операторов в процедуре ВСТАВИТЬ получим NAME[5]=NEL, NEXT[5]=4 и NEXT[3]=6; тем самым создадутся массивы, показанные на рис.3.3.

NAME

NEXT

0

__

1

1

EL1

3

2

EL4

0

3

EL2

5

4

EL3

2

5

NEL

4


Рис.3.3. Представление списка со вставленным элементом NEL.

Для того, чтобы удалить компоненту, следующую за компонентой в ячейке 1, можно положить NEXT[1]=NEXT[NEXT[1]]. При желании индекс удаленной компоненты можно поместить в список незанятых ячеек памяти.

Часто в один и тот же массив вкладываются несколько списков. Обычно один из этих списков состоит из незанятых ячеек; мы будем называть его свободным списком. Для добавления элемента к списку А можно так изменить процедуру ВСТАВИТЬ, чтобы незанятая ячейка получалась путем удаления первой ячейки в свободном списке. При удалении элемента из списка А соответствующая ячейка возвращается в свободный список для будущего устройства.

Этот метод организации памяти - не единственный прием, которым обычно пользуются, но он приведен здесь для того, чтобы показать, что операцию добавления и удаления элементов из списка можно выполнить за ограниченное число шагов, если определено местоположение элемента, который мы хотим добавить или удалить.

Еще две основные операции над списками - конкатенация(сцепление) двух списков, в результате которой образуется единственный список, и обратная к ней операция расцепления списка, стоящего после некоторого элемента, результатом которого будут два списка. Конкатенацию можно выполнить за ограниченное( постоянной величиной) число шагов, включив в представление списка еще один указатель. Этот указатель дает индекс последней компоненты списка и тем самым позволяет обойтись без просмотра всего списка

для определения его последнего элемента. Расцепление можно сделать также за ограниченное время, если известен индекс компоненты списка, непосредственно предшествующей месту расцепления [12].

Списки можно сделать проходимыми в обоих направления, если добавить еще один массив, называемый PREV. Значения PREV[1] равно ячейке, в которой помещается тот элемент списка, который стоит непосредственно перед элементом из ячейки 1.Список такого рода называется д в а ж д ы с в я з а н н ы м (рис.3.4). Из него можно удалить элемент или вставить в него элемент, не зная, где находится предыдущий элемент.

A)

i

j

k

..

l

a

b

С

m

...


Б)

NAME

PREV

NEXT

. . .

. . .

. . .

i

a

l

j

. . .

. . .

. . .

j

b

i

k

. . .

. . .

. . .

k

c

j

m

. . .

. . .

. . .

Рис.3.4. Двусвязный список :

А) Схема связей ;

Б) Представление в виде массивов.

Со списком часто работают очень ограниченными приемами. Например, элементы добавляются или удаляются только на конце списка. Иными словами, элементы вставляются и удаляются по принципу "последним вошел - первым вышел"(LIFO - last input, first output). В этом случае список называется стекомилимагазином.

Часто стек реализуется в виде одного массива. Например, список EL1, EL2, EL3 можно хранить в массиве NAME, как показано на рис.3.5. Переменная TOP является указателем последнего элемента, добавленного к стеку.

NAME

0

EL1

1

EL2

2

EL3

3

:

:

NAME

:

P

Q

R

S

T

:

BACK

(ЗАДНИЙ)

TOP

FORWARD

(ПЕРЕДНИЙ)

Рис.3.5. Реализация стека

Рис.3.6. Реализация очереди в виде одного массива

Чтобы добавить новый элемент в стек значение TOP увеличивают на 1, а затем помещают новый элемент в NAME[TOP].(Поскольку массив NAME имеет конечную длину l, перед попыткой вставить новый элемент следует проверить, что TOP<l-1). Чтобы удалить элемент из вершины стека, надо просто уменьшить значение TOP на 1. Заметим, что не обязательно физически стирать элемент, удаляемый из стека. Чтобы узнать, пуст ли стек, достаточно проверить, не имеет ли TOP значение, меьше нуля. Понятно, что время выполнения операций ВТОЛКНУТЬ и ВЫТОЛКНУТЬ и проверка пустоты не зависят от числа элементов в стеке.

Другой специальный вид списка - очередь, т.е. список, в который элементы всегда добавляются с одного (переднего) конца, а удаляются с другого. Как и стек, очередь можно реализовать одним массивом, как показано на рис.3.5, где приведена очередь, содержащая список из элементов P, Q, R, S, T. Два указателя обозначают ячейки текущего переднего и заднего концов очереди. Чтобы добавить (ВПИСАТЬ) новый элемент к очереди, полагают FORW=FORW+1 и помещают новый элемент в NAME[FORW]. Чтобы удалить (ВЫПИСАТЬ) элемент из очереди, заменяют BACK на BACK+1. Заметим, что эта техника с

точки зрения доступа к элементам основана на принципе «первый вошел - первый вышел» (FIFO - first input, first output).

Поскольку массив NAME имеет конечную длину (пусть l), указатели FORW и BACK рано или поздно доберутся до его конца. Если длина списка, представленного этой очередью, никогда не превосходит l, то можно трактовать NAME[0] как элемент, следующий за элементом NAME[l-1].

Элементы, расположенные в виде списка, сами могут быть сложными структурами. Работая, например, со списками массивов, мы на самом деле не добавляем и не удаляем массивы, ибо каждое добавление или удаление потребовало бы времени, пропорционального размеру массива. Вместо этого мы добавляем или удаляем указатели массивов. Т.о., сложную структуру можно добавить или удалить за фиксированное время, не зависящее от ее размера.