
- •Билет №8 Вычисления с вещественными числами и машинное эпсилон №8
- •Машинное эпсилон может быть вычислено с помощью следующей программы:
- •Билет №9 Общая схема итерации и №9 рекуррентные вычисления
- •Вычисления с целыми числами
- •Билет№10 Схема интерации. Вычисление частичной суммы (вещ числа) №10
- •Общая формулировка задания
- •Пример выполнения задания
- •Вопрос №12 . Основные правила аналитической верификации программ №12
- •Вопрос №14 Ограничивающая ф-ция оператора цикла.Примеры №14
- •Билет№15. Теорема о цикле, его инварианте и ограничивающей функции №15
- •Билет №17 Теорема о цикле repeat- until ,его инварианте и ограничивающей его ф-ции. Аннотирование цикла и понимание аннотации №17
- •Вопрос№18 Алгоритм анализа двоичной записи натурального числа в двоичной сист счисления №18
- •Последовательности и файлы
- •Индуктивные функции
- •Стационарные значения индуктивных функций
- •Индуктивные расширения функций
- •Примеры задач с индуктивными функциями
- •Кванторы и запись утверждений о массивах
- •Правила вывода для цикла с параметром
- •Примеры программ работы с массивами
- •Решение последовательными обменами
- •Задача перестановки сегментов разной длины (второе решение)
- •Задача перестановки сегментов разной длины (третье решение)
- •Билет№35 Задача о равнинах (с испол массива) №35
- •Линейный поиск
- •Оптимизация программы бинарного поиска
- •Билет№44 Массивы в качестве параметров:открытые массивы №44 Сведения о массивах в языке Паскаль
- •Билет№45 Двумерные (многомерные массивы). Индексация. Исполбзование процедур. Задача: о МиниМаксе(матрица как массив строк) №45 обработка двухмерных массивов
Правила вывода для цикла с параметром
Для определения правила вывода цикла for необходимо сделать следующее ограничение: тело цикла не может изменить значения параметра цикла и границ цикла. Именно таким свойством обладает цикл for в Паскале. Введем обозначения для открытых и замкнутых интервалов:
[a..b] { ia i b}, [a..b) { ia i < b},
(a..b] { ia < i b}, (a..b) { ia < i < b}.
Заметим, что [a..a) (b..b] [ ], где [ ] обозначает пустой интервал. Введем также обозначения для утверждения P(w) относительно интервала w:
P([a..b]) ( ia i b: Pi), P([a..b)) ( ia i < b: Pi),
P((a..b]) ( ia < i b: Pi), P((a..b)) ( ia < i < b: Pi).
Рассмотрим оператор цикла for i := a to b do S. В соответствии с семантикой этого оператора параметр цикла при выполнении пробегает последовательность значений
a, succ(a), succ(succ(a)), ..., pred(b), b. (4.3)
Предположим, что тело цикла S обладает св-ом:
{P([a..i))} S {P([a..i])}. (4.4)
При этом говорят, что тело цикла S действует на утверждение P индуктивно. Тогда, согласно (4.3) и (4.4), после первой итерации будет выполняться P([a..a]), после второй P([a..succ(a)]) и т. д. Наконец, при завершении последней итерации получим P([a..b]). Если дополнительно предположить, что P([a..a)), т. е. P([ ]) истинно, то из приведенных рассуждений следует правило вывода
{ (a i b) & P([a..i))} S {P([a..i])}
. (4,5) {P([ ])} for i := a to b do S {P([a..b])}
Утверждение P([a..i)) является инвариантом точки входа в тело цикла, а утверждение P([a..i]) инвариантом точки выхода из него. Далее будем называть P([a..i]) и P([a..i)) индуктивными утверждениями цикла с параметром for i := a to b do S.
Заметим, что в случае цикла for (в отличие от циклов while-do и repeat-until) проблема завершимости цикла не возникает. В силу семантики цикла for для его завершения достаточно, чтобы завершилось выполнение S(i) при всех i из интервала a i b.
Аналогичным образом можно получить правило вывода для цикла for i := b downto a do S:
{(a i b) & P((i..b])} S {P([i..b])}
(4,6) {P([ ])} for i := b downto a do S {P([a..b])}
Примеры программ работы с массивами
Пример 1. Рассмотрим применение правила вывода для цикла с параметром в задаче нахождения максимального эл-та заданного массива a[1..n]. Пусть требуется найти номер m максимального элемента, т. е. такое m, что (1 m n) & ( j: 1 j n: a[m] a[j]).
Очевидное решение состоит в просмотре всех элементов массива, реализуемом с помощью цикла for-to-do. Тогда постусловие этого цикла есть
P([1..n]) (1 m n) & ( j: 1 j n: a[m] a[j]),
а индуктивные утверждения P([1..i]) и P([1..i)) имеют вид:
P([1..i]) (1 m i) & ( j: 1 j i: a[m] a[j]),
P([1..i)) (1 m < i) & ( j: 1 j < i: a[m] a[j]),
При этом P([1..1)) true. По смыслу на i-м шаге значение m номер максимального элемента в просмотренной части a[1..i]. Решением задачи будет цикл {n>0} m := 1; for i := 1 to n do ТЕЛО ЦИКЛА. В соответствии с правилом вывода (4.5) тело цикла должно действовать индуктивно на P([1..i]), т. е. {P([1..i))} ТЕЛО ЦИКЛА {P([1..i])}.
Содержательно понятно, что расширить область истинности утверждения P(*) от [1..i) до [1..i] можно сравнением текущего максимума a[m] с добавляемым элементом a[i]:
m := 1; for i := 1 to n do if a[i] > a[m] then m := i .
Пример 2. Вычисление суммы элементов массива a[1..n]
Естественным в этой задаче выглядит применение цикла с параметром for i := 1 to n do. Постусловие этого цикла запишем в виде P([1..n]) (s = ( j: 1 j n: a[j])).
Тело цикла должно индуктивно действовать на утверждение P([1..i]) (s = ( j: 1 j i: a[j])).
Преобразовать утверждение
P([1..i)) (s = ( j: 1 j < i: a[j]))
в утверждение P([1..i]) можно оператором s := s + a[i].
Для цикла for i := n downto 1 do по правилу(4.6) аналогично имеем: P([i..n]) (s = ( j: i j n: a[j]),
P((i..n]) (s = ( j: i < j n: a[j]),
что приводит к такому же телу цикла. Таким образом, имеем два симметричных решения:
1) s := 0; for i := 1 to n do s := s + a[i];
2) s := 0; for i := n downto 1 do s := s + a[i].
Билет№29 Верификацияпрограмм с одномерными массив(прим:схема Горнера,вычисл произв полинома) №29
Пример 3. Вычисление значения полинома
pn(x)
= a1 + a2 x1 + a3 x2 +...+ an xn 1
где n порядок (количество коэффициентов) полинома.
Введем более детальные обозначения:
указав в качестве дополнительных аргументов (или параметров) функции p() порядок полинома n и набор коэффициентов a[1..n], где a[i] соответствует ai.
Рассмотрим также полином, определяемый отрезком массива a[k..m] при 1 k m n:
Как и в предыдущем примере, рассмотрим постусловие
P([1..n]) ( p = p(n, a[1..n]; x))
и две его модификации, соответствующие двум видам цикла с параметром:
P([1..j]) ( p = p(j, a[1..j]; x)), (4.7)
P([j..n]) ( p = p(n j + 1, a[j..n]; x)). (4.8)
Первый вариант (4.7), как легко видеть, приводит к программе p := 0; z := 1;
for i := 1 to n do
begin {z = x i 1}
p := p + a[i]*z;
z := z*x
end
Второй вариант (4.8) предполагает свойство тела цикла for-downto-do:
{P((j..n])} ТЕЛО ЦИКЛА {P([j..n])}. (4.9)
Поскольку
P((j..n]) (p = p(n j, a(j..n]; x) = aj + 1 + aj+2 x + ... + an xn – j 1),
P([j..n]) (p = p(n j + 1, a[j..n]; x) = aj + aj+1 x + ... + an xn – j),
aj + aj+1 x + ... + an xn – j = aj + x (aj + 1 + aj+2 x + ... + an xn – j 1),
то тело цикла, обладающее св-ом (4.9), будет иметь вид p := a[j] + p*x.
В итоге получаем программу вычисления полинома по схеме Горнера:
p := 0; for j := n downto 1 do p := a[j] + p*x
или при n > 0
p := a[n]; for j := n 1 downto 1 do p := a[j] + p*x .
Очевидно, что этот вариант соответствует последовательному вычислению полинома в порядке убывания степеней и по существу является другой формой записи алгоритма вычисления индуктивной функции (см. 3.5) в том случае, когда в массиве записана последовательность коэффициентов по возрастающим степеням.
Пример 4. Вычисление производной полинома
(4.10)
Как и ранее, полином порядка n задан массивом коэффициентов a[1..n]. Требуется найти b[1..n 1] коэффициенты полинома, являющегося производной исходного полинома pn(x):
Дифференцируя (4.10) по x, получим
( i: 1 i n 1: b[i] = i*a[i + 1]). (4.11)
Утверждение (4.11) есть постусловие. Решением явл. цикл for i := 1 to n 1 do b[i] := i*a[i+1], (4.12)
а индуктивное утверждение этого цикла есть
P([1..i]) ( j: 1 j i: b[j] = j*a[j + 1]).
В том случае, когда желательно ограничиться одним массивом, можно записать коэффициенты b1, b2, ... , bn1 в массив a[1..n]. Поскольку к моменту вычисления b[i] в (4.12) эл-т массива a[i] уже «использован» и для вычисления b[i +1..n 1] не потребуется, то вычисленное значение b[i] можно сохранить в a[i]. Тогда вместо (4.12) получим for i := 1 to n 1 do a[i] := i*a[i + 1]. (4.13)
Для того чтобы записать постусловие к этому циклу, удобно специальным образом обозначить исходное состояние массива a[1..n], например, ( i: 1 i n: a[i] = A[i]) или a[1..n] = A[1..n]. Тогда цикл (4.13) обладает св-ом : {(n>0) & (a[1..n] = A[1..n])}
for i := 1 to n 1 do a[i] := i*a[i + 1] {(n > 0) & ( i: 1 i n 1: a[i] = i*A[i+1]) & (a[n] = A[n])}.
Если определить процедуру дифференцирования заданного полинома с записью результата на то же место:
procedure DiffPoly ( n: OrderPoly; var a: CoefPoly );
{ арг : n ; арг-рез : a[1..n] }
{коэффициенты исходного полинома в массиве a[1..n]}
{коэффициенты выходного полинома в массиве a[1..n 1]}
var i: Index;
begin
{Pred: (a[1..n] = A[1..n]) & (n > 1)}
for i :=1 to n 1 do a[i] := i*a[i + 1];
{Post: (ALL i: 1<=i<=(n1) : a[i]=i*A[i+1] )&(a[n]=A[n]) }
end { DiffPoly },
для которой определены глобальные типы, например:
const MaxOrder = 10 ;
type OrderPoly = 1..MaxOrder;
Index = OrderPoly;
CoefPoly = array [Index] of Real,
и переменные
var n: OrderPoly; { порядок исходного полинома}
j: Index; { номер производной }
a: CoefPoly; { массив коэффициентов}
fout: Text; { выходной файл или экран }
то вычислить все производные заданного полинома можно так:
for j := 1 to n 1 do
begin {дифференцируем полином порядка nj+1 с коэффициентами a[1..(nj+1)]}
DiffPoly(n j + 1, a );
{результат: полином порядка nj с коэффициентами a[1.. (nj) ]}
OutPoly(n j, a, fout );{вывод или другие действия с a[1..nj]}
end { for }
Билет№30 Задача слияния упорядоченных массивов №30
Заданы два массива a[1..n] и b[1..m], каждый из которых упорядочен по возр. (неубыванию). Факт упорядоченности массива будем записывать в виде утве-рждения ort(a[1..n], n) ( i: 1 i < n: a[i] a[i + 1]).
Тогда предусловием задачи будет
Pred: (1 n) & (1 m) & Sort(a[1..n], n) & Sort(b[1..m], m).
Требуется получить упорядоченный массив c[1..k] (k = n + m), состав. из всех элементов исходных массивов a[1..n] и b[1..m]. Говорят, что массив c[1..k] получен слиянием исходных массивов.
Постусловие запишем в виде
Post:(k = n + m) & Sort(c[1..k], k) & Смесь(a[1..n], b[1..m], c[1..k]),
где предикат Смесь(a[1..n], b[1..m], c[1..k]) означает, что
( i: 1 i k: ( p: 1 p k: c[i]=c[p]) = ( p: 1 p n: c[i]=a[p]) + ( p: 1 p m: c[i]=b[p])).
В качестве инварианта рассмотрим картинку
-
1
i n
a:
обработано
&
-
1
j m
b:
обработано
&
-
1
q k
c:
обработано
или в текстовой записи
Inv (q = i + j 1) & (1 i n + 1) & (1 j m + 1) &
& Sort(c[1..q), q 1) & Смесь(a[1..i), b[1..j), c[1..q)). (4.14)
На очередном шаге цикла значение переменной q увеличится на 1 и значение одной из переменных i или j тоже увеличится на 1. Цикл следует завершить, когда один из массивов a[1..n] или b[1..m] будет полностью обработан (при этом остаток другого потребуется переписать в «хвост» выходного массива c[q..k]). Эти соображения приводят к программе
i := 1; j := 1; q := 1;
{Inv: (4.14)}
{Bound: (n i + 1)*(m j + 1) или k q + 1, где k = n + m}
while (i <= n) & (j <= m) do
begin
if a[i] < b[j] then begin c[q] := a[i]; i := i + 1 end
else {b[j] a[i]} begin c[q] := b[j]; j := j + 1 end {if};
q := q + 1
end {while};
{переписать a[i..n] или b[j..m] в c[q..k] : }
while (i <= n) do begin c[q] := a[i]; i := i + 1; q := q + 1 end;
while (j <= m) do begin c[q] := b[j]; j := j + 1; q := q + 1 end
Билет№31
Задача разделения массива на две части
(Partition)
№31
Пусть задан массив a[1..n] из эл-ов типа Elem и некоторый x: Elem. Требуется рассортировать эл-ты массива таким образом, чтобы все его эл-ты, меньшие x, находились в начальной части массива, а все эл-ты, большие или равные x, в конце. С помощью картинки-схемы это можно изобразить так:
|
1 |
j n |
|
a: |
Все Эл-ты < x |
Все Эл-ты x |
& (1 j n + 1) |
Соответствующее этой картинке постутверждение есть
(1 j n + 1) & ( i: 1 i < j: a[i] < x) & ( i: j i n: a[i] x).
Тот факт, что элементы выходного массива получены перестановкой эл-ов исходного массива, запишем в виде утверждения Perm(a[1..n], A[1..n]),
где A[1..n] исходное состояние массива a[1..n]. Запишем заголовок процедуры, которая реализует требуемое действие:
procedure Partition ( var a: Vector; n: Index; x: Elem; var j: Index1);
{Pred: (n>0) & (a[1..n] = A[1..n]) }
{Post: (1 j n + 1) & (a[1..j) < x) & (a[j..n] x) & Perm(a[1..n], A[1..n] }
При этом считаем, что глобальные для этой процедуры типы описаны, например, следующим образом:
const nMax = 1000;
type Elem = Integer; {например}
Index = 1..nMax;
Index1 = 1..nMax + 1;
Vector = array [Index] of Elem;
Очевидно, что для решения задачи необходимо просмотреть все элементы массива. В качестве инварианта цикла, реализующего просмотр и перестановки, рассмотрим следующее утверждение (картинку):
|
1 |
j i |
n |
|
a: |
Все Эл-ты < x |
? |
Все Эл-ты x |
& (1 j i + 1 n + 1) |
При этом цикл завершится, когда часть массива a[j..i], помеченная на картинке знаком «?», будет пустой, т. е. когда j = i + 1. Тогда инвариант цикла вместе с условием завершения j = i + 1 обеспечат выполнение требуемого постусловия. Ограничивающей функцией будет t = i – j + 1. Инициализация j = 1 и i = n обеспечивает истинность инварианта перед началом выполнения цикла. Итак, следующая за заголовком часть процедуры Partition имеет вид
var i: index;
begin {Partition1}
{inv: (1 j i + 1 n + 1) & (a[1..j) < x) & (a[i +1..n] x) }
{bound: i – j + 1 }
j := 1; i := n;
while j <= i do
begin
if a[j] < x then j := j+1
else {a[j] x}
if a[i] >= x then i := i 1
else {(a[j] x) & (a[i]<x)}
begin
a[j] a[i];
j := j+1; i := i 1
end { if }
end { while }
end { Partition1 }
Рассмотрим другой вариант процедуры Partition (далее обозначаемый как Partition2), основанный на инварианте цикла P([1..i])
|
1 |
j i |
n |
|
a: |
Все Эл- ты < x |
Все Эл- ты x |
? |
& (1 j i + 1 n + 1) |
Последовательное сокращение области неопределенности массива a(i..n], помеченной на картинке знаком «?», можно реализовать за n шагов циклом for i := 1 to n do, а требуемое постутверждение есть
P([1..n]) (1 j n + 1) & (a[1.. j) < x) & (a[j..n] x) & Perm(a[1..n], A[1..n]).
Соответствующее ему индуктивное утверждение есть
P([1..i]) (1 j i + 1 n + 1) & (a[1.. j) < x) & (a[j..i] x))
При этом инвариантом точки входа в тело цикла является утверждение
P([1..i)) (1 j i n) & (a[1.. j) < x) & (a[j..i) x)),
или, в форме картинки,
|
1 |
j |
i n |
|
a: |
Все Эл-ты < x |
Все Эл-ты x |
? |
& (1 j i n ) |
Эти рассуждения приводят к следующему варианту процедуры Partition:
var i: index;
begin { Partition2 }
j := 1;
for i :=1 to n do
if a[i] < x then
begin
a[j] a[i];
j := j+1
end { if }
end { Partition2 }
Эта версия процедуры Partition2 выглядит более простой, хотя работает несколько медленнее, чем Partition1, за счет того, что в Partition2 эл-т массива может попасть на окончательное место после нескольких перестановок, в то время как в Partition1 эл-т становится на свое место после первой же перестановки, в которой он участвует.
Билет№32 Задача перстановки сегментов массива разной длины.Решение,использ циклы(замкнутые траектории) перестановок №32
Задача о циклическом сдвиге
Постановка задачи. Задан отрезок (сегмент) массива a[m..n) и индекс k (m < k < n), обозначающий границу разделения этого сегмента на две части – на два прилегающих сегмента a[m..k) и a[k..n). Требуется переставить местами данные сегменты. На картинке это выглядит следующим образом:
Картинке
соответствуют предутверждение и
постутверждение:
Pred: (m < k < n) & (a[m..n) = A[m..n)),
Post: (s = m + n k) & (a[m..s) = A[k..n)) & (a[s..n) = A[m..k)).
Сформулированную задачу можно назвать задачей о перестановке в массиве прилегающих сегментов разной длины.
В частном случае при k = m + 1, т. е. при циклическом сдвиге влево на одну позицию (k m = 1), легко записать очевидное решение, изображенное на рис. 4.1 (для простоты положим m = 1).
Рис. 4.1. Циклический сдвиг влево на одну позицию
Сначала в отдельной переменной x запоминается первый эл-т массива, затем все остальные эл-ты от 2-го до (n 1)-го поочередно сдвигаются на одну позицию влево (на «освобождённое» предварительно место). Завершается операция циклического сдвига записью в (n 1)-й эл-т (последний) сохранённого в переменной x первого эл-та исходного массива. Процедура, реализующая эти действия, может иметь следующий вид:
Procedure Shift (var a: vector; n: index);
Var x: Elem; i: index;
Begin
x := a[1];
for i := 2 to n 1 do a[i 1] := a[i];
a[n 1] := x
end {Shift}
Очевидно, что решение задачи о циклическом сдвиге массива a[1..n) на k элементов влево, изображённой на картинке
может быть получено k-кратным применением процедуры Shift:
for i := 1 to k do Shift(a, n).
Общее кол-во перемещений эл-тов при этом составит n k, что вряд ли можно считать приемлемым.