- •Предисловие
- •Введение
- •1Архитектура эвм
- •1.1 Биты и их хранение
- •1.1.1Вентили и триггеры
- •1.1.2Другие способы хранения битов
- •1.1.3Шестнадцатеричная система счисления
- •1.2 Оперативная память
- •1.2.1Структура памяти
- •1.2.2Измерение емкости памяти
- •1.3 Устройства хранения данных
- •1.3.1Магнитные диски
- •1.3.2Компакт-диски
- •1.3.3Магнитные ленты
- •1.3.4Хранение и поиск файлов
- •1.4 Представление информации в виде двоичного кода
- •1.4.1Представление текста
- •1.4.2Американский национальный институт стандартов
- •1.4.3Iso - международная организация по стандартизации
- •1.4.4Представление числовых значений
- •1.4.5Представление изображений
- •1.4.6Представление звука
- •1.5 Двоичная система счисления
- •1.5.1Альтернатива двоичной системе счисления
- •1.5.2Дроби в двоичной системе счисления
- •1.5.3Аналоговые и цифровые устройства
- •1.6 Хранение целых чисел
- •1.6.1Представление в двоичном дополнительном коде
- •1.6.2Сложение в двоичном дополнительном коде
- •1.6.3Проблема переполнения
- •1.6.4Представление с избытком
- •1.7 Хранение дробей
- •1.7.1Представление с плавающей точкой
- •1.7.2Ошибка усечения
- •1.8 Сжатие данных
- •1.8.1Общие методы сжатия данных
- •1.8.2Сжатие звука
- •1.8.3Сжатие изображений
- •1.9 Ошибки передачи данных
- •1.9.1Контрольный разряд четности
- •1.9.2Коды с исправлением ошибок
- •2Манипулирование данными
- •2.1 Архитектура эвм
- •2.1.1Сложение двух чисел, хранящихся в оперативной памяти
- •2.1.2Кто и что изобрел?
- •2.2 Машинный язык
- •2.2.1Система команд
- •2.2.2Кэш-память
- •2.2.3Арифметико-логические команды
- •2.2.4Команды управления
- •2.2.5Деление двух значений, хранящихся в памяти
- •2.3 Выполнение программы
- •2.3.1Пример выполнения программы
- •2.3.2Команды переменной длины
- •2.3.3Программы и данные
- •2.4 Арифметические и логические операции
- •2.4.1Логические операции
- •2.4.2Сравнение вычислительной мощности эвм
- •2.4.3Операции сдвига
- •2.4.4Арифметические операции
- •2.5 Связь с другими устройствами
- •2.5.1Связь через контроллер
- •2.5.2Строение шины
- •2.5.3Скорость передачи данных
- •2.6 Другие архитектуры
- •2.6.1Конвейерная обработка
- •3Операционные системы и организация сетей
- •3.13.1. Эволюция операционных систем
- •3.1.1Однопроцессорные системы
- •3.1.2Многопроцессорные системы
- •3.2 Архитектура операционной системы
- •3.2.1Программное обеспечение
- •3.2.2Полезное единообразие или вредная монополия?
- •3.2.3Компоненты операционной системы
- •3.2.4Операционная система linux
- •3.2.5Начало работы операционной системы
- •3.3 Координирование действий машины
- •3.3.1Понятие процесса
- •3.3.2Управление процессами
- •3.3.3Модель «клиент-сервер»
- •3.4 Обработка конкуренции между процессами
- •3.4.1Семафор
- •3.4.2Взаимная блокировка
- •3.5 Сети
- •3.5.1Основы организации сетей
- •3.5.2Интернет
- •3.5.3Топология сети Интернет
- •3.5.4Система адресов Интернета
- •3.5.5Электронная почта
- •3.5.6Всемирная паутина
- •3.6 Сетевые протоколы
- •3.6.1Управление правом отправки сообщений
- •3.6.2Сеть ethernet
- •3.6.3Javascript, апплеты, cgi и сервлеты
- •3.6.4Многоуровневый принцип программного обеспечения Интернета
- •3.6.5Комплект протоколов tcp/ip
- •3.6.6Протоколы рорз и imap
- •3.7 Безопасность
- •3.7.1Протокол защищенных сокетов
- •3.7.2Группа компьютерной «скорой помощи»
- •4Алгоритмы
- •4.1 Понятие алгоритма
- •4.1.1Предварительные замечания
- •4.1.2Формальное определение алгоритма
- •4.1.3Определение алгоритма
- •4.1.4Абстрактная природа алгоритма
- •4.2 Представление алгоритма
- •4.2.1Примитивы
- •4.2.2Псевдокод
- •4.3 Создание алгоритма
- •4.3.1Искусство решения задач
- •4.3.2Итеративные структуры в музыке
- •4.3.3Первый шаг в решении задачи
- •4.4 Итеративные структуры
- •4.4.1Алгоритм последовательного поиска
- •4.4.2Управление циклом
- •4.4.3Алгоритм сортировки методом вставок
- •4.5Рекурсивные структуры
- •4.5.1Поиск и сортировка
- •4.5.2Алгоритм двоичного поиска
- •4.5.3Управление рекурсивными структурами
- •4.6 Эффективность и правильность
- •4.6.1Эффективность алгоритма
- •4.6.2Проверка правильности программного обеспечения
- •4.6.3По ту сторону проверки правильности программ
- •5Языки программирования
- •5.1 Исторический обзор
- •5.1.1Ранние поколения
- •5.1.2Интерплатформенное программное обеспечение
- •5.1.3Независимость от машины
- •5.1.4Парадигмы программирования
- •5.2 Основные понятия традиционного программирования
- •5.2.1Культуры языков программирования
- •5.2.2Переменные и типы данных
- •5.2.3Структуры данных
- •5.2.4Константы и литералы
- •5.2.5Операторы присваивания
- •5.2.6Управляющие операторы
- •5.2.7Комментарии
- •5.3 Процедурные единицы
- •5.3.1Процедуры
- •5.3.2Событийно-управляемые программные системы
- •5.3.3Параметры
- •5.3.4Функции
- •5.3.5Операторы ввода-вывода
- •5.4 Реализация языка программирования
- •5.4.1Процесс трансляции программы
- •5.4.2Реализация java
- •5.4.3Компоновка и загрузка
- •5.4.4Пакеты разработки программного обеспечения
- •5.5 Объектно-ориентированное программирование
- •5.5.1Классы и объекты
- •5.5.3Конструкторы
- •5.5.4Дополнительные возможности
- •5.6 Параллельные операции
- •5.7 Декларативное программирование
- •5.7.1Логическая дедукция
- •5.7.2Язык программирования Prolog
- •6Разработка программного обеспечения
- •6.1 Разработка программного обеспечения
- •6.1.1Ассоциация по вычислительной технике
- •6.1.2Институт инженеров по электротехнике и электронике
- •6.2 Жизненный цикл программы
- •6.2.1Цикл как единое целое
- •6.2.2Разработка программного обеспечения на практике
- •6.2.3Этапы разработки программного обеспечения
- •6.2.4Анализ
- •6.2.5Проектирование
- •6.2.6Реализация
- •6.2.7Тестирование
- •6.2.8Современные тенденции
- •6.3 Модульность
- •6.3.1Модульная реализация программы
- •6.3.2Связь модулей системы
- •6.3.3Связность модуля
- •6.4 Методики проектирования
- •6.4.1Нисходящее и восходящее проектирование
- •6.4.2Модели проектирования
- •6.4.3Разработка открытых программных продуктов
- •6.5 Инструменты проектирования
- •6.6 Тестирование
- •6.7 Документация
- •6.8 Право собственности на программное обеспечение и ответственность
- •Часть 3 организация данных
- •7Структуры данных
- •7.1 Основы структур данных
- •7.1.1Опять абстракция
- •7.1.2Статические и динамические структуры
- •7.1.3Указатели
- •7.2 Массивы
- •7.3 Списки
- •7.3.1Непрерывные списки
- •7.3.2Реализация непрерывных списков
- •7.3.3Связные списки
- •7.3.4Поддержка абстрактного списка
- •7.4 Стеки
- •7.4.1Откат
- •7.4.2Реализация стека
- •7.5 Очереди
- •7.5.1Проблема указателей
- •7.6 Деревья
- •7.6.1Реализация дерева
- •7.6.2Сбор мусора
- •7.6.3Пакет бинарного дерева
- •7.7 Пользовательские типы данных
- •7.7.1Пользовательские типы
- •7.7.2Классы
- •7.7.3Описательное и процедурное знание
- •7.7.4Стандартная библиотека шаблонов
- •7.8 Указатели в машинном языке
- •8Файловые структуры
- •8.1 Роль операционной системы
- •8.1.1Таблицы размещения файлов
- •8.2 Последовательные файлы
- •8.2.1Обработка последовательных файлов
- •8.2.2Консорциум производителей программного обеспечения для www
- •8.2.3Текстовые файлы
- •8.2.4Текстовые и двоичные файлы
- •8.2.5Вопросы программирования
- •8.2.6Семантическая сеть
- •8.3 Индексация
- •8.3.1Основные положения индексации
- •8.3.2Вопросы программирования
- •8.3.3Расположение файлов на дисках
- •8.4 Хэширование
- •8.4.1Хэш-система
- •8.4.2Проблемы распределения
- •8.4.3Аутентификация посредством хэширования
- •8.4.4Вопросы программирования
- •9Структуры баз данных
- •9.1 Общие вопросы
- •9.2 Многоуровневый подход к реализации базы данных
- •9.2.1Система управления базой данных
- •9.2.2Распределенные базы данных
- •9.2.3Модели баз данных
- •9.3 Реляционная модель баз данных
- •9.3.1Вопросы реляционного проектирования
- •9.3.2Системы баз данных для персональных компьютеров
- •9.3.3Хронологические базы данных
- •9.3.4Реляционные операции
- •9.3.5Вопросы реализации
- •9.3.6Язык sql
- •9.4 Объектно-ориентированные базы данных
- •9.5 Поддержка целостности базы данных
- •9.5.1Пространственные базы данных
- •9.5.2Протоколы фиксации/отката изменений
- •9.5.3Блокировка
- •9.6 Воздействие технологий баз данных на общество
- •10Искусственный интеллект
- •10.1 Интеллект и машины
- •10.1.1Конечный результат или имитация
- •10.1.2Истоки искусственного интеллекта
- •10.1.3Тест Тьюринга
- •10.1.4Машина для решения головоломки из восьми фишек
- •10.2 Распознавание образов
- •10.3 Мышление
- •10.3.1Продукционные системы
- •10.3.2Интеллект, основанный на поведении
- •10.3.3Деревья поиска
- •10.3.4Эвристика
- •10.4 Искусственные нейронные сети
- •10.4.1Основные свойства
- •10.4.2Приложение теории
- •10.4.3Ассоциативная память
- •10.5 Генетические алгоритмы
- •10.6 Прочие области исследования
- •10.6.1Обработка лингвистической информации
- •10.6.2Рекурсия в естественных языках
- •10.6.3Роботы
- •10.6.4Системы баз данных
- •10.6.5Экспертные системы
- •10.7 Обдумывая последствия
- •10.7.1Сильный искусственный интеллект против слабого
- •11Теория вычислений
- •11.1 Функции и их вычисление
- •11.1.1Теория рекурсивных функций
- •11.2 Машины Тьюринга
- •11.2.1Основы машины Тьюринга
- •11.2.2Истоки машины Тьюринга
- •11.2.3Тезис Черча-Тьюринга
- •11.3 Универсальные языки программирования
- •11.3.1Скелетный язык
- •11.3.2Существуют ли инопланетяне?
- •11.3.3Универсальность скелетного языка
- •11.4 Невычислимая функция
- •11.4.1Проблема останова
- •11.4.2Неразрешимость проблемы останова
- •11.5 Сложность задач
- •11.5.1Измерение сложности задачи
- •11.5.2Пространственная сложность
- •11.5.3Полиномиальные и не полиномиальные задачи
- •11.5.5Детерминированность против недетерминированности
- •11.6Шифрование с открытым ключом
- •11.6.1Шифрование при помощи задачи о ранце
- •11.6.2Популярные системы шифрования
- •11.6.3Модульная арифметика
- •11.6.4Обратно к шифрованию
4.6 Эффективность и правильность
В этом разделе мы обсудим два понятия, о которых следует помнить при создании алгоритма. Первое понятие — это эффективность, а второе — правильность.
4.6.1Эффективность алгоритма
Хотя современные машины способны выполнять миллионы команд в секунду, эффективность остается главной проблемой при построении алгоритмов. Часто выбор между эффективным и неэффективным алгоритмом означает выбор между практичным и непрактичным решением задачи.
Рассмотрим задачу, стоящую перед секретарем университета, которому нужно просмотреть и обновить данные некоторого студента. Хотя в университете обучаются примерно 10 000 студентов, файл студентов содержит информацию более чем о 30 000 человек, которые считаются студентами по той причине, что они записались по крайней мере на один курс за последние несколько лет, но не закончили обучение. Предположим, что эти данные хранятся в компьютере секретаря в виде списка, упорядоченного по идентификационному номеру студента. Для того чтобы найти информацию о каком-либо студенте, секретарь должен отыскать в этом списке некоторый идентификационный номер.
Мы обсудили два алгоритма для поиска в таком списке: алгоритмы последовательного и двоичного поиска. Вопрос состоит в том, является ли существенным для секретаря выбор того или иного алгоритма. Начнем с рассмотрения алгоритма последовательного поиска.
При заданном идентификационном номере алгоритм последовательного поиска сравнивает этот номер с элементами списка с самого начала списка. Не зная ничего о происхождении искомого значения, мы не может сделать вывод, как далеко от начала списка оно находится. Однако можно сказать, что средняя глубина поиска составляет половину списка: некоторые номера находятся ближе к началу, другие ближе к концу списка. Мы заключаем, что за некоторый промежуток времени алгоритм последовательного поиска изучит примерно 15 000 записей. Если для извлечения идентификационного номера каждой записи требуется 10 мс, то поиск одного номера займет в среднем 150 с или 2,5 мин. Для секретаря это означает невыносимо долгое ожидание, пока информация о студенте появится на экране.
В отличие от алгоритма последовательного поиска, алгоритм двоичного поиска начинает работу со сравнения искомого значения с элементом, находящимся в середине списка. Если они не совпадают, тогда, по крайней мере, при дальнейшем поиске можно ограничиться только половиной списка. Следовательно, после рассмотрения среднего элемента списка, состоящего из 30 000 элементов, алгоритм должен обработать самое большее 15 000 записей. После второго запроса остается только 7500 записей, а после третьего извлечения номера рассматриваемый список сократится до 3750 элементов. Таким образом, искомая запись будет найдена (или не найдена) после извлечения не более 15 элементов списка. Следовательно, если извлечение номера выполняется за 10 мс, то процесс поиска отдельной записи займет только 0,15 с. То есть доступ к отдельной записи для секретаря покажется мгновенным. Можно сделать вывод, что выбор между алгоритмами последовательного и двоичного поиска является существенным для решения данной прикладной задачи1.
Этот пример иллюстрирует важность раздела вычислительной техники, который называется анализом алгоритмов и занимается изучением ресурсов, таких как время, объем памяти, требующихся для выполнения алгоритма. Такие исследования применяются, главным образом, при оценке относительных преимуществ использования того или иного алгоритма. В нашем случае, чтобы определить, какой из алгоритмов лучше использовать при решении определенной прикладной задачи, мы анализировали время, которое требуется алгоритмам последовательного и двоичного поиска для нахождения элемента списка. Обычно такой анализ осуществляется с использованием более широкого контекста. То -есть, рассматривая алгоритмы поиска, мы не сосредоточиваем наше внимание на списке определенной длины, а вместо этого пытаемся найти формулу, которая бы описывала работу алгоритма для списков произвольной длины. Как правило, анализируются наилучшие, наихудшие и средние случаи.
Здесь мы сравнивали средний случай работы алгоритма последовательного поиска с наихудшим случаем работы алгоритма двоичного поиска. Несмотря на то что мы анализировали список определенной длины, не составит труда применить наши рассуждения к списку произвольной длины. В частности, если применить алгоритм последовательного поиска к списку, состоящему из п элементов, то в процессе работы он рассмотрит в среднем и/2 элементов. В то время как алгоритм двоичного поиска в наихудшем случае извлечет максимально lg n элементов (здесь под нотацией lg n мы будем понимать логарифм числа п по основанию 2, что строго записывается так: log2 п).
Рассмотрим также алгоритм сортировки методом вставки (см. листинг 4.2). Вспомните, что этот алгоритм выбирает из списка элемент, который называется ведущим элементом, сравнивает его с предшествующими элементами, пока не поместит его в правильную позицию. Поскольку главным в алгоритме является действие сравнения двух элементов, мы будем подсчитывать число сравнений, которые выполняются в процессе сортировки списка, имеющего длину п.
Сначала алгоритм выбирает в качестве ведущего второй элемент списка. А затем извлекает последовательно все элементы до тех пор, пока не достигнет конца списка. В наилучшем случае каждый ведущий элемент уже находится на своем месте и, следовательно, его нужно сравнить только с одним элементом. Таким образом, в наилучшем случае для сортировки списка методом вставки потребуется л-1 сравнений.
В
наихудшем случае каждый ведущий элемент
сравнивается со всеми элементами,
расположенными до него, пока не будет
найдено его место. Так происходит в том
случае, когда элементы сортируемого
списка расположены в обратном порядке.
Тогда первый ведущий элемент (второй
элемент списка) сравнивается с одним
элементом, второй ведущий элемент
(третий элемент списка) сравнивается
с двумя элементами и т. д. (рис. 4.11).
Следовательно, общее число сравнений
в процессе сортировки списка, состоящего
из п элементов, равно 1 + 2 + 3 + ... + (и-1), что
эквивалентно выражению п(п-\)/2 или
1/2(п2-п). В частности, если список содержит
10 элементов, для того чтобы упорядочить
его, в наихудшем случае потребуется 45
сравнений.
В среднем в случае сортировки методом вставки каждый ведущий элемент сравнивается с половиной элементов, предшествующих ему. В результате требуется в два раза меньше сравнений, чем в наихудшем случае, то есть '/4(и2-я), чтобы упорядочить список, состоящий из п элементов. Например, если мы применим этот алгоритм для сортировки некоторых списков, состоящих из 10 элементов, то среднее количество сравнений, необходимых для сортировки, будет равно 22,5.
Полученные нами результаты имеют большое значение, поскольку по количеству сравнений, выполненных в процессе сортировки, можно определить приблизительное количество времени, необходимое для выполнения алгоритма. Зависимость между временем и длиной списка показана на рис. 4.12. Этот график построен с помощью данных, полученных при анализе наихудшего случая, когда мы пришли к выводу, что для сортировки списка, имеющего длину и, потребуется, по большей мере, 1/2{п2~п) сравнений. На графике через равные промежутки отмечены несколько значений длины списка и значения интервала времени, соответствующего каждой длине. Обратите внимание на то, что при изменении длины списка на одинаковое количество элементов промежуток времени, необходимый для его сортировки, возрастает на значительно больший интервал. Следовательно, при увеличении длины списка алгоритм становится менее эффективным.
Применим этот подход к алгоритму двоичного поиска. Сначала вспомним, что мы пришли к выводу, что для нахождения какого-либо элемента в списке, длина которого равна п, нужно рассмотреть максимально lg n элементов. Это наблюдение также дает представление о приблизительном количестве времени, необходимом для выполнения алгоритма. График зависимости времени поиска от длины списка изображен на рис. 4.13. Обратите внимание на то, что время выполнения алгоритма увеличивается на меньшие интервалы при возрастании длины списка на равное количество элементов. То есть при увеличении длины списка эффективность алгоритма двоичного поиска повышается.
Главное различие между этими двумя графиками состоит в форме кривой. Именно форма кривой показывает, насколько эффективным будет алгоритм при большом количестве входных данных. Обратите внимание на то, что форма кривой определяется типом функции, изображенной на графике, а не ее параметрами. Все линейные функции имеют вид прямой линии; все квадратичные — параболы; все логарифмические — логарифмической кривой (см. рис. 4.13). Поэтому принято задавать форму кривой с помощью функции, имеющей самую простую запись. В частности, парабола задается выражением п2, логарифмическая кривая — выражением lg п.
Таким образом, мы показали, что форма графика, получаемого путем соотнесения времени, необходимого для выполнения алгоритмом задачи, и количества входных данных, отражает характеристики эффективности алгоритма. Поэтому принято классифицировать алгоритмы согласно форме графика, который обычно строится на анализе наихудшего случая. Система представления, которая используется для определения этих классов, иногда называется «©-представлением (big-theta notation). Все алгоритмы, графики которых имеют форму параболы, например алгоритм сортировки методом вставки, составляют класс, который записывается как &(п2). Все алгоритмы, графики которых являются логарифмической кривой, алгоритм двоичного поиска, составляют класс, представленный выражением 0(lg n). Зная класс, к которому относится алгоритм, можно предсказывать его эффективность и сравнивать его с другими алгоритмами, выполняющими ту же задачу. Два алгоритма, принадлежащие к классу 0(/г2), имеют похожие временные требования при увеличении количества входных данных. А промежуток времени, необходимый для выполнения алгоритма класса 0(lg n), будет возрастать не так быстро, как в случае алгоритмов класса Э(и2).
