Реферат: VB, MS Access, VC++, Delphi, Builder C++ принципы(технология), алгоритмы программирования

Введение.................................................................................................. 8

Целевая аудитория.............................................................................. 10

Глава 1. Основные понятия.................................................................. 15

Что такое алгоритмы?........................................................................ 15

Анализ скорости выполнения алгоритмов...................................... 16

Пространство — время..................................................................... 17

Оценка с точностью до порядка....................................................... 17

Поиск сложных частей алгоритма................................................... 19

Сложность рекурсивных алгоритмов............................................... 20

Многократная рекурсия.................................................................... 21

Косвенная рекурсия.......................................................................... 22

Требования рекурсивных алгоритмов к объему памяти................. 22

Наихудший и усредненный случай.................................................. 23

Часто встречающиеся функции оценки порядка сложности....... 24

Логарифмы........................................................................................ 25

Реальные условия — насколько быстро?........................................ 25

Обращение к файлу подкачки.......................................................... 26

Псевдоуказатели, ссылки на объекты и коллекции......................... 27

Резюме................................................................................................... 29

Глава 2. Списки..................................................................................... 30

Знакомство со списками.................................................................... 31

Простые списки................................................................................... 31

Коллекции.......................................................................................... 32

Список переменного размера........................................................... 33

Класс SimpleList................................................................................ 36

Неупорядоченные списки.................................................................. 37

Связные списки................................................................................... 41

Добавление элементов к связному списку....................................... 43

Удаление элементов из связного списка.......................................... 44

Уничтожение связного списка.......................................................... 44

Сигнальные метки............................................................................. 45

Инкапсуляция связных списков........................................................ 46

Доступ к ячейкам.............................................................................. 47

Разновидности связных списков...................................................... 49

Циклические связные списки............................................................ 49

Проблема циклических ссылок........................................................ 50

Двусвязные списки............................................................................ 50

Потоки............................................................................................... 53

Другие связные структуры................................................................ 56

Псевдоуказатели................................................................................. 56

Резюме................................................................................................... 59

Глава 3. Стеки и очереди...................................................................... 60

Стеки..................................................................................................... 60

Множественные стеки....................................................................... 62

Очереди................................................................................................. 63

Циклические очереди........................................................................ 65

Очереди на основе связных списков................................................ 69

Применение коллекций в качестве очередей................................... 70

Приоритетные очереди..................................................................... 70

Многопоточные очереди.................................................................. 72

Резюме................................................................................................... 74

Глава 4. Массивы.................................................................................. 75

Треугольные массивы........................................................................ 75

Диагональные элементы................................................................... 77

Нерегулярные массивы...................................................................... 78

Прямая звезда.................................................................................... 78

Нерегулярные связные списки.......................................................... 79

Разреженные массивы........................................................................ 80

Индексирование массива.................................................................. 82

Очень разреженные массивы............................................................ 85

Резюме................................................................................................... 86

Глава 5. Рекурсия.................................................................................. 86

Что такое рекурсия?........................................................................... 87

Рекурсивное вычисление факториалов........................................... 88

Анализ времени выполнения программы........................................ 89

Рекурсивное вычисление наибольшего общего делителя............. 90

Анализ времени выполнения программы........................................ 91

Рекурсивное вычисление чисел Фибоначчи................................... 92

Анализ времени выполнения программы........................................ 93

Рекурсивное построение кривых Гильберта................................... 94

Анализ времени выполнения программы........................................ 96

Рекурсивное построение кривых Серпинского.............................. 98

Анализ времени выполнения программы...................................... 100

Опасности рекурсии......................................................................... 101

Бесконечная рекурсия..................................................................... 101

Потери памяти................................................................................. 102

Необоснованное применение рекурсии......................................... 103

Когда нужно использовать рекурсию............................................ 104

Хвостовая рекурсия.......................................................................... 105

Нерекурсивное вычисление чисел Фибоначчи............................. 107

Устранение рекурсии в общем случае............................................ 110

Нерекурсивное построение кривых Гильберта............................ 114

Нерекурсивное построение кривых Серпинского....................... 117

Резюме................................................................................................. 121

Глава 6. Деревья.................................................................................. 121

Определения...................................................................................... 122

Представления деревьев................................................................... 123

Полные узлы.................................................................................... 123

Списки потомков............................................................................. 124

Представление нумерацией связей................................................ 126

Полные деревья............................................................................... 129

Обход дерева...................................................................................... 130

Упорядоченные деревья................................................................... 135

Добавление элементов.................................................................... 135

Удаление элементов........................................................................ 136

Обход упорядоченных деревьев.................................................... 139

Деревья со ссылками........................................................................ 141

Работа с деревьями со ссылками.................................................... 144

Квадродеревья................................................................................... 145

Изменение MAX_PER_NODE......................................................... 151

Использование псевдоуказателей в квадродеревьях..................... 151

Восьмеричные деревья................................................................... 152

Резюме................................................................................................. 152

Глава 7. Сбалансированные деревья.................................................. 153

Сбалансированность дерева............................................................ 153

АВЛ‑деревья....................................................................................... 154

Удаление узла из АВЛ‑дерева........................................................ 161

Б‑деревья............................................................................................ 166

Производительность Б‑деревьев.................................................... 167

Вставка элементов в Б‑дерево........................................................ 167

Удаление элементов из Б‑дерева.................................................... 168

Разновидности Б‑деревьев.............................................................. 169

Улучшение производительности Б‑деревьев................................. 171

Балансировка для устранения разбиения блоков.......................... 171

Вопросы, связанные с обращением к диску.................................. 173

База данных на основе Б+дерева.................................................... 176

Резюме................................................................................................. 179

Глава 8. Деревья решений.................................................................. 179

Поиск в деревьях игры..................................................................... 180

Минимаксный поиск........................................................................ 181

Улучшение поиска в дереве игры.................................................. 185

Поиск в других деревьях решений................................................. 187

Метод ветвей и границ.................................................................... 187

Эвристики........................................................................................ 191

Другие сложные задачи.................................................................... 207

Задача о выполнимости.................................................................. 207

Задача о разбиении......................................................................... 208

Задача поиска Гамильтонова пути................................................. 209

Задача коммивояжера..................................................................... 210

Задача о пожарных депо................................................................. 211

Краткая характеристика сложных задач........................................ 212

Резюме................................................................................................. 212

Глава 9. Сортировка........................................................................... 213

Общие соображения......................................................................... 213

Таблицы указателей........................................................................ 213

Объединение и сжатие ключей....................................................... 215

Примеры программ........................................................................... 217

Сортировка выбором........................................................................ 219

Рандомизация.................................................................................... 220

Сортировка вставкой....................................................................... 221

Вставка в связных списках.............................................................. 222

Пузырьковая сортировка................................................................. 224

Быстрая сортировка......................................................................... 227

Сортировка слиянием....................................................................... 232

Пирамидальная сортировка............................................................ 234

Пирамиды........................................................................................ 235

Приоритетные очереди................................................................... 237

Алгоритм пирамидальной сортировки.......................................... 240

Сортировка подсчетом..................................................................... 241

Блочная сортировка.......................................................................... 242

Блочная сортировка с применением связного списка................... 243

Блочная сортировка на основе массива......................................... 245

Резюме................................................................................................. 248

Глава 10. Поиск................................................................................... 248

Примеры программ........................................................................... 249

Поиск методом полного перебора................................................... 249

Поиск в упорядоченных списках.................................................... 250

Поиск в связных списках................................................................ 251

Двоичный поиск................................................................................ 253

Интерполяционный поиск............................................................... 255

Строковые данные............................................................................ 259

Следящий поиск................................................................................ 260

Интерполяционный следящий поиск............................................. 261

Резюме................................................................................................. 262

Глава 11. Хеширование...................................................................... 263

Связывание........................................................................................ 265

Преимущества и недостатки связывания....................................... 266

Блоки.................................................................................................. 268

Хранение хеш‑таблиц на диске...................................................... 270

Связывание блоков.......................................................................... 274

Удаление элементов........................................................................ 275

Преимущества и недостатки применения блоков......................... 277

Открытая адресация......................................................................... 277

Линейная проверка.......................................................................... 278

Квадратичная проверка................................................................... 284

Псевдослучайная проверка............................................................. 286

Удаление элементов........................................................................ 289

Резюме................................................................................................. 291

Глава 12. Сетевые алгоритмы............................................................ 292

Определения...................................................................................... 292

Представления сети.......................................................................... 293

Оперирование узлами и связями.................................................... 295

Обходы сети....................................................................................... 296

Наименьшие остовные деревья...................................................... 298

Кратчайший маршрут...................................................................... 302

Установка меток.............................................................................. 304

Коррекция меток............................................................................. 308

Другие задачи поиска кратчайшего маршрута.............................. 311

Применения метода поиска кратчайшего маршрута.................... 316

Максимальный поток...................................................................... 319

Приложения максимального потока.............................................. 325

Резюме................................................................................................. 327

Глава 13. Объектно‑ориентированные методы................................. 327

Преимущества ООП......................................................................... 328

Инкапсуляция.................................................................................. 328

Полиморфизм.................................................................................. 330

Наследование и повторное использование.................................... 333

Парадигмы ООП............................................................................... 335

Управляющие объекты................................................................... 335

Контролирующий объект............................................................... 336

Итератор.......................................................................................... 337

Дружественный класс..................................................................... 338

Интерфейс....................................................................................... 340

Фасад............................................................................................... 340

Порождающий объект..................................................................... 340

Единственный объект...................................................................... 341

Преобразование в последовательную форму................................ 341

Парадигма Модель/Вид/Контроллер............................................. 344

Резюме................................................................................................. 346

Требования к аппаратному обеспечению...................................... 346

Выполнение программ примеров.................................................... 346

programmer@newmail.ru

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

Введение

Программирование под Windows всегда было нелегкой задачей. Интерфейс прикладного программирования (Application Programming Interface) Windows предоставляет в распоряжение программиста набор мощных, но не всегда безопасных инструментов для разработки приложений. Можно сравнить его с бульдозером, при помощи которого удается добиться поразительных результатов, но без соответствующих навыков и осторожности, скорее всего, дело закончится только разрушениями и убытками.

Эта картина изменилась с появлением Visual Basic. Используя визуальный интерфейс, Visual Basic позволяет быстро и легко разрабатывать законченные приложения. При помощи Visual Basic можно разрабатывать и тестировать сложные приложения без прямого использования функций API. Избавляя программиста от проблем с API, Visual Basic позволяет сконцентрироваться на деталях приложения.

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

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

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

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

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

=============xi

Все алгоритмы также представлены в виде исходных текстов на Visual Basic, которые вы можете использовать в своих программах без каких‑либо изменений. Они демонстрируют использование алгоритмов в программах, а также важные характерные особенности работы самих алгоритмов.

Что дают вам эти знания

После ознакомления с данным материалом и примерами вы получите:

1. Понятие об алгоритмах. После прочтения данного материала и выполнения примеров программ, вы сможете применять сложные алгоритмы в своих проектах на Visual Basic и критически оценивать другие алгоритмы, написанные вами или кем‑либо еще.

2. Большую подборку исходных текстов , которые вы сможете легко добавить к вашим программам. Используя код, содержащийся в примерах, вы сможете легко добавлять мощные алгоритмы к вашим приложениям.

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

Целевая аудитория

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

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

Даже если вы еще не овладели в полной мере программированием на Visual Basic, вы сможете скомпилировать примеры программ и сравнить производительность различных алгоритмов. Более того, вы сможете выбрать удовлетворяющие вашим требованиям алгоритмы и добавить их к вашим проектам на Visual Basic.

Совместимость с разными версиями Visual Basic

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

=================xii

Некоторые новые понятия, такие как ссылки на объекты, классы и коллекции, которые были впервые введены в 4-й версии Visual Basic, облегчают понимание, разработку и отладку некоторых алгоритмов. Классы могут заключать некоторые алгоритмы в хорошо продуманных модулях, которые легко вставить в программу. Хотя для того, чтобы применять эти алгоритмы, необязательно разбираться в новых понятиях языка, эти новые возможности предоставляют слишком большие преимущества, чтобы ими можно было пренебречь.

Поэтому примеры алгоритмов в этом материале написаны для использования в 4-й и 5-й версиях Visual. Если вы откроете их в 5-й версии Visual Basic, среда разработки предложит вам сохранить их в формате 5-й версии, но никаких изменений в код вносить не придется. Все алгоритмы были протестированы в обеих версиях.

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

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

Языки программирования зачастую развиваются в сторону усложнения, но редко в противоположном направлении. Замечательным примером этого является наличие оператора goto в языке C. Это неудобный оператор, потенциальный источник ошибок, который почти не используется большинством программистов на C, но он по‑прежнему остается в синтаксисе языка с 1970 года. Он даже был включен в C++ и позднее в Java, хотя создание нового языка было хорошим предлогом избавиться от него.

Так и новые версии Visual Basic будут продолжать вводить новые свойства в язык, но маловероятно, что из них будут исключены строительные блоки, использованные при применении алгоритмов, описанных в данном материале. Независимо от того, что будет добавлено в 6-й, 7-й или 8-й версии Visual Basic, классы, массивы и определяемые пользователем типы данных останутся в языке. Большая часть, а может и все алгоритмы из приведенных ниже, будут выполняться без изменений в течение еще многих лет.

Обзор глав

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

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

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

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

В 6 главе используются многие из ранее описанных приемов, такие как рекурсия и связные списки, для изучения более сложной темы — деревьев. Эта глава также охватывает различные представления деревьев, такие как деревья с полными узлами (fat node) и представление в виде нумерацией связей (forward star). В ней также описаны некоторые важные алгоритмы работы с деревьями, таки как обход вершин дерева.

В 7 главе затронута более сложная тема. Сбалансированные деревья обладают особыми свойствами, которые позволяют им оставаться уравновешенными и эффективными. Алгоритмы сбалансированных деревьев удивительно просто описываются, но их достаточно трудно реализовать программно. В этой главе используется одна из наиболее мощных структур подобного типа — Б+дерево (B+Tree) для создания сложной базы данных.

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

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

В главе 10 рассматривается близкая к сортировке тема. После выполнения сортировки списка, программе может понадобиться найти элементы в нем. В этой главе сравнивается несколько наиболее эффективных методов поиска элементов в сортированных списках.

=========xiv

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

В главе 12 описана другая категория алгоритмов — сетевые алгоритмы. Некоторые из этих алгоритмов, такие как вычисление кратчайшего пути, непосредственно применимы к физическим сетям. Эти алгоритмы также могут косвенно применяться для решения других задач, которые на первый взгляд не кажутся связанными с сетями. Например, алгоритмы поиска кратчайшего расстояния могут разбивать сеть на районы или определять критичные задачи в расписании проекта.

В главе 13 объясняются методы, применение которых стало возможным благодаря введению классов в 4‑й версии Visual Basic. Эти методы используют объектно‑ориентированный подход для реализации нетипичного для традиционных алгоритмов поведения.

===================xv

Аппаратные требования

Для работы с примерами вам потребуется компьютер, конфигурация которого удовлетворяет требованиям для работы программной среды Visual Basic. Эти требования выполняются почти для всех компьютеров, на которых может работать операционная система Windows.

На компьютерах разной конфигурации алгоритмы выполняются с различной скоростью. Компьютер с процессором Pentium Pro с тактовой частотой 2000 МГц и 64 Мбайт оперативной памяти будет работать намного быстрее, чем машина с 386 процессором и всего 4 Мбайт памяти. Вы быстро узнаете, на что способно ваше аппаратное обеспечение.

Изменения во втором издании

Самое большое изменение в новой версии Visual Basic — это появление классов. Классы позволяют рассмотреть некоторые задачи с другой стороны, позволяя использовать более простой и естественный подход к пониманию и применению многих алгоритмов. Изменения в коде программ в этом изложении используют преимущества, предоставляемые классами. Их можно разбить на три категории:

1. Замена псевдоуказателей классами. Хотя все алгоритмы, которые были написаны для старых версий VB, все еще работают, многие из тех, что были написаны с применением псевдоуказателей (описанных во 2 главе), гораздо проще понять, используя классы.

2. Инкапсуляция. Классы позволяют заключить алгоритм в компактный модуль, который легко использовать в программе. Например, при помощи классов можно создать несколько связных списков и не писать при этом дополнительный код для управления каждым списком по отдельности.

3. Объектно‑ориентированные технологии. Использование классов также позволяет легче понять некоторые объектно‑ориентированные алгоритмы. В главе 13 описываются методы, которые сложно реализовать без использования классов.

Как пользоваться этим материалом

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

В 6 главе обсуждаются понятия, которые используются в 7, 8 и 12 главах, поэтому вам следует прочитать 6 главу до того, как браться за них. Остальные главы можно читать в любом порядке.

=============xvi

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

Последний план дает порядок для изучения всего материала целиком. Хотя 7 и 8 главы логически вытекают из 6 главы, они сложнее для изучения, чем следующие главы, поэтому они изучаются несколько позже.

Почему именно Visual Basic ?

Наиболее часто встречаются жалобы на медленное выполнение программ, написанных на Visual Basic. Многие другие компиляторы, такие как Delphi, Visual C++ дают более быстрый и гибкий код, и предоставляют программисту более мощные средства, чем Visual Basic. Поэтому логично задать вопрос — «Почему я должен использовать именно Visual Basic для написания сложных алгоритмов? Не лучше было бы использовать Delphi или C++ или, по крайней мере, написать алгоритмы на одном из этих языков и подключать их к программам на Visual Basic при помощи библиотек?» Написание алгоритмов на Visual Basic имеет смысл по нескольким причинам.

Во‑первых, разработка приложения на Visual C++ гораздо сложнее и проблематичнее, чем на Visual Basic. Некорректная реализация в программе всех деталей программирования под Windows может привести к сбоям в вашем приложении, среде разработки, или в самой операционной системе Windows.

Во‑вторых, разработка библиотеки на языке C++ для использования в программах на Visual Basic включает в себя много потенциальных опасностей, характерных и для приложений Windows, написанных на C++. Если библиотека будет неправильно взаимодействовать с программой на Visual Basic, она также приведет к сбоям в программе, а возможно и в среде разработки и системе.

В-третьих, многие алгоритмы достаточно эффективны и показывают неплохую производительность даже при применении не очень быстрых компиляторов, таких, как Visual Basic. Например, алгоритм сортировки подсчетом,

@Таблица 1. Планы занятий

===============xvii

описываемый в 9 главе, сортирует миллион целых чисел менее чем за 2 секунды на компьютере с процессором Pentium с тактовой частотой 233 МГц. Используя библиотеку C++, можно было бы сделать алгоритм немного быстрее, но скорости версии на Visual Basic и так хватает для большинства приложений. Скомпилированные при помощи 5‑й версией Visual Basic исполняемые файлы сводят отставание по скорости к минимуму.

В конечном счете, разработка алгоритмов на любом языке программирования позволяет больше узнать об алгоритмах вообще. По мере изучения алгоритмов, вы освоите методы, которые сможете применять в других частях своих программ. После того, как вы овладеете в совершенстве алгоритмами на Visual Basic, вам будет гораздо легче реализовать их на Delphi или C++, если это будет необходимо.

=============xviii

Глава 1. Основные понятия

В этой главе содержатся общие понятия, которые нужно усвоить перед началом серьезного изучения алгоритмов. Начинается она с вопроса «Что такое алгоритмы?». Прежде чем углубиться в детали программирования алгоритмов, стоит потратить немного времени, чтобы разобраться в том, что это такое.

Затем в этой главе дается введение в формальную теорию сложности алгоритмов (complexity theory). При помощи этой теории можно оценить теоретическую вычислительную сложность алгоритмов. Этот подход позволяет сравнивать различные алгоритмы и предсказывать их производительность в разных условиях. В главе приводится несколько примеров применения теории сложности к небольшим задачам.

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

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

Что такое алгоритмы?

Алгоритм – это последовательность инструкций для выполнения какого‑либо задания. Когда вы даете кому‑то инструкции о том, как отремонтировать газонокосилку, испечь торт, вы тем самым задаете алгоритм действий. Конечно, подобные бытовые алгоритмы описываются неформально, например, так:

Проверьте, находится ли машина на стоянке.

Убедитесь, что машина поставлена на ручной тормоз.

Поверните ключ.

И т.д.

==========1

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

Если же составляется алгоритм для исполнения компьютером, вы не можете полагаться на то, что компьютер поймет что‑либо, если это не описано заранее. Словарь компьютера (язык программирования) очень ограничен и все инструкции для компьютера должны быть сформулированы на этом языке. Поэтому для написания компьютерных алгоритмов используется формализованный стиль.

Интересно попробовать написать формализованный алгоритм для обычных ежедневных задач. Например, алгоритм вождения машины мог бы выглядеть примерно так:

Если дверь закрыта:

Вставить ключ в замок

Повернуть ключ

Если дверь остается закрытой, то:

Повернуть ключ в другую сторону

Повернуть ручку двери

И т.д.

Этот фрагмент «кода» отвечает только за открывание двери; при этом даже не проверяется, какая дверь открывается. Если дверь заело или в машине установлена противоугонная система, то алгоритм открывания двери может быть достаточно сложным.

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

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

Анализ скорости выполнения алгоритмов

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

Пространство — время [RP1]

Многие алгоритмы предоставляют выбор между скоростью выполнения и используемыми программой ресурсами. Задача может выполняться

быстрее, используя больше памяти, или наоборот, медленнее, заняв меньший объем памяти.

===========2

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

При этом мы получим результат практически мгновенно, но это потребует большого объема памяти. Карта улиц для большого города, такого как Бостон или Денвер, может содержать сотни тысяч точек. Для такой сети таблица кратчайших расстояний содержала бы более 10 миллиардов записей. В этом случае выбор между временем исполнения и объемом требуемой памяти очевиден: поставив дополнительные 10 гигабайт оперативной памяти, можно заставить программу выполняться гораздо быстрее.

Из этой связи вытекает идея пространственно‑временной сложности алгоритмов. При этом подходе сложность алгоритма оценивается в терминах времени и пространства, и находится компромисс между ними.

В этом материале основное внимание уделяется временной сложности, но мы также постарались обратить внимание и на особые требования к объему памяти для некоторых алгоритмов. Например, сортировка слиянием (mergesort), обсуждаемая в 9 главе, требует больше временной памяти. Другие алгоритмы, например пирамидальная сортировка (heapsort), которая также обсуждается в 9 главе, требует обычного объема памяти.

Оценка с точностью до порядка

При сравнении различных алгоритмов важно понимать, как сложность алгоритма соотносится со сложностью решаемой задачи. При расчетах по одному алгоритму сортировка тысячи чисел может занять 1 секунду, а сортировка миллиона — 10 секунд, в то время как расчеты по другому алгоритму могут потребовать 2 и 5 секунд соответственно. В этом случае нельзя однозначно сказать, какая из двух программ лучше — это будет зависеть от исходных данных.

Различие производительности алгоритмов на задачах разной вычислительной сложности часто более важно, чем просто скорость алгоритма. В вышеприведенном случае, первый алгоритм быстрее сортирует короткие списки, а второй — длинные.

Производительность алгоритма можно оценить по порядку величины. Алгоритм имеет сложность порядка O(f(N)) (произносится «О большое от F от N»), если время выполнения алгоритма растет пропорционально функции f(N) с увеличением размерности исходных данных N. Например, рассмотрим фрагмент кода, сортирующий положительные числа:

For I = 1 To N

'Поиск наибольшего элемента в списке.

MaxValue = 0

For J = 1 to N

If Value(J) > MaxValue Then

MaxValue = Value(J)

MaxJ = J

End If

Next J

'Вывод наибольшего элемента на печать.

Print Format$(MaxJ) & ":" & Str$(MaxValue)

'Обнуление элемента для исключения его из дальнейшего поиска.

Value(MaxJ) = 0

Next I

===============3

В этом алгоритме переменная цикла I последовательно принимает значения от 1 до N. Для каждого приращения I переменная J в свою очередь также принимает значения от 1 до N. Таким образом, в каждом внешнем цикле выполняется еще N внутренних циклов. В итоге внутренний цикл выполняется N*N или N2 раз и, следовательно, сложность алгоритма порядка O(N2 ).

При оценке порядка сложности алгоритмов используется только наиболее быстро растущая часть уравнения алгоритма. Допустим, время выполнения алгоритма пропорционально N3 +N. Тогда сложность алгоритма будет равна O(N3 ). Отбрасывание медленно растущих частей уравнения позволяет оценить поведение алгоритма при увеличении размерности данных задачи N.

При больших N вклад второй части в уравнение N3 +N становится все менее заметным. При N=100, разность N3 +N=1.000.100 и N3 равна всего 100, или менее чем 0,01 процента. Но это верно только для больших N. При N=2, разность между N3 +N =10 и N3 =8 равна 2, а это уже 20 процентов.

Постоянные множители в соотношении также игнорируются. Это позволяет легко оценить изменения в вычислительной сложности задачи. Алгоритм, время выполнения которого пропорционально 3*N2 , будет иметь порядок O(N2 ). Если увеличить N в 2 раза, то время выполнения задачи возрастет примерно в 22 , то есть в 4 раза.

Игнорирование постоянных множителей позволяет также упростить подсчет числа шагов алгоритма. В предыдущем примере внутренний цикл выполняется N2 раз, при этом внутри цикла выполняется несколько инструкций. Можно просто подсчитать число инструкций If, можно подсчитать также инструкции, выполняемые внутри цикла или, кроме того, еще и инструкции во внешнем цикле, например операторы Print.

Вычислительная сложность алгоритма при этом будет пропорциональна N2 , 3*N2 или 3*N2 +N. Оценка сложности алгоритма по порядку величины даст одно и то же значение O(N3 ) и отпадет необходимость в точном подсчете количества операторов.

Поиск сложных частей алгоритма

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

============4

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

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

Если процедура Slow вызывается в каждом цикле процедуры Fast, порядки сложности процедур перемножаются. В этом случае сложность алгоритма равна произведению O(N2 ) и O(N3 ) или O(N3 *N2 )=O(N5 ). Приведем иллюстрирующий этот случай фрагмент кода:

Sub Slow()

Dim I As Integer

Dim J As Integer

Dim K As Integer

For I = 1 To N

For J = 1 To N

For K = 1 To N

' Выполнить какие‑либо действия.

Next K

Next J

Next I

End Sub

Sub Fast()

Dim I As Integer

Dim J As Integer

Dim K As Integer

For I = 1 To N

For J = 1 To N

Slow ' Вызов процедуры Slow.

Next J

Next I

End Sub

Sub MainProgram()

Fast

End Sub

С другой стороны, если процедуры независимо вызываются из основной программы, их вычислительная сложность суммируется. В этом случае полная сложность будет равна O(N3 )+O(N2 )=O(N3 ). Такую сложность, например, будет иметь следующий фрагмент кода:

Sub Slow()

Dim I As Integer

Dim J As Integer

Dim K As Integer

For I = 1 To N

For J = 1 To N

For K = 1 To N

' Выполнить какие‑либо действия.

Next K

Next J

Next I

End Sub

Sub Fast()

Dim I As Integer

Dim J As Integer

For I = 1 To N

For J = 1 To N

' Выполнить какие‑либо действия.

Next J

Next I

End Sub

Sub MainProgram()

Slow

Fast

End Sub

==============5

Сложность рекурсивных алгоритмов

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

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

Sub CountDown(N As Integer)

If N <= 0 Then Exit Sub

CountDown N - 1

End Sub

===========6

Многократная рекурсия

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

Нижеприведенная подпрограмма похожа на предыдущую подпрограмму CountDown, только она вызывает саму себя дважды:

Sub DoubleCountDown(N As Integer)

If N <= 0 Then Exit Sub

DoubleCountDown N - 1

DoubleCountDown N - 1

End Sub

Можно было бы предположить, что время выполнения этой процедуры будет в два раза больше, чем для подпрограммы CountDown, и оценить ее сложность порядка 2*O(N)=O(N). На самом деле ситуация немного сложнее.

Если T(N) — число раз, которое выполняется процедура DoubleCountDown с параметром N, то легко заметить, что T(0)=1. Если вызвать процедуру с параметром N равным 0, то она просто закончит свою работу после первого шага.

Для больших значений N процедура вызывает себя дважды с параметром, равным N-1, выполняясь 1+2*T(N-1) раз. В табл. 1.1 приведены некоторые значения функции T(0)=1 и T(N)=1+2*T(N-1). Если обратить внимание на эти значения, можно увидеть, что T(N)=2(N+1) -1, что дает оценку сложности процедуры порядка O(2N ). Хотя процедуры CountDown и DoubleCountDown и похожи, вторая процедура требует выполнения гораздо большего числа шагов.

@Таблица 1.1. Значения функции времени выполнения для подпрограммы DoubleCountDown

Косвенная рекурсия

Процедура также может вызывать другую процедуру, которая в свою очередь вызывает первую. Такие процедуры иногда даже сложнее анализировать, чем процедуры с множественной рекурсией. Алгоритм вычисления кривой Серпинского, который обсуждается в 5 главе, включает в себя четыре процедуры, которые используют как множественную, так и непрямую рекурсию. Каждая из этих процедур вызывает себя и другие три процедуры до четырех раз. После довольно сложных подсчетов можно показать, что этот алгоритм имеет сложность порядка O(4N ).

Требования рекурсивных алгоритмов к объему памяти

Для некоторых рекурсивных алгоритмов важен объем доступной памяти. Можно легко написать рекурсивный алгоритм, который будет запрашивать

============7

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

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

Приведенная ниже подпрограмма запрашивает память при каждом вызове. После 100 или 200 рекурсивных вызовов, процедура займет всю свободную память, и программа аварийно остановится с ошибкой «Out of Memory».

Sub GobbleMemory(N As Integer)

Dim Array() As Integer

ReDim Array (1 To 32000)

GobbleMemory N + 1

End Sub

Даже если внутри процедуры память не запрашивается, система выделяет память из системного стека (system stack) для сохранения параметров при каждом вызове процедуры. После возврата из процедуры память из стека освобождается для дальнейшего использования.

Если в подпрограмме встречается длинная последовательность рекурсивных вызовов, программа может исчерпать стек, даже если выделенная программе память еще не вся использована. Если запустить на исполнение следующую подпрограмму, она быстро исчерпает всю свободную стековую память и программа аварийно прекратит работу с сообщением об ошибке «Out of stack Space». После этого вы сможете узнать значение переменной Count, чтобы узнать, сколько раз подпрограмма вызывала себя перед тем, как исчерпать стек.

Sub UseStack()

Static Count As Integer

Count = Count + 1

UseStack

End Sub

Определение локальных переменных внутри подпрограммы также может занимать память из стека. Если изменить подпрограмму UseStack из предыдущего примера так, чтобы она определяла три переменных при каждом вызове, программа исчерпает стековое пространство еще быстрее:

Sub UseStack()

Static Count As Integer

Dim I As Variant

Dim J As Variant

Dim K As Variant

Count = Count + 1

UseStack

End Sub

В 5 главе рекурсивные алгоритмы обсуждаются более подробно.

==============8

Наихудший и усредненный случай

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

Function LocateItem(target As Integer) As Integer

For I = 1 To N

If Value(I) = target Then Exit For

Next I

LocateItem = I

End Sub

Если искомый элемент находится в конце списка, придется перебрать все N элементов для того, чтобы его найти. Это займет N шагов, значит сложность алгоритма порядка O(N). В этом, так называемом наихудшем случае (worst case) время выполнения алгоритма будет наибольшим.

С другой стороны, если искомое число в начале списка, алгоритм завершит работу практически сразу, совершив всего несколько итераций. Это так называемый наилучший случай (best case) со сложностью порядка O(1). Обычно и наилучший, и наихудший случаи встречаются относительно редко, и интерес представляет оценка усредненного или ожидаемого (expected case) поведения.

Если первоначально числа в списке распределены случайно, искомый элемент может оказаться в любом месте списка. В среднем потребуется проверить N/2 элементов для того, чтобы его найти. Значит, сложность этого алгоритма в усредненном случае порядка O(N/2), или O(N), если убрать постоянный множитель.

Для некоторых алгоритмов порядок сложности для наихудшего и наилучшего вариантов различается. Например, сложность алгоритма быстрой сортировки из 9 главы в наихудшем случае порядка O(N2 ), но в среднем его сложность порядка O(N*log(N)), что намного быстрее. Иногда алгоритмы типа быстрой сортировки бывают очень длинными, чтобы наихудший случай достигался крайне редко.

Часто встречающиеся функции оценки порядка сложности

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

==============9

@Таблица 1.2. Часто встречающиеся функции оценки порядка сложности

Сложность алгоритма, определяемая уравнением, которое представляет собой сумму функций из таблицы, будет сводиться к сложности той из функций, которая расположена в таблице ниже. Например, O(log(N)+N2 ) — это то же самое, что и O(N2 ).

Обычно алгоритмы со сложностью порядка N*log(N) и менее сложных функций выполняются очень быстро. Алгоритмы порядка NC при малых C, например N2 выполняются достаточно быстро. Вычислительная же сложность алгоритмов, порядок которых определяется функциями CN или N! очень велика и эти алгоритмы пригодны только для решения задач с небольшим N.

В качестве примера в табл. 1.3 показано, как долго компьютер, выполняющий миллион инструкций в секунду, будет выполнять некоторые медленные алгоритмы. Из таблицы видно, что при сложности порядка O(CN ) могут быть решены только небольшие задачи, и еще меньше параметр N может быть для задач со сложностью порядка O(N!). Для решения задачи порядка O(N!) при N=24 потребовалось бы время, большее, чем время существования вселенной.

Логарифмы

Перед тем, как продолжить дальше, следует остановиться на логарифмах, так как они играют важную роль в различных алгоритмах. Логарифм числа N по основанию B это степень P, в которую надо возвести основание, чтобы получить N, то есть BP =N. Например, если 23 =8, то соответственно log2 (8)=3.

==================10

@Таблица 1.3. Время выполнения сложных алгоритмов

Можно привести логарифм к другому основанию при помощи соотношения logB (N)=logC (N)/logC (B). Например, чтобы вычислить логарифм числа по основанию 10, зная его логарифм по основанию 2, можно воспользоваться формулой log10 (N)=log2 (N)/log2 (10). При этом log2 (10) — это табличная константа, примерно равная 3,32. Так как постоянные множители при оценке сложности алгоритма можно опустить, то O(log2 (N)) — это же самое, что и O(log10 (N)) или O(logB (N)) для любого B. Поскольку основание логарифма не имеет значения, часто просто пишут, что сложность алгоритма порядка O(log(N)).

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

Реальные условия — насколько быстро?

Хотя при исследовании сложности алгоритма обычно полезно отбросить малые члены уравнения и постоянные множители, иногда их все‑таки необходимо учитывать, особенно если размерность данных задачи N мала, а постоянные множители достаточно велики.

Допустим, мы рассматриваем два алгоритма решения одной задачи. Один выполняется за время порядка O(N), а другой — порядка O(N2 ). Для больших N первый алгоритм, вероятно, будет работать быстрее.

Тем не менее, если взять конкретные функции оценки времени выполнения для каждого из двух алгоритмов, например, для первого f(N)=30*N+7000, а для второго f(N)=N2 , то в этом случае при N меньше 100 второй алгоритм будет выполняться быстрее. Поэтому, если известно, что размерность данных задачи не будет превышать 100, возможно будет целесообразнее применить второй алгоритм.

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

==================11

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

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

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

Обращение к файлу подкачки

Важным фактором при работе в реальных условиях является частота обращения к файлу подкачки (page file). Операционная система Windows отводит часть дискового пространства под виртуальную память (virtual memory). Когда исчерпывается оперативная память, Windows сбрасывает часть ее содержимого на диск. Освободившаяся память предоставляется программе. Этот процесс называется подкачкой, поскольку страницы, сброшенные на диск, могут быть подгружены системой обратно в память при обращении к ним.

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

Приведенная в числе примеров программа Pager запрашивает все больше и больше памяти под создаваемые массивы до тех пор, пока программа не начнет обращаться к файлу подкачки. Введите количество памяти в мегабайтах, которое программа должна запросить, и нажмите кнопку Page (Подкачка). Если ввести небольшое значение, например 1 или 2 Мбайт, программа создаст массив в оперативной памяти, и будет выполняться быстро.

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

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

============12

Если же вы нажмете на кнопку Thrash (Пробуксовка), программа будет случайно обращаться к разным участкам памяти. При этом вероятность того, что нужная страница находится в этот момент на диске, намного возрастает. Это избыточное обращение к файлу подкачки называется пробуксовкой [RP2] памяти (thrashing). В табл. 1.4 приведено время исполнения программы Pager на компьютере с процессором Pentium с тактовой частотой 90 МГц и 24 Мбайт оперативной памяти. В зависимости от конфигурации вашего компьютера, скорости работы с диском, количества установленной оперативной памяти, а также наличия других запущенных параллельно приложений время выполнения программы может сильно различаться.

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

Для уменьшения числа обращений к файлу подкачки есть несколько способов. Основной прием — экономное расходование памяти. При этом надо помнить, что программа обычно не может занять всю физическую память, потому что часть ее занимает система и другие программы. Компьютер, на котором были получены результаты, приведенные в табл. 1.4, начинал интенсивно обращаться к диску, когда программа занимала 20 Мбайт из 24 Мбайт физической памяти.

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

@Таблица 1.4. Время выполнения программы Pager в секундах

===============13

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

Псевдоуказатели, ссылки на объекты и коллекции

В некоторых языках, например в C, C++ или Delphi, можно определять переменные, которые являются указателями (pointers) на участки памяти. В этих участках могут содержаться массивы, строки, или другие структуры данных. Часто указатель ссылается на структуру, которая содержит другой указатель и так далее. Используя структуры, содержащие указатели, можно организовывать всевозможные списки, графы, сети и деревья. В последующих главах рассматриваются некоторые из этих сложных структур.

До третьей версии Visual Basic не содержал средств для прямого создания ссылок. Тем не менее, поскольку указатель всего лишь ссылается на какой‑либо участок данных, то можно, создав массив, использовать целочисленный индекс массива в качестве указателя на его элементы. Это называется псевдоуказателем (fake pointer).

Ссылки

В 4-й версии Visual Basic были впервые введены классы. Переменная, указывающая на экземпляр класса, является ссылкой на объект. Например, в следующем фрагменте кода переменная obj — это ссылка на объект класса MyClass. Эта переменная не указывает ни на какой объект, пока она не определяется при помощи зарезервированного слова New. Во второй строке оператор New создает новый объект и записывает ссылку на него в переменную obj.

Dim obj As MyClass

Set obj = New MyClass

Ссылки в Visual Basic — это разновидность указателей.

Объекты в Visual Basic используют счетчик ссылок (reference counter) для упрощения работы с объектами. Когда создается новая ссылка на объект, счетчик ссылок увеличивается на единицу. После того, как ссылка перестает указывать на объект, счетчик ссылок соответственно уменьшается. Когда счетчик ссылок становится равным нулю, объект становится недоступным программе. В этот момент Visual Basic уничтожает объект и возвращает занятую им память.

В следующих главах более подробно обсуждаются ссылки и счетчики ссылок.

Коллекции

Кроме объектов и ссылок, в 4-й версии Visual Basic также появились коллекции. Коллекцию можно представить как разновидность массива. Они

================14

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

Вопросы производительности

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

Программа Faker на диске с примерами демонстрирует взаимосвязь между псевдоуказателями, ссылками и коллекциями. Когда вы вводите число и нажимаете кнопку Create List (Создать список), программа создает список элементов одним из трех способов. Вначале она создает объекты, соответствующие отдельным элементам, и добавляет ссылки на объекты к коллекции. Затем она использует ссылки внутри самих объектов для создания связанного списка объектов. И, наконец, она создает связный список при помощи псевдоуказателей. Пока не будем останавливаться на том, как работают связные списки. Они будут подробно разбираться во 2 главе.

После нажатия на кнопку Search List (Поиск в списке), программа Faker выполняет поиск по всем элементам списка, а после нажатия на кнопку Destroy List (Уничтожить список) уничтожает все списки и освобождает память.

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

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

При использовании псевдоуказателей создание и уничтожение списка происходит так быстро, что этим временем можно практически пренебречь. Системе при этом не надо заботиться о ссылках, счетчиках ссылок и об освобождении объектов.

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

@Таблица 1.5. Время Создания/Поиска/Уничтожения списков в секундах

==============15

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

Резюме

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

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

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

==============16

Глава 2. Списки

Существует четыре основных способа распределения памяти в Visual Basic: объявление переменных стандартных типов (целые, с плавающей точкой и т.д.); объявление переменных типов, определенных пользователем; создание экземпляров классов при помощи оператора New и изменение размера массивов. Существует еще несколько способов, например, создание нового экземпляра формы или элемента управления, но эти способы не дают больших возможностей при создании сложных структур данных.

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

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

В этой главе описаны методы создания динамических списков в Visual Basic. Различные типы списков обладают разными свойствами. Некоторые из них просты и обладают ограниченной функциональностью, другие же, такие как циклические списки, одно‑ или двусвязные списки, являются более сложными и поддерживают более развитые средства управления данными.

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

Знакомство со списками

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

=============17

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

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

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

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

Простые списки

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

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

Коллекции

Программа может использовать коллекции Visual Basic для хранения списка переменного размера. Метод Add Item добавляет элемент в коллекцию. Метод Remove удаляет элемент. Следующий фрагмент кода демонстрирует программу, которая добавляет три элемента к коллекции и затем удаляет второй элемент.

Dim list As New Collection

Dim obj As MyClass

Dim I As Integer

‘ Создать и добавить 1 элемент.

Set obj = New MyClass

list.Add obj

‘ Добавить целое число.

i = 13

list.Add I

‘ Добавить строку.

list.Add "Работа с коллекциями"

‘ Удалить 2 элемент (целое число).

list.Remove 2

===============18

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

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

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

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

Список переменного размера

Оператор Visual Basic ReDim позволяет изменять размер массива. Вы можете использовать это свойство для построения простого списка переменного размера. Начните с объявления безразмерного массива для хранения элементов списка. Также определите переменную NumInList для отслеживания числа элементов в списке. При добавлении элементов к списку используйте оператор ReDim для увеличения размера массива, чтобы новый элемент мог поместиться в нем. При удалении элемента также используйте оператор ReDim для уменьшения массива и освобождения ненужной больше памяти.

Dim List() As String ‘ Список элементов.

Dim NumInList As Integer ‘ Число элементов в списке.

Sub AddToList(value As String)

‘ Увеличить размер массива.

NumInList = NumInList + 1

ReDim Preserve List (1 To NumInList)

‘ Добавить новый элемент к концу списка.

List(NumInList) = value

End Sub

Sub RemoveFromList()

‘ Уменьшить размер массива, освобождая память.

NumInList = NumInList – 1

ReDim Preserve List (1 To NumInList)

End Sub

==================19

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

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

Подобным же образом можно избежать изменения размера массива при каждом удалении элемента из списка. Можно подождать, пока в массиве не накопится 20 неиспользуемых ячеек, прежде чем уменьшать его размер. При этом нужно оставить 10 свободных ячеек для того, чтобы можно было добавлять новые элементы без необходимости снова увеличивать размер массива.

Заметим, что максимальное число неиспользуемых ячеек (20) должно быть больше, чем минимальное число (10). Это уменьшает число изменений размера массива при удалении или добавлении его элементов.

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

Dim List() As String ‘ Список элементов.

Dim ArraySize As Integer ‘ Размер массива.

Dim NumInList As Integer ‘ Число используемых элементов.

‘ Если массив заполнен, увеличить его размер, добавив 10 ячеек.

‘ Затем добавить новый элемент в конец списка.

Sub AddToList(value As String)

NumInList = NumInList + 1

If NumInList > ArraySize Then

ArraySize = ArraySize + 10

ReDim Preserve List(1 To ArraySize)

End If

List(NumInList) = value

End Sub

‘ Удалить последний элемент из списка. Если осталось больше

‘ 20 пустых ячеек, уменьшить список, освобождая память.

Sub RemoveFromList()

NumInList = NumInList – 1

If ArraySize – NumInList > 20 Then

ArraySize = ArraySize –10

ReDim Preserve List(1 To ArraySize)

End If

End Sub

=============20

Для очень больших массивов это решение может также оказаться не самым лучшим. Если вам нужен список, содержащий 1000 элементов, к которому обычно добавляется по 100 элементов, то все еще слишком много времени будет тратиться на изменение размера массива. Очевидной стратегией в этом случае было бы увеличение приращения размера массива с 10 до 100 или более ячеек. Тогда можно было бы добавлять по 100 элементов одновременно без частого изменения размера списка.

Более гибким решением будет изменение приращения в зависимости от размера массива. Для небольших списков это приращение было бы также небольшим. Хотя изменения размера массива происходили бы чаще, они потребовали бы относительно немного времени для небольших массивов. Для больших списков, приращение размера будет больше, поэтому их размер будет изменяться реже.

Следующая программа пытается поддерживать примерно 10 процентов списка свободным. Когда массив заполняется, его размер увеличивается на 10 процентов. Если свободное пространство составляет более 20 процентов от размера массива, программа уменьшает его.

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

Const WANT_FREE_PERCENT = .1 ‘ 10% свободного места.

Const MIN_FREE = 10 ‘ Минимальное число пустых ячеек.

Global List() As String ‘ Массив элементов списка.

Global ArraySize As Integer ‘ Размер массива.

Global NumItems As Integer ‘ Число элементов в списке.

Global ShrinkWhen As Integer ‘ Уменьшить размер, если NumItems < ShrinkWhen.

‘ Если массив заполнен, увеличить его размер.

‘ Затем добавить новый элемент в конец списка.

Sub Add(value As String)

NumItems = NumItems + 1

If NumItems > ArraySize Then ResizeList

List(NumItems) = value

End Sub

‘ Удалить последний элемент из списка.

‘ Если в массиве много пустых ячеек, уменьшить его размер.

Sub RemoveLast()

NumItems = NumItems – 1

If NumItems < ShrinkWhen Then ResizeList

End Sub

‘ Увеличить размер массива, чтобы 10% ячеек были свободны.

Sub ResizeList()

Dim want_free As Integer

want_free = WANT_FREE_PERCENT * NumItems

If want_free < MIN_FREE Then want_free = MIN_FREE

ArraySize = NumItems + want_free

ReDim Preserve List(1 To ArraySize)

‘ Уменьшить размер массива, если NumItems < ShrinkWhen.

ShrinkWhen = NumItems – want_free

End Sub

===============21

Класс SimpleList

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

Классы Visual Basic могут сильно облегчить выполнение этой задачи. Класс SimpleList инкапсулирует эту структуру списка, упрощая управление списками. В этом классе присутствуют методы Add и Remove для использования в основной программе. В нем также есть процедуры извлечения свойств NumItems и ArraySize, с помощью которых программа может определить число элементов в списке и объем занимаемой им памяти.

Процедура ResizeList объявлена как частная внутри класса SimpleList. Это скрывает изменение размера списка от основной программы, поскольку этот код должен использоваться только внутри класса.

Используя класс SimpleList, легко создать в приложении несколько списков. Для того чтобы создать новый объект для каждого списка, просто используется оператор New. Каждый из объектов имеет свои переменные, поэтому каждый из них может управлять отдельным списком:

Dim List1 As New SimpleList

Dim List2 As New SimpleList

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

=============22

Программа SimList добавляет к массиву еще 50 процентов пустых ячеек, если необходимо увеличить его размер, и всегда оставляет при этом не менее 1 пустой ячейки. Эти значения был выбраны для удобства работы с программой. В реальном приложении, процент свободной памяти должен быть меньше, а число свободных ячеек больше. Более разумным в таком случае было бы выбрать значения порядка 10 процентов от текущего размера списка и минимум 10 свободных ячеек.

Неупорядоченные списки

В некоторых приложениях может понадобиться удалять элементы из середины списка, добавляя при этом элементы в конец списка. В этом случае порядок расположения элементов может быть не важен, но при этом может быть необходимо удалять определенные элементы из списка. Списки такого типа называются неупорядоченными списками (unordered lists). Они также иногда называются «множеством элементов». [RP3]

Неупорядоченный список должен поддерживать следующие операции:

* добавление элемента к списку;

* удаление элемента из списка;

* определение наличия элемента в списке;

* выполнение каких‑либо операций (например, вывода на дисплей или принтер) для всех элементов списка.

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

Удаление из массива элемента при таком подходе может занять достаточно много времени, особенно если удаляется элемент в начале списка. Чтобы удалить первый элемент из массива с 1000 элементов, потребуется сдвинуть влево на одну позицию 999 элементов. Гораздо быстрее удалять элементы можно при помощи простой схемы чистки памяти ( garbage collection)[RP4] .

Вместо удаления элементов из списка, пометьте их как неиспользуемые. Если элементы списка — данные простых типов, например целые, можно помечать элементы, используя определенное, так называемое «мусорное» значение (garbage value).

@Рисунок 2.1 Удаление элемента из середины массива

===========23

Для целых чисел можно использовать для этого значение ‑32.767. Для переменной типа Variant можно использовать значение NULL. Это значение присваивается каждому неиспользуемому элементу. Следующий фрагмент кода демонстрирует удаление элемента из подобного целочисленного списка:

Const GARBAGE_VALUE = -32767

‘ Пометить элемент как неиспользуемый.

Sub RemoveFromList(position As Long)

List(position) = GARBAGE_VALUE

End Sub

Если элементы списка — это структуры, определенные оператором Type, вы можете добавить к такой структуре новое поле IsGarbage. Когда элемент удаляется из списка, значение поля IsGarbage устанавливается в True.

Type MyData

Name As Sring ‘ Данные.

IsGarbage As Integer ‘ Этот элемент не используется?

End Type

‘ Пометить элемент, как не использующийся.

Sub RemoveFromList (position As Long)

List(position).IsGarbage = True

End Sub

Для простоты далее в этом разделе предполагается, что элементы данных являются данными универсального типа и их можно помечать значением NULL.

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

‘ Печать элементов списка.

Sub PrintItems()

Dim I As Long

For I = 1 To ArraySize

If Not IsNull(List(I)) [RP5] Then ‘ Если элемент не помечен

Print Str$(List(I)) ‘ напечатать его.

End If

Next I

End Sub

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

=============24

Для того, чтобы избежать этого, можно периодически запускать процедуру очистки памяти (garbage collection routine). Эта процедура перемещает все непомеченные записи в начало массива. После этого можно добавить их к свободным элементам в конце массива. Когда потребуется добавить к массиву дополнительные элементы, их также можно будет использовать без изменения размера массива.

После добавления помеченных элементов к другим свободным ячейкам массива, полный объем свободного пространства может стать достаточно большим, и в этом случае можно уменьшить размер массива, освобождая память:

Private Sub CollectGarbage()

Dim i As Long

Dim good As Long

good = 1 ‘ Первый используемый элемент.

For i = 1 To m_NumItems

‘ Если он не помечен, переместить его на новое место.

If Not IsNull(m_List(i)) Then

m_List(good) = m_list(i)

good = good + 1

End If

Next i

‘ Последний используемый элемент.

m_NumItems(good) = good - 1

‘ Необходимо ли уменьшать размер списка?

If m_NumItems < m_ShrinkWhen Then ResizeList

End Sub

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

Можно выбирать разные моменты для запуска процедуры чистки памяти. Один из них — когда массив достигает определенного размера, например, когда список содержит 30000 элементов.

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

===========25

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

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

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

Dim GarbageCount As Long ‘ Число ненужных элементов.

Dim MaxGarbage As Long ‘ Это значение определяется в ResizeList.

‘ Пометить элемент как ненужный.

‘ Если «мусора» слишком много, начать чистку памяти.

Public Sub Remove(position As Long)

m_List(position) = Null

m_GarbageCount = m_GarbageCount + 1

‘ Если «мусора» слишком много, начать чистку памяти.

If m_GarbageCount > m_MaxGarbage Then CollectGarbage

End Sub

Программа Garbage демонстрирует этот метод чистки памяти. Она пишет рядом с неиспользуемыми элементами списка слово «unused», а рядом с помеченными как ненужные — слово «garbage». Программа использует класс GarbageList примерно так же, как программа SimList использовала класс SimpleList, но при этом она еще осуществляет «сборку мусора».

Чтобы добавить элемент к списку, введите его значение и нажмите на кнопку Add (Добавить). Для удаления элемента выделите его, а затем нажмите на кнопку Remove (Удалить). Если список содержит слишком много «мусора», программа начнет выполнять чистку памяти.

При каждом изменении размера списка объекта GarbageList, программа выводит окно сообщения, в котором приводится число используемых и свободных элементов в списке, а также значения переменных MaxGarbage и ShrinkWhen. Если удалить достаточное количество элементов, так что больше, чем MaxGarbage элементов будут помечены как ненужные, программа начнет выполнять чистку памяти. После ее окончания, программа уменьшает размер массива, если он содержит меньше, чем ShrinkWhen занятых элементов.

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

==========26

Связные списки

Другая стратегия используется при управлении связанными списками. Связанный список хранит элементы в структурах данных или объектах, которые называются ячейками (cells). Каждая ячейка содержит указатель на следующую ячейку в списке. Так как единственный тип указателей, которые поддерживает Visual Basic — это ссылки на объекты, то ячейки в связном списке должны быть объектами.

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

Public EmpName As String

Public SSN As String

Public JobTitle As String

Public NextCell As EmpCell

Программа создает новые ячейки при помощи оператора New, задает их значения и соединяет их, используя переменную NextCell.

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

Dim top_cell As EmpCell

Dim cell1 As EmpCell

Dim cell2 As EmpCell

Dim cell3 As EmpCell

‘ Создание ячеек.

Set cell1 = New EmpCell

cell1.EmpName = "Стивенс”

cell1.SSN = "123-45-6789"

cell1.JobTitle = "Автор"

Set cell2 = New EmpCell

cell2.EmpName = "Кэтс”

cell2.SSN = "123-45-6789"

cell2.JobTitle = "Юрист"

Set cell3 = New EmpCell

cell3.EmpName = "Туле”

cell3.SSN = "123-45-6789"

cell3.JobTitle = "Менеджер"

‘ Соединить ячейки, образуя связный список.

Set cell1.NextCell = cell2

Set cell2.NextCell = cell3

Set cell3.NextCell = Nothing

‘ Сохранить ссылку на вершину списка.

Set top_cell = cell1

===============27

На рис. 2.2 показано схематическое представление этого связного списка. Прямоугольники представляют ячейки, а стрелки — ссылки на объекты. Маленький перечеркнутый прямоугольник представляет значение Nothing, которое обозначает конец списка. Имейте в виду, что top_cell, cell1 и cell2 – это не настоящие объекты, а только ссылки, которые указывают на них.

Следующий код использует связный список, построенный при помощи предыдущего примера для печати имен сотрудников из списка. Переменная ptr используется в качестве указателя на элементы списка. Она первоначально указывает на вершину списка. В коде используется цикл Do для перемещения ptr по списку до тех пор, пока указатель не дойдет до конца списка. Во время каждого цикла, процедура печатает поле EmpName ячейки, на которую указывает ptr. Затем она увеличивает ptr, указывая на следующую ячейку в списке. В конце концов, ptr достигает конца списка и получает значение Nothing, и цикл Do останавливается.

Dim ptr As EmpCell

Set ptr = top_cell ‘ Начать с вершины списка.

Do While Not (ptr Is Nothing)

‘ Вывести поле EmpName этой ячейки.

Debug.Print ptr.Empname

‘ Перейти к следующей ячейке в списке.

Set ptr = ptr.NextCell

Loop

После выполнения кода вы получите следующий результат:

Стивенс

Кэтс

Туле

@Рис. 2.2. Связный список

=======28

Использование указателя на другой объект называется косвенной адресацией (indirection), поскольку вы используете указатель для косвенного манипулирования данными. Косвенная адресация может быть очень запутанной. Даже для простого расположения элементов, такого, как связный список, иногда трудно запомнить, на какой объект указывает каждая ссылка. В более сложных структурах данных, указатель может ссылаться на объект, содержащий другой указатель. Если есть несколько указателей и несколько уровней косвенной адресации, вы легко можете запутаться в них

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

Добавление элементов к связному списку

Простой связный список, показанный на рис. 2.2, обладает несколькими важными свойствами. Во‑первых, можно очень легко добавить новую ячейку в начало списка. Установим указатель новой ячейки NextCell на текущую вершину списка. Затем установим указатель top_cell на новую ячейку. Рис. 2.3 соответствует этой операции. Код на языке Visual Basic для этой операции очень прост:

Set new_cell.NextCell = top_cell

Set top_cell = new_cell

@Рис. 2.3. Добавление элемента в начало связного списка

Сравните размер этого кода и кода, который пришлось бы написать для добавления нового элемента в начало списка, основанного на массиве, в котором потребовалось бы переместить все элементы массива на одну позицию, чтобы освободить место для нового элемента. Эта операция со сложностью порядка O(N) может потребовать много времени, если список достаточно длинный. Используя связный список, моно добавить новый элемент в начало списка всего за пару шагов.

======29

Так же легко добавить новый элемент и в середину связного списка. Предположим, вы хотите вставить новый элемент после ячейки, на которую указывает переменная after_me. Установим значение NextCell новой ячейки равным after_me.NextCell. Теперь установим указатель after_me.NextCell на новую ячейку. Эта операция показана на рис. 2.4. Код на Visual Basic снова очень прост:

Set new_cell.NextCell = after_me.NextCell

Set after_me.NextCell = new_cell

Удаление элементов из связного списка

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

Set top_cell = top_cell.NextCell

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

Так же просто удалить элемент из середины списка. Предположим, вы хотите удалить элемент, стоящий после ячейки after_me. Просто установите указатель NextCell этой ячейки на следующую ячейку. Эта операция показана на рис. 2.6. Код на Visual Basic прост и понятен:

after_me.NextCell = after_me.NextCell.NextCell

@Рис. 2.4. Добавление элемента в середину связного списка

=======30

@Рис. 2.5. Удаление элемента из начала связного списка

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

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

Уничтожение связного списка

Можно предположить, что для уничтожения связного списка необходимо обойти весь список, устанавливая значение NextCell для всех ячеек равным Nothing. На самом деле процесс гораздо проще: только top_cell принимает значение Nothing.

Когда программа устанавливает значение top_cell равным Nothing, счетчик ссылок для первой ячейки становится равным нулю, и Visual Basic уничтожает эту ячейку.

Во время уничтожения ячейки, система определяет, что в поле NextCell этой ячейки содержится ссылка на другую ячейку. Поскольку первый объект уничтожается, то число ссылок на второй объект уменьшается. При этом счетчик ссылок на второй объект списка становится равным нулю, поэтому система уничтожает и его.

Во время уничтожения второго объекта, система уменьшает число ссылок на третий объект, и так далее до тех пор, пока все объекты в списке не будут уничтожены. Когда в программе уже не будет ссылок на объекты списка, можно уничтожить и весь список при помощи единственного оператора Set top_cell = Nothing.

@Рис. 2.6. Удаление элемента из середины связного списка

========31

Сигнальные метки

Для добавления или удаления элементов из начала или середины списка используются различные процедуры. Можно свести оба этих случая к одному и избавиться от избыточного кода, если ввести специальную сигнальную метку (sentinel) в самом начале списка. Сигнальную метку нельзя удалить. Она не содержит данных и используется только для обозначения начала списка.

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

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

В табл. 2.1 сравнивается сложность выполнения некоторых типичных операций с использованием списков на основе массивов со «сборкой мусора» или связных списков.

Списки на основе массивов имеют одно преимущество: они используют меньше памяти. Для связных списков необходимо добавить поле NextCell к каждому элементу данных. Каждая ссылка на объект занимает четыре дополнительных байта памяти. Для очень больших массивов это может потребовать больших затрат памяти.

Программа LnkList1 демонстрирует простой связный список с сигнальной меткой. Введите значение в текстовое поле ввода, и нажмите на элемент в списке или на метку. Затем нажмите на кнопку Add After (Добавить после), и программа добавит новый элемент после указанного. Для удаления элемента из списка, нажмите на элемент и затем на кнопку Remove After (Удалить после).

@Таблица 2.1. Сравнение списков на основе массивов и связных списков

=========32

Инкапсуляция связных списков

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

Private Sub CmdRemoveAfter_Click()

Dim ptr As ListCell

Dim position As Integer

If SelectedIndex < 0 Then Exit Sub

‘ Найти элемент.

Set ptr = Sentinel

position = SelectedIndex

Do While position > 0

position = position - 1

Set ptr = ptr.nextCell

Loop

‘ Удалить следуюший элемент.

Set ptr.NextCell = ptr.NextCell.NextCell

NumItems = NumItems - 1

SelectItem SelectedIndex ‘ Снова выбрать элемент.

DisplayList

NewItem.SetFocus

End Sub

Чтобы упростить использование связного списка, можно инкапсулировать его функции в классе. Это реализовано в программе LnkList2 . Она аналогична программе LnkList1, но использует для управления списком класс LinkedList.

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

Это намного упрощает основную программу. Например, следующий код показывает, как программа LnkList2 удаляет элемент из списка. Только одна строка в программе в действительности отвечает за удаление элемента. Остальные отображают новый список. Сравните этот код с предыдущей процедурой:

Private sub CmdRemoveAfter_Click()

Llist.RemoveAfter SelectedIndex

SelectedItem SelectedList ‘ Снова выбрать элемент.

DisplayList

NewItem.SetFocus

CmdClearList.Enabled

End Sub

=====33

Доступ к ячейкам

Класс LinkedList, используемый программой LnkLst2, позволяет основной программе использовать список почти как массив. Например, подпрограмма Item, приведенная в следующем коде, возвращает значение элемента по его положению:

Function Item(ByVal position As Long) As Variant

Dim ptr As ListCell

If position < 1 Or position > m_NumItems Then

‘ Выход за границы. Вернуть NULL.

Item = Null

Exit Function

End If

‘ Найти элемент.

Set ptr = m_Sentinel

Do While position > 0

position = position - 1

Set ptr = ptr.NextCell

Loop

Item = ptr.Value

End Function

Эта процедура достаточно проста, но она не использует преимущества связной структуры списка. Например, предположим, что программе требуется последовательно перебрать все объекты в списке. Она могла бы использовать подпрограмму Item для поочередного доступа к ним, как показано в следующем коде:

Dim i As Integer

For i = 1 To LList.NumItems

‘ Выполнить какие‑либо действия с LList.Item(i).

:

Next i

При каждом вызове процедуры Item, она просматривает список в поиске следующего элемента. Чтобы найти элемент I, программа должна пропустить I‑1 элементов. Чтобы проверить все элементы в списке из N элементов, процедура пропустит 0+1+2+3+…+N-1 =N*(N-1)/2 элемента. При больших N программа потеряет много времени на пропуск элементов.

Класс LinkedList может ускорить эту операцию, используя другой метод доступа. Можно использовать частную переменную m_CurrentCell для отслеживания текущей позиции в списке. Для возвращения значения текущего положения используется подпрограмма CurrentItem. Процедуры MoveFirst, MoveNext и EndOfList позволяют основной программе управлять текущей позицией в списке.

=======34

Например, следующий код содержит подпрограмму MoveNext:

Public Sub MoveNext()

‘ Если текущая ячейка не выбрана, ничего не делать.

If Not (m_CurrentCell Is Nothing) Then _

Set m_CurrentCell = m_CurrentCell.NextCell

End Sub

При помощи этих процедур, основная программа может обратиться ко всем элементам списка, используя следующий код. Эта версия несколько сложнее, чем предыдущая, но она намного эффективнее. Вместо того чтобы пропускать N*(N-1)/2 элементов и опрашивать по очереди все N элементов списка, она не пропускает ни одного. Если список состоит из 1000 элементов, это экономит почти полмиллиона шагов.

LList.MoveFirst

Do While Not LList.EndOfList

‘ Выполнить какие‑либо действия над элементом LList.Item(i).

:

LList.MoveNext

Loop

Программа LnkList3 использует эти новые методы для управления связным списком. Она аналогична программе LnkList2, но более эффективно обращается к элементам. Для небольших списков, используемых в программе, эта разница незаметна. Для программы, которая обращается ко всем элементам большого списка, эта версия класса LinkedList более эффективна.

Разновидности связных списков

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

Циклические связные списки

Вместо того, чтобы устанавливать указатель NextCell равным Nothing, можно установить его на первый элемент списка, образуя циклический список (circular list), как показано на рис. 2.7.

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

===========35

@Рис. 2.7. Циклический связный список

‘ Здесь находится код для создания и настройки списка и т.д.

:

‘ Напечатать календарь на месяц.

‘ first_day — это индекс структуры, содержащей день недели для

‘ первого дня месяца. Например, месяц может начинаться

‘ в понедельник.

‘ num_days — число дней в месяце.

Private Sub ListMonth(first_day As Integer, num_days As Integer)

Dim ptr As ListCell

Dim i As Integer

Set ptr = top_cell

For i = 1 to num_days

Print Format$(i) & ": " & ptr.Value

Set ptr = ptr.NextCell

Next I

End Sub

Циклические списки также позволяют достичь любой точки в списке, начав с любого положения в нем. Это вносит в список привлекательную симметрию. Программа может обращаться со всеми элементами списка почти одинаковым образом:

Private Sub PrintList(start_cell As Integer)

Dim ptr As Integer

Set ptr = start_cell

Do

Print ptr.Value

Set ptr = ptr.NextCell

Loop While Not (ptr Is start_cell)

End Sub

========36

Проблема циклических ссылок

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

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

Проблема циклических ссылок может встретиться не только в этом случае. Многие сети содержат циклические ссылки — даже одиночная ячейка, поле NextCell которой указывает на саму эту ячейку, может вызвать эту проблему.

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

Set top_cell.NextCell = Nothing

Set top_cell = Nothing

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

Двусвязные списки

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

Добавим новое поле указателя к каждой ячейке, которое указывает на предыдущую ячейку в списке. Используя это новое поле, можно легко создать двусвязный список (doubly linked list), который позволяет перемещаться вперед и назад по списку. Теперь можно легко удалить ячейку, вставить ее перед другой ячейкой и перечислить ячейки в любом направлении.

@Рис. 2.8. Двусвязный список

============37

Класс DoubleListCell, который используется для таких типов списков, может объявлять переменные так:

Public Value As Variant

Public NextCell As DoubleListCell

Public PrevCell As DoubleListCell

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

На рис. 2.9 показан двусвязный список с сигнальными метками. На этом рисунке неиспользуемые указатели меток NextCell и PrevCell установлены в Nothing. Поскольку программа опознает концы списка, сравнивая значения указателей ячеек с сигнальными метками, и не проверяет, равны ли значения Nothing, установка этих значений равными Nothing не является абсолютно необходимой. Тем не менее, это признак хорошего стиля.

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

@Рис. 2.9. Двусвязный список с сигнальными метками

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


Работы, похожие на Реферат: VB, MS Access, VC++, Delphi, Builder C++ принципы(технология), алгоритмы программирования
Основы дискретной математики
Федеральное агентство по образованию Новомосковский институт (филиал) Государственного образовательного учреждения высшего профессионального ...
Дерево сортировки - бинарное дерево, каждый узел которого содержит ключ и обладает следующим свойством: значения ключа во всех узлах левого поддерева меньше, а во всех узлах ...
Сформируем два массива R и next размерности n, в которых R(i) - имя множества, содержащего элемент i, a next(i) указывает номер следующего элемента массива R, принадлежащего тому ...
Раздел: Рефераты по информатике, программированию
Тип: учебное пособие
Проектирование и разработка сетевых броузеров на основе теоретико ...
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ УКРАИНЫ ТАВРИЧЕСКИЙ НАЦИОНАЛЬНЫЙ УНИВЕРСИТЕТ им. В.И.Вернандского МАТЕМАТИЧЕСКИЙ ФАКУЛЬТЕТ КАФЕДРА ИНФОРМАТИКИ ...
D[w] - массив, в котором к концу работы алгоритма будут содержаться кратчайшие длины путей из х в w для всех вершин w X.
if List.Objects[i] <> nil then
Раздел: Рефераты по информатике, программированию
Тип: дипломная работа
Защита информации в системах дистанционного обучения с монопольным ...
АННОТАЦИЯ Данная диссертация посвящена вопросам построения систем защиты информации для программных пакетов, используемых в монопольном доступе. В ...
Dim handle As Integer
Private Sub Command1_Click(Index As Integer)
Раздел: Рефераты по информатике, программированию
Тип: реферат
Програмирование на Visual Basic
VISUAL BASIC 6 Глава 1. ТЕХНОЛОГИЯ ПРОГРАММИРОВАНИЯ НА VISUAL BASIC 4 В СРЕДЕ WINDOWS 1. 1. ЭКРАННЫЕ ЭЛЕМЕНТЫ После запуска Visual Basic на экране ...
Sub Check3 Click() IfCheck3.Value = 0 Then List!.Columns = 1 Else List 1.Columns = 2 End If
Private Sub Text3_KeyPress(keyAscii As Integer) Dim Filedata As String If keyAscii = 13 Then Filedata = Text3.Text
Раздел: Рефераты по кибернетике
Тип: реферат
Object Pascal
1. Основы языка Object Pascal 1.1. Алфавит языка Основными символами языка Object Pascal являются: - символы _ - 26 больших и 26 малых латинских букв ...
Indexes: set of integer = [300 .
В следующем примере в секции Var описаны простая переменная i и два одномерных массива A и V как целые переменные типа Integer.
Раздел: Рефераты по информатике
Тип: реферат
Организация документооборота с помощью "Visual Basic for ...
СОДЕРЖАНИЕ АНОТАЦИЯ ВВЕДЕНИЕ 1. ТЕОРЕТИЧЕСКИЙ РАЗДЕЛ. 1.1 Обоснование языка программирования 1.2 Введение в Visual Basic for Application 1.2.1 Об ...
Sub DemoDialogs() Dim idx As Long
Set fd = Application.FileDialog(msoFileDialogFilePicker) Dim itm As Variant With fd
Раздел: Рефераты по информатике, программированию
Тип: курсовая работа
Массивы. Двумерные массивы
Министерство образования и науки Республики Казахстан Казахстанский Инженерно Технологический университет (колледж) Кафедра "Вычислительная техника ...
Описывать массив DIM A(N) - это значит предоставить < N > свободных ячеек в памяти ЭВМ для массива с именем А. Если описание массива отсутствует, то под одномерный массив ...
i, j: integer; { индексы массива }
Раздел: Рефераты по информатике, программированию
Тип: курсовая работа
Сравнительный анализ алгоритмов построения выпуклой оболочки на ...
Аннотация Тема данной курсовой работы - " Сравнительный анализ алгоритмов построения выпуклой оболочки на плоскости". Для сравнения взяты четыре ...
... удалить цепочку вершин и вставить место нее p. Для этого нам нужно хранить многоугольник не писком, как это делалось в предыдущих алгоритмах, а AWL или другим сбалансированным ...
Для каждого узла в этом дереве мы должны иметь возможность получать доступ к самой левому узлу.
Раздел: Рефераты по математике
Тип: реферат
Программирование логической игры на visual basic
° by Valery V Shmeleff Moscow/Russia www.oflameron.ru (095)771-46-90 Руководство по разработке динамической логической игры на Visual Basic 6.0 ...
Массив Dim level(5)
Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
Раздел: Рефераты по информатике, программированию
Тип: реферат


5rik.ru - Материалы для учебы и научной работы