Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции СД.doc
Скачиваний:
212
Добавлен:
19.03.2015
Размер:
1.81 Mб
Скачать
    1. 4.5. Таблицы

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

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

Основной операцией при работе с таблицами является операция доступа к записи по ключу. Она реализуется процедурой поиска. Поскольку поиск может быть значительно более эффективным в таблицах, упорядоченных по значениям ключей, довольно часто над таблицами необходимо выполнять операции сортировки. Такие операции рассматриваются в разделе 4.6.

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

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

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

    1. 4.6. Операции над статическими структурами

      1. 4.6.1. Алгоритмы поиска

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

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

В таком случае лучше всего рассматривать процедуру сравнения в виде «черного ящика» – функции с четко определенным интерфейсом, которая в качестве входных параметров принимает указатели на два элемента и возвращает результат сравнения – первый элемент больше, меньше или равен второму.

В последующем изложении все описания алгоритмов даны для работы с классом TList, а функции сравнения имеют следующий тип:

{ Объявление прототипа функции сравнения }

TCompareFunc=function(aData1,aData2:Pointer):Integer;

Тип входных параметров определяет сама функция, и решает, привести ли их к определенному классу или просто к типу, который не является указателем. Пример реализации функции сравнения для целых типа LongWord приведен ниже.

{ Функция сравнения двух целых чисел, заданных своими указателями }

function CompareLongWord(aData1, aData2: Pointer): Integer;

begin

{ Значение первого элемента меньше значения второго }

if LongWord(aData1^) < LongWord(aData2^) then

Result:=-1else

{ Значения элементов равны }

if LongWord(aData1^) = LongWord(aData2^) then

Result:=0 else

{ Значение первого элемента больше значения второго }

Result:=1;

end;

Линейный поиск. Единственно возможным методом поиска элемента по значению ключа, находящегося в неупорядоченном наборе данных, является последовательный (линейный) просмотр каждого элемента, который продолжается до тех пор, пока не будет найден искомый элемент. Если просмотрен весь набор, но элемент не найден, искомый ключ отсутствует. Для последовательного поиска в среднем требуется (n+1)/2 сравнений и порядок алгоритма линейный O(n).

Программная иллюстрация линейного поиска в неупорядоченном массиве приведена ниже, где aList – исходный массив, aItem – искомый ключ; функция возвращает индекс найденного элемента или -1, если элемент отсутствует.

{ Линейный поиск в неотсортированном массиве }

function LineNonSortedSearch(aList: TList;

aItem: Pointer; aCompare: TCompareFunc): Integer;

var i: Integer;

begin

Result:=-1;

for i:=0 to aList.Count-1 do

if aCompare(aList.List[i],aItem) = 0 then

begin

Result:=i;

Break;

end;

end;

Последовательный поиск для отсортированного массива ничем не отличается от приведенного и имеет порядок алгоритма O(n). Однако алгоритм можно несколько улучшить. Если искомого элемента нет в массиве, поиск можно выполнить быстрее, т.к. итерации должны выполняться только до тех пор, пока не будет найден элемент, больший или равный искомому. Все последующие элементы будут больше искомого и поиск можно прекратить.

{ Линейный поиск в отсортированном массиве }

function LineSortedSearch(aList: TList;

aItem: Pointer; aCompare: TCompareFunc): Integer;

var i, CompareResult: Integer;

begin

Result:=-1;

{ Искать первый элемент, больший или равный искомому }

for i:=0 to aList.Count-1 do

begin

CompareResult:=aCompare(aList.List[i], aItem);

if CompareResult >= 0 then

begin

if CompareResult = 0 then

Result:=i else

Result:=-1;

Exit;

end;

end;

end;

Обратите внимание, функция сравнения вызывается только один раз при каждой итерации, т.к. временные параметры ее выполнения заранее не известны, и поэтому вызывать ее желательно следует как можно реже.

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

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

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

{ Двоичный поиск }

function BinarySearch(aList: TList;

aItem: Pointer; aCompare: TCompareFunc): Integer;

var L, R, M, CompareResult: Integer;

begin

{ Значения индексов первого и последнего элементов }

L:=0; R:=aList.Count-1;

while L <= R do

begin

{ Индекс среднего элемента }

M:=(L+R)div2;

{ Сравнить значение среднего элемента с искомым }

CompareResult:=aCompare(aList.List[M], aItem);

{ Если значение среднего элемента меньше искомого -

переместить левый индекс на позицию до среднего индекса }

if CompareResult < 0 then

L:=M+1 else

{ Если значение среднего элемента больше искомого -

переместить правый индекс на позицию после среднего индекса }

if CompareResult > 0 then

R:=M-1 else

begin

Result:=M;

Exit;

end;

end;

Result:=-1;

end;

Трассировка бинарного поиска ключа 275 в исходной последовательности: {75, 151, 203, 275, 318, 489, 524, 519, 647, 777} представлена в табл. 4.7.

Табл. 4.7. Трассировка бинарного поиска.

Итерация

L

R

M

a[M]

1

1

10

5

318

2

1

4

2

151

3

3

4

3

203

4

4

4

4

275

Алгоритм бинарного поиска можно представить несколько иначе, используя рекурсивное описание. В этом случае граничные индексы интервала L и R являются параметрами алгоритма. Рекурсивная процедура бинарного поиска представлена ниже. Для выполнения поиска необходимо при вызове функции задать значения ее формальных параметров L и R – 1 и n соответственно.

{ Рекурсивный алгоритм двоичного поиска }

functionBinaryRecurSearch(aList:TList;

aItem: Pointer; L,R: Integer; aCompare: TCompareFunc): Integer;

var i, CompareResult: Integer;

begin

{ Проверка ширины интервала }

if L > R then

Result:=-1 else

begin

i:=(L + R) div 2;

CompareResult:=aCompare(aList.List[i], aItem);

{ Ключ найден, возврат индекса }

if CompareResult = 0 then

Result:=i else

if CompareResult = -1 then

{ Поиск в правом подинтервале }

Result:=BinaryRecurSearch(aList,aItem,i+1,R,aCompare) else

{ Поиск в левом подинтервале }

Result:=BinaryRecurSearch(aList,aItem,L,i-1,aCompare);

end;

end;