Джулиан Бакнелл - Фундаментальные алгоритмы и структуры данных в Delphi
- Название:Фундаментальные алгоритмы и структуры данных в Delphi
- Автор:
- Жанр:
- Издательство:ДиаСофтЮП
- Год:2003
- ISBN:ISBN 5-93772-087-3
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Джулиан Бакнелл - Фундаментальные алгоритмы и структуры данных в Delphi краткое содержание
Книга "Фундаментальные алгоритмы и структуры данных в Delphi" представляет собой уникальное учебное и справочное пособие по наиболее распространенным алгоритмам манипулирования данными, которые зарекомендовали себя как надежные и проверенные многими поколениями программистов. По данным журнала "Delphi Informant" за 2002 год, эта книга была признана сообществом разработчиков прикладных приложений на Delphi как «самая лучшая книга по практическому применению всех версий Delphi».
В книге подробно рассматриваются базовые понятия алгоритмов и основополагающие структуры данных, алгоритмы сортировки, поиска, хеширования, синтаксического разбора, сжатия данных, а также многие другие темы, тесно связанные с прикладным программированием. Изобилие тщательно проверенных примеров кода существенно ускоряет не только освоение фундаментальных алгоритмов, но также и способствует более квалифицированному подходу к повседневному программированию.
Несмотря на то что книга рассчитана в первую очередь на профессиональных разработчиков приложений на Delphi, она окажет несомненную пользу и начинающим программистам, демонстрируя им приемы и трюки, которые столь популярны у истинных «профи». Все коды примеров, упомянутые в книге, доступны для выгрузки на Web-сайте издательства.
Фундаментальные алгоритмы и структуры данных в Delphi - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Какое отношение имеет выравнивание данных к приложениям? При программировании необходимо убедиться, что переменные типа longint и указатели выровнены по четырехбайтовой или 32-битовой границе. Если они переходят через границу 4 байт, процессору придется выдать две команды на считывание кэш-памяти: первая команда для считывания первой части, а вторая - второй части. Затем процессору потребуется соединить две части значения и отбросить ненужные биты. (В ряде процессоров 32-битные значения всегда должны выравниваться по границе 32 бит. В противном случае возникает ошибка нарушения доступа. К счастью, процессоры Intel не требуют этого, что, в свою очередь, провоцирует программистов на некоторую небрежность.)
Всегда убеждайтесь в том, что 32-битные значения выровнены по границе 32 бит, а 16-битные значения - по границе 16 бит. Для увеличения быстродействия следует убедиться, что 64-битные значения (например, переменные типа double) выровнены по 64-битной границе.
Все это звучит достаточно сложно, но в действительности программисту очень помогает компилятор Delphi. В результате особое внимание нужно уделять только объявлению типа record. Все глобальные и локальные атомарные переменные (т.е. переменные простых типов) выравниваются должным образом. Если тип выравнивания не установлен, то 32-разрядный компилятор Delphi будет автоматически выравнивать и поля типа record. Для этого он добавляет незначащие байты. В 16-разрядной версии автоматическое выравнивание переменных атомарных типов не используется, поэтому будьте осторожны.
Автоматическое выравнивание переменных иногда может ввести программиста в заблуждение. Если, например, объявлен следующий тип record в 32-разрядной версии Delphi, каким будет результат выполнения операции sizeof(TMyRecord)?
type
TMyRecord = record
aByte : byte;
aLong : longint;
end;
Многие без сомнения ответят, что, дескать, 5 байт (и это было бы правильно для Delphi1). Однако верным ответом будет 8 байт. Компилятор автоматически вставит три байта между полем aByte и along, просто чтобы выровнять последнее поле по границе 4 байт.
Если тип record объявить следующим образом:
type
TMyRecord = packed record
aByte : byte;
aLong : longint;
end;
то функция sizeof(TMyRecord) даст результат 5. Однако в этом случае доступ к полю aLong потребует больше времени, чем в предыдущем примере, поскольку значение поля будет переходить через границу 4 байт. Следовательно, правило можно сформулировать так: если используется ключевое слово packed, поля в записи должны располагаться таким образом, чтобы данные были выровнены по границе 4 байт. Сначала объявите 4-байтные поля, а затем уже все остальные. Это правило применяется во всех кодах, приведенных в настоящей книге. И еще одно, никогда не угадывайте размер записи, а пользуйтесь для этого функцией sizeof.
Кстати, следует знать, что диспетчер динамического распределения памяти Delphi также помогает выполнять выравнивание данных. Он выравнивает не только 4-байтные значения по границе 4 байт, но и 8-байтные значения по границе 8 байт. Это имеет большое значение для переменных типа double: операции с числами с плавающей запятой выполняются быстрее, если переменные типа double выровнены по границе 8 байт. Если задачи по программированию связаны с использованием большого количества числовых переменных, убедитесь, что поля типа double в записях выровнены по границе 8 байт.
Пространство или время
Чем больше мы изучаем, разрабатываем и анализируем алгоритмы, тем чаще мы сталкиваемся с одним универсальным законом вычислительной техники: быстрые алгоритмы, как правило, требуют больше памяти. Таким образом, для использования быстрого алгоритма необходимо располагать большим объемом памяти.
Рассмотрим это на простом примере. Предположим, что требуется разработать алгоритм, который бы определял количество установленных бит в байте. Первый вариант алгоритма показан в листинге 1.3.
Листинг 1.3. Первоначальная функция определения количества установленных битов в байте
function CountBitsl(B : byte):byte;
begin
Result := 0;
while (B <> 0) do
begin
if Odd(B) then
inc(Result);
B := B shr 1;
end;
end;
Как видите, в этой функции не используются промежуточные переменные. Она просто считает установленные биты путем деления значения на 2 (сдвиг целого значения на один бит вправо эквивалентно делению на 2) и определения количества полученных нечетных результатов. Цикл завершается, когда будет получено значение 0, поскольку в этом случае очевидно, что установленных битов больше нет. Значение О большого для приведенного алгоритма зависит от количества установленных битов в параметре, и в худшем случае внутренний цикл будет выполнен восемь раз. Таким образом, это алгоритм класса O(n).
Описанный алгоритм вполне очевиден и, если не принимать во внимание возможность его реализации на языке ассемблера, улучшить его практически невозможно.
Тем не менее, давайте рассмотрим назначение алгоритма с другой точки зрения. В качестве входного значения функция принимает 1-байтный параметр, с помощью которого можно передать всего 256 значений. В таком случае, почему бы нам заранее не вычислить все возможные ответы и не записать их в статический массив? Реализация нового алгоритма приведена в листинге 1.4.
Листинг 1.4. Улучшенная функция определения количества установленных битов в байте const
BitCounts : array [0..255] of byte =
(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8);
function CountBits2(B : byte): byte;
begin
Result := BitCounts[B];
end;
Здесь за счет статического 256-байтного массива значений функция намного упростилась. Более того, приведенный алгоритм не содержит цикла. Независимо от значения входного параметра, количество установленных битов вычисляется за один простой шаг. (Обратите внимание, что значения для статического массива были вычислены автоматически с помощью простой программы, использующей первую функцию.)
На компьютере автора книги последний алгоритм оказался в 10 раз быстрее, чем первый: 10 вызовов второй функции занимает столько же времени, сколько один вызов первой. (Обратите внимание, что здесь речь идет о среднем случае. В лучшем случае для первой функции значение параметра равно 0 и функция практически не будет требовать времени на выполнение.)
Таким образом, за счет введения 256-байтного массива мы разработали алгоритм, который быстрее в 10 раз. Увеличение скорости было достигнуто за счет увеличения требуемого объема памяти: можно получить быструю функцию, использующую большой статический массив (который будет скомпилирован в выполняемый файл, об этом также следует помнить), или более медленную функцию, не требующую больших объемов памяти. (Существует еще одна альтернатива. Можно заполнять массив значениями в процессе выполнения функции, при ее первом вызове. В этом случае массив не будет компилироваться в выполняемый файл, но первый вызов функции займет достаточно длительное время.)
Читать дальшеИнтервал:
Закладка: