Отправляет email-рассылки с помощью сервиса Sendsay

Всё о работе в Интернет

  Все выпуски  

Занятие 24.


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

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

ЕЩЁ ПАРА АЛГОРИТМОВ С КОМАНДОЙ ВЕТВЛЕНИЯ

1. Решение задачи “Сопротивление”.

Задача V.1.4. “Сопротивление”. Построить алгоритм и подпрограмму вычисления общего сопротивления параллельного соединения двух резисторов с сопротивлениями R1 и R2.

Сформулированная задача известна из школьного курса физики.

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

  ,

где R1 и R2 – сопротивления ветвей, а R общее сопротивление. Из этой формулы следует другая, которая будет использована нами в алгоритме:

.

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

Алгоритм решения задачи целесообразно построить в виде функции, имя которой, а также обозначения аргументов (R, R1 и R2 соответственно) привязаны к основной расчётной формуле. Совершенно очевидно, что алгоритм должен иметь две ветви, в которых реализованы рассмотренные физические факты, связанные с величиной общего сопротивления параллельного соединения. В основной ветви будем учитывать случай, когда оно равно нулю, в альтернативной, – когда оно вычисляется как величина, обратная сумме проводимостей отдельных ветвей. Алгоритм даёт осмысленный результат при условии, что имеют смысл значения аргументов, то есть R1 >= 0 и R2 >= 0.

Алгоритм и его перевод на Паскаль выглядят следующим образом (комментарий дано и надо впредь будем опускать):

алг R ( R1, R2: вещ ): вещ
нач
   если (R1 = 0) или (R2 = 0)
       то R := 0 иначе R := 1 / ( 1 / R1 + 1 / R2 )
кон

Function R ( R1, R2: Real ): Real;
Begin
   If (R1 = 0) Or (R2 = 0)
      Then R := 0 Else R := 1 / (1/R1 + 1/R2)
End;

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

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

Нужное нам выражение имеет вид: Rобщ = R1 + R ( R ( R2, R3 ), R4 + R5 ). Просто удивительно, насколько компактно могут быть записаны выражения для громоздких вычислений при использовании функций!

2. Команда досрочного завершения алгоритма. Оператор выхода.

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

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

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

На этот случай в АЯ предусмотрена специальная команда.    

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

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

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

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

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

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

3. Решение задачи “Квадратное уравнение”.

Задача V.1.5. “Квадратное уравнение”. Заданы коэффициенты a, b, c квадратного уравнения a · x2 + b · x + c = 0. Построить алгоритм и подпрограмму вычисления его действительных корней.

В условии задачи сказано, что заданы коэффициенты a, b, c именно квадратного уравнения. Из этого следует, что коэффициент <> 0, иначе уравнение перестаёт быть квадратным. В этом случае для вычисления действительных корней квадратного уравнеия x1 и x2 школьный курс математики предлагает формулу

,

где дискриминант = b2 – 4 · a · c.

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

Применение для корней квадратного уравнения теоремы Виета даёт известную формулу x1 + x2 = / a. Этим можно воспользоваться для сокращения количества вычислений, то есть корень x1 вычислить по основной формуле, а второй корень – по формуле x2 = / a  x1.

Учитывая то, что действительные корни квадратного уравнения для заданных коэффициентов a, b, c могут и не существовать (при отрицательном дискриминанте), в алгоритме в качестве результатов следует предусмотреть не только значения корней x1 и x2, но и флажок z логического типа. Значение флажка вычисляется с помощью выражения D >= 0. Это значит, что в случае наличия действительных корней получим = ДА, а в случае их отсутствия = НЕТ. Разумеется, значения дискриминанта и флажка должны вычисляться в первую очередь. Затем, если = НЕТ, то есть при не = ДА, алгоритм можно завершить досрочно с помощью команды выход. В противном случае, то есть при продолжении алгоритма, вычисляются оба корня уравнения.

Алгоритм и его перевод на Паскаль выглядят следующим образом:

алг КвУр(арг a,b,c:вещ; рез x1,x2:вещ, z:лог )
нач
D: вещ
   D := b * b – 4 * a * c; z := D >= 0;
   если не
z то выход;
   x1 := (–b + Sqrt(D) ) / ( 2 * a ); x2 := – b / ax1
кон

Procedure Square_Equation(a,b,c:Real;Var x1,x2:Real;Var z: Boolean);
   Var D: Real;
Begin
   D := b * b – 4 * a * c; z := D >= 0;
   If Not z Then Exit;
   x1 := ( –b + Sqrt(D) ) / ( 2 * a ); x2 := – b / a – x1
End;

Для вычисления корней может быть применён также равносильный вариант:

если z то нс x1 := ( –b + Sqrt (D) ) / (2 * a); x2 := –b / a – x1 кс;

 

Однако, на мой взгляд, он выглядит несколько более громоздким, нежели предложенный в алгоритме КвУр (Квадратное Уравнение).

4. Правильно ли мы решили задачу “Квадратное уравнение”?

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

{$B+,D+,E+,I+,L+,N+,Q+,R+,X-}
Program V_01_05;
   Procedure Square_Equation ( a,b,c: Real; Var x1,x2: Real; Var z: Boolean);
      Var D: Real;
   Begin
      D := b*b - 4*a*c; z := D >= 0;
      If Not z Then Exit;

      x1 := ( -b + Sqrt(D) ) / (2*a); x2 := -b/a - x1
   End;
   Var a,b,c,x1,x2: Real; z: Boolean;
Begin
   Write('Введите коэффициенты a,b,c: ');
   ReadLn(a,b,c);
   Square_Equation(a,b,c,x1,x2,z);
   If z Then WriteLn('x1=',x1,', x2=',x2)
        Else WriteLn('Действительные корни отсутствуют');
End.

Программу следует протестировать. Для этого возьмём следующее квадратное уравнение: x2 – 0,66 · x + 0,1089 = 0. Точные значения его корней известны: x1 = x2 = 0,33.

В результате выполнения программы для a = 1b = –0.66, c = 0,1089 получаем результат не совсем тот, который ожидался, а именно:

x1 = 3.30000057220332E–0001,

x2 = 3.29999942779523E–0001.

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

Возьмём для проверки ещё одно квадратное уравнение 3 · x2 – 1,98 · x + 0,3267 = 0 с теми же самыми равными корнями x1 = x2 = 0,33.

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

Вывод из всего этого, прямо скажем, неутешительный. Получается, что со времён первого учебника по информатике, то есть уже около 25 лет, миллионы школьников на просторах бывшего СССР, а затем и СНГ, занимались и занимаются изучением того, как составлять ОШИБОЧНУЮ программу решения квадратного уравнения.

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

Выход лежит практически на поверхности! Совершенно очевидно, что первопричина ошибок – плохая точность вычисления дискриминанта квадратного уравнения. Как уже упоминалось ранее (см. материалы занятия 14), потеря точности происходит в тех случаях, когда предполагается получить результат вычитания близких по значению величин вещественного типа. Поэтому нулевое значение дискриминанта, которое должно было быть полученным, фактически оказалось “замусоренным”. Чтобы избавиться от “мусора” и “очистить” нуль дискриминанта, применим команду := Int ( D * 1E10 ) / 1E10.

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

 

алг КвУр(арг a,b,c:вещ; рез x1,x2:вещ, z:лог )
нач
D: вещ
   D := b * b – 4 * a * c; D := Int (D*1E10)/1E10;
   z := D >= 0; если не
z то выход;
   x1 := (–b + Sqrt(D) ) / ( 2 * a ); x2 := – b / ax1
кон

 

Procedure Square_Equation(a,b,c:Real;Var x1,x2:Real;Var z: Boolean);
   Var D: Real;
Begin
   D := b * b – 4 * a * c; D := Int ( D * 1E10) / 1E10;
   z := D >= 0; If Not z Then Exit;
   x1 := ( –b + Sqrt(D) ) / ( 2 * a ); x2 := – b / a – x1
End;

 

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

 

Уважаемые подписчики! Следующее занятие будет посвящено применению средств построения множественных ветвлений.

Уважаемые подписчики! При необходимости задать вопрос, проконсультироваться, уточнить или обсудить что-либо обращайтесь через Гостевую книгу моего персонального сайта http://a-morgun.narod.ru. При этом настоятельно рекомендую пользоваться браузером Internet Explorer.

С уважением, Александр.

 


В избранное