Украшения. Аксессуары. Дизайн ногтей. Цвета. Нанесение. Ногти

Что такое алгоритм? Понятие алгоритма. Свойства алгоритма

ПОНЯТИЕ АЛГОРИТМА. СВОЙСТВА АЛГОРИТМА. ВИДЫ АЛГОРИТМОВ. СПОСОБЫ ОПИСАНИЯ АЛГОРИТМОВ

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

Основными свойствами алгоритма являются:

  1. детерминированность (определенность). Предполагает получение однозначного результата вычислительного процecca при заданных исходных данных. Благодаря этому свойству процесс выполнения алгоритма носит механический характер;
  2. результативность. Указывает на наличие таких исходных данных, для которых реализуемый по заданному алгоритму вычислительный процесс должен через конечное число шагов остановиться и выдать искомый результат;
  3. массовость. Это свойство предполагает, что алгоритм должен быть пригоден для решения всех задач данного типа;
  4. дискретность. Означает расчлененность определяемого алгоритмом вычислительного процесса на отдельные этапы, возможность выполнения которых исполнителем (компьютером) не вызывает сомнений.

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

Наибольшее распространение благодаря своей наглядности получил графический (блок-схемный) способ записи алгоритмов.

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

При всем многообразии алгоритмов решения задач в них можно выделить три основных вида вычислительных процессов:

  • линейный;
  • ветвящийся;
  • циклический.

Линейным называется такой вычислительный процесс, при котором все этапы решения задачи выполняются в естественном порядке следования записи этих этапов.

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

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

  • Перевод

При разработке игр нам часто нужно находить пути из одной точки в другую. Мы не просто стремимся найти кратчайшее расстояние, нам также нужно учесть и длительность движения. Передвигайте звёздочку (начальную точку) и крестик (конечную точку), чтобы увидеть кратчайший путь. [Прим. пер.: в статьях этого автора всегда много интерактивных вставок, рекомендую сходить в оригинал статьи.]

Для поиска этого пути можно использовать алгоритм поиска по графу, который применим, если карта представляет собой граф. A* часто используется в качестве алгоритма поиска по графу. Поиск в ширину - это простейший из алгоритмов поиска по графу, поэтому давайте начнём с него и постепенно перейдём к A*.

Представление карты

Первое, что нужно при изучении алгоритма - понять данные . Что подаётся на вход? Что мы получаем на выходе?

Вход : алгоритмы поиска по графу, в том числе и A*, получают в качестве входных данных граф. Граф - это набор точек («узлов») и соединений («рёбер») между ними. Вот граф, который я передал A*:

A* не видит ничего другого. Он видит только граф. Он не знает, находится ли что-то в помещении или за его пределами, дверь это или комната, насколько велика область. Он видит только граф! Он не понимает никакой разницы между картой сверху и вот этой:

Выход : определяемый A* путь состоит из узлов и рёбер. Рёбра - это абстрактное математическое понятие. A* сообщает нам, что нужно перемещаться из одной точки в другую, но не сообщает, как это нужно делать. Помните, что он ничего не знает о комнатах или дверях, он видит только граф. Вы сами должны решить, чем будет являться ребро графа, возвращённое A* - перемещением с тайла на тайл, движением по прямой линии, открытием двери, бегом по кривому пути.

Компромиссы: для каждой игровой карты есть множество разных способов передачи графа поиска пути алгоритму A*. Карта на рисунке выше превращает двери в узлы.

А что, если мы превратим двери в рёбра?

А если мы применим сетку для поиска пути?

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

Алгоритмы

Существует множество алгоритмов, работающих с графами. Я рассмотрю следующие:

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

Алгоритм Дейкстры (также называемый поиском с равномерной стоимостью) позволяет нам задавать приоритеты исследования путей. Вместо равномерного исследования всех возможных путей он отдаёт предпочтение путям с низкой стоимостью. Мы можем задать уменьшенные затраты, чтобы алгоритм двигался по дорогам, повышенную стоимость, чтобы избегать лесов и врагов, и многое другое. Когда стоимость движения может быть разной, мы используем его вместо поиска в ширину.

A* - это модификация алгоритма Дейкстры, оптимизированная для единственной конечной точки. Алгоритм Дейкстры может находить пути ко всем точкам, A* находит путь к одной точке. Он отдаёт приоритет путям, которые ведут ближе к цели.

Я начну с самого простого - поиска в ширину, и буду добавлять функции, постепенно превращая его в A*.

Поиск в ширину

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


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


Алгоритм описывается всего в десяти строках кода на Python:

Frontier = Queue() frontier.put(start) visited = {} visited = True while not frontier.empty(): current = frontier.get() for next in graph.neighbors(current): if next not in visited: frontier.put(next) visited = True
В этом цикле заключается вся сущность алгоритмов поиска по графу этой статьи, в том числе и A*. Но как нам найти кратчайший путь? Цикл на самом деле не создаёт путей, он просто говорит нам, как посетить все точки на карте. Так получилось потому, что поиск в ширину можно использовать для гораздо большего, чем просто поиск путей. В этой статье я показываю, как он применяется в играх tower defense, но его также можно использовать в картах расстояний, в процедурной генерации карт и многом другом. Однако здесь мы хотим использовать его для поиска путей, поэтому давайте изменим цикл так, чтобы отслеживать, откуда мы пришли для каждой посещённой точки, и переименуем visited в came_from:

Frontier = Queue() frontier.put(start) came_from = {} came_from = None while not frontier.empty(): current = frontier.get() for next in graph.neighbors(current): if next not in came_from: frontier.put(next) came_from = current
Теперь came_from для каждой точки указывает на место, из которого мы пришли. Это похоже на «хлебные крошки» из сказки. Нам этого достаточно для воссоздания целого пути. Посмотрите, как стрелки показывают обратный путь к начальной позиции.

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

Current = goal path = while current != start: current = came_from path.append(current) path.append(start) # optional path.reverse() # optional
Таков простейший алгоритм поиска путей. Он работает не только в сетках, как показано выше, но и в любой структуре графов. В подземелье точки графа могут быть комнатами, а рёбра - дверями между ними. В платформере узлы графа могут быть локациями, а рёбра - возможными действиями: переместиться влево, вправо, подпрыгнуть, спрыгнуть вниз. В целом можно воспринимать граф как состояния и действия, изменяющие состояние. Подробнее о представлении карт я написал . В оставшейся части статьи я продолжу использовать примеры с сетками, и расскажу о том, для чего можно применять разновидности поиска в ширину.

Ранний выход

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

Код достаточно прямолинеен:

Frontier = Queue() frontier.put(start) came_from = {} came_from = None while not frontier.empty(): current = frontier.get() if current == goal: break for next in graph.neighbors(current): if next not in came_from: frontier.put(next) came_from = current

Стоимость перемещения

Пока мы делали шаги с одинаковой стоимостью. В некоторых случаях поиска путей у разных типов движения есть разная стоимость. Например, в Civilization движение через равнины или пустыню может стоить 1 очко движения, а движение через лес - 5 очков движения. На карте в самом начале статьи прохождение через воду стоит в 10 раз дороже, чем движение по траве. Ещё одним примером является диагональное движение в сетке, которое стоит больше, чем движение по осям. Нам нужно, чтобы поиск пути учитывал эту стоимость. Давайте сравним количество шагов от начала с расстоянием от начала:

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

< cost_so_far: cost_so_far = new_cost priority = new_cost frontier.put(next, priority) came_from = current
Использование очереди с приоритетами вместо обычной очереди изменяет способ расширения границы . Контурные линии позволяют это увидеть. Посмотрите видео , чтобы понаблюдать, как граница расширяется медленнее через леса, и поиск кратчайшего пути выполняется вокруг центрального леса, а не сквозь него:


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

Эвристический поиск

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

Def heuristic(a, b): # Manhattan distance on a square grid return abs(a.x - b.x) + abs(a.y - b.y)
В алгоритме Дейкстры для порядка очереди с приоритетами мы использовали расстояние от начала . В жадном поиске по первому наилучшему совпадению для порядка очереди с приоритетами мы вместо этого используем оцененное расстояние до цели . Точка, ближайшая к цели, будет исследована первой. В коде используется очередь с приоритетами из поиска в ширину, но не применяется cost_so_far из алгоритма Дейкстры:

Frontier = PriorityQueue() frontier.put(start, 0) came_from = {} came_from = None while not frontier.empty(): current = frontier.get() if current == goal: break for next in graph.neighbors(current): if next not in came_from: priority = heuristic(goal, next) frontier.put(next, priority) came_from = current
Давайте посмотрим, как это работает:


Ого! Потрясающе, правда? Но что случится на более сложной карте?


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

Алгоритм A*

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

Код очень похож на алгоритм Дейкстры:

Frontier = PriorityQueue() frontier.put(start, 0) came_from = {} cost_so_far = {} came_from = None cost_so_far = 0 while not frontier.empty(): current = frontier.get() if current == goal: break for next in graph.neighbors(current): new_cost = cost_so_far + graph.cost(current, next) if next not in cost_so_far or new_cost < cost_so_far: cost_so_far = new_cost priority = new_cost + heuristic(goal, next) frontier.put(next, priority) came_from = current
Сравните алгоритмы: алгоритм Дейкстры вычисляет расстояние от начальной точки. Жадный поиск по первому наилучшему совпадению оценивает расстояние до точки цели. A* использует сумму этих двух расстояний.

Попробуйте в оригинале статьи делать отверстия в разных местах стены. Вы обнаружите, что жадный поиск находит правильный ответ, A* тоже его находит, исследуя ту же область. Когда жадный поиск по первому наилучшему находит неверный ответ (более длинный путь), A* находит правильный, как и алгоритм Дейкстры, но всё равно исследует меньше, чем алгоритм Дейкстры.

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

И… на этом всё! В этом и заключается алгоритм A*.

Дополнительное чтение

Вы готовы реализовать его? Попробуйте использовать готовую библиотеку. Если вы хотите реализовать его самостоятельно, то у меня есть инструкция по пошаговой реализации графов, очередей и алгоритмов поиска пути на Python, C++ и C#.

Какой алгоритм стоит использовать для поиска путей на игровой карте?

  • Если вам нужно найти пути из или ко всем точкам, используйте поиск в ширину или алгоритм Дейкстры. Используйте поиск в ширину, если стоимость движения одинакова. Используйте алгоритм Дейкстры, если стоимость движения изменяется.
  • Если нужно найти пути к одной точке, используйте жадный поиск по наилучшему первому или A*. В большинстве случаев стоит отдать предпочтение A*. Когда есть искушение использовать жадный поиск, то подумайте над применением A* с «недопустимой» эвристикой .
А что насчёт оптимальных путей? Поиск в ширину и алгоритм Дейкстры гарантированно найдут кратчайший путь по графу. Жадный поиск не обязательно его найдёт. A* гарантированно найдёт кратчайший путь, если эвристика никогда не больше истинного расстояния. Когда эвристика становится меньше, A* превращается в алгоритм Дейкстры. Когда эвристика становится больше, A* превращается в жадный поиск по наилучшему первому совпадению.

А как насчёт производительности? Лучше всего устранить ненужные точки графа. Если вы используете сетку, то . Уменьшение размера графа помогает всем алгоритмам поиска по графам. После этого используйте простейший из возможных алгоритмов. Простые очереди выполняются быстрее. Жадный поиск обычно выполняется быстрее, чем алгоритм Дейкстры, но не обеспечивает оптимальных путей. Для большинства задач по поиску путей оптимальным выбором является A*.

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

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

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

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

Также смотрите другие материалы этой серии: , и .

Если не хочется копаться и разбираться, но есть потребность быстро понять основы оценки сложности, идите .

Асимптотический анализ

Когда мы говорим об измерении сложности алгоритмов, мы подразумеваем анализ времени, которое потребуется для обработки очень большого набора данных. Такой анализ называют асимптотическим. Сколько времени потребуется на обработку массива из десяти элементов? Тысячи? Десяти миллионов? Если алгоритм обрабатывает тысячу элементов за пять миллисекунд, что случится, если мы передадим в него миллион? Будет ли он выполняться пять минут или пять лет? Не стоит ли выяснить это раньше заказчика?

Все решают мелочи!

Порядок роста

Порядок роста описывает то, как сложность алгоритма растет с увеличением размера входных данных. Чаще всего он представлен в виде O-нотации (от нем. «Ordnung» - порядок)  : O(f(x)) , где f(x) - формула, выражающая сложность алгоритма. В формуле может присутствовать переменная n, представляющая размер входных данных. Ниже приводится список наиболее часто встречающихся порядков роста, но он ни в коем случае не полный.

Константный - O(1)

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

Public int GetCount(int items) { return items.Length; }

Линейный - O(n)

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

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

Public long GetSum(int items) { long sum = 0; foreach (int i in items) { sum += i; } return sum; }

Логарифмический – O( log n)

Порядок роста O( log n) означает, что время выполнения алгоритма растет логарифмически с увеличением размера входного массива. (Прим. пер.: в анализе алгоритмов по умолчанию используется логарифм по основанию 2). Большинство алгоритмов, работающих по принципу «деления пополам», имеют логарифмическую сложность. Метод Contains бинарного дерева поиска (binary search tree) также имеет порядок роста O( log n) .

Линеарифметический - O(n· log n)

Линеарифметический (или линейно-логарифмический) алгоритм имеет порядок роста O(n· log n) . Некоторые алгоритмы типа «разделяй и властвуй» попадают в эту категорию. В следующих частях мы увидим два таких примера - сортировка слиянием и быстрая сортировка.

Квадратичный - O(n  2)

Время работы алгоритма с порядком роста O(n  2) зависит от квадрата размера входного массива. Несмотря на то, что такой ситуации иногда не избежать, квадратичная сложность - повод пересмотреть используемые алгоритмы или структуры данных. Проблема в том, что они плохо масштабируются. Например, если массив из тысячи элементов потребует
1 000 000 операций, массив из миллиона элементов потребует 1 000 000 000 000 операций. Если одна операция требует миллисекунду для выполнения, квадратичный алгоритм будет обрабатывать миллион элементов 32 года. Даже если он будет в сто раз быстрее, работа займет 84 дня.

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

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

Что мы имеем в виду, когда говорим, что порядок роста сложности алгоритма - O(n) ? Это усредненный случай? Или наихудший? А может быть, наилучший?

Обычно имеется в виду наихудший случай, за исключением тех случаев, когда наихудший и средний сильно отличаются. К примеру, мы увидим примеры алгоритмов, которые в среднем имеют порядок роста O(1) , но периодически могут становиться O(n) (например, ArrayList.add). В этом случае мы будем указывать, что алгоритм работает в среднем за константное время, и объяснять случаи, когда сложность возрастает.

Самое важное здесь то, что O(n) означает, что алгоритм потребует не более n шагов.

Что мы измеряем?

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

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

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

Операции, количество которых мы будем измерять, включают в себя:

  • сравнения («больше», «меньше», «равно»);
  • присваивания;
  • выделение памяти.

То, какие операции мы учитываем, обычно ясно из контекста.

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

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

Продолжение следует

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

Как Яндекс использует ваши данные и машинное обучение для персонализации сервисов - .

Слово "Алгоритм" происходит от algorithmi - латинского написания имени аль-Хорезми, под которым в средневековой Европе знали величайшего математика из Хорезма (город в современном Узбекистане) Мухаммеда бен Мусу, жившего в 783-850 гг. В своей книге "Об индийском счете" он сформулировал правила записи натуральных чисел с помощью арабских цифр и правила действий над ними столбиком. В дальнейшем алгоритмом стали называть точное предписание, определяющее последовательность действий, обеспечивающую получение требуемого результата из исходных данных. Алгоритм может быть предназначен для выполнения его человеком или автоматическим устройством. Создание алгоритма, пусть даже самого простого, - процесс творческий. Он доступен исключительно живым существам, а долгое время считалось, что только человеку. Другое дело - реализация уже имеющегося алгоритма. Ее можно поручить субъекту или объекту, который не обязан вникать в существо дела, а возможно, и не способен его понять. Такой субъект или объект принято называть формальным исполнителем. Примером формального исполнителя может служить стиральная машина-автомат, которая неукоснительно исполняет предписанные ей действия, даже если вы забыли положить в нее порошок. Человек тоже может выступать в роли формального исполнителя, но в первую очередь формальными исполнителями являются различные автоматические устройства, и компьютер в том числе. Каждый алгоритм создается в расчете на вполне конкретного исполнителя. Те действия, которые может совершать исполнитель, называются его его допустимыми действиями . Совокупность допустимых действий образует систему команд исполнителя. Алгоритм должен содержать только те действия, которые допустимы для данного исполнителя.

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

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

Такими свойствами являются:

    Дискретность (прерывность, раздельность) - алгоритм должен представлять процесс решения задачи как последовательное выполнение простых (или ранее определенных) шагов. Каждое действие, предусмотренное алгоритмом, исполняется только после того, как закончилось исполнение предыдущего.

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

    Результативность (конечность) - алгоритм должен приводить к решению задачи за конечное число шагов.

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

На основании этих свойств иногда дается определение алгоритма, например: “Алгоритм – это последовательность математических, логических или вместе взятых операций, отличающихся детерменированностью, массовостью, направленностью и приводящая к решению всех задач данного класса за конечное число шагов.” Такая трактовка понятия “алгоритм” является неполной и неточной. Во-первых, неверно связывать алгоритм с решением какой-либо задачи. Алгоритм вообще может не решать никакой задачи. Во-вторых, понятие “массовость” относится не к алгоритмам как к таковым, а к математическим методам в целом. Решение поставленных практикой задач математическими методами основано на абстрагировании – мы выделяем ряд существенных признаков, характерных для некоторого круга явлений, и строим на основании этих признаков математическую модель, отбрасывая несущественные признаки каждого конкретного явления. В этом смысле любая математическая модель обладает свойством массовости. Если в рамках построенной модели мы решаем задачу и решение представляем в виде алгоритма, то решение будет “массовым” благодаря природе математических методов, а не благодаря “массовости” алгоритма.

Разъясняя понятие алгоритма, часто приводят примеры “бытовых алгоритмов”: вскипятить воду, открыть дверь ключом, перейти улицу и т. д.. : рецепты приготовления какого-либо лекарства или кулинарные рецепты являются алгоритмами. Но для того, чтобы приготовить лекарство по рецепту, необходимо знать фармакологию, а для приготовления блюда по кулинарному рецепту нужно уметь варить. Между тем исполнение алгоритма – это бездумное, автоматическое выполнение предписаний, которое в принципе не требует никаких знаний. Если бы кулинарные рецепты представляли собой алгоритмы, то у нас просто не было бы такой специальности – повар.

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

Само выражение “свойства алгоритма” некорректно. Свойствами обладают объективно существующие реальности. Можно говорить, например, о свойствах какого-либо вещества. Алгоритм – искусственная конструкция, которую мы сооружаем для достижения своих целей. Чтобы алгоритм выполнил свое предназначение, его необходимо строить по определенным правилам. Поэтому нужно говорить не о свойствах алгоритма, а о правилах построения алгоритма, или о требованиях, предъявляемых к алгоритму.

Первое правило – при построении алгоритма прежде всего необходимо задать мно-жество объектов, с которыми будет работать алгоритм. Формализованное (закодирован-ное) представление этих объектов носит название данных. Алгоритм приступает к работе с некоторым набором данных, которые называются входными, и в результате своей рабо-ты выдает данные, которые называются выходными. Таким образом, алгоритм пре-образует входные данные в выходные.

Это правило позволяет сразу отделить алгоритмы от “методов” и “способов”. Пока мы не имеем формализованных входных данных, мы не можем построить алгоритм.

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

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

Третье правило – дискретность. Алгоритм строится из отдельных шагов (действий, операций, команд). Множество шагов, из которых составлен алгоритм, конечно.

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

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

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

Любая работа на компьютере – это есть обработка информации. Работу компьютера можно схематически изобразить следующим образом:

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

Компьютер преобразует информацию по определенным правилам. Эти правила (операции, команды) заранее занесены в память компьютера. В совокупности эти правила преобразования информации называются алгоритмом. Данные, которые поступают в компьютер, называются входными данными. Результат работы компьютера – выходные данные. Таким образом, алгоритм преобразует входные данные в выходные:


Теперь можно поставить вопрос: а может ли человек обрабатывать информацию? Конечно, может. В качестве примера можно привести обычный школьный урок: учитель задает вопрос (входные данные), ученик отвечает (выходные данные). Самый простой пример: учитель дает задание – умножить 6 на 3 и результат написать на доске. Здесь числа 6 и 3 – входные данные, операция умножения – алгоритм, результат умножения – выходные данные:


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

Рассмотрим следующую задачу.

Длина класса 7 метров, ширина – 5 метров, высота – 3 метра. В классе 25 учеников. Сколько кв. м площади и сколько куб. м воздуха приходится на одного ученика?

Решение задачи:

1. Вычислить площадь класса:

2. Вычислить объем класса:

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

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

105: 25 = 4,2
Ответ: на одного ученика приходится 1,4 кв. метров площади и 4,2 куб. метров воздуха.

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

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

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

1. Вычислить площадь класса и записать в переменную S.

2. Вычислить объем класса и записать в переменную V.

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

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

5. Вывести на экран значения переменных S1 и V1.

Теперь остается только перевести команды алгоритма с русского языка на язык, понятный компьютеру, и получится программа. Программирование – это есть перевод алгоритма с “человеческого” языка на “компьютерный” язык.

Трактовка работы алгоритма как преобразования входных данных в выходные естественным образом подводит нас к рассмотрению понятия “постановка задачи”. Для того, чтобы составить алгоритм решения задачи, необходимо из условия выделить те величины, которые будут входными данными и четко сформулировать, какие именно величины требуется найти. Другими словами, условие задачи требуется сформулировать в виде “Дано... Требуется” – это и есть постановка задачи.

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

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

    Механические алгоритмы , или иначе детерминированные, жесткие (например алгоритм работы машины, двигателя и т.п.);

    Гибкие алгоритмы , например стохастические, т.е. вероятностные и эвристические.

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

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

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

    Линейный алгоритм – набор команд (указаний), выполняемых последовательно во времени друг за другом.

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

    Циклический алгоритм – алгоритм, предусматривающий многократное повторение одного и того же действия (одних и тех же операций) над новыми исходными данными. К циклическим алгоритмам сводится большинство методов вычислений, перебора вариантов.

Цикл программы – последовательность команд (серия, тело цикла), которая может выполняться многократно (для новых исходных данных) до удовлетворения некоторого условия.

На рисунке продемонстрированы в условных обозначениях схемы основных конструкций алгоритмов:

а). линейного алгоритма;

б,в,г). разветвляющихся алгоритмов (б-ответвление, в-раздвоение, г-переключение);

д,е,ж). циклических алгоритмов (д,ж-проверка в начале цикла, е-проверка в конце цикла).

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

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

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

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

Можно встретить даже такое утверждение: “Внешне алгоритм представляет собой схему – набор прямоугольников и других символов, внутри которых записывается, что вычисляется, что вводится в машину и что выдается на печать и другие средства отображения информации “. Здесь форма представления алгоритма смешивается с самим алгоритмом.

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

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

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

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

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

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

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

Очень ярко эта особенность человеческой психологии – неалгоритмичность мышления – проявилась в методичесом пособии А. Г. Гейна и В. Ф. Шолоховича. В пособии излагаются решения задач из известного учебника. Решения задач должны быть представлены в виде алгоритмов. Однако авторы пособия понимают, что если просто написать алгоритм решения задачи, то разобраться в самом решении будет трудно. Поэтому они сначала приводят “нечеткое изложение алгоритма” (т. е. объясняют решение задачи), а затем пишут сам алгоритм.



Л И Т Е Р А Т У Р А

1. Нестеренко А. В. ЭВМ и профессия программиста.

М., Просвещение, 1990.

2. Брудно А. Л., Каплан Л. И. Московские олимпиады по программированию.

М., Наука, 1990.

3. Кузнецов О. П., Адельсон-Вельский Г. М. Дискретная математика для инженера.

М., Энергоатомиздат, 1988.

4. Гейн А.Г. и др.. Основы информатики и вычислительной техники.

М., Просвещение, 1994.

5. Информатика. Еженедельное приложение к газете “Первое сентября”. 1998, № 1.

6. Радченко Н. П. Ответы на вопросы выпускных экзаменов. – Инфоматика и

образование, 1997, №4.

7. Касаткин В.Н. Информация, алгоритмы, ЭВМ. М., Просвещение, 1991.

8. Каныгин Ю. М., Зотов Б. И. Что такое информатика?

М., Детская литература, 1989.

9. Гейн А. Г., Шолохович В.Ф. Преподавание курса “Основы информатики и вычислительной техники” в средней школе. Руководство для учителя.

Екатеринбург, 1992.

10. Извозчиков В.А. Информатика в понятиях и терминах.

11. Газета «Информатика», №35, 1997г.

12. Л.З. Шауцуков Основы информатики в вопросах и ответах.


Автор: Богашова Татьяна, Донец Сергей (КПИ,ФАКС) г.Киев, 1999г.
Оценка:отл.
Сдавался: ПТУ №34
E-Mail:[email protected]



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

Ну, а теперь главный вопрос: Что такое алгоритм?

Свойства алгоритмов

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

  1. Конечность(результативность) алгоритма означает, что за конечное число шагов должен быть получен результат;
  2. Дискретность алгоритма означает, что алгоритм должен быть разбит на последовательность выполняемых шагов;
  3. Понятность алгоритма означает, что алгоритм должен содержать только те команды, которые входят в набор команд, который может выполнить конкретный исполнитель;
  4. Точность алгоритма означает, что каждая команда должна пониматься однозначно;
  5. Массовость алгоритма означает, что однажды составленный алгоритм должен подходить для решения подобных задач с разными исходными данными.
  6. Детерминированность (определенность) . Алгоритм обладает свойством детерминированности, если для одних и тех же наборов исходных данных он будет выдавать один и тот же результат, т.е. результат однозначно определяется исходными данными.

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

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


Я хочу порезать апельсин. Как это сделать?

Виды алгоритмов

    • Линейный(Команды последовательны без повторов и переходов);

Пример алгоритма:

Начало
достань нож
порежь апельсин(Именно апельсин, а не любой другой фрукт. За это отвечает ТОЧНОСТЬ)
съешь апельсин
конец

    • Циклический(Есть группа действий, повторяющихся по некоторому условию);

Пример алгоритма:

Начало
достань нож
ПОКА апельсины не закончились
порежь апельсин
съешь все апельсины
конец

    • Разветвляющийся(Выполнение команды зависит от условия).

Пример алгоритма:

Начало
достань нож
ЕСЛИ нож тупой поточи
порежь апельсин
съешь апельсин
конец

Вот и все. На следующем уроке мы с вами рассмотрим структуру программы в Паскаль.