Обртање цифара - Решење

Поставка

У овом поступку се комбинују два алгоритма. Први је алгоритам за одређивања цифара у декадном запису броја, здесна налево, одређивањем остатка при целобројном дељењу. Други алгоритам је Хорнерова схема за одређивање вредности броја на основу његових цифара слева надесно. Тај алгоритам је описан у задатку Број формиран од датих цифра с лева на десно. Скидамо једну по једну цифру броја \(n\) здесна и дописујемо је на резултат, такође здесна.

Пример. Размотримо поступно рад алгоритма на једном примеру.

n r i 123000 0 0 12300 0 1 1230 0 2 123 0 3 12 3 4 1 32 5 0 321 6

Коректност овог алгоритма се заснива на следећој инваријанти. Након \(i\) корака петље број \(n\) садржи број који се добије када се од почетног броја \(n_0\) обрише последњих \(i\) цифара, док број \(r\) садржи број који се добије обртањем тих последњих \(i\) цифара броја \(n_0\).

Имплементација се може направити на следећи начин.

#include <iostream>

using namespace std;

int obrni(int n) {
  int r = 0;
  while (n > 0) {
    r = 10*r + n % 10;
    n /= 10;
  }
  return r;
}

int main() {
  int n;
  cin >> n;
  cout << obrni(n) << endl;
  return 0;
}

Доказ коректности. Пошто је програм написан у терминима математичких операција, ако желимо да спроведемо потпуно прецизан, математички формалан доказ, и инваријанту морамо изразити у терминима математичких операција. Нажалост, испоставиће се да крајње нуле у запису броја, које постају водеће нуле у резултату значајно компликују прецизно изражавање инваријанте и наставак доказа чине прилично мукотрпним. Наиме, број који се обрће може се завршити нулама, тако да на пример бројеви \(123\), \(1230\), \(12300\) сви имају исту вредност броја који се записује истим цифрама као оригинални број али у обрнутом поретку (\(321\)). Али број корака петље који ће бити потребан да се та вредност израчуна ће се разликовати у тим ситуацијама (и биће једнак броју цифара броја \(n\)). Отуда је битно приметити да је број завршних нула у запису броја битан за предложени алгоритам.

Лема: Услов да је \(n_0 = n \cdot 10^i + \mathit{rev}(r) \cdot 10^s\) (где је \(s\) број завршних нула у запису броја \(n_0\)) је инваријанта петље. При том је број \(r\) једнак 0 док је \(i \leq s\) (има 0 цифара у запису), односно има тачно \(i-s\) цифара у запису када је \(i > s\) и водећа цифра му је различита од нуле. Важи и да је \(n \geq 0\).

Докажимо ову лему.

Теорема: Након извршавања кода важи да је \(r = \mathit{rev}(n_0)\).

Пошто је \(n \geq 0\) и није \(n > 0\) (јер се петља завршила), важи да је \(n = 0\). Зато је \(n_0 = \mathit{rev}(r) \cdot 10^s\). тј. \(\mathit{rev}(r) = \frac{n_0}{10^s}\). Пошто је водећа цифра броја \(r\) различита од нуле и пошто је последња цифра броја \(\frac{n_0}{10^s}\) такође различита од нуле (јер \(n_0\) има тачно \(s\) завршних нула које се дељењем са \(10^s\) бришу), важи да је \(r = \mathit{rev}(\frac{n_0}{10^s})\). Међутим, важи и да је \(\mathit{rev}(n_0) = \mathit{rev}(\frac{n_0}{10^s})\), јер \(\mathit{rev}(n_0)\) има тачно \(s\) водећих нула које се могу обрисати без утицаја на коначни резултат.

Заустављање се доказује прилично једноставно јер је \(n \geq 0\) и константно се смањује, па мора доћи до нуле.