Доказательство введением псевдокода
Я не очень понимаю, как использовать доказательство по индукции на psuedocode. Кажется, это не работает так же, как использование его в математических уравнениях.
Я пытаюсь подсчитать количество целых чисел, которые делятся на k в массиве.
Algorithm: divisibleByK (a, k)
Input: array a of n size, number to be divisible by k
Output: number of numbers divisible by k
int count = 0;
for i <- 0 to n do
if (check(a[i],k) = true)
count = count + 1
return count;
Algorithm: Check (a[i], k)
Input: specific number in array a, number to be divisible by k
Output: boolean of true or false
if(a[i] % k == 0) then
return true;
else
return false;
Как можно доказать, что это правильно? Спасибо
2 ответа
В этом случае я бы интерпретировал "индуктивно" как "индукцию по количеству итераций".
Для этого мы сначала устанавливаем так называемый инвариант петли. В этом случае инвариант цикла:
count
хранит количество чисел, делимых на k
с индексом ниже чем i
,
Этот инвариант сохраняется при входе в цикл и гарантирует, что после цикла (когда i = n
) count
содержит количество значений, делимых на k
во всем массиве.
Индукция выглядит так:
Базовый случай: инвариант цикла сохраняется при входе в цикл (после 0 итераций)
поскольку
i
равно 0, элементы не имеют индекс нижеi
, Поэтому нет элементов с индексом меньшеi
делятся наk
, Таким образом, так какcount
равно 0, инвариант имеет место.Индукционная гипотеза: мы предполагаем, что инвариант выполняется в верхней части цикла.
Индуктивный шаг. Покажем, что инвариант имеет место в нижней части тела цикла.
После того, как тело было выполнено,
i
был увеличен на единицу. Для инварианта цикла, чтобы держать в конце цикла,count
должны быть скорректированы соответственно.Так как теперь есть еще один элемент (
a[i]
) который имеет индекс меньше чем (новый)i
,count
должен был быть увеличен на единицу, если (и только если)a[i]
делится наk
, что именно то, что обеспечивает if-утверждение.Таким образом, инвариант цикла сохраняется и после того, как тело было выполнено.
QED.
В Hoare-логике это доказано (формально) примерно так (переписав это как цикл while для ясности):
{ I }
{ I[0 / i] }
i = 0
{ I }
while (i < n)
{ I ∧ i < n }
if (check(a[i], k) = true)
{ I[i + 1 / i] ∧ check(a[i], k) = true }
{ I[i + 1 / i][count + 1 / count] }
count = count + 1
{ I[i + 1 / i] }
{ I[i + 1 / i] }
i = i + 1
{ I }
{ I ∧ i ≮ n }
{ count = ∑ 0 x < n; 1 if a[x] ∣ k, 0 otherwise. }
куда I
(инвариант) это:
count
= ∑ x 1, если a[x]
∣ k, 0 в противном случае.
(Для любых двух последовательных строк утверждения ({...}
) есть обязательство доказательства (первое утверждение должно подразумевать следующее), которое я оставляю как упражнение для читателя;-)
Мы доказываем правильность по индукции на n
количество элементов в массиве. Ваш диапазон неверен, он должен быть от 0 до n-1 или от 1 до n, но не от 0 до n. Мы примем 1 к п.
В случае n=0 (базовый случай) мы просто проходим алгоритм вручную. counter
инициируется со значением 0, цикл не повторяется, и мы возвращаем значение счетчика, которое, как мы сказали, было 0. Это правильно.
Мы можем сделать второй базовый случай (хотя это и не нужно, как в обычной математике). п =1. Счетчик инициализируется с 0. Цикл делает один проход, в котором i
принимает значение 1, и мы увеличиваем counter
если первое значение в a
делится на k
(что верно из-за очевидной правильности Check
алгоритм).
Поэтому мы возвращаем 0, если a[1]
не делится на k
, а в противном случае мы возвращаем 1. Этот случай также работает.
Индукция проста. Мы предполагаем корректность для n-1 и докажем для n (опять же, как в обычной математике). Чтобы быть должным образом формальным, отметим, что counter
содержит правильное значение, которое мы возвращаем к концу последней итерации в цикле.
По нашему предположению мы знаем, что после n-1 итераций counter
содержит правильное значение относительно первых n-1 значений в массиве. Мы вызываем базовый случай n=1, чтобы доказать, что он добавит 1 к этому значению, если последний элемент делится на k, и, следовательно, конечное значение будет правильным значением для n.
QED.
Вам просто нужно знать, на какую переменную производить индукцию. Обычно размер ввода наиболее полезен. Кроме того, иногда вам нужно предполагать правильность для всех натуральных чисел меньше, чем n, иногда просто n-1. Опять же, как и в обычной математике.