- •Предисловие
- •Введение
- •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.5Рекурсивные структуры
Рекурсивные структуры являются другим видом повторяющихся структур. Цикл означает повторное выполнение набора команд, при этом весь набор команд полностью выполняется, а затем повторяется. Рекурсия же предполагает повторное выполнение набора команд как подзадачу самой себя. В качестве примера можно привести обработку входящих телефонных звонков, когда незаконченный телефонный разговор откладывается и обрабатывается другой входящий звонок. В результате принимаются два звонка. Однако они обрабатываются не один за другим, как в цикле, а один обрабатывается в другом.
Для того чтобы познакомиться с рекурсией, рассмотрим алгоритм двоичного поиска (binary search), в котором в процессе поиска применяется метод разбиения.
4.5.1Поиск и сортировка
Алгоритмы последовательного и двоичного поиска — это только два алгоритма из множества существующих алгоритмов для осуществления процесса поиска. Точно так же, сортировка методом вставок представляет собой только один из многих алгоритмов сортировки. Другими классическими алгоритмами являются сортировка слиянием (глава 11), сортировка методом отбора (упражнение 5 раздела 4.4), сортировка методом «пузырька» (упражнение 6 раздела 4.4), быстрая сортировка (алгоритм применяет к процессу сортировки методом разбиения) и пирамидальная сортировка (алгоритм использует сложную технику нахождения записей, которые следует переместить в начало списка).
4.5.2Алгоритм двоичного поиска
Обратимся снова к задаче поиска, или проверке, находится ли определенный элемент в упорядоченном списке. По на этот раз мы будем использовать метод, который мы применяем, когда ищем какое-либо слово в словаре. В таком случае мы не просматриваем весь список элемент за элементом или страница за страницей, а начинаем поиск, открывая словарь на странице, на которой предположительно могло бы находиться это слово. Если нам посчастливится, то мы сразу обнаружим нужное слово. В противном случае мы должны продолжать поиск. Но действуя таким образом, мы значительно сужаем поле поиска.
Конечно, когда мы ищем слово в словаре, мы располагаем знаниями о том, где оно вероятнее всего находится. Если мы ищем слово «сомнамбулизм», мы начнем поиск с той части словаря, которая ближе к концу. Однако в случае произвольных списков у нас нет такого преимущества, поэтому условимся всегда начинать поиск с элемента, находящегося в «середине» списка. Слово «середина» заключено в кавычки, потому что список может содержать четное количество элементов и, следовательно, его середины просто не существует. В таком случае будем принимать за «середину» последний элемент первой половины списка.
Если элемент, находящийся в середине списка, является искомым, то поиск признается успешным. В противном случае мы, по крайней мере, можем ограничить процесс поиска первой или второй половиной списка, в зависимости от того, больше или меньше искомый элемент, чем рассматриваемый элемент. (Не забывайте, что наш список упорядочен.)
Для поиска в оставшейся части списка можно применить последовательный поиск, но вместо этого воспользуемся тем же методом, который мы применяли к целому списку. То есть мы выбираем для рассмотрения элемент, находящийся в «середине» оставшейся части списка. Как и раньше, если этот элемент является искомым, то процесс поиска завершается. В противном случае мы можем свести поле поиска к части списка меньшего размера.
Процесс поиска записи John с помощью этого метода изображен на рис. 4.7. Сначала мы рассматриваем элемент Harry, находящийся в середине. Так так искомый элемент должен располагаться после этого элемента, то мы продолжаем поиск только в нижней половине исходного списка. В середине этого подсписка находится запись Larry. Так как искомая запись должна предшествовать данной, мы сосредотачиваем наше внимание на первой половине подсписка. Серединой этой части подсписка является искомый элемент John, следовательно, поиск успешно завершен. Говоря проще, наш метод состоит в последовательном разделении рассматриваемого списка на сегменты до тех пор, пока не будет найден искомый элемент или поле поиска не сузится до пустого сегмента.
Следует обратить внимание на последнее утверждение. Если в исходном списке нет искомого элемента, то процесс поиска с помощью разделения списка на сегменты будет продолжаться до тех пор, пока рассматриваемый сегмент не окажется пустым. В этом случае наш алгоритм признает поиск неудачным.
Эти идеи, записанные с помощью псевдокода,
представлены в листинге 4.3. Согласно
им, поиск следует начать с проверки
того, не является ли список пустым. Если
да, то поиск признается неудачным. В
противном случае мы рассматр
иваем
элемент, находящийся в середине списка.
Если он не является искомым, мы продолжаем
поиск или в первой, или во второй половине
списка. Было бы неплохо при этом
использовать абстрактный инструмент.
В частности, в нашем случае мы применяем
для выполнения дополнительного поиска
процедуру Поиск. Следовательно, для
того чтобы наша программа работала, мы
должны создать такую процедуру.
Листинг 4.3. Принцип метода двоичного поиска
if (Список пустой) then
(сообщить, что поск неудачен.) else
[Назначить элемент, находящийся в середине Список, как ПроверяемоеЗначение; Выполнить набор команд, соответствующий одному из случаев: случай 1: ИскомоеЗначение = ПроверяемоеЗначение
(Поиск успешно завершен.) случай 2: ИскомоеЗначение < ПроверяемоеЗначение
(Поиск ИскомоеЗначение в части списка, расположенной до ПроверяемоеЗначение, и сообщить о результатах этого поиска.) случай 3: ИскомоеЗначение > ПроверяемоеЗначение
(Поиск ИскомоеЗначение в части списка, расположенной после ПроверяеноеЗначение, и сообщить о результатах этого поиска.) ] end if
Но эта процедура должна выполнять ту же задачу, которую мы только что записали с помощью псевдокода. Она сначала должна проверять, не является ли список пустым, и если нет — рассматривать элемент, находящийся в середине списка. Следовательно, мы можем получить необходимую процедуру, просто назвав текущую подпрограмму процедурой Поиск (листинг 4.4).
Листинг 4.4. Алгоритм двоичного поиска, записанный с помощью псевдокода
Procedure Поиск (Список. ИскомоеЗначение) if (Список пустой) then
(Признать поск неудачным.)
else
[Назначить элемент, находящийся в середине Список, как ПроверяемоеЗначение: Выполнить набор команд, соответствующий одному из случаев: случай 1: ИскомоеЗначение = ПроверяемоеЗначение
(Поиск успешно завершен.) случай 2: ИсконоеЗначение < ПроверяемоеЗначение
(Применить процедуру Поиск для обнаружения ИскомоеЗначение в части Список, предшествующей ПроверяемоеЗначение. и сообщить о результатах поиска.) случай 3: ИскоиоеЗначение > ПроверяемоеЗначение
(Применить процедуру Поиск для обнаружения ИскомоеЗначение в части Список, следующей за ПроверяемоеЗначение, и сообщить о результатах поиска.) ] end if
Обратите внимание на то, что эта процедура ссылается на саму себя. Когда мы выполняем эту процедуру и достигаем команды Применить процедуру Поиск..., мы должны будем применить ту же процедуру, которую мы использовали для целого списка, к списку меньшего размера. Если этот поиск оказывается успешным, то мы объявляем и первоначальный поиск успешным. Если же этот дополнительный поиск завершился неудачей, то мы объявляем исходный поиск безрезультатным.
РЕКУРСИВНЫЕ СТРУКТУРЫ В ИСКУССТВЕ
С
ледующую
рекурсивную структуру можно применить
к прямоугольному холсту, чтобы создать
рисунок в стиле голландского художника
Пита Мондриана (Piet Mondrian, 1872-1944), создававшего
картины, в которых прямоугольный холст
разделялся на прямоугольники меньшего
размера. Попробуйте выполнить эту
процедуру, чтобы создать рисунок,
подобный изображенному. Начните с
применения этой процедуры к прямоугольнику,
представляющему рабочий холст.
procedure Mondrian (Прямоугольник)
if (Прямоугольник слишком большой на ваш «художественный» взгляд) then (разделить Прямоугольник на два прямоугольника меньшего размера;
применить процедуру Mondrian к одному из прямоугольников; ________применить процедуру Mondrian к другому прямоугольнику).
Для того чтобы проследить, как процедура (см. листинг 4.4) выполняет задачу, рассмотрим процесс поиска имени Bill в списке Alice, Bill, Carol, David, Evelyn, Fred и George. Сначала мы выбираем для рассмотрения имя David (так как оно находится в середине нашего списка). Поскольку искомое имя Bill должно располагаться выше этого элемента, мы применяем процедуру Поиск к списку элементов, предшествующих имени David, то есть к списку Alice, Bill и Carol. Поступая так, мы создаем вторую копию процедуры поиска и назначаем ей выполнение этой дополнительной задачи.
Теперь в нашем распоряжении находятся две выполняемые копии процедуры поиска (рис. 4.8). Выполнение исходной процедуры временно приостанавливается на команде
Применить процедуру Поиск для обнаружения ИскомоеЗначение в части Список, предшествующей ПроверяемоеЗначение. и сообщить о результатах поиска.
в то время как мы используем вторую копию для поиска нужного элемента в списке Alice, Bill и Carol. Когда этот дополнительный поиск завершается, мы передаем его результаты исходной процедуре и продолжаем ее выполнение. Таким образом, вторая копия процедуры является вспомогательной по отношению к исходной процедуре. Она выполняет задачи, назначаемые ей исходным модулем, а затем исчезает.
В процессе дополнительного поиска рассматривается элемент Bill, поскольку он расположен в середине списка Alice, Bill и Carol. Так как он совпадает с искомым элементом, то поиск успешно завершается.
На этом этапе мы завершили дополнительный поиск, как предписывалось исходной процедурой, поэтому можно продолжить выполнение этой исходной копии, то есть объявить результат дополнительного поиска результатом первоначального поиска. Следовательно, поиск элемента Bill успешно завершен. В процессе поиска было правильно установлено, что имя Bill является элементом списка Alice, Bill, Carol, David, Evelyn, Fred и George.
Теперь рассмотрим, что произойдет, если мы будем использовать процедуру, приведенную в листинге 4.4, чтобы найти запись David в списке Alice, Carol, Evelyn, Fred и George. На этот раз исходная копия процедуры рассматривает элемент Evelyn и делает вывод, что искомая запись должна находиться в первой половине списка. Следовательно, она предписывает другой копии процедуры выполнить поиск имени в списке, состоящем из двух элементов, Alice и Carol (рис. 4.9).
Вторая копия процедуры выбирает для рассмотрения элемент Carol1 и делает вывод о том, что искомая запись должна находиться после этого элемента. Затем она предписывает третьей копии процедуры выполнить поиск имени в списке, который располагается после элемента Carol. Этот подсписок является пустым. Таким образом, третья копия процедуры должна искать имя David в пустом списке (рис. 4.10). Задача исходной копии процедуры состоит в том, чтобы найти нужный элемент в списке Alice, Carol, Evelyn, Fred и George, при этом рассматривается элемент Evelyn. Задача второй копии процедуры — найти нужный элемент в списке Alice и Carol; рассматривается элемент Carol. Третья копия собирается осуществлять процесс поиска в пустом списке.
К
онечно,
третья копия объявляет поиск неудачным.
Завершение работы третьей копии
позволяет второй копии продолжить
выполнение своей задачи. Она обнаруживает,
что затребованный поиск оказался
безрезультатным, сообщает о том, что
ее поиск является неудачным, и завершает
работу. Это сообщение ожидает исходная
процедура, поэтому теперь она может
продолжить свою работу. Так как
затребованный поиск закончился неудачей,
она объявляет поиск, выполняемый ею
самой, неудачным и завершается. Таким
образом, наша программа сделала
правильный вывод о том, что список
Alice, Carol, Evelyn, Fred и George не содержит имени
David. Возвращаясь к предыдущим примерам,
можно заметить, что алгоритм, представленный
в листинге 4.4, многократно делит список
на части меньшего размера так, что при
последующем поиске можно ограничиться
только одной из этих двух частей. Именно
поэтому этот алгоритм и называется
алгоритмом двоичного поиска.
