NetNado
  Найти на сайте:

Учащимся

Учителям



1. /_Методички/Мультимедиа-системы. Архитектура, стандарты, алгоритмы.pdf
2. /_Методички/Организация ЭВМ 2006.doc
3. /_Методички/Основы сетевых технологий.djvu
4. /_Методички/Программирование/Биные Деревья Поиска/2_01БДПоиска.doc
5. /_Методички/Программирование/Биные Деревья Поиска/2_02БинДП.doc
6. /_Методички/Программирование/Биные Деревья Поиска/2_03СлучайБДП.doc
7. /_Методички/Программирование/Деревья Хаффмана/1.doc
8. /_Методички/Программирование/Деревья Хаффмана/1_1ДХафф.doc
9. /_Методички/Программирование/Деревья Хаффмана/1_2_3_ДХафф.doc
10. /_Методички/Программирование/Деревья Хаффмана/1_4_ДХафф.doc
11. /_Методички/Программирование/Деревья Хаффмана/1_5_ДХафф.doc
12. /_Методички/Программирование/Деревья Хаффмана/1_6_7_ДХафф.doc
13. /_Методички/Программирование/Деревья Хаффмана/1_8ДХафф.doc
14. /_Методички/Программирование/Деревья Хаффмана/1_9ДХафф.doc
15. /_Методички/Программирование/Деревья Хаффмана/ДерХафф full.doc
16. /_Методички/Программирование/Деревья Хаффмана/КарлКлара.doc
17. /_Методички/Программирование/Деревья Хаффмана/Кодируется текст АБРАКАДАБРА.doc
18. /_Методички/Программирование/Динамические СД/00Титул.rtf
19. /_Методички/Программирование/Динамические СД/0Введение.rtf
20. /_Методички/Программирование/Динамические СД/1Списки.doc
21. /_Методички/Программирование/Динамические СД/2СтекОчДек1.rtf
22. /_Методички/Программирование/Динамические СД/3Деревья.rtf
23. /_Методички/Программирование/Динамические СД/4прим_лит.rtf
24. /_Методички/Программирование/Динамические СД/5оглавление.doc
25. /_Методички/Программирование/Динамические СД/Динамическое программирование - Задача о перемножении матриц.doc
26. /_Методички/Программирование/Пособие по разработке корректных программ/00Титул1_3.doc
27. /_Методички/Программирование/Пособие по разработке корректных программ/01Введение.rtf
28. /_Методички/Программирование/Пособие по разработке корректных программ/1АлгоритмЕвклида.doc
29. /_Методички/Программирование/Пособие по разработке корректных программ/2ОсновыАналитВерифПрогРовно.doc
30. /_Методички/Программирование/Пособие по разработке корректных программ/3ИндуктФункции.doc
31. /_Методички/Программирование/Пособие по разработке корректных программ/4Массивы.doc
32. /_Методички/Программирование/Пособие по разработке корректных программ/5Поиск.doc
33. /_Методички/Программирование/Пособие по разработке корректных программ/ОглавФонар.doc
34. /_Методички/Программирование/Пособие по разработке корректных программ/СписЛит&УслОбознач.doc
35. /_Методички/Сетевые технологии.pdf
36. /_Методички/Средства моделирования вычислительных сетей.pdf
37. /_Методички/Управление вычислительными сетями.pdf
Учебное пособие Редактор А. В. Крейцер Издательство спбгэту «лэти» 1 97376, С. Петербург, ул. Проф. Попова, 5
2. Деревья поиска Идеально сбалансированные бинарные деревья
2 Случайные бинарные деревья поиска
Абракадабра!, содержащий 12 символов, включая специальный символ !
Задача кодирования сообщений. Префиксные коды и деревья Пусть задан алфавит
1 Код Фано-Шеннона
1 Метод Хаффмана
1 Реализация алгоритмов кодирования
1 Доказательство оптимальности кода Хаффмана Лемма 1
1 Энтропийная оценка средней длины кода
1 Динамическое кодирование по Хаффману
Абракадабра!, содержащий 12 символов, включая специальный символ !
Абракадабра!, содержащий 12 символов, включая специальный символ !
А. Ю. Алексеев с. А. Ивановский д. В. Куликов
При обучении программированию особую трудность вызывает работа с динамическими структурами данных
2. стеки и очереди спецификация стека и очереди
3 Определения дерева, леса, бинарного дерева. Скобочное представление
Примечания и библиографические указания
Списки
Задача о порядке перемножения матриц
С. А. Ивановский разработкакорректныхпрограм м санкт-Петербург 2003
Программирования
Разработка, доказательство корректности и анализ алгоритма
2. основы аналитической верификации программ основные правила аналитической верификации программ
3. индуктивные функции на последовательностях
4. корректность программ при работе с массивами
5. поиск в массиве линейный поиск
Разработка, доказательство корректности
Шень А. Программирование: теоремы и задачи: Учеб пособие

скачать doc


.

4. КОРРЕКТНОСТЬ ПРОГРАММ ПРИ РАБОТЕ С МАССИВАМИ
На основе сформулированных ранее правил вывода операторов языка Паскаль и правил верификации программ далее рассматриваются вопросы верификации и проектирования программ, работающих с массивами. Вводятся новые способы записи утверждений (предикатов), расширяющие возможности верификации программ при работе с массивами. Использование кванторов позволяет формулировать утверждения о массивах или об их частях.
4.1. Кванторы и запись утверждений о массивах
Квантор существования. Пусть m и n – выражения с целочисленными значениями (m  n). Рассмотрим предикат

Pm or m + 1 or ... or Pn  1, (4.1)

где каждое Pi – предикат. Предикат (4.1) истинен, если истинно хотя бы одно из Pi. Недостатком такой формы записи является то, что здесь используется знак «...», апеллирующий к интуиции читателя. Предикат (4.1) можно записать в другой форме, использующей квантор существования :

( im  i < nPi). (4.2)

Здесь i называется квантифицированной переменной, а неравенство m  i < n задает область её значений. На естественном языке (4.2) читается следующим образом: существует по крайней мере одно (целое) значение i, лежащее между m и n  1 (включительно), для которого выполнено утверждение Pi.

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



Их иногда записывают в форме





что совсем близко к виду (4.2). Далее там, где удобно, будем записывать их в виде

(  im  i < n:   ai), (  im  i < n:   ai).

Формально квантор существования  можно определить рекурсивно (так же как и кванторы  и   ср. с определением индуктивных функций в разд. 3) следующим образом.

Определение :

1) для m : ( im  i < m:  Pi) = false,

2) для n = k + 1 > m: ( im  i < k + 1:  Pi) = ( im  i < k:  Pior Pk.

Следует обратить внимание на базис этого рекурсивного (индуктивного) определения. При пустой области значений квантифицированной переменной дизъюнкция нуля предикатов есть false. Например, ( i: 0  i < 0:  22=4) = false. Это аналогично определениям (  im  i < m:  ai) = 0,   (  im  i < m:  ai) = 1, а также определениям f(), используемым в разд. 3.

Квантор всеобщности. Содержательно определим квантор всеобщности  (читается: «для всех») как

( im  i < nPi) = Pm & m + 1 & ... & Pn  1.

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

Pm & m + 1 & ... & Pn  1 = not not (Pm & m + 1 & ... & Pn  1 ) =

not (not Pm or not P m + 1 or ... or not Pn  1) = not ( im  i < n:  not Pi),

что дает возможность определить квантор  через квантор .

Определение : ( im  i < nPi) = not ( im  i < n:  not Pi).

Из этого определения следует, что

( im  i < m:  Pi) = not ( im  i < m:  not Pi) = not false = true.

В качестве упражнения определите квантор всеобщности рекурсивно (подобно данному ранее определению квантора существования).

Квантор количества (счёта). Для количества различных значений i из области m  i < n, при которых истинно Pi, удобно иметь специальное обозначение ( im  i < n:  Pi) .

Дадим формальное определение (по существу, это определение некоторой индуктивной функции).

Определение :

1) для m : ( im  i < m:  Pi) = 0,

2) для n = k +1 > m: (i k + 1: Pi)=

= ( i kPi)+(if Pk then 1 else 0).

Заметим, что кванторы существования и всеобщности можно выразить через квантор количества:

( im  i < n:  Pi) = ( ( im  i < n :  Pi)  1),

( im  i < n:  Pi) = ( ( im  i < n :  Pi) = n  m).

В качестве примера использования квантора количества приведем ситуацию, когда нужно записать утверждение о том, что k  второе по величине из чисел, для которых выполняется Pk (из ряда возможных P0P1, ...). Если бы требовалось найти наименьшее k, при котором истинно Pk, то это можно было бы выразить таким образом: (k  0) & ( i: 0  i < k:  not Pi) & Pk.

Утверждение о втором по величине k выглядит уже более громоздко:

(k  0) & (0  j < k) & ( i: 0  i < j:  not Pi) & Pj &  ( i+ 1  i < k:  not Pi) & Pk,

не говоря уже об утверждениях относительно третьего и следующих по величине k. С помощью квантора счёта все эти утверждения можно записать лаконичнее и яснее:

для первого k: ( ( i: 0  i < k :  Pi) = 0 ) & Pk,

для второго k: ( ( i: 0  i < k :  Pi) = 1 ) & Pk,  ...

для -го по величине k: ( ( i: 0  i < k :  Pi) =   1 ) & Pk.

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

( im  i  n:  Pi) = ( im  i < n + 1:  Pi).

Замечание 2. Кванторы с прилегающими друг к другу областями можно соединять следующим образом:

( im  i < n:  Pior ( in  i < q:  Pi) = ( im  i < q:  Pi) ,

( im  i < n:  Pi) & ( in  i < q:  Pi) = ( im  i < q:  Pi) ,

( im  i < n:  Pi) + ( in  i < q:  Pi) = ( im  i < q:  Pi) .

Рассмотрим примеры записи утверждений о массивах с помощью кванторов. Пусть имеется массив a[1..n] и целые переменные j и k, такие, что 1  j  k + 1  n + 1. Запись a[j..k] обозначает отрезок (или сегмент) массива. Первый элемент этого сегмента есть a[j], а последний  a[k]. При j = k + 1 сегмент пуст. В каждом приведенном ниже примере даны содержательная и формальная формулировки утверждения:

1) все элементы a[j..k]  нулевые: ( ij  i < k + 1:  a[i] = 0);

2) все элементы a[j..k]  ненулевые: ( ij  i < k + 1:  a[i]  0), или not ( ij  i < k + 1:  a[i] = 0);

3) значения элементов a[j..k] расположены в возрастающем порядке: ( ij  i < k:  a[i] < a[+ 1]);

4) любой элемент a[1..j] меньше y, а любой элемент a[j+1..n] больше y: (0   n) & ( i: 1  i < j + 1:  a[i] < y) & ( ij + 1  i < n + 1:  a[i] > y);

5) значение y встречается в массивах b[1..n] и c[1..m] одинаковое число раз: ( i: 1  i  n:  b[i] = y) = ( i: 1  i  m:  c[i] = y).

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



Предикат Сокращение



( i: 1  i < j + 1:  a[i] < ya[1..j] < y

( ij  i < k + 1:  a[i] = 0) a[j..k] = 0

( ij  i < k + 1:  a[i] = y) y  a[j..k]

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




1

j k

n




a:

?

= 0

?

& (1  j  k + 1  n + 1)


эквивалентна предикату (1  j  k + 1  n + 1) & ( ij  i < k + 1:  a[i] = 0), а картинка




1 j

n




a:

y

y

& (0  j  n )

эквивалентна предикату

(0  j  n ) & ( i: 1  i < j + 1:  a[i] < y) & ( ij + 1  i < n + 1:  a[i] > y).

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

4.2. Правила вывода для цикла с параметром
Для определения правила вывода цикла 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])}
4.3. Примеры программ работы с массивами
Пример 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 .

В качестве упражнения, используя правила вывода условного оператора и оператора присваивания, докажите, что

{ P([1..i)) }  if a[i] > a[m]  then m := i { P([1..i]) }.

Замечание 1. Ясно, что при n >1 цикл можно начать с i = 2, т. е.

m := 1;  for i := 2 to n do  if a[i] > a[m]  then m := i .

Замечание 2. Полезно сравнить решение этой задачи с решением задачи для последовательности, читаемой из файла (см. 3.2).

Пример 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].

Следующий пример демонстрирует тот факт, что подобная симметрия имеется не всегда и использование цикла for-downto-do может оказаться предпочтительным.

Пример 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;