Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Шпаргалка по программированию.DOC
Скачиваний:
55
Добавлен:
01.05.2014
Размер:
1.9 Mб
Скачать

Правила вывода для цикла с параметром

Для определения правила вывода цикла for необходимо сделать следующее ограничение: тело цикла не может изменить значения параметра цикла и границ цикла. Именно таким свойством обладает цикл for в Паскале. Введем обозначения для открытых и замкнутых интервалов:

[a..b]  { ia  i  b}, [a..b)  { ia  i < b},

(a..b]  { ia < i  b}, (a..b)  { ia < 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 := 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   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 = (  ji  j  n:  a[j]),

P((i..n])  (s = (  ji < 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(na[1..n]; x))

и две его модификации, соответствующие двум видам цикла с параметром:

P([1..j])  ( p = p(ja[1..j]; x)), (4.7)

P([j..n])  ( p = p(n  + 1, a[j..n]; x)). (4.8)

Первый вариант (4.7), как легко видеть, приводит к программе p := 0; z := 1;

for := 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  ja(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 := 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]).

В том случае, когда желательно ограничиться одним массивом, можно записать коэффициенты b1b2, ... , 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   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<=(n1) : a[i]=i*A[i+1] )&(a[n]=A[n]) }

end { DiffPoly },

для которой определены глобальные типы, например:

const MaxOrder = 10 ;

type  OrderPoly = 1..MaxOrder;

Index = OrderPoly;

CoefPolyarray [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  + 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  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   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 +  1) & (1  i  + 1) & (1  j  m + 1) & 

Sort(c[1..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]). Эти соображения приводят к программе

:= 1; j := 1; q := 1;

{Inv: (4.14)}

{Bound: ( + 1)*( + 1) или  + 1, где m}

while  (i <= n) & (j <= mdo

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 <= ndo  begin  c[q] := a[i]; i := i + 1; q := q + 1   end;

while  (j <= mdo  begin  c[q] := b[j]; j := j + 1; q := q + 1   end

Билет№31 Задача разделения массива на две части (Partition) №31

Пусть задан массив a[1..n] из эл-ов типа Elem и некоторый xElem. Требуется рассортировать эл-ты массива таким образом, чтобы все его эл-ты, меньшие x, находились в начальной части массива, а все эл-ты, большие или равные x,  в конце. С помощью картинки-схемы это можно изобразить так:

1

j n

a:

Все Эл-ты < x

Все Эл-ты  x

& (1   n + 1)

Соответствующее этой картинке постутверждение есть

(1   n + 1) & ( i: 1  i < j:  a[i] < x) & ( ij   n:  a[i]  x).

Тот факт, что элементы выходного массива получены перестановкой эл-ов исходного массива, запишем в виде утверждения Perm(a[1..n], A[1..n]),

где A[1..n]  исходное состояние массива a[1..n]. Запишем заголовок процедуры, которая реализует требуемое действие:

procedure Partitionvar  a: Vector; n: Index; x: Elemvar  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;

Vectorarray [Indexof  Elem;

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

1

j i

n

a:

Все Эл-ты < x

?

Все Эл-ты  x

& (1   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   i + 1  n + 1) & (a[1..j) < x) & (a[i +1..n]  x) }

{bound: i – + 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   i + 1 n + 1)

Последовательное сокращение области неопределенности массива a(i..n], помеченной на картинке знаком «?», можно реализовать за n шагов циклом for i := 1 to n do, а требуемое постутверждение есть

P([1..n])  (1   n + 1) & (a[1.. j) < x) & (a[j..n]  x) & Perm(a[1..n], A[1..n]).

Соответствующее ему индуктивное утверждение есть

P([1..i])  (1   i + 1 n + 1) & (a[1.. j) < x) & (a[j..i]  x))

При этом инвариантом точки входа в тело цикла является утверждение

P([1..i))  (1   i  n) & (a[1.. j) < x) & (a[j..i)  x)),

или, в форме картинки,

1

j

i n

a:

Все Эл-ты < x

Все Эл-ты  x

?

& (1   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: (n) & (a[m..n) = A[m..n)),

Post: (s = m + n  k) & (a[m..s) = A[k..n)) & (a[s..n) = A[m..k)).

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

В частном случае при + 1, т. е. при циклическом сдвиге влево на одну позицию (k  m = 1), легко записать очевидное решение, изображенное на рис. 4.1 (для простоты положим m = 1).

Рис. 4.1. Циклический сдвиг влево на одну позицию

Сначала в отдельной переменной x запоминается первый эл-т массива, затем все остальные эл-ты от 2-го до (n  1)-го поочередно сдвигаются на одну позицию влево (на «освобождённое» предварительно место). Завершается операция циклического сдвига записью в (n  1)-й эл-т (последний) сохранённого в переменной x первого эл-та исходного массива. Процедура, реализующая эти действия, может иметь следующий вид:

Procedure Shift (var avector; 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, что вряд ли можно считать приемлемым.