Алгоритмы. Свойства алгоритма
![]()
Алгоритм
Алгоритм – это четко определенный план
действий для исполнителя.
Свойства алгоритма
• дискретность: состоит из отдельных шагов (команд)
• понятность: должен включать только команды,
известные исполнителю (входящие в СКИ)
• определенность: при одинаковых исходных данных
всегда выдает один и тот же результат
• конечность: заканчивается за конечное число шагов
• массовость: может применяться многократно при
различных исходных данных
• корректность: дает верное решение при любых
допустимых исходных данных
1
2.
Программа
Программа – это
• алгоритм, записанный на каком-либо языке
программирования
• набор команд для компьютера
Команда – это описание действий, которые
должен выполнить компьютер.
• откуда взять исходные данные?
• что нужно с ними сделать?
Оператор – это команда языка
программирования высокого уровня.
2
3.
3
Переменные
Переменная – это величина, имеющая имя, тип
и значение. Значение переменной можно
изменять во время работы программы.
Значение
Другой тип
данных
Имя
!
?
Поместится?
В переменной хранятся данные
определенного типа!
4.
Как записать значение в переменную?
Оператор
присваивания
a := 5;
5
!
При записи нового
значения старое
стирается!
Оператор – это команда языка программирования (инструкция).
Оператор присваивания – это команда для
записи нового значения в переменную.
4
5.
Блок-схема линейного алгоритма
начало
блок «начало»
ввод a, b
блок «ввод»
c := a + b;
блок «процесс»
вывод c
блок «вывод»
конец
блок «конец»
5
6.
Разветвляющиеся алгоритмы
Особенность: действия исполнителя зависят от
некоторых условий (если … иначе …).
Алгоритмы, в которых последовательность шагов
зависит от выполнения некоторых условий, называются
разветвляющимися.
6
7.
7
Вариант 1. Блок-схема
начало
блок
«решение»
ввод a,b
да
a > b?
max:= a;
полная
форма
ветвления
нет
max:= b;
вывод max
конец
?
Если a = b?
8.
8
Вариант 2. Блок-схема
начало
ввод a,b
max:= a;
да
b > a?
max:= b;
вывод max
конец
нет
неполная
форма
ветвления
9.
Сложные условия
Задача. Фирма набирает сотрудников от 25 до 40 лет
включительно. Ввести возраст человека и определить,
подходит ли он фирме (вывести ответ «подходит» или
«не подходит»).
Особенность: надо проверить, выполняются ли два
условия одновременно.
?
Можно ли решить известными методами?
9
10.
10
Вариант 1. Алгоритм
начало
ввод x
да
да
‘подходит’
x x >= 25?
нет
нет
‘не подходит’
конец
‘не подходит’
11.
11
Вариант 2. Алгоритм
начало
ввод x
да
x >= 25
и
x нет
‘не подходит’
‘подходит’
конец
12.
Сложные условия
Сложное условие – это условие, состоящее из
нескольких простых условий (отношений), связанных с
помощью логических операций:
• НЕ (отрицание, инверсия)
• И (логическое умножение, конъюнкция,
одновременное выполнение условий)
• ИЛИ (логическое сложение, дизъюнкция,
выполнение хотя бы одного из условий)
12
13.
Циклы
Цикл – это многократное выполнение одинаковой
последовательности действий.
• цикл с известным числом шагов
• цикл с неизвестным числом шагов (цикл с
условием)
13
14.
Алгоритм (с блоком «цикл»)
начало
i := 1,8
i2 := i * i;
i3 := i2 * i;
i, i2, i3
блок «цикл»
конец
тело цикла
14
15.
Алгоритм (цикл с предусловием)
начало
обнулить
счетчик цифр
ввод n
count := 0;
выполнять
«пока n <> 0»
n <> 0?
нет
да
count := count + 1;
n := n div 10;
count
конец
15
16.
Цикл с постусловием: алгоритм
начало
ввод n
нет
n > 0?
да
основной
алгоритм
конец
тело цикла
условие
ВЫХОДА
блок «типовой
процесс»
16
17.
Вспомогательный алгоритм
Вспомогательный алгоритм — это блок
последовательных действий в основном алгоритме,
который выделен в качестве
самостоятельного алгоритма, имеющего свое имя
Применение:
• выполнение одинаковых действий в разных местах
программы
• разбивка программы на подзадачи для лучшего
восприятия
Задача
Подзадача1
Подзадача2
Подзадача3
17
Алгоритм
Алгоритм — это четкая последовательность действий, выполнение которой дает какой-то заранее известный результат. Простыми словами, это набор инструкций для конкретной задачи. Известнее всего этот термин в информатике и компьютерных науках, где под ним понимают инструкции для решения задачи эффективным способом.

Освойте профессию «Data Scientist»
Сейчас под этим словом понимают любые последовательности действий, которые можно четко описать и разделить на простые шаги и которые приводят к достижению какой-то цели. Например, пойти на кухню, налить воду и положить в нее пакетик чая — это алгоритм для выполнения задачи «Заварить чай».
Алгоритмы в информатике — инструкции для компьютеров, набор шагов, который описывается программным кодом. Существуют конкретные алгоритмы для тех или иных действий, причем некоторые из них довольно сложные. Одна из целей использования алгоритмов — делать код эффективнее и оптимизировать его.
Науки о данных
Онлайн-магистратура совместно с МФТИ. Погрузитесь в мир Data Science и постройте карьеру в Big Data, Artificial Intelligence или Machine Learning. Получите опыт на реальных проектах и выйдите на новый уровень в профессии и карьере.

Кто пользуется алгоритмами
В общем смысле — абсолютно все живые и некоторые неживые существа, потому что любую последовательность действий, ведущую к цели, можно считать алгоритмом. Поиск еды животным — алгоритм, движения робота тоже описываются алгоритмом.
В узком смысле, в котором понятие используется в компьютерных науках, алгоритмами пользуются разработчики, некоторые инженеры и аналитики, а также специалисты по машинному обучению, тестировщики и многие другие. Это одно из ключевых понятий в IT.
Читайте также Востребованные IT-профессии 2023 года: на кого учиться онлайн
Для чего нужны алгоритмы
Алгоритмы в информатике нужны для эффективного решения различных задач, в том числе тех, выполнение которых «в лоб» имеет высокую сложность или вовсе невозможно. На практике существуют алгоритмы практически для чего угодно: сортировки, прохождения по структурам данных, поиска элементов, фильтрации информации, математических операций и так далее.
Например, отсортировать массив можно в ходе полного перебора — это самое очевидное решение. А можно воспользоваться алгоритмом быстрой сортировки: он сложнее и не так очевиден, зато намного быстрее работает и не так сильно нагружает мощности компьютера. Строго говоря, полный перебор — это тоже алгоритм, но очень простой.
Существуют алгоритмически неразрешимые задачи, для решения которых нет и не может существовать алгоритма. Но большинство задач в IT разрешимы алгоритмически, и алгоритмы активно используются в работе с ними.
Алгоритмы применяются во всех направлениях IT и во многих других отраслях. Инструкции для автоматизированного станка или линии производства — алгоритмы, рецепт блюда — тоже.
Алгоритмизация
Алгоритмизация — это процесс разработки и описания последовательности шагов, которые необходимо выполнить для решения определенной задачи или достижения конкретной цели. Алгоритмизация является ключевым этапом при программировании и разработке программного обеспечения.
При алгоритмизации задачи создаются четкие инструкции, которые компьютер может понять и выполнять. Алгоритмы могут быть записаны в виде текстового описания, блок-схемы, псевдокода или других формализованных представлений. Они служат основой для написания кода программы, который позволяет компьютеру автоматически решать задачи в соответствии с предварительно разработанными инструкциями.
Алгоритмизация играет важную роль в информатике и программировании, так как хорошо разработанные алгоритмы обеспечивают эффективное и корректное выполнение задач, а также упрощают процесс отладки и поддержки программного кода.
Основные свойства алгоритмов
Дискретность. Алгоритм — не единая неделимая структура, он состоит из отдельных маленьких шагов, или действий. Эти действия идут в определенном порядке, одно начинается после завершения другого.
Результативность. Выполнение алгоритма должно привести к какому-либо результату и не оставлять неопределенности. Результат может в том числе оказаться неудачным — например, алгоритм может сообщить, что решения нет, — но он должен быть.
Детерминированность. На каждом шаге не должно возникать разночтений и разногласий, инструкции должны быть четко определены.
Массовость. Алгоритм обычно можно экстраполировать на похожие задачи с другими исходными данными — достаточно поменять изначальные условия. Например, стандартный алгоритм по решению квадратного уравнения останется неизменным вне зависимости от того, какие числа будут использоваться в этом уравнении.
Понятность. Алгоритм должен включать только действия, известные и понятные исполнителю.
Конечность. Алгоритмы конечны, они должны завершаться и выдавать результат, в некоторых определениях — за заранее известное число шагов.
Какими бывают алгоритмы
Несмотря на слово «последовательность», алгоритм не всегда описывает действия в жестко заданном порядке. Особенно это актуально сейчас, с распространением асинхронности в программировании. В алгоритмах есть место для условий, циклов и других нелинейных конструкций.
Линейные. Это самый простой тип алгоритма: действия идут друг за другом, каждое начинается после того, как закончится предыдущее. Они не переставляются местами, не повторяются, выполняются при любых условиях.
Ветвящиеся. В этом типе алгоритма появляется ветвление: какие-то действия выполняются, только если верны некоторые условия. Например, если число меньше нуля, то его нужно удалить из структуры данных. Можно добавлять и вторую ветку: что делать, если условие неверно — например, число больше нуля или равно ему. Условий может быть несколько, они могут комбинироваться друг с другом.
Циклические. Такие алгоритмы выполняются в цикле. Когда какой-то блок действий заканчивается, эти действия начинаются снова и повторяются некоторое количество раз. Цикл может включать в себя одно действие или последовательность, а количество повторений может быть фиксированным или зависеть от условия: например, повторять этот блок кода, пока в структуре данных не останется пустых ячеек. В некоторых случаях цикл может быть бесконечным.
Рекурсивные. Рекурсия — это явление, когда какой-то алгоритм вызывает сам себя, но с другими входными данными. Это не цикл: данные другие, но «экземпляров» работающих программ несколько, а не одна. Известный пример рекурсивного алгоритма — расчет чисел Фибоначчи.
Рекурсия позволяет изящно решать некоторые задачи, но с ней надо быть осторожнее: такие алгоритмы могут сильно нагружать ресурсы системы и работать медленнее других.
Вероятностные. Такие алгоритмы упоминаются реже, но это довольно интересный тип: работа алгоритма зависит не только от входных данных, но и от случайных величин. К ним, например, относятся известные алгоритмы Лас-Вегас и Монте-Карло.
Основные и вспомогательные. Это еще один вид классификации. Основной алгоритм решает непосредственную задачу, вспомогательный решает подзадачу и может использоваться внутри основного — для этого там просто указываются его название и входные данные. Пример вспомогательного алгоритма — любая программная функция.

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

В схемах подписаны конкретные действия, условия, количество повторений циклов и другие детали. Это позволяет нагляднее воспринимать алгоритмы.
Сложность алгоритма
Понятие «сложность» — одно из ключевых в изучении алгоритмов. Оно означает не то, насколько трудно понять тот или иной метод, а ресурсы, затраченные на вычисление. Если сложность высокая, алгоритм будет выполняться медленнее и, возможно, тратить больше аппаратных ресурсов; такого желательно избегать.
Сложность обычно описывают большой буквой O. После нее в скобках указывается значение, от которого зависит время выполнения. Это обозначение из математики, которое описывает поведение разных функций.
Какой бывает сложность. Полностью разбирать математическую O-нотацию, как ее называют, мы не будем — просто перечислим основные обозначения сложности в теории алгоритмов.
- O(1) означает, что алгоритм выполняется за фиксированное константное время. Это самые эффективные алгоритмы.
- O(n) — это сложность линейных алгоритмов. n здесь и дальше обозначает размер входных данных: чем больше n, тем дольше выполняется алгоритм.
- O(n²) тоже означает, что чем больше n, тем выше сложность. Но зависимость тут не линейная, а квадратичная, то есть скорость возрастает намного быстрее. Это неэффективные алгоритмы, например с вложенными циклами.
- O(log n) — более эффективный алгоритм. Скорость его выполнения рассчитывается логарифмически, то есть зависит от логарифма n.
- O(√n) — алгоритм, скорость которого зависит от квадратного корня из n. Он менее эффективен, чем логарифмический, но эффективнее линейного.
Существуют также O(n³), O(nn) и другие малоэффективные алгоритмы с высокими степенями. Их сложность растет очень быстро, и их лучше не использовать.
Графическое описание сложности. Лучше разобраться в сложности в O-нотации поможет график. Он показывает, как изменяется время выполнения алгоритма в зависимости от размера входных данных. Чем более пологую линию дает график, тем эффективнее алгоритм.
O-нотацию используют, чтобы оценить, эффективно ли использовать ту или иную последовательность действий. Если данные большие или их много, стараются искать более эффективные алгоритмы, чтобы ускорить работу программы.
Использование алгоритмов в IT
Мы приведем несколько примеров использования разных алгоритмов в отраслях программирования. На самом деле их намного больше — мы взяли только часть, чтобы помочь вам понять практическую значимость алгоритмов.
Разработка ПО и сайтов. Алгоритмы используются для парсинга, то есть «разбора» структур с данными, таких как JSON. Парсинг — одна из базовых задач, например в вебе. Также алгоритмы нужны при отрисовке динамических структур, выводе оповещений, настройке поведения приложения и многом другом.
Работа с данными. Очень активно алгоритмы применяются при работе с базами данных, файлами, где хранится информация, структурами вроде массивов или списков. Данных может быть очень много, и выбор правильного алгоритма позволяет ускорить работу с ними. Алгоритмы решают задачи сортировки, изменения и удаления нужных элементов, добавления новых данных. С их помощью наполняют и проходят по таким структурам, как деревья и графы.
Отдельное значение алгоритмы имеют в Big Data и анализе данных: там они позволяют обработать огромное количество информации, в том числе сырой, и не потратить на это слишком много ресурсов.
Поисковые задачи. Алгоритмы поиска — отдельная сложная отрасль. Их выделяют в отдельную группу, в которой сейчас десятки разных алгоритмов. Поиск важен в науке о данных, в методах искусственного интеллекта, в аналитике и многом другом. Самый очевидный пример — поисковые системы вроде Google или Яндекса. Кстати, подробности об используемых алгоритмах поисковики обычно держат в секрете.
Машинное обучение. В машинном обучении и искусственном интеллекте подход к алгоритмам немного другой. Если обычная программа действует по заданному порядку действий, то «умная машина» — нейросеть или обученная модель — формирует алгоритм для себя сама в ходе обучения. Разработчик же описывает модель и обучает ее: задает ей начальные данные и показывает примеры того, как должен выглядеть конечный результат. В ходе обучения модель сама продумывает для себя алгоритм достижения этого результата.
Такие ИИ-алгоритмы могут быть еще мощнее обычных и используются для решения задач, которые разработчик не в силах разбить на простые действия сознательно. Например, для распознавания предметов нужно задействовать огромное количество процессов в нервной системе: человек просто физически не способен описать их все, чтобы повторить программно.
В ходе создания и обучения модели разработчик тоже может задействовать алгоритмы. Например, алгоритм распространения ошибки позволяет обучать нейросети.
Data Scientist
Дата-сайентисты решают поистине амбициозные задачи. Научитесь создавать искусственный интеллект, обучать нейронные сети, менять мир и при этом хорошо зарабатывать. Программа рассчитана на новичков и плавно введет вас в Data Science.
Структуры данных и алгоритмы в Python
В этой статье мы кратко рассмотрим встроенные структуры данных: списки, кортежи, словари и т.д., а также некоторые пользовательские структуры данных: связанные списки, деревья и графы. Более подробно затронем алгоритмы обхода, поиска и сортировки.
Чтиво длинное, так что если вы точно знаете, что вам нужно, вот содержание:
- Список
- Кортеж
- Множество
- Frozen set
- Строка
- Словарь
- Матрица
- Байт-массив
- Связанный список
- Стек (Stack)
- Очередь (Queue)
- Очередь с приоритетом
- Куча (Heap)
- Бинарное дерево (Binary tree)
- Бинарное дерево поиска (Binary Search Tree)
- Графы (Graphs)
- Рекурсия
- Динамическое программирование
- Алгоритмы поиска
- Алгоритмы сортировки
Список
Списки в Python — это упорядоченные коллекции данных, в качестве элементов в них могут выступать объекты любых других типов.
Для списков очень «затратной» с точки зрения расходования памяти операцией является вставка элемента в начало или его удаление с первой позиции, так как все элементы должны быть сдвинуты. Вставка и удаление в конец списка также могут быть «затратными» в случае, если переполняется предварительно выделенная память.
Доступ к элементам списка можно получить по индексу. В Python начальный индекс списка (и любого другого составного объекта) всегда равен 0, а конечный (если в нём N элементов) — N-1.
Кортеж
Кортежи в Python похожи на списки, но они неизменяемы, т.е. элементы в них нельзя изменить после создания. Как и список, кортеж может содержать элементы различных типов.
В Python кортежи создаются путем перечисления значений через запятую с использованием круглых скобок или без них.
Примечание: Чтобы создать кортеж из одного элемента, обязательно нужно поставить запятую после него. Например, (8,) создаст кортеж, содержащий 8 в качестве единственного элемента.
Множество
В Python это изменяемый набор из уникальных элементов. Множества используются в основном для нахождения общих элементов и устранения дубликатов. Для всех действий используется хэширование — популярная техника, позволяющая выполнять вставку, удаление и обход в среднем за O(1).
Если несколько значений присутствуют в одной и той же позиции индекса, то значение добавляется к этой позиции индекса, формируя связанный список.
Frozen set
Это неизменяемые объекты (как и кортежи), состоящие из уникальных элементов (как и множества). В то время как элементы множества могут быть изменены после его создания, элементы frozen sets — нет.
Строка
Строки в Python — это неизменяемый массив байтов, представляющих собой набор символов в Unicode. В Python нет символьного типа данных, отдельный символ — это тоже строка, состоящая из одного элемента.
Словарь
Словарь — это неупорядоченная коллекция данных, хранящихся в формате пары ключ:значение. Он похож на хэш-таблицы в любом другом языке со сложностью равной O(1). Индексация словаря Python осуществляется с помощью ключей. Это может быть любой хэшируемый тип данных: строки, числа, кортежи и т.д. Мы можем создать словарь с помощью фигурных скобок ( <> ) или dictionary comprehension .
Матрица
Матрица — это двумерный массив, в котором каждый элемент имеет строго одинаковый размер. Для создания матрицы мы будем использовать библиотеку NumPy.
import numpy as np a = np.array([[1,2,3,4],[4,55,1,2], [8,3,20,19],[11,2,22,21]]) m = np.reshape(a,(4, 4)) print(m) >>>OUTPUT [[ 1 2 3 4] [ 4 55 1 2] [ 8 3 20 19] [11 2 22 21]] # Доступ к элементам print(a[1]) print(a[2][0]) # [ 4 55 1 2] # 8 # Добавление элемента m = np.append(m,[[1, 15,13,11]],0) print(m) >>>OUTPUT [[ 1 2 3 4] [ 4 55 1 2] [ 8 3 20 19] [11 2 22 21] [ 1 15 13 11]] # Удаление элемента m = np.delete(m,[1],0) print(m) # Удаление элемента: [[ 1 2 3 4] [ 8 3 20 19] [11 2 22 21] [ 1 15 13 11]]
Байт-массив
Байт-массив в Python — это изменяемая последовательность целых чисел в диапазоне 0
# Создание массива a = bytearray((12, 8, 25, 2)) print(a) # bytearray(b'\x0c\x08\x19\x02') # Доступ к элементам print("\nДоступ к элементам:", a[1]) # 8 # Изменение элемента a[1] = 3 print("\nПосле изменения:") print(a) # После изменения: # bytearray(b'\x0c\x03\x19\x02') # Добавление элемента a.append(30) print("\nПосле добавления:") print(a) # После добавления: # bytearray(b'\x0c\x03\x19\x02\x1e')
Связанный список
Связанный список — это линейная структура данных, в которой элементы не хранятся в смежных областях памяти. Элементы в таком списке связаны между собой с помощью указателей, как показано на рисунке ниже:
Связанный список представлен как указатель на первый узел, который называется Head. Если связанный список пуст, то значение в Head равно NULL. Каждый узел в списке состоит как минимум из двух частей: данных и указателя (или ссылки) на следующий узел.
Реализуем связанный список:
# Класс узла class Node: # Инициализация объекта узел def __init__(self, data): self.data = data # присваиваем данные self.next = None # инициализируем # далее - null # Класс связанного списка class LinkedList: # Инициализация связанного списка # объект - список def __init__(self): self.head = None
Создадим простой связный список с тремя узлами:
# Исполнение начинается отсюда if __name__=='__main__': # начнём с пустого списка llist = LinkedList() llist.head = Node(1) second = Node(2) third = Node(3) ''' Создали три узла. Их имена: head, second и third llist.head second third | | | | | | +----+------+ +----+------+ +----+------+ | 1 | None | | 2 | None | | 3 | None | +----+------+ +----+------+ +----+------+ ''' llist.head.next = second # связываем первый узел со вторым ''' Первый узел ссылается на второй. Теперь они связаны. llist.head second third | | | | | | +----+------+ +----+------+ +----+------+ | 1 | o-------->| 2 | null | | 3 | null | +----+------+ +----+------+ +----+------+ ''' second.next = third # связываем второй узел с третьим ''' Второй узел ссылается на третий. Теперь они связаны. llist.head second third | | | | | | +----+------+ +----+------+ +----+------+ | 1 | o-------->| 2 | o-------->| 3 | null | +----+------+ +----+------+ +----+------+ '''
Обход связанного списка
В предыдущем скрипте мы создали простой связный список с тремя узлами. Давайте обойдем его и выведем данные каждого узла. Для обхода напишем метод print_list() , который выводит в консоль любой данный ей список.
class Node: def __init__(self, data): self.data = data self.next = None class LinkedList: def __init__(self): self.head = None # печатает содержимое связанного списка def print_list(self): temp = self.head while temp: print(temp.data) temp = temp.next if __name__=='__main__': llist = LinkedList() llist.head = Node(1) second = Node(2) third = Node(3) llist.head.next = second second.next = third llist.print_list() >>>OUTPUT # 1 # 2 # 3
Стек (Stack)
Стек — это линейная структура данных, которая хранит элементы по принципу «последний вошел/первый вышел» (LIFO) или «первый вошел/последний вышел» (FILO). В стеке новый элемент добавляется и удаляется только с одного конца. Операции вставки и удаления часто называют push и pop.
Функции, связанные со стеком:
- empty() — возвращает, пуст ли стек, сложность: O(1)
- size() — возвращает размер стека, сложность: O(1)
- top() — возвращает ссылку на самый верхний элемент стека, сложность: O(1)
- push(a) — вставляет элемент ‘a’ на вершину стека, сложность: O(1)
- pop() — удаляет самый верхний элемент стека, сложность: O(1)
stack = [] # добавление элементов stack.append('g') stack.append('f') stack.append('g') print('Начальный стек') print(stack) # Начальный стек # ['g', 'f', 'g'] # удаление элементов print('\nУдаление элементов:') print(stack.pop()) print(stack.pop()) print(stack.pop()) # Удаление элементов: # g # f # g print('\nФинальный стек:') print(stack) # Финальный стек: # []
Очередь (Queue)
Как и стек, очередь представляет собой линейную структуру данных, которая хранит элементы по принципу «первым пришел — первым ушел» (FIFO). В очереди первым удаляется последний добавленный элемент.
С очередью связаны следующие операции:
Enqueue: добавляет элемент в очередь. Если очередь переполнена, то говорят о состоянии переполнения, сложность: O(1)
Dequeue: удаляет элемент из очереди. Элементы выгружаются в том же порядке, в котором они были вставлены. Если очередь пуста, то считается, что это условие переполнения, сложность: O(1)
Front: получает первый элемент из очереди, сложность: O(1)
Rear: получает последний элемент из очереди, cложность: O(1)
# создаём очередь queue = [] # добавляем в неё элементы queue.append('g') queue.append('f') queue.append('g') print("Исходная очередь") print(queue) # Исходная очередь # ['g', 'f', 'g'] # убираем элементы из очереди print("\nУдалённые элементы") print(queue.pop(0)) print(queue.pop(0)) print(queue.pop(0)) # Удалённые элементы # g # f # g print("\nОчередь после удаления элементов") print(queue) # Очередь после удаления элементов # []
Очередь с приоритетом
Это абстрактные структуры данных, в которых каждое значение в очереди имеет определённый приоритет. Например, в авиакомпаниях багаж с названием «Бизнес» или «Первый класс» прибывает раньше остальных. Очередь с приоритетом — это расширение обычной очереди со следующими свойствами:
- элемент с высоким приоритетом выгружается раньше элемента с низким приоритетом.
- если два элемента имеют одинаковый приоритет, они обслуживаются в соответствии с их порядком в очереди.
# простая реализация очереди с приоритетом class PriorityQueue(object): def __init__(self): self.queue = [] def __str__(self): return ' '.join([str(i) for i in self.queue]) # проверяет, пуста ли очередь def isEmpty(self): return len(self.queue) == 0 # добавляет элемент в очередь def insert(self, data): self.queue.append(data) # удаляет элемент по приоритету def delete(self): try: max = 0 for i in range(len(self.queue)): if self.queue[i] > self.queue[max]: max = i item = self.queue[max] del self.queue[max] return item except IndexError: print() exit() if __name__ == '__main__': myQueue = PriorityQueue() myQueue.insert(12) myQueue.insert(1) myQueue.insert(14) myQueue.insert(7) print(myQueue) while not myQueue.isEmpty(): print(myQueue.delete()) >>>OUTPUT # 12 1 14 7 # 14 # 12 # 7 # 1
Куча (Heap)
Модуль heapq в Python позволяет создать структуру данных под элегантным названием «куча» или Heap, которая, как правило, представляет собой очередь с приоритетом. Свойство этой структуры данных заключается в том, что она всегда выдает наименьший элемент (min heap).
При добавлении или удалении элемента структура данных сохраняется. heap[0] также каждый раз возвращает наименьший элемент.
Временная сложность — O (log n).
В целом, кучи могут быть двух типов:
Max-Heap: ключ, находящийся в корневом узле, должен быть наибольшим среди ключей, находящихся во всех его дочерних узлах. Это же свойство должно быть рекурсивно истинным для всех поддеревьев этого дерева.
Min-Heap: ключ, находящийся в корневом узле, должен быть наименьшим среди ключей, присутствующих во всех его дочерних узлах. Это же свойство должно быть рекурсивно истинным для всех поддеревьев этого дерева.
# импортируем библиотеку import heapq # создаём список li = [5, 7, 9, 1, 3] # превращаем список в кучу heapq.heapify(li) # выводим кучу print("Куча: ",end="") print(list(li)) # Куча: [1, 3, 9, 7, 5] # добавляем элемент heapq.heappush(li,4) # выводим результат print("Изменённая куча: ",end="") print(list(li)) # Изменённая куча: [1, 3, 4, 7, 5, 9] # получаем наименьший элемент print("Наименьший элемент - ",end="") print(heapq.heappop(li)) # Наименьший элемент - 1
Бинарное дерево (Binary tree)
Дерево — это иерархическая структура данных, которая выглядит так, как показано ниже.
дерево ---- jСамый верхний узел дерева называется корнем, а самые нижние узлы или узлы, не имеющие детей, называются листовыми узлами. Узлы, которые находятся непосредственно под узлом, называются его детьми, а узлы, которые находятся непосредственно над узлом, называются его родителем.
Бинарное дерево — это дерево, элементы которого могут иметь два дочерних элемента. Поскольку каждый элемент бинарного дерева может иметь только 2 дочерних элемента, мы обычно называем их просто левый и правый. Узел бинарного дерева содержит следующие части:
- Данные
- Указатель на левый дочерний элемент
- Указатель на правый дочерний элемент
Давайте создадим дерево с четырьмя узлами. Предположим, что структура дерева выглядит следующим образом:
дерево ---- 1# класс как узел бинарного дерева class Node: def __init__(self,key): self.left = None self.right = None self.val = key # создаём узел root = Node(1) ''' вот что должно получиться: 1 / \ None None''' root.left = Node(2); root.right = Node(3); ''' 1 / \ 2 3 / \ / \ None None None None''' root.left.left = Node(4); ''' 1 / \ 2 3 / \ / \ 4 None None None / \ None None'''Обход бинарного дерева
Деревья можно обходить различными способами. Ниже перечислены наиболее часто используемые. Рассмотрим следующее дерево:
дерево ---- 1Обход дерева в глубину:
- Центрированный (левое поддерево, корень, правое поддерево) : 4 2 5 1 3
- Прямой (корень, левое поддерево, правое поддерево) : 1 2 4 5 3
- Обратный (левое поддерево, правое поддерево, корень) : 4 5 2 3 1
- Пройти по левому поддереву, т.е. вызвать Inorder(left-subtree) .
- Зайти в корень.
- Пройти по правому поддереву, т.е. вызвать Inorder(right-subtree) .
- Зайти в корень.
- Обратиться к левому поддереву, т.е. вызвать Preorder(left-subtree) .
- Пройти по правому поддереву, т.е. вызвать Preorder(right-subtree) .
- Обратиться к левому поддереву, т.е. вызвать Postorder(left-subtree) .
- Пройти по правому поддереву, т.е. вызвать Postorder(right-subtree) .
- Зайти в корень.
# узел бинарного дерева class Node: def __init__(self, key): self.left = None self.right = None self.val = key # ф-ия для обхода дерева по # центрированному алгоритму def print_inorder(root): if root: # рекурсивно возвращаемся к левому поддереву print_inorder(root.left) # выводим данные узла print(root.val), # рекурсивно возвращаемся к правому поддереву print_inorder(root.right) # ф-ия для обхода дерева по # обратному алгоритму def print_postorder(root): if root: # рекурсивно возвращаемся к левому поддереву print_postorder(root.left) # рекурсивно возвращаемся к правому поддереву print_postorder(root.right) # выводим данные узла print(root.val), # ф-ия для обхода дерева по # прямому алгоритму def print_preorder(root): if root: # сначала выводим данные print(root.val), # рекурсивно возвращаемся к левому поддереву print_preorder(root.left) # рекурсивно возвращаемся к правому поддереву print_preorder(root.right) # запускаем шайтан-код root = Node(1) root.left = Node(2) root.right = Node(3) root.left.left = Node(4) root.left.right = Node(5) print("Обход по прямому алгоритму:") print_preorder(root) # Обход по прямому алгоритму: # 1 # 2 # 4 # 5 # 3 print("\nОбход по центрированному алгоритму:") print_inorder(root) # Обход по центрированному алгоритму: # 4 # 2 # 5 # 1 # 3 print("\nОбход по обратному алгоритму:") print_postorder(root) # Обход по обратному алгоритму: # 4 # 5 # 2 # 3 # 1
Временная сложность — O(n).
Обход дерева в ширину:
Для приведённого выше дерева порядок следования уровней следующий: 1 2 3 4 5.
Сначала посещается сам узел, а затем его дочерние узлы помещаются в очередь. Ниже приведён алгоритм для этого:
- Создать пустую очередь q
- temp_node = root /*начать с корня*/
- Запустить цикл while пока temp_node is not None
- вывести данные в temp_node
- записать дочерние узлы temp_node (сначала левый, затем правый) в q
- удалить узел из q
# обход дерева в ширину с помощью очереди # задаём структуру узла class Node: def __init__(self ,key): self.data = key self.left = None self.right = None # итеративный метод вывода # высоты дерева def print_level_order(root): if root is None: return # создаём пустую очередь queue = [] # добавляем узел queue.append(root) while(len(queue) > 0): # выводим первый элемент в очереди # и убираем его print (queue[0].data) node = queue.pop(0) # добавляем в очередь левое поддерево if node.left is not None: queue.append(node.left) # добавляем в очередь правое поддерево if node.right is not None: queue.append(node.right) # запускаем шайтан-код root = Node(1) root.left = Node(2) root.right = Node(3) root.left.left = Node(4) root.left.right = Node(5) print("Обходим дерево по ширине:") print_level_order(root) >>>OUTPUT # Обходим дерево по ширине: # 1 # 2 # 3 # 4 # 5Временная сложность — O(n).
Бинарное дерево поиска (Binary Search Tree)
Это основанная на узлах двоичная древовидная структура данных, обладающая следующими свойствами:
- Левое поддерево узла содержит только узлы с ключами, меньшими, чем ключ узла.
- Правое поддерево узла содержит только узлы с ключами, большими, чем ключ узла.
- Каждое левое и правое поддерево также должно быть двоичным деревом поиска.
Такие свойства дерева обеспечивают упорядочивание ключей, поэтому операции поиска, нахождение минимума и максимума могут быть выполнены быстро. Если порядка нет, то для поиска заданного ключа может потребоваться сравнение каждого ключа.
Поиск элемента
- Начинаем с корня.
- Сравниваем искомый элемент с корнем. Если он меньше корня, то выполняем перебор слева, если больше, то справа.
- Если искомый элемент найден, возвращаем True, иначе — False.
# ф-ия поиска ключа def search(root,key): # Базовый случай: узел пуст или ключ в узле if root is None or root.val == key: return root # Ключ больше того, что в узле if root.val < key: return search(root.right,key) # Ключ меньше того, что в узле return search(root.left,key)
Вставка ключа
- Начинаем с корня.
- Сравниваем вставляемый элемент с корнем, если он меньше, чем корень, то выполняем перебор слева, иначе — выполняем перебор элементов справа.
- Достигнув конца, вставляем этот узел слева (если он меньше текущего), иначе — справа.
# задаём структуру узла class Node: def __init__(self, key): self.left = None self.right = None self.val = key # ф-ия вставки нового узла с # заданным ключом def insert(root, key): if root is None: return Node(key) else: if root.val == key: return root elif root.val < key: root.right = insert(root.right, key) else: root.left = insert(root.left, key) return root # ф-ия для центрированного обхода дерева def inorder(root): if root: inorder(root.left) print(root.val) inorder(root.right) # запускаем шайтан-код # чтобы создать такое дерево: # 50 # / \ # 30 70 # / \ / \ #20 40 60 80 r = Node(50) r = insert(r, 30) r = insert(r, 20) r = insert(r, 40) r = insert(r, 70) r = insert(r, 60) r = insert(r, 80) inorder(r) >>>OUTPUT # 20 # 30 # 40 # 50 # 60 # 70 # 80
Графы (Graphs)
Граф — это нелинейная структура данных, состоящая из узлов и рёбер. Узлы иногда также называют вершинами, а рёбра — линиями или дугами, которые соединяют любые две вершины графа. Более формально граф можно определить как структуру, состоящую из конечного набора вершин (или узлов) и набора рёбер, которые соединяют пару вершин.
В приведенном выше графе множество вершин V = и множество ребер E = . Наиболее часто используемыми представлениями графа являются следующие два типа:
- матрица смежности;
- список смежности.
Матрица смежности
Матрица смежности — это двумерный массив размером V на V, где V — количество вершин в графе. Пусть двумерный массив будет adj[][] , слот adj[i][j] = 1 означает, что существует ребро из вершины i в вершину j . Матрица смежности для неориентированного графа всегда симметрична; она также используется для представления взвешенных графов. Если adj[i][j] = w , то существует ребро из вершины i в вершину j с весом w .
# Простое представление графа с помощью матрицы смежности class Graph: def __init__(self,numvertex): self.adj_matrix = [[-1]*numvertex for x in range(numvertex)] self.numvertex = numvertex self.vertices = <> self.verticeslist =[0]*numvertex def set_vertex(self,vtx,id): if 0>>OUTPUT # Матрица смежности: [('a', 'c', 20), ('a', 'e', 10), ('b', 'c', 30), ('b', 'e', 40), ('c', 'a', 20), ('c', 'b', 30), ('d', 'e', 50), ('e', 'a', 10), ('e', 'b', 40), ('e', 'd', 50), ('e', 'f', 60), ('f', 'e', 60)] print("Матрица смежности:") print(G.get_matrix()) >>>OUTPUT # Матрица смежности: [[-1, -1, 20, -1, 10, -1], [-1, -1, 30, -1, 40, -1], [20, 30, -1, -1, -1, -1], [-1, -1, -1, -1, 50, -1], [10, 40, -1, 50, -1, 60], [-1, -1, -1, -1, 60, -1]]Список смежности
Для его создания используется массив списков. Размер массива равен количеству вершин. Пусть массив представляет собой array[] . Запись array[i] представляет собой список вершин, смежных с вершиной i . Веса рёбер могут быть представлены в виде списков пар. Вот представление в виде списка смежности для графа выше.
# Класс для представления списка смежности узла class AdjNode: def __init__(self, data): self.vertex = data self.next = None # Класс для представления графа. # Граф - это список списков смежности. # Размер массива равен кол-ву вершин "V". class Graph: def __init__(self, vertices): self.V = vertices self.graph = [None] * self.V # Функция для добавления ребра в неориентированный граф def add_edge(self, src, dest): # Добавление узла к исходному узлу node = AdjNode(dest) node.next = self.graph[src] self.graph[src] = node # Добавление узла источника к узлу назначения # так как это неориентированный граф node = AdjNode(src) node.next = self.graph[dest] self.graph[dest] = node # Функция для вывода графа def print_graph(self): for i in range(self.V): print("Список смежности вершин <>\n начало".format(i), end="") temp = self.graph[i] while temp: print(" -> <>".format(temp.vertex), end="") temp = temp.next print(" \n") if __name__ == "__main__": V = 5 graph = Graph(V) graph.add_edge(0, 1) graph.add_edge(0, 4) graph.add_edge(1, 2) graph.add_edge(1, 3) graph.add_edge(1, 4) graph.add_edge(2, 3) graph.add_edge(3, 4) graph.print_graph() # Список смежности вершины 0 # начало -> 4 -> 1 # Список смежности вершины 1 # начало -> 4 -> 3 -> 2 -> 0 # Список смежности вершины 2 # начало -> 3 -> 1 # Список смежности вершины 3 # начало -> 4 -> 2 -> 1 # Список смежности вершины 4 # начало -> 3 -> 1 -> 0Обходы графа
Обход в ширину (Breadth-First Search или BFS)
Обход в ширину для графа похож на аналогичный алгоритм для дерева. Единственная загвоздка в том, что в отличие от деревьев, графы могут содержать циклы, поэтому мы можем прийти к одному и тому же узлу снова. Чтобы избежать обработки узла более одного раза, мы используем массив из булевых значений «посещён/не посещён». Для простоты предполагается, что все вершины достижимы из начальной вершины.
Например, в следующем графе мы начинаем обход с вершины 2. Когда мы приходим к вершине 0, мы ищем все смежные с ней вершины. 2 также является смежной вершиной 0. Если мы не пометим посещённые вершины, то 2 будет обработана снова, и процесс зациклится. BFS следующего графа — 2, 0, 3, 1.
from collections import defaultdict # Этот класс представляет направленный граф, # построенный с помощью списка смежности class Graph: # конструктор def __init__(self): # словарь для хранения графа self.graph = defaultdict(list) # ф-ия для добавления ребра к графу def add_edge(self,u,v): self.graph[u].append(v) # ф-ия для печати результата обхода графа def BFS(self, s): # Пометить все вершины как не посещённые visited = [False] * (max(self.graph) + 1) # Создание очереди для BFS queue = [] # Пометить исходный узел как посещённый # и поставить его в очередь queue.append(s) visited[s] = True while queue: # Удалить вершину из # очереди и вывести её s = queue.pop(0) print (s, end = " ") # Получить все смежные вершины отложенной вершины s. # Если смежная вершина не была посещена, то пометить # её посещённой и поставить в очередь for i in self.graph[s]: if visited[i] == False: queue.append(i) visited[i] = True # магия g = Graph() g.add_edge(0, 1) g.add_edge(0, 2) g.add_edge(1, 2) g.add_edge(2, 0) g.add_edge(2, 3) g.add_edge(3, 3) print("Обход в ширину (начиная с вершины 2)") g.BFS(2) # Обход в ширину (начиная с вершины 2) # 2 0 3 1Временная сложность — O (V+E), где V — количество вершин в графе, а E — количество рёбер.
Обход в глубину (Depth First Search или DFS)
- Создайте рекурсивную функцию, которая принимает индекс узла и список visited .
- Пометить текущий узел как посещённый и вывести его на печать.
- Пройти по всем соседним и не помеченным узлам и вызвать рекурсивную функцию с индексом соседнего узла.
from collections import defaultdict # Этот класс представляет направленный граф, # построенный с помощью списка смежности class Graph: # конструктор def __init__(self): # словарь для хранения графа self.graph = defaultdict(list) # ф-ия для добавления ребра к графу def add_edge(self, u, v): self.graph[u].append(v) # ф-ия для ф-ии DFS def DFSUtil(self, v, visited): # помечаем узел как посещённый # и выводим его visited.add(v) print(v, end=' ') # Повторить для всех вершин, # смежных с данной вершиной for neighbour in self.graph[v]: if neighbour not in visited: self.DFSUtil(neighbour, visited) # ф-ия для обхода графа в глубину. # в ней используется рекурсивная ф-ия DFSUtil() def DFS(self, v): # создаём множество, где хранятся все # посещённые вершины visited = set() # вызов рекурсивной спромогательной ф-ии # для вывода результата обхода графа в глубину self.DFSUtil(v, visited) # создаём граф # как на диаграмме выше g = Graph() g.add_edge(0, 1) g.add_edge(0, 2) g.add_edge(1, 2) g.add_edge(2, 0) g.add_edge(2, 3) g.add_edge(3, 3) print("Обход графа в глубину (начиная с вершины 2)") g.DFS(2) # Обход графа в глубину (начиная с вершины 2) # 2 0 1 3Рекурсия
Процесс, в котором функция прямо или косвенно вызывает саму себя, называется рекурсией, а соответствующая функция — рекурсивной. Некоторые проблемы можно решить довольно легко, если использовать рекурсивные алгоритмы. Примерами таких задач являются Ханойские башни (TOH), обходы деревьев и т.д.
Что такое базовое условие в рекурсии?
В рекурсивной программе дается решение базового случая, а решение большей задачи разбивается на меньшие.
def fact(n): # базовый случай if (n < = 1) return 1 else return n*fact(n-1)
Как выделяется память для различных вызовов функций при рекурсии?
Когда мы вызываем функцию из main() , для неё на стеке выделяется память. Рекурсивная функция вызывает сама себя, память для вызываемой функции выделяется поверх памяти, выделенной для вызывающей функции, и для каждого вызова функции создается своя копия локальных переменных. Когда достигается базовый вариант, функция возвращает своё значение функции, которой она была вызвана, память деаллоцируется, и процесс продолжается.
Давайте рассмотрим работу рекурсии на примере простой функции.
def printFun(test): if (test < 1): return else: print(test, end=" ") printFun(test-1) print(test, end=" ") return test = 3 printFun(test) # 3 2 1 1 2 3
Стек памяти показан на следующей диаграмме.
Динамическое программирование
Динамическое программирование — это, в основном, оптимизация обычной рекурсии. Везде, где мы видим рекурсивное решение, которое имеет повторяющиеся вызовы для одних и тех же входов, можем его оптимизировать с помощью динамического программирования. Идея заключается в том, чтобы хранить результаты подзадач, а не вычислять их заново, когда они понадобятся позже. Эта простая оптимизация уменьшает временные сложности от экспоненциальных до полиномиальных.
Например, если мы напишем простое рекурсивное решение для чисел Фибоначчи, то получим экспоненциальную временную сложность, а если мы оптимизируем его, сохранив решения подзадач, то временная сложность уменьшится до линейной.
# экспоненциальная временная сложность: def fib1(n): if n
Табуляция в сравнении с мемоизацией
Существует два различных способа хранения значений, чтобы их можно было использовать повторно. Здесь будут рассмотрены два способа решения задачи динамического программирования (DP):
- Табуляция: снизу вверх
- Мемоизация: сверху вниз
Табуляция
Давайте опишем состояние для нашей задачи как dp[x] с dp[0] в качестве базового состояния и dp[n] в качестве состояния назначения. Итак, нам нужно найти значение состояния назначения, т.е. dp[n] .
Если мы начинаем переход из базового состояния, т.е. dp[0] , и, следуя соотношению перехода состояний, достигаем состояния назначения dp[n] , мы называем это подходом «снизу вверх», поскольку совершенно ясно, что мы начали переход из нижнего базового состояния и достигли самого верхнего желаемого состояния.
Итак, почему мы называем это методом табуляции?
Чтобы узнать это, давайте сначала напишем код для вычисления факториала числа с использованием подхода «снизу вверх». Сначала определяем состояние. В данном случае мы определяем состояние как dp[x] , где dp[x] — это нахождение факториала числа x.
Теперь совершенно очевидно, что dp[x+1] = dp[x] * (x+1):
# находим факториал с помощью табуляции dp = [0]*MAXN # базовый случай dp[0] = 1; for i in range(n+1): dp[i] = dp[i-1] * i
Мемоизация
Снова опишем это как переход состояний. Если нам нужно найти значение для некоторого состояния, скажем dp[n] , и вместо того, чтобы начинать с базового состояния, т.е. dp[0] , мы запрашиваем ответ из состояний, которые могут достичь целевого состояния dp[n] , следуя отношению перехода состояний, то это нисходящий пример динамического программирования.
Здесь мы начинаем путь с самого верхнего состояния назначения и вычисляем его ответ, подсчитывая значения состояний, которые могут достичь состояния назначения, пока не достигнем самого нижнего базового состояния.
# находим факториал с помощью меморизации dp[0]*MAXN def solve(x): if (x==0) return 1 if (dp[x]!=-1) return dp[x] return (dp[x] = x * solve(x-1))
ЦП Автоматизированные системы управления и промышленная безопасность
25. Алгоритм и его свойства. Способы записи алгоритмов
26.08.2014 14:18 Александр

Понятие алгоритма - одно из самых фундаментальных понятий информатики. Алгоритмизация наряду с моделированием выступает в качестве общего метода информатики. К реализации определенных алгоритмов сводятся процессы управления в различных системах, что делает понятие алгоритма близким к кибернетике.
Алгоритмы являются объектом систематического исследования пограничной между математикой и информатикой научной дисциплины, примыкающей к математической логике - теории алгоритмов. Особенность положения, однако, состоит в том, что при решении практических задач, предполагающих разработку алгоритмов для реализации на ЭВМ, и тем более при использовании на практике информационных технологий, можно, как правило, не опираться на высокую формализацию данного понятия, и поэтому представляется целесообразным познакомиться с алгоритмами и алгоритмизацией на основе содержательного толкования сущности понятия алгоритма и рассмотрения основных его свойств. При таком подходе алгоритмизация более выступает в основном как набор определенных практических приемов, особых специфических навыков рационального мышления в рамках заданных языковых средств. Можно провести аналогию между этим обстоятельством и рассмотренным выше подходом к измерению информации: тонкие математические построения при "кибернетическом" подходе не очень нужны при использовании гораздо более простого "объемного" подхода при практической работе с компьютером.
Само слово "алгоритм" происходит от algorithmi - латинской формы написания имени великого математика IX века аль-Хорезми, который сформулировал правила выполнения арифметических действий. Первоначально под алгоритмами и понимали только правила выполнения четырех арифметических действий над многозначными числами.
Алгоритм - это определенным образом организованная последовательность действий, за конечное число шагов приводящая к решению задачи.
Алгоритм должен быть составлен таким образом, чтобы исполнитель, в расчете на которого он создается, мог однозначно и точно следовать командам алгоритма и эффективно получать определенный результат. Это накладывает на записи алгоритмов целый ряд обязательных требований, суть которых вытекает, вообще говоря, уже из приведенного выше неформального толкования понятия алгоритма. Сформируем эти требования в виде перечня свойств, которым должны удовлетворять алгоритмы, адресуемые заданному исполнителю.
1. Одно из первоначальных требований, которое предъявляется к алгоритму, состоит в том, что описываемый процесс должен быть разбит на последовательность отдельных шагов. Возникающая в результате такого разбиения запись представляет собой упорядоченную совокупность четко отделенных друг от друга предписаний (директив, команд, операторов), образующих прерывную (или, как говорят, дискретную) структуру алгоритма. Только выполнив требования одного предписания, можно приступить к выполнению следующего. Дискретная структура алгоритмической записи может, например, подчеркиваться сквозной нумерацией отдельных команд алгоритма, хотя это требование не является обязательным. Рассмотренное свойство алгоритмов называют дискретность.
2. Используемые на практике алгоритмы составляются с ориентацией на определенного исполнителя. Чтобы составить для него алгоритм, нужно знать, какие команды этот исполнитель может понять и исполнить, а какие не может. Известно, что у каждого исполнителя имеется своя система команд. Очевидно, что, составляя запись алгоритма для определенного исполнителя, можно использовать лишь те команды, которые имеются в его СКИ. Это свойство алгоритмов будем называть понятностью.
3. Будучи понятным, алгоритм не должен все же содержать предписаний, смысл которых может восприниматься неоднозначно. Это означает, что одна и та же команда, будучи понятна разным исполнителям, после исполнения каждым из них должна давать одинаковый результат.
В данном случае речь фактически идет о том, что запись алгоритма должна быть настолько четкой, полной и продуманной в деталях, чтобы у исполнителя никогда не могло возникнуть потребности в принятии каких-либо самостоятельных решений, не предусмотренных составителем алгоритма. Говоря иначе, алгоритм не должен оставлять места для произвола исполнителя. Кроме того, в алгоритмах недопустимы также ситуации, когда после выполнения очередной команды алгоритма исполнителю неясно, какая из команд алгоритма должна выполняться на следующем шаге. Отмеченное свойства алгоритмов называют определенностью или детерминированностью.
4. Обязательное требование к алгоритмам - результативность. Смысл этого требования состоит в том, что при точном исполнении всех предписаний алгоритма процесс должен прекратиться за конечное число шагов и при этом должен получиться определенный ответ на вопрос задачи (либо вывод о том, что решения не существует).
5. Наиболее распространены алгоритмы, обеспечивающие решение не одной исключительной задачи, а некоторого класса задач данного типа. Это свойство алгоритма называют массовостью. В простейшем случае массовость обеспечивает возможность использования различных значений исходных данных.
Способы записи алгоритма
Основными изобразительными средствами алгоритмов являются следующие способы их записи:
Словесный способ – это содержание этапов вычислений задается на естественном языке в произвольной форме с требуемой детализацией.
Формульно-словесный способ - это задание инструкций с использованием математических символов и выражений в сочетании со словесными пояснениями.
Блок-схемный способ - это графическое изображение логической структуры алгоритма, в котором каждый этап процесса переработки данных представляется в виде геометрических фигур (блоков), имеющих определенную конфигурацию в зависимости от характера выполняемых операций.
В приведенной ниже табл.16 представлены основные блоки, используемые при построении алгоритма в блок-схемной форме.
Таблица 16. Графические изображения блок-схем

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


