Практика программирования (Бейсик, Си, Паскаль)
Работа с числовыми данными
Современные представления о числовых данных базируются на так называемых позиционных системах счисления. Для этих систем характерно основание системы р, степень которого определяет вес цифры а (а = о, 1, 2.....P-I) в зависимости от занимаемой позиции:
ak ak-1 . . . a1a0, b-1b-2 ... b-m = ak*pk + ak-1*pk-1 + . . . + a1*p1 + a0*p0 + b-1p-1 + + b-2*p-2 + . . . + b-m*p-m
Главенствующую роль в человеческом общении играет десятичная система счисления (р = 10; а =0, 1, ..., 9). Однако ЭВМ используют более рациональную двоичную систему (р = 2; а = 0,1). Единственным ее недостатком является большая длина чисел. Количество разрядов (цифр) в двоичном представлении числа примерно в три раза превышает количество десятичных цифр. Для преодоления этого неудобства программисты прибегают к более компактной записи двоичных кодов в виде восьмеричных или шест-надцатеричных чисел. При этом три или четыре смежные двоичные цифры заменяют одной восьмеричной или шестнадцатеричной цифрой. Например:
19210 - 110000002,
110000002 = 3003,
110000002 = C016.
Внешнее и внутреннее представление числовых данных
Под внешним представлением числовой информации подразумеваются способы записи данных, используемые в текстах программ, при наборе чисел, вводимых в ЭВМ по запросу программы, при отображении результатов на экране дисплея или на принтере. Кроме естественного представления числовых констант в виде целого или вещественного числа, языки программирования допускают различные добавки в начале ("префиксы") или конце ("суффиксы") числа, определяющие способы преобразования и хранения данных в памяти компьютера.
Во входном языке системы QBasic такого рода добавки представлены одним из символов %, !, & или #, приписываемым вслед за числом, и одной из двухсимвольных комбинаций &в, &о или &н, располагаемой перед числом:
5% — целое число;
5& — целое число с удвоенной точностью;
5 или 5! — вещественное число;
5# — вещественное число с удвоенной точностью;
&В00111 001 001 10111 — двоичное число;
&034467 — восьмеричное число;
&Н3937 — шестнадцатиричное число.
В Си к аналогичным суффиксам относятся указания об удвоенной длине целых чисел (буквы L или l), указания о вещественном формате числа, не содержащего в своей записи десятичной точки или десятичного порядка (буква F или f), указания об использовании беззнакового представления целых чисел (буква и или и). Префиксы в Си используются для записи восьмеричных (число начинается с о) или шестнадцатеричных (числу предшествует одна из комбинаций Ох или ох) констант:
5 — короткое целое число со знаком;
5U — короткое целое число без знака;
5L — длинное целое число со знаком;
5LU или 5UL — длинное целое число без знака;
05 — восьмеричное число;
0x5 или 0X5 — шестнадцатеричное число;
5f или 5F — вещественное число со знаком.
В Паскале используется единственный префикс — символ $, предшествующий шестнадцатеричному числу:
$ОА, $FOA5, $FF00140D.
Наличие в естественной записи числа точки (3.1415) или указателя десятичного порядка (314.1592б5е-02) означает, что соответствующее значение представлено в ЭВМ в виде вещественного числа с плавающей запятой.
Машинные форматы представления чисел мы будем называть внутренними, и из приведенных ранее примеров следует, что машинные числа бывают целыми и вещественными. В свою очередь, каждый из этих типов данных допускает несколько представлений) отличающихся диапазоном допустимых чисел. Остановимся более подробно на машинных форматах числовых данных, соответствующих их аналогам в алгоритмических языках.
Самые короткие числа со знаком представлены в памяти ЭВМ одним байтом, в котором может разместиться любое число из диапазона от —128 до + 127. В Си для описания данных такого типа используется спецификатор char, а в Паскале — shortint.
В одном же байте может быть расположено и самое короткое целое число без знака. В Си для описания таких данных служит спецификатор unsigned char, в Паскале — byte, диапазон допустимых данных при этом смещается вправо и равен [0, 255]. QBasic не располагает языковыми средствами для работы с однобайтовыми целыми числами.
Вторая категория целых чисел представлена двухбайтовыми данными. В варианте со знаком они предлагают диапазон от —32 768 до +32 767, в варианте без знака — от 0 до 65 535.
QBasic обеспечивает работу с двухбайтовыми числами со знаком для переменных, описанных как AS INTEGER или устаревшее DEFINT,. и переменных, чьи имена заканчиваются символом %. Двухбайтовые целые без знака в QBasic записываются в виде двоичных, восьмеричных или шестнадцатерич-ных констант. Однако арифметические операции над такими данными иногда выполняются некорректно.
Си использует для описания двухбайтовых целочисленных данных спецификаторы int и unsigned int. В Паскале для этой же цели служат спецификаторы integer и word. Оба языка корректно выполняют арифметические операции и с беззнаковыми данными при условии, что результат не выходит за пределы разрешенного диапазона.
Третья категория целых чисел в IBM PC представлена четырехбайтовыми данными. В варианте со знаком они перекрывают диапазон от —2 147 483 648 до +2 147 483 647, в варианте без знака — от 0 до 4 294 967 295.
В QBasic допустимы только данные со знаком и имена переменных такого типа описываются как AS LONG или DEFLNG. К ним же относятся и переменные, чьи имена заканчиваются символом &.
Для описания четырехбайтовых данных целого типа в Си используются спецификаторы long (эквивалент long int) и unsigned long. В Паскале работа с длинными целыми без знака не предусмотрена. Там можно оперировать с четырехбайтовыми данными только типа longint.
Следует помнить, что для хранения любых целых чисел со знаком в IBM PC используется дополнительный код, что сказывается на представлении отрицательных чисел:
+5 0 0000101 0 000000000000101
-5 1 1111011 1 111111111111011
Наиболее часто применяемые типы вещественных чисел представлены короткими (4 байта) и длинными (8 байт) данными.
В QBasic им соответствуют описания AS SINGLE (устаревшее — DEFSNG) и AS DOUBLE (устаревшее — DEFDBL). Си использует для этой же цели спецификаторы float И double, Паскаль — single И double.
Короткий вещественный формат по модулю обеспечивает представление чисел в диапазоне от 10-38 до 10+38 примерно с 7—8 значащими цифрами. Для 8-байтового формата диапазон существенно расширяется — от 10-308 до 10+308, а количество значащих цифр увеличивается до 15—16.
Сопроцессор IBM PC предлагает еще два формата данных, занимающих соответственно 8 и 10 байт. Первый из них допускает работу с целыми числами из диапазона от —263 до 263-1. Второй формат перекрывает диапазон данных (по модулю) от 10-4932 до 10+4932, сохраняя 19—20 значащих цифр. Расширенный формат вещественных данных можно использовать в программах на Си (long double) и на Паскале (extended). А формат сверхдлинных целых чисел используется только в Паскале, но там они почему-то отнесены к вещественным данным типа сотр.
В Паскале по традиции сохранился еще один тип вещественных данных -real, занимающий в памяти 6 байт. Его диапазон совпадает с типом single, однако количество значащих цифр несколько больше — 10—12. Формат real не поддерживается аппаратными средствами IBM PC, поэтому операции над такими данными выполняются с помощью подпрограмм, что существенно увеличивает время решения задачи.
В машинном представлении вещественных данных разного типа на IBM PC не выдержана какая-то общая идеология. Объясняется это, по всей вероятности, разными наслоениями на прежние аппаратные решения, которые принимались при разработке процессоров в разных отделениях фирмы Intel. Поэтому здесь имеют место такие нюансы, как сохранение или не сохранение старшего бита мантиссы, представление мантиссы в виде чисто дробного (0,5 < < m < i) или смешанного (1 < m < 2) числа и т. п. Прикладных программистов эти детали мало интересуют, однако при создании специальных системных компонент с точным представлением данных приходится считаться.
Ввод числовой информации
Каких-либо особых проблем с вводом числовой информации в программах не возникает. Но на некоторые детали в том или ином алгоритмическом языке надо обратить внимание.
В QBasic можно натолкнуться на неприятности при вводе нескольких числовых значений в одном операторе. Например:
INPUT А,В,С
Числовые данные, набираемые пользователем, должны быть разделены пробелом. Если в наборе второго или третьего значения вы допустите ошибку, то последует сообщение Redo from start, которое заставит вас повторить
ввод всех трех чисел с самого начала. Некоторые программисты даже предпочитают вводить одним оператором только одно значение. Несоответствие между типом переменной и вводимым значением приводит к ошибке при попытке ввести вещественное значение в целочисленную переменную или при наборе недопустимого символа в числе.
В Си основные неприятности форматного ввода (функция scant) связаны с попыткой указать в списке ввода не адрес переменной, а ее имя:
scanf("%d",x); //правильно было бы scanf("%d",&x);
Компилятор ТС/ВС такую ошибку, к сожалению, не замечает и преобразует имя х в какой-то фантастический адрес. Последующую работу программы в этом случае предсказать трудно. Не обращает внимания компилятор Си и на несоответствие между спецификатором формата и типом переменной из списка ввода. Всего этого можно избежать, используя потоковый ввод:
cin » х;
Самый тщательный .контроль за соответствием между типами вводимых значений и типами соответствующих переменных в списке параметров процедур read и readin обеспечивает Паскаль. Однако здесь при попытке ввести непредусмотренный символ в числе вслед за сообщением об ошибке задача снимается, если программист не смог предвидеть заранее обход системной реакции на особые ситуации.
Вывод числовых результатов
Наиболее приятный вид имеет числовая информация, организованная по табличному типу — в виде колонок фиксированной ширины, в которых одноименные числовые разряды располагаются друг под другом (единицы -под единицами, десятки — под десятками, сотни — под сотнями и т. д.). При этом, в частности, более рационально используется площадь экрана, что достигается за счет управления форматами выводимых данных.
В QBasic для этой цели служит оператор PRINT USING, совмещающий в себе и строку с описанием формата и список выводимых значений:
PRINT USING "А=##.## B=### С=#.####^^^^"; А,В,С
Аналогичные средства имеются и в Си:
printf ("A=%6.3f B=%3d С=%10.4е",А,В,С);
При выводе в поток, когда к Си-программе подключаются заголовочные файлы iostream.h и iomanip.h, тоже существует возможность управлять форматом выводимых данных:
cout << "А=" << setw(6) << setprecision(5) << А;
Несколько скромнее выглядит управление форматом в Паскале:
write('А=',А:6:3,"В=',В:3,'С=',С:10);
Среди форматных спецификаторов в Си есть дополнительные возможности, отсутствующие в QBasic и Паскале. Например:
printf("%4x %6о",х,у);
Приведенные здесь спецификаторы позволяют вывести значения целочисленных переменных х и у соответственно в виде шестнадцатеричного числа с четырьмя цифрами и восьмеричного числа с шестью цифрами.
В Си можно прижимать выводимое число к левой границе отведенного поля (по умолчанию действует правый прижим), печатать знак "+" у положительных чисел, подавлять или разрешать вывод незначащих нулей и др. Подробную информацию о структуре форматного описателя можно найти в файлах помощи.
Задачи,советы и ответы
Задание 2.01. Ввод и вывод целочисленных данных
Ввести 20 целых чисел. Вывести их компактно (в одну или несколько строк), размещая перед каждым числом его порядковый номер. После нажатия какой-либо клавиши вывести числа столбиком, располагая одноименные разряды друг под другом, подвести под столбиком черту и напечатать сумму введенных чисел.
Совет 1
Предположим, что вы будете работать с двухбайтовыми целыми числами. Тогда для размещения самого длинного числа потребуется не менее 7 позиций (число —32 768 занимает 6 позиций плюс пробел между числами). Если перед числом необходимо вывести его номер, то дополнительно потребуются еще, как минимум, 3 позиции (2 под номер из диапазона [1,20] и разделитель между номером и числом, например в виде двоеточия). Таким образом, для компактного вывода на каждое число вместе с его номером можно отвести 10 позиций, что хорошо согласуется с длиной строки на дисплее. Если бы оказалось, что длина строки не кратна ширине колонки, то часть последнего числа была бы автоматически перенесена на следующую строку.
Совет 2
Как организовать ожидание в программе до нажатия какой-либо клавиши? В QBasic для этой цели подойдет системная переменная INKEY$. Достаточно присвоить ее значение какой-либо символьной переменной, например А$, и организовать бесконечный цикл до тех пор пока длина значения А$ перестанет отличаться от нуля:
А$=""
М10 : A$=INKEY$ ; IF А$="" THEN GOTO M10
Можно воспользоваться и другим приемом — включить в программу оператор ввода в какую-либо переменную символьного типа. Такая переменная предпочтительнее числовой, т. к. в нее можно ввести пустое значение, нажав только клавишу <Enter>. Кроме того, набор любого отображаемого символа не приведет к ошибке.
В Си временный приостанов до нажатия какой-либо клавиши организуют с помощью функции getch.
В Паскале можно организовать бесконечный цикл, аналогичный приведенному выше варианту для QBasic, с помощью логической функции KeyPressed:
while not KeyPressed;
Аналогом ввода пустого значения INPUT А$ в QBasic в Паскале является использование процедуры read/readln без параметров. В этом случае для проталкивания программы потребуется нажать клавишу <Enter>.
Программа 2_01.bas
RЕМ Ввод, вывод и суммирование 20 целых чисел
DIM A(20)
F$="##:###### "
PRINT "Введите 20 чисел, по одному в строке"
FOR 1=0 ТО 19: INPUT A(I): S=S+A(I): NEXT I
FOR 1=0 TO 19: PRINT USING F$;I+1;A(I); : NEXT I
INPUT A$
FOR 1=0 TO 19: PRINT USING F$;I+1;A(I): NEXT I
PRINT " —
PRINT USING " ######"; S
END
Программа 2_01.с
/* Ввод, вывод и суммирование 20 целых чисел */
#include <stdio.h>
#include <conio.h>
main() {
int j,s=0,a[20];
clrscr();
printf("Введите 20 чисел, по одному в строке\n"
for(j=0; j<20; j++) { scanf("%d",&a[j]); s+=a[j]; }
for(j=0;j<20;
printf("%2d:%-6d ",j+l,a[j]>; for(j=0;j<20; j++)
printf("\n%2d:%6d ",j+l,a[j]) ; printf("\n -------\n%9d",s);
getch();
Программа 2_01.pas
program io_numbers;
{ Ввод, вывод и суммирование 20 целых чисел }
uses Crt;
var
j, s:integer;
a:array [1..20] of integer; begin
s: =0 ;
clrscr;
writeln('Введите 20 чисел, по одному в строке"!
for j:=1 to 20 do
begin readln(a[j]); s:=s+a[j]; end;
for j:=l to 20 do write(j:2,':',a[j]:6,' ');
writeln('Нажмите любую клавишу';
readkey; clrscr;
for j:=l to 20 do writeln(j:2,':',a[j]:6,' ');
writeln(' --------');
writeln (s: 9).;
readln; end.
Задание 2.02. Ввод и вывод вещественных данных
Самостоятельно выполнить задание 2.01 в предположении, что вводимые числа вещественные и имеют две значащие цифры в дробной части.
Задание 2.03. Преобразование десятичного числа в системы С основанием 2, 8, 16
Ввести длинное неотрицательное число. Вывести его в двоичном, восьмеричном и шестнадцатеричном представлении.
Совет 1 (QBasic)
Предлагается воспользоваться стандартными функциями ост? и НЕХ$, преобразующими числовой аргумент в строку с его представлением в соответствующей системе счисления. Для двоичного представления можно распечатать восьмеричные цифры их трехразрядными двоичными эkвивaлeнтawш
Совет 2 (Си)
Предлагается воспользоваться библиотечной функцией Itoa, преобразующей свой первый аргумент из машинного представления числа в строку, заданную вторым аргументом. Третий аргумент этой функции определяет основание системы счисления, в которую конвертируется исходное число. Вообще говоря, восьмеричный и шестнадцатеричный эквиваленты числа проще вывести, используя спецификатор формата %о и %х. Для вывода двоичного представления числа можно было бы организовать цикл со сдвигом числа на один разряд влево (N = N « 1;), пробой старшей цифры (N & 0x8000) и выводом соответствующего символа в зависимости от результата сравнения.
Совет 3 (Паскаль)
В этом языке отсутствуют какие-либо системные функции или процедуры, отмеченные выше. Поэтому единственным средством будет лобовое преобразование путем последовательного деления исходного числа на основание соответствующей системы и запоминание получающихся остатков в некотором массиве. Так как длинное число занимает в памяти ЭВМ 4 байта, максимальный размер массива для хранения цифр числа в соответствующем представлении не должен превышать 32 элемента. Небольшие проблемы могут возникнуть при выводе шестнадцатеричных цифр от А до F. Из них придется вычитать 10 и добавлять полученную разницу к коду буквы А.
Программа 2_03.bas
REM Перевод числа в системы с основаниями 2, 8 и 16
CLS
INPUT "Введите положительное число : ",N&
А$=ОСТ$(N&)
PRINT "В двоичном представлении ";N&;"= ";
FOR k=l TO LEN(A$)
B$=MID$(A$,k,1) : ' Выделение очередной восьмеричной цифры
SELECT CASE B$
CASE "О": IF k=l THEN PRINT ""; ELSE PRINT "000";
CASE "1": IF k=l THEN PRINT "1"; ELSE PRINT "001";
CASE "2": IF k=l THEN PRINT "10"; ELSE PRINT "010";
CASE "3": IF k=l THEN PRINT "11"; ELSE PRINT "01l";
CASE "4": PRINT "100";
CASE "5": PRINT "101";
CASE "6": PRINT "111";
CASE "7": PRINT "111";
END SELECT
NEXT k
PRINT "В восьмеричном представлении ";N&;"= ";OCT$(N&)
PRINT "В шестнадцатеричном представлении ";N&;"=";HEX$(N&)
END
Программа 2_03a.bas
REM Перевод числа в системы с основаниями 2, 8 и 16
CLS
INPUT "Введите положительное число : ",n&
а$=ОСТ$(п&) : ' Перевод в восьмеричную систему
IF n&=0 THEN
PRINT "Это число в любой системе равно 0" STOP END IF PRINT "В двоичном представлении ";п&;"= ";
B$=LEFT$(а$,1}: : ' Выделение очередной восьмеричной цифры SELECT CASE B$
SELECT CASE B$
CASE "0": PRINT "";
CASE "1": PRINT "1";
CASE "2": PRINT "10";
CASE "3": PRINT "11";
CASE "4": PRINT "100";
CASE "5": PRINT "101";
CASE "6": PRINT "111";
CASE "7": PRINT "111";
END SELECT
FOR K = 2 TO LEN(a$)
B$ = MID$(a$, K, 1)
SELECT CASE B$
CASE "0": PRINT "000";
CASE "1": PRINT "001";
CASE "2": PRINT "010";
CASE "3": PRINT "011";
CASE "4": PRINT "100";
CASE "5": PRINT "101";
CASE "6": PRINT "111";
CASE "7": PRINT "111";
END SELECT
NEXT K
PRINT "В восьмеричном представлении ";n&;"= ";OCT$(n&)
PRINT "В шестнадцатеричном представлении ";n&;"= ";HEX$(n&) END
END
Программа 2_03.с
/* Перевод числа в системы счисления с основаниями 2, 8 и 16 */
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
main()
{
long N;
char a[33];
clrscr();
printf("Введите положительное число : ") ;
scanf("%ld",&N);
if(N==0) {
printf("\n Такое число в любой системе = 0") ;
exit(1);
}
ltoa(N,a,2);
/* перевод в двоичную систему */
printf("\n B двоичном представлении %ld = %s",N,a);
ltoa(N,a,8);
/* перевод в восьмеричную систему */
printf("\nВ восьмеричном представлении %ld = %s",N,a);
ltoa(N,a,16);
/* перевод в шестнадцатеричную систему */
printf("\n В шестнадцатеричном представлении %ld = %s",N,a);
getch();
}
Программа 2_03.pas
program _2_8__16;
{ Перевод числа в системы с основаниями 2, 8 и 16 }
uses crt; var
N1,N:longint;
a:array [0..31] of byte;
j,k:byte;
s:char; begin
clrscr;
write('Введите положительное число : ');
readln(N);
if N=0 then begin
writeln('Такое число в любой системе = 0');
exit;
end;
N1:=N;
for j:=0 to 31 do
a[j]:=0;
while Nl<>O do
begin
a[j]:=N1 mod 2; {цикл выделения двоичных цифр}
dec(j);
N1:=N1 div 2;
end;
write('В двоичном представлении ',N,'=');
for k:=j+l to 31
do write(a[k]:1);
writeln;
N1:=N;
for j:=0 to 10 do a[j]:=0;
while N1<>0 do begin
a[j]:=Nl mod 8; {цикл выделения восьмеричных цифр)
dec(j);
N1:=N1 div 8;
end;
write (' В восьмеричном представлении ',N,'=');
for k:=j+l to 10
do write(a[K]:i);
writeln; N1:=N;
for j:=0 to т 7 do a[j];=0;
while N1<>0 do begin
a[j]:=N1 mod 16;
dec(j);
N1:=N1 div 16;{ цикл выделения шестнадцатеричных цифр}
end; write('В шестнадцатеричном представлении ',N,'=');
for k:=j+l to 7 do begin
if a[k]<10
then s:=chr(ord('0')+a[k]}
else s:=chr(ord('A')+a[k]-10);
write (s) ;
end;
readln;
end.
Задание 2.04. Преобразование десятичного числа в Систему с основанием r
Составить функцию num_to_str (пшп, г), возвращающую в качестве своего значения строку с представлением натурального числа num в системе счисления с основанием г. Предполагается, что число num в памяти компьютера представлено 4-байтовым целым, а основание r принадлежит диапазону |2, 16]. Для обозначения цифр, превосходящих 9, рекомендуется воспользоваться латинскими буквами А, в, ... , F.
Совет 1 (общий)
Так как основание системы может быть любым, необязательно совпадающим с наиболее распространенными вариантами (r = 2,8,16), то наиболее универсальным способом перевода остается последовательное деление исходного числа на основание с запоминанием целочисленных частных и остатков. Небольшое осложнение будет вызвано преобразованием двухзначных остатков (при r > 10) в односимвольную "цифру". Однако подход к решению этого вопроса уже был продемонстрирован в предыдущей программе на Паскале. Очередной остаток q надо сравнить с 10 и, в зависимости от исхода сравнения, либо преобразовывать в символ полученную цифру, либо добавлять разницу q-10 к коду буквы А. Вторая тонкость заключается в том, что предлагаемый способ перевода позволяет получать цифры разложения справа налево, начиная с младшей. Поэтому перед выдачей результата полученную строку придется перевернуть.
Совет 2 (QBasic)
Вы можете выбрать один из двух способов описания типов данных — более современный, хотя и несколько более длинный, с использованием переменных,
описанных как AS LONG, AS STRING, AS INTEGER, или более старомодный с добавками %, &, $ в конце имени переменной. Возможность "складывать" символьные значения в любом порядке позволяет избежать инвертирования результирующей строки. Для этого вместо оператора А$ = A$ + CIF$ удобнее воспользоваться левосторонней конкатенацией А$ = CIF$+A$.
Совет 3 (Си)
Выбор длины строки, в которой будет накапливаться промежуточный или окончательный результат, определяется самым длинным разложением при r = 2 (т. е. 32 разряда плюс еще один байт под признак конца строки). Заводить две строки под правое и левое представление, наверное, не стоит. Проще произвести перестановку симметричных байтов (первый с последним, второй с предпоследним и т. д.) в одной строке. Для выполнения этой операции придется ввести счетчик количества цифр или прибегнуть к библиотечной функции strlen из раздела string, h.
Программа 2_04.bas
REM Перевод числа в систему с основанием r
DECLARE FUNCTION NToStr$(NUM&,R%)
CLS
INPUT "Введите натуральное число : ",N&
PRINT "Его представление в разных системах счисления таково : "
FOR J%=2 TO 16
PRINT "по основанию ";
J%,N&;"= ";NToStr$(N&,J%) NEXT J% END
FUNCTION NToStr$(NUM&,R%) A$="": MS=NUM& DO
CIF=M& MOD R% : ' Выделение очередной цифры
IF CIF<10 THEN
A$=CHR$(ASC("0")+CIF)+A$ : ' Замена цифры кодом ASCII ELSE
A$=CHR$(ASC("A")+CIF-10)+A$ END IF
М&=(М&-CIF)/R% : ' Исключение обработанной цифры
LOOP UNTIL M&=0 NToStr$=A$ END FUNCTION
Программа 2_04.с
/* Перевод числа в систему с основанием r */
#include <stdio.h>
# include <conio.h>
char *num_to_str (long num, char r) ;
void main (void) {
long N;
char j ;
printf ("\n Введите натуральное число ");
scanf ("%ld",&N);
printf ("Его представление в разных системах счисления таково :\n")
for(j=2; j<17; j++)
printf ("\n по основанию %2d = %s", j ,num_to_str (N, j ) ) ;
getch ( ) ;
}
char *num_to_str(long num, char r) {
static char a[33];
char cif, k, count=0; do {
cif=num % r;
/* Выделение очередной цифры */
if(cif<10) a[count++]='0'+cif;
/* Замена цифры кодом ASCII, если цифра меньше 10 */
else a[count++]='A'+cif-10;
/* Замена цифры кодом ASCII, если цифра больше 9 */
num=num/r;
/* Исключение обработанной цифры */ }
/* Цикл изменения порядка цифр в массиве а */
while (num != 0);
a[count--]=0х0;
for(k=0; k<=count/2; k++)
{ cif=a[count-k]; a[count-k]=a[k]; a[k]=cif; } return a; }
Программа 2_04.pas
program num to pos;
{ Перевод числа в систему с основанием г }
var
N:longint;
j:byte;
function num_to_str(num: longint;r:byte):string; var
a:string[33];
cif,k:byte; begin
a:='';
repeat
cif:=num mod r; { Выделение очередной цифры }
if (cif<10) then a:=chr(48+cif)+a
{ Замена цифры кодом ASCII, если цифра меньше 10 }
else a:=chr(65+cif-lO)+a;
{ Замена цифры кодом ASCII, если цифра больше 9 }
num:=num div r; { Исключение обработанной цифры }
until (num=0);
num_to_str:=а; end; begin
write('Введите натуральное число - ');
readln(N);
writelnf'Ero представление в разных системах счисления таково :']
for j :=2 to 16 do
writeln('no основанию ',j:2,' = ',num_to_str(N,j));
readln; end.
Задание 2.05. Симметричное разложение с наименьшим основанием
Дано натуральное число N (N < зоооо). Найти систему счисления с наименьшим основанием Pmin, в которой N имеет симметричное представление. Например,
ДЛЯ N=0 Pmin-2 (910 = 10012),
ДЛЯ N-1000 Emin=S (100010 - 13319)
Программа должна запрашивать число N, выдавать Pmin и значения
цифр в представлении числа N в этой системе (цифры можно выводить в виде обычных десятичных чиселл).
Cовет 1(oбщий)
Очевидно, что самой расточительной по количеству цифр является двоичная система счисления. Однако при заданном ограничении на диапазон исходных чисел для записи самого большого числа N потребуется не более 15 двоичных разрядов. Поэтому для хранения цифр, получаемых при переводе в ту или иную систему счисления, вполне можно обойтись массивом из 15 элементов.
Второй очевидный факт заключается в том, что всегда найдется такая система счисления, в которой разложение любого числа N будет симметричным. Такой системой, в частности, является система с основанием N-1, т. к. в ней число N выглядит как 11. Поэтому остается только организовать цикл по основаниям систем от 2 до N-I и, как только будет найдено симметричное разложение, прекратить перебор.
Совет 2 (общий)
Разумно организовать в программе две внешние функции — perevod (перевод исходного числа в систему с заданным основанием) и proba (проверка симметрии полученных цифр). Первая функция может быть построена по аналогии с функцией num_to_str, с той лишь разницей, что цифры, получаемые в процессе перевода, надо запоминать в массиве.
Совет 3 (QBasic)
Обратите внимание на схему вывода разложения, которое может не убраться в одной строке. Поэтому принято решение выводить в каждой строке по одному элементу разложения. Количества строк на экране вполне достаточно для размещения самого длинного представления в двоичной системе.
Программа 2_05.bas
RЕМ Поиск симметричного разложения числа
DECLARE FUNCTION perevod!(n%,p%,a%())
DECLARE FUNCTION proba! (a%(),k%)
DIM a%(16)
CLS
INPUT "Введите число из диапазона [0,30000] : ",n%
FOR p%=2 TO n%-l
k%=perevod(n%,p%,a%()) : ' Перевод в р-ричную систему
IF proba(a%(), k%) о 0 THEN
PRINT "Симметричное разложение с минимальным основанием
FOR i = 0 ТО k% - 1
PRINT TAB(0);а%(i);"*";р%; "^";k%-i;"+";
NEXT i
PRINT :
PRINT a% (0);"*";p%;"^";0
END
END IF
NEXT p%
END
FUNCTION perevod (n%,p%,a%())
REM Перевод числа n в систему с основанием р
RЕМ Цифры р-ричного числа запоминаются в массиве а
m%=n%
FOR i=0 TO 15
a%(i)=m% MOD p%
m%=(m%-a%(i))/p%
IF m%=0 THEN perevod=i: EXIT FUNCTION
NEXT i
END FUNCTION
FUNCTION proba (a%(),k%)
REM Анализ числа, представленного k цифрами в массиве а
REM Если число - палиндром, то proba=l
proba=l
FOR i=0 TO k%/2
IF a% (i)0a% (k%-i) THEN proba=0: EXIT FUNCTION
NEXT i
END FUNCTION
Программа 2_05.с
/* Поиск симметричного разложения числа */
#include <conio.h>
#include <stdio.h>
int perevod( int n, int p, int *a);
int proba(int *a, int k);
int i ;
main() {
int k,p,N,a[16];
clrscr () ;
scanf("%d",&N);
for (p=2; p<N; p++)
k=perevod(N, p, a); /* Перевод в р-ричную систему */
if (proba(a,k)==0) continue;
printf("Хпминимальное основание = %d\n",p);
for(i=0; i<=k; i++)
printf("%d ",a[i]);
break;
getch(); }
int perevod(int n,int p,int *a) /* Перевод числа п в систему с основанием р
Цифры р-ричного числа запоминаются в массиве а */
for(1=0; 1<1б; 1++)
a[i]=n % р; n=n/р;
if(n==0) return i;
}
int proba (int *a, int k)
/* Анализ числа, представленного k цифрами в массиве а
Если число - палиндром, то proba=l */ {
for(i = 0; i <= k/2; 1++)
if(a[i] != a[k-i]) return 0; return 1 ;
Программа 2_05.pas
program min_base;
{ Поиск симметричного разложения числа }
uses Crt;
var
i, k, p,N: integer;
a:array [0..15] of integer;
function perevod(n,p:integer) :integer;
{ Перевод числа n в систему с основанием р
Цифры р-ричного числа запоминаются в массиве а}
begin
for i:=0 to 15 do
begin
a[i]:=n mod p;
n:=n div p;
if n=0 then
begin perevod:=i;
exit;
end;
end;
end;
function proba(k:integer):integer;
{ Анализ числа, представленного k цифрами в массиве а
Если число - палиндром, то proba=l }
begin
for i:=0 to k div 2 do
if a[i]<>a[k-i] then
begin proba:=0;
exit;
end;
proba:=1;
end;
begin clrscr;
writeln('Поиск симметричного разложения с минимальным основанием');
writeln('Введите число');
readln(N);
for p:=2 to N do
begin
k:=perevod(N,p);
if proba(k)=0 then continue;
writeln('минимальное основание = ',p);
for i:=0 to k do writeln(a[i]);
break;
end;
readln;
end.
Задание 2.06. Суммирование десятичных цифр
Составить функцию sum_dig(n), аргументом которой является длинное целое число. Возвращаемое значение должно быть равно сумме десятичных цифр числа n.
Совет 1 (общий)
Обратите внимание на ситуацию, когда аргумент отрицателен. Остаток от де-ления отрицательного числа на 10 будет также отрицательным.
Совет 2 (OBasic)
Будьте внимательны при определении очередного частного от деления числа на 10. QBasic производит округления, и результат деления, например, целого числа 15 на целое число 10 даст в частном не 1, а 2.
Совет 3 (Си, Паскаль)
Обратите внимание на то, что циклы do (Си) и repeat (Паскаль) требуют противоположных условий выхода из цикла.
Программа 2_06.bas
RЕМ Суммирование десятичных цифр числа
DECLARE FUNCTION SUMDIG (N&)
CLS
INPUT "Введите целое число";М&
K=SUMDIG(M&)
PRINT "Сумма его цифр = ";К
END
FUNCTION SUMDIG(N&)
IF N&<0 THEN N&=-N& : ' Смена знака у отрицательного числа
RESULT=0
DO
RESULT=RESULT+(N& MOD 10&) : ' Накопление суммы цифр
N&=(N&-(N& MOD 10&))/10& : ' Удаление обработанной цифры
LOOP WHILE N&<>O
SUMDIG=RESULT
END FUNCTION
Программа 2_06.с
/* Суммирование десятичных цифр числа */
#include <stdlib.h>
int sum_digits(long N);
main()
}
long M;
printf ("\n Введите целое число: ");
scanf ("%ld", &M) ;
printf ("\n Сумма его цифр = %d", sum_digits (M) ) ;
getch ( ) ; }
int sum_digits (long N) {
int Result=0;
N=labs (N) ; /* Смена знака у отрицательного числа */
do {
Result=Result+N%10; /* Накопление суммы цифр */
N=N/10; /* Удаление обработанной цифры */ }
while (N != 0) ;
return Result;
}
Программа 2_06.pas
program sumdigits;
{ Суммирование десятичных цифр числа }
var
M:longint;
function sum_digits (N: longint) : integer;
const
Result : integer=0 ;
begin
if N<0 then N:=-N;
{ Смена знака у отрицательного числа } repeat
Result :=Result+N mod 10; { Накопление суммы цифр }
N:=N div 10; { Удаление обработанной цифры }
until N=0;
sum_digits : =Result;
end;
begin
write ( ' Введите целое число : ' ) ;
readln (M) ;
writeln('Сумма его цифр =',sum_digits(M)! readln;
end.
Задание 2.07. "Счастливый" Сияет
Составить логическую функцию luck(n), аргументом которой является число п из диапазона [0, 999999]. Функция должна возвращать значение true (Паскаль) или i (Си, QBasic), если ее аргумент представлен "счастливым" числом, у которого суммы трех старших и трех младших цифр совпадают.
Программа 2_07.bas
REM Анализ "счастливого" билета
DECLARE FUNCTION LUCK(M AS LONG)
INPUT "Введите номер билета ";N& IF LUCK(N&)=1 THEN
PRINT "Радуйтесь - счастливый" ELSE
PRINT "Нет счастья в жизни" END IF END
FUNCTION LUCK(M AS LONG)
REM Подсчет и сравнение сумм старших и младших цифр М
REM Если суммы совпадают LUCK=1
DIM A(6)
LUCK=0
IF M<0 OR M>999999 THEN
PRINT "luck : недопустимый аргумент":
EXIT FUNCTION
END IF
FOR i=0 TO 5
A(I)=M MOD 10 : ' Выделение очередной цифры
M=(M-A(I))/10 : ' Удаление обработанной цифры
NEXT I
IF (A(0)+A(1)+A(2)=A(3)+A(4)+A(5)) THEN LUCK=1
END FUNCTION
Программа 2_07.с
/* Анализ "счастливого" билета */
#include <stdio.h>
int luck(long M);
main() {
long N;
printf("\n Введите номер билета ");
scanf("%ld",&N);
if (luck(N)==l)
printf("\n Радуйтесь - счастливый");
else
printf("\n Нет счастья в жизни"); getch(); }
int luck(long M)
/* Подсчет и сравнение сумм старших и младших цифр М
Если суммы совпадают luck=l*/ {
int i ; char a[6];
if((M<0) || (M>999999)) {
printf("\nluck : недопустимый аргумент");
exit(0); }
for(i=0; i<6;.i++) {
a[i]=M % 10; /* Вьщеление очередной цифры */
M=M/10; /* Удаление обработанной цифры */
}
if(a[0]+a[l]+a[2]=a[3]+a[4]+a[5]> return 1;
return 0; }
Программа 2_07.pas
program lucky_ticket;
{ Анализ "счастливого" билета
var
N:longint;
function luck(M:longint):boolean;
{ Подсчет и сравнение сумм старших и младших цифр М
Если суммы совпадают luck=true }
var
i:integer;
a:array [1..6] of byte;
begin
if (M<0) or (M>999999) then begin
writeln('luck : недопустимый аргумент');
exit;
end;
for i:=l to 6 do
begin
a[i]:=M mod 10; { Выделение очередной цифры }
M:=M div 10; { Удаление обработанной цифры }
end;
luck:=(a[l]+a[2]+a[3])=(а[4]+а[5]+а[6]);
end;
begin
writeln('Введите номер билета');
readln(N);
if luck(N) then
writeln('Радуйтесь - счастливый') else
writelnt'HeT счастья в жизни');
readln;
end.
Задание 2.08. Количество различных цифр в десятичном числе
Составить функцию num_digits (n), аргументом которой является длинное целое число. Возвращаемое значение должно быть равно количеству различных цифр в десятичной записи п. Например: num_digits (1998) =3.
Совет 1 (общий)
Предлагается завести массив из 10 счетчиков, каждый из которых будет подсчитывать количество своих цифр — первый счетчик считает нули, второй —
единицы и т. д. Вначале массив счетчиков обнуляется, а после выделения всех цифр числа проверяется, сколько счетчиков хранят ненулевые значения. Вообще говоря, можно не заниматься подсчетом количества появлений каждой цифры —достаточно фиксировать сам факт появления той или иной цифры.
Совет 2 (Си, Паскаль)
Обратите внимание на различное оформление функций num_digits на Си и Паскале. Локальные переменные в функциях на Си, которым присваиваются начальные значения, при повторных обращениях к функциям инициализируются заново. В процедурах и функциях Паскаля такая инициализация производится только один раз, поэтому повторный вызов может привести к неверным результатам.
Программа 2_08.bas
RЕМ Определение количества различных цифр в числе
DECLARE FUNCTION NumDig!(N&)
CLS
INPUT "Введите число : ", N&
PRINT "Количество разных цифр в его записи = ";NumDig(N&)
END
FUNCTION NumDig(N&)
REM Выделение и подсчет количества разных цифр в числе N
DIM d(10) : ' Массив для фиксации обнаруженных цифр
КЕМ При вызове функции d(i)=0
IF N&<10 THEN NumDig=l: EXIT FUNCTION
DO
k%=N& MOD 10 : ' Выделение очередной цифры
d(k%)=d(k%)+l : ' Подсчет количества цифр, равных k
N&=(NS-k%)/10 : ' Удаление обработанной цифры
LOOP UNTIL N&=0
FOR k%=0 TO 9 : ' Цикл подсчета количества обнаруженных цифр
IF d(k%)<>0 THEN s=s+l : ' Если d(i)=0, цифры i не было
NEXT k%
NumDig=s
END FUNCTION
Программа 2_08.с
/* Определение количества различных цифр в числе */
#include <stdio.h>
#include <conio.h>
int num_digits(long N);
main() {
long N;
printf("\n Введите число - ");
scanf("%ld",&N);
printf("\Количество разных цифр в его записи = %d ",
num_digits(N));
getch(); }
int num_digits(long N)
/* Выделение и подсчет количества разных цифр в числе N */ {
char d[10]={0,0,0,0,0,0,0,0,0,0}; /* Массив счетчиков цифр */
int k,s=0;
if(N<10) return 1;
while (N) {
d[N % 10]++; /* Выделение и учет очередной цифры */
N=N/10; /* Удаление обработанной цифры */ }
/* Цикл подсчета количества обнаруженных цифр */
for(k=0; k<10; k++)
if(d[k]) s++;
return s; }
Программа 2_08.pas
program NumDigits;
{ Определение количества различных цифр в числе }
var
N:longint;
function num_digits(N:longint):byte;
{ Выделение и подсчет количества разных цифр в числе N } var
d:array [0..9] of byte; { Массив счетчиков }
s,k:byte;
begin
if N<10 then num_digits:=1
else begin
for k:=0 to 9 do d[k]=0; { Сброс счетчиков }
s: =0 ;
while N<> 0 do begin
inc(d[N mod 10]); { Выделение и учет очередной цифры }
N := N div 10; { Удаление обработанной цифры }
end;
{ Цикл подсчета количества обнаруженных цифр }
for k:=0 to 9 do
if(d[k]<>0) then inc(s);
num_digits:=s;
end;
end;
begin
write('Введите число : ');
readln(N);
writeln('Количество разных цифр в его записи = ',num_digits(N));
readln;
end.
Задание 2.09. Определение цифры в заданной позиции
Составить функцию digit_in_pos (n,i), аргументами которой являются длинное целое число n и номер i позиции цифры в десятичном представлении n. Отсчет номеров позиций ведется справа налево от 0 и соответствует "весу" цифры (степени основания), с которым цифра входит в число. Например:
n=1985
в 0-й позиции находится цифра 5;
в 1-й позиции находится цифра 8;
во 2-й позиции находится цифра 9;
в 3-й позиции находится цифра 1;
Функция digit_in_pos должна возвращать цифру, расположенную в 1-й позиции числа п.
Программа 2_09.bas
RЕМ Анализ цифр в каждой позиции заданного числа
DECLARE FUNCTION DIGINPOS(N AS LONG,J AS INTEGER)
INPUT "Введите целое число: ";М&
FOR K%=0 ТО 9
DIGIT=DIGINPOS(M&,К%)
PRINT "В позиции ";К%;" находится ",DIGIT NEXT K%
END
FUNCTION DIGINPOS(N AS LONG,J AS INTEGER)
REM Определение десятичной цифры числа N в позиции j
N1&=N
FOR K%=0 TO J
RESULT=N1& MOD 10
N1&=N1&-RESULT)/10 NEXT K%
DIGINPOS=RESULT
END FUNCTION
Программа 2_09.с
/* Анализ цифр в каждой позиции заданного числа */
#include <stdlib.h>
int digit_in_pos(long N,int j);
main () {
long M;
int k;
printf("ХпВведите целое число: ");
scanf("%ld",&M);
for(k=0; k<10; k++)
printf("\nB позиции %d находится %d",k,digit_in_pos(M, k)) ;
getch(); }
int digit_in_pos(long N,int j)
/* Определение десятичной цифры числа N в позиции j */ {
int Result,k;
for (k=0; k<=j; k++) {
Result=N % 10; N=N/10;
}
return Result; }
Программа 2 09.pas
program DigitlnPos;
{ Анализ цифр в каждой позиции заданного числа }
var
M:longint;
k:integer;
function digit_in_pos(N:longint;j:integer):integer;
{ Определение десятичной цифры числа N в позиции j }
var
Result,k:integer;
begin
for k:=0 to j do
begin
Result:=N mod 10;
N:=N div 10;
end;
digit_in_pos:=Result;
end;
begin
writeln('Введите целое число:');
readln(M);
for k:=0 to 9 do
writeln('В позиции ',k,' находится ',digit_in_pos(M,k));
readln;
end.
Задание 2.10. Генерация чисел с заданной суммой цифр
Составить программу, которая выдает все числа из диапазона [0, 999], сумма цифр которых равна вводимому числу N (о < N < 27).
Программа 2_10.bas
REM Генерация чисел с заданной суммой цифр
INPUT "Введите число в диапазоне от 0 до 27 : ",n
PRINT "Список чисел, сумма цифр которых равна ";n;" :"
FOR a2%=0 ТО 9
FOR a1%=0 TO 9
FOR a0%=0 TO 9
IF a2%tal%+aO%=n THEN
PRINT USING "####";100*a2%+10*al%+a0%;
END IF
NEXT a0%,al%,a2%
END
Программа 2_10.c
/* Генерация чисел с заданной суммой цифр */
#include <stdio.h>
#include <conio.h>
main () {
int a2,al,a0,n;
printf("\n Введите число в диапазоне от 0 до 27 : ");
scanf("%d",&n);
printf("\n Список чисел, сумма цифр которых равна %d :\n",n);
for(a2=0; a2<10; а2++)
for(al=0; al<10; al++)
for(a0=0; a0<10; a0++)
if(a2+al+a0==n) printf("%4d",100*a2+10*al+a0);
getch(); }
Программа 2_10.pas
program numbers;
{ Генерация чисел с заданной суммой цифр }
var
а2,al,aO,n:integer; begin
write('Введите число в диапазоне от 0 до 27 : ');
readln(n);
writeln(' Список чисел, сумма цифр которых равна ',n, ' :');
for a2:=0 to 9 do
for al:=0 to 9 do
for a0:=0 to 9 do
if (a2+al+a0)=n then write(100*a2+10*al+a0:4);
readln;
end.
Задание 2.11. Вывод числа словами
Составить строковую функцию num_to_3tr(n), аргументом которой является целое число (|n| < 1000). Возвращаемое значение должно быть строкой, в которой величина п представлена словами. Например:
num_to_str(-87) = "минус восемьдесят семь".
Совет 1 (общий)
Очевидно, что придется завести набор строк с символьными эквивалентами некоторых чисел. Конечно, нежелательно, чтобы в таком массиве было использовано 1000 констант — "ноль", "один"....."девятьсот девяносто девять". Однако без минимального набора из трех массивов — малые числа ("ноль", "один", ..., "девятнадцать"), десятки ("двадцать", "тридцать", "девяносто") И СОТНИ ("сто", "двести"..... "девятьсот") — нам не обойтись. Перед анализом цифр числа у него целесообразно удалить знак и, в случае необходимости, приклеить в начале результирующей строки значение "минус".
Совет 2 (Си)
Для правильной работы функции num_to_str следует подключить заголовочный файл string.h. Обратите внимание на объявление локальной переменной Result в теле функции. Если исключить из этого описания спецификатор static, то переменная Result превратится в локальную строку и память под нее будет выделена только на время работы функции. Поэтому нет никакой гарантии, что сформированный в ней результат работы функции сохранится после возврата в головную программу. Попробуйте удалить описатель static и сравните значение Result в теле функции с тем, что выдаст головная программа, например при N=999.
Совет 3 (QBasic)
Система QBasic не допускает использования операторов DATA-READ в теле функции. Поэтому можно сформировать значения элементов символьных массивов numl$, num2$ и numЗ$ в головной программе, а затем передать их в списке параметров при вызове функции. На этом примере вы можете ознакомиться с использованием массивов в качестве формальных (при описании заголовка функции) и фактических (при обращении к функции) параметров. Вторая возможность, реализованная в программе 2_11a.bas, заключается в объявлении совместного доступа к массивам numis, num2$ и num3$ с помощью оператора DIM SHARED.
Совет 4 (Паскаль)
В качестве наиболее простого способа задания значений элементов массивов
num1. num2 и num3 предлагается поместить соответствующие описания в раздел типизированных "констант".
Программа 2_11.bas
RЕМ Формирование словесного описания числа
DECLARE FUNCTION NumToStr$(m%,numl$(),num2$(),num3$())
CLS
DATA "ноль","один","два","три","четыре","пять","шесть","семь"
DATA "восемь","девять","десять","одиннадцать","двенадцать" .
DATA "тринадцать","четырнадцать","пятнадцать","шестнадцать"
DATA "семнадцать","восемнадцать","девятнадцать"
DIM numl$ (20)
FOR k=0 TO 19: READ numl$(k): NEXT k
DATA "двадцать ","тридцать ","сорок ","пятьдесят "
DATA "шестьдесят ","семьдесят "("восемьдесят ","девяносто "
DIM num2$(8)
FOR k=0 TO 7: READ num2$(k): NEXT k
DATA "","CTO ","двести ","триста ","четыреста ","пятьсот "
DATA "шестьсот ","семьсот ","восемьсот ","девятьсот "
DIM num3$(10)
FOR k=0 TO 9: READ num3$(k): NEXT k
INPUT "Введите целое число от -999 до 999: ",n%
PRINT n%;"= ";NumToStr$(n%,numl$(),num2$(),num3$())
END
FUNCTION NumToStr$(m%,numl$(),num2$() , num3$())
IF m%=0 THEN NumToStr$=numl$(0): EXIT FUNCTION
IF m%<0 THEN m%=-m%: Res$="минус " : 'Учет знака числа
dlg100%=m%\100 : ' Вьщеление сотен
Res$=Res$+num3$(dig100%) /Приклеили обозначение сотен
m%=m%-100*dig100% : "Удаление обработанных сотен
IF m%=0 THEN NumToStr$=Res$: EXIT FUNCTION
IF m%<20 THEN NumToStr$=Res$+numl$ (m%) : EXIT FUNCTION
dig10%=m%\10 :' Вьщелеыие десятков, если dig10 >=20
Res$=Res; +num2$ (diglO%-2)
digl%=m% MOD 10 :' Приклеили обозначение десятков
КЕМ Если в числе присутствуют ненулевые разряды единиц
IF digl%<>0 THEN Res$=Res$+numl$(digl%)
NumToStr$=Res$
END FUNCTION
Программа 2_11a.bas
RЕМ Формирование словесного описания числа
DECLARE FUNCTION NumToStr$(m%)
CLS
DATA "нуль","один","два","три","четыре","пять","шесть", "семь"
DATA "восемь","девять","десять","одиннадцать","двенадцать"
DATA "тринадцать","четырнадцать","пятнадцать","шестнадцать"
DATA "семнадцать","восемнадцать","девятнадцать"
DIM SHARED numl$(20)
FOR k=0 TO 19: READ numl$(k): NEXT k
DATA "двадцать ","тридцать ","сорок "," пятьдесят "
DATA "шестьдесят ","семьдесят ","восемьдесят ","девяносто "
DIM SHARED num2$(8)
FOR k=0 TO 7: READ num2$(k): NEXT k
DATA "","сто ","двести ","триста ","четыреста ","пятьсот "
DATA "шестьсот ","семьсот ","восемьсот ","девятьсот "
DIM SHARED num3$(10)
FOR k=0 TO 9: READ num3$(k): NEXT k
INPUT "Введите целое число от -999 до 999: ",n%
PRINT n%;"= ";NumToStr$(n%) '
END
FUNCTION NumToStr$(m%)
IF m%=0 THEN NumToStr$=numl$(0): EXIT FUNCTION
IF m%<0 THEN m%=-m%: ResS-''мииус " : 'Учет знака имела
dig100%=m%\100 : 'Выделение сотен
Res$=Res$+num3$(dig!00%) :' Приклеили обозначение сотен
m%=m%-100*diglOO% : ' Удаление обработанных сотен
IF m%=0 THEN NumToStr$=Res$: EXIT FUNCTION
IF m%<20 THEN NumToStr$-Res$-t-numl$ (m%) : EXIT FUNCTION diglO%=m%\10 :
' Выделение десятков, если
dig10>=20 R@s$=R.es$-l-num2$ (diglO%-2) :' Приклеили обозначение десятков
diglS-^m* MOD 10
КЕМ Если в числе присутствуют ненулевые разряды единиц
IF digl%<>0 THEN Res$=Res$+numl$(digll)
NumToStr$=Res$ END FUNCTION
Программа 2_11.с
/* Формирование словесного описания числа */
#include <stdio.h>
#include <conio.h>
#include <string.h>
char *num_to_str(long m) ;
main() {
long n;
clrscr() ; m:
printf("ХпВведите целое число : ");
scanf("%ld",&n);
printf("\n%ld = %s",n,num_to_str(n));
goto m;
getch(); }
char *num_to_str(long m)
/* Преобразование числа в словесное описание */ {
char *numl[]={"ноль","один","два","три",
"четыре","пять","шесть","семь","восемь","девять",
"десять","одиннадцать","двенадцать",
"тринадцать","четырнадцать",
"пятнадцать","шестнадцать","семнадцать", "восемнадцать",
"девятнадцать " }';
char *num2[]=1
"двадцать ","тридцать ","сорок ",
"пятьдесят ","шестьдесят ",
"семьдесят ","восемьдесят ","девяносто "};
char *num3[]=("",
"сто ","двести ","триста ","четыреста ",
"пятьсот ", "шестьсот ","семьсот "/'восемьсот "/'девятьсот "} ;
static char Result[50]="";
int digl,dig10,dig100;
if(m==0) return numl[0]; Result[0]=0x0;
if(m<0) /* Учет знака числа */ {
m=-m;
strcpy(Result,"минус ") ; }
diglOO=m/100; /* Выделение сотен */
strcat(Result,num3[diglOO]);
/* Приклеили обозначение сотен */
m=m-100*dig100;
/* Удаление обработанных сотен */
if (m=0) return Result;
/* Если две оставшиеся цифры - нули */
if(m<20) /* Если две младшие цифры меньше 20 */
{
strcat(Result,numl[m]);
return Result; }
diglO=m/10;
/* Выделение десятков, если diglO >=20 */
strcat(Result,num2[diglO-2]);
digl=m % 10;
/* Если в числе присутствуют ненулевые разряды единиц */
if(digl != 0)
strcat(Result,numl[digl]);
return Result; }
Программа 2_11.pas
program nd_10;
{ Формирование словесного описания числа )
uses Crt;
var
n:longint;
function num_to_str(m:longint):string;
{ Преобразование числа в словесное описание }
label ret;
"const
numl :array [0. .19] o£ string= (
1 ноль', 'один', 'два', 'три', 'четыре', 'пять', 'шесть', 'семь','восемь','девять','десять','одиннадцать', 'двенадцать','тринадцать','четырнадцать', 'пятнадцать','шестнадцать','семнадцать', 'восемнадцать','девятнадцать');
num2:array [2..9] of string=(
'двадцать ','тридцать ','сорок ','пятьдесят ', 'шестьдесят ','семьдесят ','восемьдесят ','девяносто ');
num3:array [0..9] of string = ('',
'сто ','двести ','триста ','четыреста ','пятьсот ', 'шестьсот ','семьсот ','восемьсот ','девятьсот ');
var
digl,diglO,diglOO: byte;
Result:string;
begin
if m=0 then begin
Result:=numl[0];
goto ret;
end;
Result:='';
if m<0 then { Учет знака числа }
begin
m:=-m;
Result:='минус '; end;
diglOO:=m div 100; { Выделение сотен }
Result:=Result + num3[diglOO];
{ Приклеили обозначение сотен }
m:=m-100*diglOO;
{ Удаление обработанных сотен }
if m=0 then goto ret;
{ Если две оставшиеся цифры - нули }
if m<20 then begin
{ Если две младшие цифры меньше 20 }
Result:=Result+numl [m] ;
goto ret;
end;
dig10:=m div 10;
{ Выделение десятков, если diqlO >=20 }
Result:=Result+num2[dig10] ;
digl:=m mod 10; { Если в числе присутствуют ненулевые разряды единиц }
if dig100 then Result:=Result + numl[digl];
ret:
num_to_str:=Result;
end;
begin
clrscr;
write{'Введите целое число : ');
readln (n) ;
writeln(n, ' = ' ,num_to__str (n) ) ;
readln; end.
Задание 2.12. Суммирование двоичных цифр
Составить функцию sum_bits(n), аргументом которой является длинное целое число. Возвращаемое значение должно быть равно количеству единиц в двоичном представлении числа п.
Совет 1 (общий)
Обратите внимание на тот факт, что отрицательные числа представлены в памяти компьютера в дополнительном коде. Поэтому число -1 содержит 32 единицы в своем двоичном разложении.
Совет 2 (QBasic)
Так как входной язык QBasic не содержит операции сдвига, схитрим — разделим на 2 битовую шкалу J&, заставив сдвинуться влево ее единственную единицу. Начать с J&=&H80000000 нельзя, т. к. это очень странное "число". Поэтому приходится знак N проверять отдельно, а в цикле анализ производить, начиная с 30-го разряда.
Программа 2_12.bas
RЕМ Суммирование двоичных цифр введенного числа
INPUT "Вводи N"/ N&
Js=&H40000000
RESULT=0
IF N&<0 THEN RESULT=1 : ' Учет 1 в знаке отрицательного числа
FOR K=31 TO I STEP -1
IF N& AND J& THEN RESULT=RESULT+1 : ' Учет k-того разряда
J&=J&/2 : ' Сдвиг шкалы на разряд вправо NEXT К PRINT RESULT END
Программа 2_12.с
/* Суммирование двоичных цифр введенного числа */
#include <stdlib.h>
int sum_bits(long N) ;
main () {
long M;
printf("ХпВведите целое число: ");
scanf("%ld",&M);
printf("\n Сумма его цифр = %d",sum_bits(M));
getch(); }
int sum_bits(long N)
/* Подсчет единиц в двоичном представлении N */
{
int Result=0,k;
unsigned long j=0x80000000;
for(k=31; k >= 0; k--) {
if(j & N) Result++;
/* Учет k-того разряда */
j=j >> 1; }
return Result;
}
Программа 2_12.pas
program sumbits;
{ Суммирование двоичных цифр введенного числа }
var
M:longint;
function sum bits(N:longint):integer;
const
Result:integer=0;
j:longint=$80000000; var
k:integer; begin
for k:=31 downto 0 do
begin
if (N and j)<>0 then inc(Result);
{ Учет k-того разряда }
j:=j shr 1;
end;
sum_bits:=Result;
end;
begin
write('Введите целое число: ');
readln(M);
writeln('Сумма его цифр = ',sum_bits(M));
readln; end.
Задание 2.13. Позиция старшей единицы
Составить функцию lef t_bit (n), аргументом которой является длинное целое положительное число. Возвращаемое значение должно совпадать с номером старшей единицы в двоичном представлении п. Нумерация двоичных разрядов ведется справа налево от 0 до 31.
Совет 1 (общий)
Наиболее прямой способ заключается в проверке битов заданного числа слева (от самого старшего разряда) направо. Как только в цикле проверки появляется первая единица, работа подпрограммы завершается. Так как аргумент функции неотрицателен, проверку можно начинать с 30-го разряда. Для сдвига битовой шкалы в Си следует использовать операцию сдвига вправо ( » ), в Паскале — операцию shr, а в QBasic придется прибегнуть к делению на 2.
Программа 2_13.oas
RЕМ Определение позиции старшей единицы в двоичном числе
DECLARE FUNCTION LeftBit!(N&)
INPUT "Введите целое число: "/Ms
IF M&=0 THEN PRINT "D этом числе единиц нет" : END
PRINT "Старший разряд находится в позиции номер ";
LeftBit(M£)
END
FUNCTION LertBil(N&)
REM Определение позиции старшей единицы в числе N
КЕМ Если N=0, то LeftBit=-l
LeftBit=-l
j&=&H40000000
FOR k=30 TO 0 STEP -1
IF (j& AND N&) THEN ' Анализ k-го разряда LeftBit-k EXIT FUNCTION
END IF
j&=j&/2 NEXT k
END FUNCTION
Программа 2_13.с
/* Определение позиции старшей единицы в двоичном числе */
#include <stdlib.h>
int left_bit(long N) ;
main() {
long M;
printf("\n Введите целое число: ");
scanf("%ld",&M);
if(M==0) {
printf("\n B этом числе единиц нет");
exit(0); }
printf("\n Старший разряд находится в позиции номер %d", left__bit(M));
getch(); }
int left_bit(long N)
/* Определение позиции старшей единицы в числе N
Если N=0, функция возвращает -1 */ {
int k;
long j=0x80000000;
for(k=31; k >= 0; k--) {
if(j & N) return k;
j=j >> 1;
}
return -1; }
Программа 2_13.pas
program LeftBit;
{ Определение позиции старшей единицы в двоичном числе }
var
M:longint;
function left_bit(N:longint):byte;
{Определение позиции старшей единицы в числе N Если N=0, функция возвращает -1 }
var
k:byte; const
j:longint=$80000000;
begin
left_bit:=-l;
for k:=31 downto 0 do
begin
if (j and N)<> 0 then begin
left_bit:=k;
exit;
end;
j:=j shr 1;
end;
end;
begin
write('Введите целое число: ');
readln(M);
if M=0 then writeln('B этом числе единиц нет')
else
writelnf'Старший разряд находится в позиции номер ', left_bit(M));
readln; end.
Задание 2.14. Максимальное количество подряд стоящих единиц
Составить функцию max_bits(n), аргументом которой является длинное целое положительное число. Возвращаемое значение должно быть равно максимальному числу подряд расположенных единиц в двоичном представлении п. Например:
n = 3310 = 100012 max_bits (33) =1
n = 2810 = 111002 max_bits(28) =3
Совет 1 (общий)
Основная идея заключается в том, чтобы вести подсчет подряд стоящих единиц в двоичном представлении числа п до ближайшего нуля и сравнивать это количество с предшествующим претендентом на максимум. При этом не следует забывать, что отрицательные числа в памяти компьютера представлены в дополнительном коде и, например, такое число, как -1 (шестнадцатеричный эквивалент OXFFFFFFFF) содержит 32 единицы. Это означает, что знаковый разряд должен учитываться наравне с остальными. В QBasic такой учет вызывает дополнительные трудности.
Совет 2 (QBasic)
Так как входной язык не предлагает операцию сдвига кодов, то для сдвига вправо можно воспользоваться умножением на 2.
Совет 3 (Си)
Для большей наглядности тестирующей программы можно воспользоваться библиотечной функцией itoa и вывести двоичное представление числа п. Проверку битов целесообразно начать с самого старшего знакового разряда и в цикле сдвигать тестируемый код на 1 разряд влево. Если начать проверку с младшего разряда и сдвигать проверяемое число на 1 разряд вправо, то при отрицательном аргументе возникает бесконечный цикл из-за размножения знакового разряда. Условие окончания цикла (N не равно 0) при этом никогда не выполнится. Конечно, эту неприятность можно обойти, организовав фиксированное количество повторений цикла (точно 32 раза). Однако в этом случае зачастую совершается лишняя работа — единиц уже нет, а цикл еще не закончился. Можно предложить и такой вариант, когда вместо сдвига аргумента сдвигается тестирующая шкала.
Программа 2_14.bas
RЕМ Определение максимальной группы единиц в двоичном числе
DECLARE FUNCTION MaxBitS!(N&,s$)
CLS
INPUT "Введите целое число : ",М&
k%=MaxBits(M&,a$)
PRINT "Двоичное представление этого числа :"
PRINT a$
PRINT "Максимальное число подряд стоящих единиц = "; k%
END
FUNCTION MaxBits(NS,s$)
REM Перевод числа N в двоичное символьное представление
REM Поиск самой длинной группы единиц (MaxBits = длина)
IF N&=0 THEN MaxBits=0: s$="0": EXIT FUNCTION
FOR j=0 TO 31 : ' Формирование двоичного числа в строке s$
IF N& AND &H1 THEN
kBits=kBits+l : ' Подсчет подряд идущих единиц
IF kBits>max THEN max=kBits : ' Запомнили максимум
s$="l"+s$ : ' Приписали очередную единицу ELSE
kBits=0 : ' Сброс счетчика при встрече нуля
s$="0"+s$ : ' Приписали очередной ноль END IF
N&=(N&-k)/2 : ' Сдвиг на один разряд вправо
NEXT j
MaxBits=max
END FUNCTION
Программа 2_14.с
/* Определение максимальной группы единиц в двоичном числе */
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
int max_bits(long N);
main ()
{
long M;
char str[33];
clrscr () ;
printf("\nВведите целое число : ");
scanf("%ld",&M);
printf("Двоичное представление этого числа :");
ltoa(M,str,2);
printf("\n%32s",str);
printf("\ n Максимальное число подряд стоящих единиц = %d",
max_bits(M));
getch(); }
int max_bits(long N)
/* Перевод числа N в двоичное символьное представление
Поиск самой длинной группы единиц (max_bits = длина) */
{
int k_bits=0,max=0;
char s[33];
if(N==0) return 0;
while (N) /* Формирование двоичного числа в строке s$ */
{
if(N & 0x80000000) /* если очередной разряд равен 1 */
{
/* подсчет подряд идущих единиц */
k_bits++;
if(k_bits>max) max=k_bits; /* запоминание максимума */
}
else
k_bits=0; /* сброс счетчика, если очередной разряд = 0 */
N=N << 1 ; /* сдвиг на 1 разряд влево */
}
return max; }
Программа 2_14.pas
program MaxBits;
{ Определение максимальной группы единиц в двоичном числе }
uses Crt;
var
M:longint;
str:string[33];
function max_bits(N:longint)rshortint;
{ Перевод числа N в двоичное символьное представление
Поиск самой длинной группы единиц (max_bits = длина) }
var
k bits, max:byte;
begin
k_bits:=0;
max:=0;
if N=0 then
begin
max_bits:=0;
exit;
end;
while N<>0 do { Формирование двоичного числа в строке s$ }
begin
if (N and $00000001)00 then { если разряд равен 1 }
begin
str:='l'+str;
inc(k_bits); { подсчет подряд идущих единиц }
if k_bits>max then max:=k_bits end else begin
str:='0'+str;
k_bits:=0; { сброс счетчика, если очередной разряд = 0 }
end;
N:= N shr 1; { сдвиг на 1 разряд вправо }
end;
max_bits:=max; end;
begin clrscr;
write('Введите целое число : ') ;
readln(M);
writeln('Максимальное число подряд стоящих единиц = ',max_bits(M));
readln;
end.
Задание 2.15. Расстояние между двоичными кодами
Под "расстоянием" между двумя n- разрядными двоичными кодами понимают количество несовпадений в каждой из п позиций. Например:
01011010
00101110
-------------
=***=*== "расстояние"=4
Составить функцию rasst (ni,n2), аргументами которой являются два длинных положительных числа, рассматриваемые как 32-разрядные двоичные коды. Возвращаемое функцией значение должно совпадать с "расстоянием" между nl и п2.
Совет 1 (общий)
Наиболее рациональный алгоритм — воспользоваться логической операцией XOR, осуществляющей поразрядное сложение двоичных кодов по модулю 2, а затем подсчитать общее количество двоичных единиц в полученном результате. Менее эффективный по числу тактов способ состоит в сравнении одноименных разрядов каждого аргумента функции.
Программа 2_15.bas
REM Определение расстояния между двоичными кодами
DECLARE FUNCTION NumToBin$(N&)
DECLARE FUNCTION SumBits!(N&)
CLS
INPUT "Введи первое число :",N1&
INPUT "Введи второе число :",N2&
PRINT "Двоичное представление этих чисел :"
PRINT NumToBin$(N1&) : ' Двоичное разложение N1
PRINT NumToBin$(N2&) : ' Двоичное разложение N2
PRINT "Расстояние между этими кодами = "; SumBits(N1& XOR N2&)
END
FUNCTION NumToBin$(N&)
REM Формирование в строке двоичного представления числа N
а$="": К=&Н1
IF N&=0 THEN NumToBin$="0": EXIT FUNCTION
FOR J=0 TO 30
IF N& AND К THEN s$="l"+S$ ELSE s$="0"+s$
K=K*2 : ' Сдвиг шкалы на 1 разряд влево
NEXT J
IF N&<0 THEN s$="l"+s$ : ' Учет единицу в знаковом разряде
NumToBin$=s$
END FUNCTION
FUNCTION SumBits (N&)
КЕМ Подсчет количества единиц в двоичном числе
J&=&H40000000
RESULT=0
IF N&<0 THEN RESULT=1
FOR K=31 TO 1 STEP -1
IF N& AND J& THEN RESULT=RESULT+1
J&=J&/2 NEXT К
SumBits=RESULT END FUNCTION
Программа 2_15.c
/* Определение расстояния между двоичными кодами */
#include <stdlib.h>
int rasst(long nl, long n2);
char s [ 4 0];
main() {
long M1,M2;
printf("\ n Введите два целых числа : ");
scanf("%ld %ld",&Ml,&M2);
printf("\nMl =%32s",ltoa(Ml,s,2)); /* Двоичное разложение N1*/
printf("\nM2 =%32s",ltoa(M2,s,2)); /* Двоичное разложение N2*/
printf("\nXOR=%32s",ltoa(MlAM2,s,2));
printf("\nРасстояние между этим кодами = %5d",rasst(M1,M2));
getch();
}
int rasst(long nl,long n2)
{
int Result=0,k;
long j=0x00000001;
for(k=0; k <32; k++)
{
/* Сравнение одноименных двоичных разрядов */
if((j & nl)!=(j & n2)) Result++;
j=j « 1; /* сдвиг шкалы на разряд влево */
}
return Result; }
Программа 2_15.pas
program distance;
{ Определение расстояния между двоичными кодами }
var
Ml,M2:longint;
function rasst(nl,n2:longint):byte;
const
Result:byte=0 ;
j:longint=$00000001;
var
k:byte;
n3:longint;
begin
n3:=nl xor n2;
for k:=0 to 31 do { цикл подсчета единиц в двоичном числе }
begin
if (j and n3)<>0 then inc(Result);
j:=j shl 1;
{ сдвиг шкалы на разряд влево }
end;
rasst:=Result; end; begin
write('Введите два целых числа : ');
readln(Ml,M2);
writeln('Расстояние между этим кодами = ',rasst(Ml,M2));
readln; end.
Задание 2.16. Чтение числа справа налево
Составить функцию invert (n), аргументом которой является длинное целое число. Возвращаемое значение должно быть равно числу, полученному из n перестановкой цифр в обратном порядке. Знак числа при этом сохраняется на прежнем месте. Например:
invert(314159) =951413
invert(-2718) = -8172
Совет 1 (общий)
Очевидно, что самый простой алгоритм состоит в том, чтобы последовательно находить десятичные цифры, начиная с младшей, и тут же использовать их для вычисления перевернутого числа. Нужно только не забыть отделить от числа его знак, иначе каждый остаток от деления на 10 будет наследовать знак исходного числа.
Программа 2_16.bas
RЕМ Перестановка старших и младших разрядов в числе
DECLARE FUNCTION invert!(N&)
CLS
INPUT "Введите целое число : ",М&
PRINT " Справа налево оно выглядит так ";invert(MS)
END
FUNCTION invert(N&)
Res&=0: sign=SGN(N&) : ' Учет знака числа
IF N&<0 THEN N&=-N& '
DO
k%=(N& MOD 10) : ' очередная цифра справа
Res&=Res&*10+k% : ' формирование перевернутого результата
N&=(Ns-k%)/10 : ' удаление обработанной цифры
LOOP UNTIL N&=0
invert=Res&*sign : ' приклеили знак
END FUNCTION
Программа 2_16.с
/* Перестановка старших и младших разрядов в числе */
#include <stdlib.h>
long invert(long N);
main()
{
' long M;
printf("\nВведите целое число : ");
scanf("%ld",&M);
printf("\nСправа налево оно выглядит так %ld",invert(M));
getch();
}
long invert(long N) {/* инвертирование числа */
long Result=0, sign=l;
/* Учет знака числа */
if(N<0)
{ N=-N; sign=-l;}
while (N!=0) {
Result=Result*10 + (N % 10);
/* формирование результата */
N=N/10;
/* удаление обработанной цифры */
}
return Result*sign; /* приклеили знак */ }
Программа 2._16.pas
program rotate;
{ Перестановка старших и младших разрядов в числе }
var
N:longint;
function invert(N:longint):longint; const
Result:longint=0; sign:shortint=l;
begin
if N<0 then { Учет знака числа }
begin N:=-N;
sign:=-l;
end;
while (N<>0) do begin
Result:=Result*10+(N mod 10);{ формирование результата }
N:=N div 10; { удаление обработанной цифры }
end;
invert:=Result*sign; { приклеили знак }
end;
begin
write('Введите целое число : ');
readln(N);
writeln('Справа налево оно выглядит так : ',invert(N));
readln;
end.
Задание 2.17. Числовые палиндромы
Натуральное число N = a1a2... ak называют палиндромом, если его величина совпадает со значением, прочитанным справа налево, NI ak...a2a1. При этом предполагается, что a1> о. Например, 1881 — палиндром, а 1812 — нет. Составить функцию paiindrom(n), аргументом которой является длинное положительное целое число. Функция должна возвращать значение true (Паскаль) или 1 (QBasic, Си), если ее аргумент является числовым палиндромом.
Совет 1 (общий)
Конечно, функцию palindrom можно построить на базе функции invert. Достаточно возвратить в качестве значения функции результат сравнения N и invert (N). Но для разнообразия мы предлагаем вам другой алгоритм, в котором формируется массив десятичных цифр числа и производится сравнение его симметрично расположенных элементов.
Совет 2 (общий)
Более компактный вариант программы можно построить, заменив цикл формирования массива десятичных цифр стандартной функцией преобразования числа в его символьный эквивалент. Такого рода функции имеются в каждом алгоритмическом языке— STR$ в QBasic, Itoa в Си, процедура str в Паскале. Один из примеров — программа 2_17a.pas —демонстрирует указанный подход. Кроме того, в ее реализации показана уникальная особенность Паскаля, позволяющего в качестве индексов у элементов массива использовать нечисловые значения.
Совет 3 (QBasic)
В программе 2_l7.bas также реализован второй подход. Но здесь следует
иметь в виду, что функция STR$ выдает результат с пробелом в первой позиции для положительных чисел. Поэтому сравнение цифр начинается со второй позиции. Для выделения симметрично расположенных цифр использована функция MID$, второй аргумент которой определяет начальную позицию в строке, а третий — количество выделяемых символов.
Программа 2_17.bas
REM Является ли введенное число палиндромом?
DECLARE FUNCTION palindrom!(NS)
СLS
DIM noyes$(2)
noyes$(0)="не"
INPUT "Введите целое число: ",М&
PRINT "Это - "; noyes$(palindrom(M&));" палиндром"
END
FUNCTION palindrom(N&)
REM Если N - палиндром, то palindrom = 1
palindrom=l
IF N&<10 THEN EXIT FUNCTION : ' Одноразрядное - всегда палиндром
s$=STR$(N&) : ' Перевод числа в строку
k%=LEN(s$)
FOR j=2 TO l+k%/2 : ' Цикл проверки симметрии цифр
IF MID$ (s$, j,l)OMID$ (s$,k%+2-j,l) THEN palindrom=0
NEXT j
END FUNCTION
Программа 2_17.с
/* Является ли введенное число палиндромом? */
#include <stdio.h>
#include <conio.h>
int palindrom(long N);
main () {
long M;
printf("\n Введите целое число: ");
scanf("%ld",&M);
if (palindrom(M)) printf("\n Это - палиндром");
else printf("\пЭто - не палиндром");
getch(); }
int palindrom(long N)
{
/* Если N - палиндром, то palindrom = 1 */
int j,k=l;
char digit[10];
if(N<10) return 1; /* Одноразрядное - всегда палиндром */
for(j=0; j<10; j++)
{
/* цикл выделения десятичных цифр */
digit[j]=N%10;
N=N/10; /* удаление обработанной цифры */
if(N!=0) k++;
}
for(j=0; j<=k/2; j++) /* Цикл проверки симметрии цифр */
if(digit[j]!=digit[k-j-1]) return 0;
return 1;
}
Программа 2_17.pas
program Palindroms;
{ Является ли введенное число палиндромом ? }
var
М: longint; function
palindrom(N:longint):boolean;
{ Если N - палиндром, то palindrom = true }
var
j,k:integer;
digit:array [0..9] of byte;
begin
palindr om: =t rue ;
if N<10 then exit; { Одноразрядное - всегда палиндром }
k:=0;
for j:=0 to 9 do { цикл выделения десятичных цифр }
begin
digit[j]:=N mod 10; { очередная цифра числа N }
N:=N div 10; { удаление обработанной цифры }
if(N<>0) then k:=k+l; { счетчик цифр в числе N }
end;
for j:=1 to (k div 2) do { Цикл проверки симметрии цифр }
if digit[j]odigit[k-j] then palindrom:=false;
end;
begin
write('Введите целое число: ');
readln(M);
if palindrom(M) then
writeln('Это - палиндром') else
writeln('Это не палиндром');
readln;
end.
Программа 2_17a.pas
program Palindroms;
{ Является ли введенное число палиндромом? }
var
М: longint; const
noyes:array [false..true] of string=('не', '');
function palindrom(N:longint):boolean;
{ Если N - палиндром, то palindrom = true }
var
j,k:byte ;
s:string[16];
begin
palindrom:=true;
if N<10 then exit; { Одноразрядное - всегда палиндром }
str(N,s); { перевод числа в символьную строку }
k:=length(s) ;
for j:=l to (k div 2) do { Цикл проверки симметрии цифр }
if s[j]<>s[k+l-j] then palindrom:=false; end; begin
write('Введите целое число: ');
readln(М);
writeln('Это - ',noyes[palindrom(М)],' палиндром');
readln;
end.
Задание 2.18. Генерация палиндромов с заданным свойством
Составить программу, выводящую на экран все четырехзначные палиндромы, квадраты которых тоже являются палиндромами. Задачу можно несколько усложнить, если считать количество цифр в исходном палиндроме параметром k (k < 5).
Совет 1 (общий)
Вместо перебора всех чисел в диапазоне [1000, 9999] целесообразно организовать цикл по формированию четырехзначных палиндромов вида a1a2a2a1, который будет повторяться всего 90 раз.
Программа 2_18.bas
RЕМ Генерация палиндромов, квадраты которых тоже палиндромы
DECLARE FUNCTION palindrom!(N&)
CLS
FOR Al=l TO 9: FOR A2=0 TO 9
M&=(((A1*10)+A2)*10+A2)*10+A1
M2&=M&*M&
IF palindrom(M2&) THEN PRINT M&,M2& NEXT A2, A1 END
FUNCTION palindrom (N&)
REM Если N - палиндром, то palindrom = 1
palindrom=l
IF N&<10 THEN EXIT FUNCTION : ' Одноразрядное - всегда палиндром
s$=STR$(N&) : ' перевод числа в символьную строку
k%=LEN(s$)
FOR j=2 TO l+k%/2 : ' Цикл проверки симметрии цифр
IF MID$ (s$, j,l)OMID$(s$,k%+2-j,l) THEN palindrom=0 NEXT j
END FUNCTION
Программа 2_18.с
/* Генерация палиндромов, квадраты которых тоже палиндромы */
#include <stdio.h>
#include <conio.h>
int palindrom (long N);
ma in ()
{
long M,M2;
int al,a2;
for(al=l; al<10; al++)
for(a2=0; a2<10; a2++)
{
M=(((al*10)+a2)*10+a2)*10+a1;
M2=M*M;
if(palindrom(M2))
printf ("\пчисло=%1d квадрат=%1d",М,М2) ; }
getch();
int palindromflong N)
{/* Если N - палиндром, то palindrom = 1 */
int j,k=l;
char digit[10];
if(N<10) return 1; /* Одноразрядное - всегда палиндром */
for(j=0; j<10; j++) /* Цикл выделения цифр числа N */
{
digit[j]=N % 10; N=N/10;
if(N!=0) k++;
}
for(j=0; j <= k/2; j++) /* Цикл проверки симметрии цифр */
if(digit[j]!=digit[k-j-1])
return 0;
return 1; }
Программа 2_18.pas
program sqr_pal;
{ Генерация палиндромов, квадраты которых тоже палиндромы }
var
M,M2:longint;
al,a2:integer;
function palindrom(N:longint):boolean; { Если N - палиндром, то palindrom = true }
var
j,k:integer;
digit:array [0..9] of byte; begin k:=l;
palindrom:=true;
if N < 10 then exit; { Одноразрядное - всегда палиндром } for j : =0 to 9 do { Цикл выделения цифр числа^ N }
begin
digit[j]:=N mod 10; N:=N div 10;
if N00 then inc(k);
end;
for j:=0 to k div 2 do { Цикл проверки симметрии цифр }
if digit[j]odigit[k-j-1] then palindrom:=false; end;
begin
for al:=l to 9 do
for a2:=0 to 9 do begin
M:=(((al*10)+a2)*10+a2)*10+al;
M2:=M*M;
if palindrom(M2) then
writeln('число=',M,' квадрат=',М2);
end;
readln; end.
Задание 2.19. Числовые преобразования до обнаружения зацикливания
Составить программу, которая вводит длинное целое число N и подвергает его многократному преобразованию по следующей схеме:
N1+i = a(i,k)3+ a(i,k-l)3+ ... + a(i,0}3
Здесь через a (i,k), a(i,k-i),... , a(i,0) обозначены цифры числа NL Обработка прекращается при обнаружении зацикливания (N! = N-J, i > j).
Совет 1 (общий)
Очень большое девятиразрядное число 1999999999 дает максимальную сумму кубов цифр, равную 6562. Поэтому один из возможных вариантов решения — завести массив Q из 6562 элементов, предварительно обнулив все его элементы. Затем в процессе преобразования текущего числа NI записывать в элемент Q [Ni] индекс i, если значение этого элемента было равно 0. Цикл будет обнаружен, как только получится число Nj, для которого в элемент Q[NJ] уже что-то заносилось. В связи с тем, что в используемых системах программирования глобальные числовые массивы перед запуском программы чистятся, можно отказаться от обнуления элементов массива Q.
Совет 2 (QBasic)
В предположении, что длина цикла не очень велика, можно пойти на запоминание всех промежуточных результатов в небольшом массиве MASS. Для проверки зацикливания можно сравнивать очередное число со всеми ранее полученными.
Совет 3 (общий)
Попробуйте модифицировать программу таким образом, чтобы обнаружить все числовые последовательности, образующие цикл с самого первого шага. В качестве примера приведем некоторые из них:
1 —> 1 (одношаговый цикл),
136 -> 244 —> -136 (двухшаговый цикл),
55 -> 250 -> 133 -> (трехшаговый цикл).
Программа 2_19.bas
RЕМ Поиск циклов при суммировании кубов цифр
DECLARE FUNCTION nkub& (N&)
DIM MASS(100) : ' Массив для последовательности чисел
CLS
INPUT "Введите число - ", N&
К=0: MAS&(0)=N& ml: К=К+1 : ' Счетчик числа шагов
MASS(K)=nkub&(MASS(K-l))
PRINT "Шаг =";К," N =";MAS&(K)
FOR 1=0 ТО К-1 : ' Поиск совпадения с предыдущими числами
IF MAS&(I)=MASS(K) THEN GOTO m2
NEXT I
GOTO ml m2: PRINT "Обнаружен цикл": PRINT "Начало цикла шаг ";1
PRINT "Конец цикла шаг ";К-1
END
FUNCTION nkub&(N&)
КЕМ Преобразование числа в сумму кубов его цифр
DIM S AS INTEGER, M AS LONG S=0: M=N& WHILE M<>0
D=M MOD 10: M=(M-D)/10 : ' Выделение очередной цифры
S=S+D*D*D : ' Накопление суммы кубов
WEND nkub&=S
END FUNCTION
Программа 2_19.c
/* Поиск циклов при суммировании кубов цифр */
#include <stdio.h>
#include <conio.h>
long n_kub(long N);
long N;
int i=l,Q[6562];
main ()
{
clrscr();
printf("\nВведите число ");
scanf("%ld",&N); Q[N-l]=i; ml:
printf ("\n Шaг=%2d N=%ld", i,N) ;
N=n_kub(N);
if(Q[N-1]!=0) /* He было ли раньше такого же числа ?*/ {
printf("\nHa %d шаге обнаружен цикл N=%ld",i+l,N);
getch ();
exit(0) ;
}
Q[N-l]=i++; /* Фиксация очередного числа в массиве Q*/
goto ml;
}
/ *-------------------------------------* /
long n_kub(long N)
{
/* Преобразование числа в сумму кубов его цифр */
int k;
long s=0; while (N)
{
k=N%10;
N=N/10; /* Выделение очередной цифры */
s+=k*k*k; /* Накопление суммы кубов */
}
return s;
}
Программа 2_19.pas
program loop cub
{ Поиск циклов при суммировании кубов цифр }
uses Crt; label ml; var
N:longint;
Q:array [1..6562] of integer; i:integer;
function n_kub(N:longint):integer;
{ Преобразование числа в -сумму кубов его цифр }
var
s,k:integer; begin s:=0;
while (N<>0) do begin
k:=N mod 10;
N:=N div 10; { Выделение очередной цифры }
s:=s+k*k*k; { Накопление суммы кубов }
end;
n kub:=s;
end;
begin clrscr;
write('Введите число');
readln(N);
for i:=l to 6562 do Q[i]:=0;
{ Очистка массива Q}
i:=l;
Q[N]:=1;
ml:
writeln('Шar=',i:2, ' N=',N);
N:=n_kub{N);
if Q[N]<>0 then { He было ли раньше такого же числа ?}
begin
writeln('Ha шаге ',1+1:2,' обнаружен цикл');
readln;
halt;
end;
inc(1); { Счетчик числа шагов )
Q[N]:=i; { Запоминание счетчика в массиве Q }
goto ml;
end.
Задание 2.20. Разложение числа на простые множители
Составить программу, которая выдает разложение заданного целого числа N на простые множители. Например:
128 = 2*2*2*2*2*2*2 17 = простое число
Совет 1 (общий)
Самый простой способ заключается в попытке делить N на числа k = 2, 3, ... до тех пор, пока N делится. Очевидно, что перебор делителей k не имеет смысл продолжать для k > N/2. Переменная j в программах используется как признак того, что был найден хотя бы один нормальный делитель. Нулевое значение J означает, что исходное число — простое.
Программа 2_20.bas
RЕМ Разложение числа на простые множители
CLS
К& = 2: J% = 0
INPUT "Введите целое число: ", М&: М1& = Ms / 2
PRINT M&; "=";
Ml:
IF MS MOD K&=0 THEN
J=l: M&=M&/K&: PRINT K&;
IF M&<>l THEN PRINT "*";
ELSE K&=K&+1
END IF
IF K&<=M1& THEN GOTO Ml
IF J=0 THEN PRINT " простое число"
END
Программа 2_20.с
/* Разложение числа на простые множители */
#include <stdio.h>
#include <conio.h>
main() {
long M,M1,k=2;
char j=l;
printf("\n Введите целое число: ");
scanf("%ld",&M);
Ml=M/2;
printf("\n%ld=",M);
ml:
if (M % k=0) {
j=0;
M=M/k;
printf("%ld",k) ;
if(M!=l)printf("*"); }
else k++;
if(k<=Ml) goto ml;
if(j==l)
printf("простое число");
getch(); }
Программа 2_20.pas
program razlojenie;
{ Разложение числа на простые множители }
var
M,Ml,k:longint ;
j:boolean;
IF K&<=Ml& THEN GOTO Ml
IF J=0 THEN PRINT " простое число"
END
Программа 2_20,с
/* Разложение числа на простые множители */
#include <stdio.h>
#include <conio.h>
main ()
{
long M,M1,k=2;
char j=l;
printf("\n Введите целое число: ");
scanf("%ld",&M);
Ml=M/2;
printf("\n%ld=",M);
ml:
if(M % k==0)
{
j=0; M=M/k;
printf("%ld",k) ;
if(M!=l) printf("*"); }
else k++;
if(k<=Ml) goto ml;
if(j==l) printf ("простое число");
getch();
}
Программа 2_20.pas
program razlojenie;
{
Разложение числа на простые множители }
var
М,М1,k:longint;
j:boolean;
label 1;
begin
j:=true; k: =2 ;
write('Введите целое число: ');
readln(M);
M1:=M div 2;
write(M,' = ');
1:
if(M mod k)=0 then begin
j:=false;
M:=M div k;
write (k) ;
if Mol then write ('*');
end
else k:=k+l; if k<=Ml then goto 1;
if j then write('простое число'};
readln; end.
Задание 2.21. Проверка числа на "простоту"
Составить функцию prime (п), аргументом которой является длинное целое положительное число. Функция должна возвращать значение true (Паскаль) или 1 (Си, QBasic), если ее аргумент является простым числом. В противном случае функция должна возвращать значение false или о.
Совет 1 (общий)
Один из наиболее легко реализуемых алгоритмов проверки числа на "простоту" заключается в том, что исходное число N последовательно делят на 2, 3, 5, 7, 9, ..., 2*p+l ( (2*p+l)2 < N). Если ни один из остатков отделения не равен нулю, то N — простое. Конечно, этот алгоритм далек от совершенства — например, зачем делить на 9 или 15, если число не делилось на 3. По идее, в проверке должны участвовать только простые делители — 2, 3, 5, 7, 11, 13, ... Но построить такую программу несколько сложнее (см. задание 2.22).
Программа 2_21.bas
DECLARE FUNCTION prime!(N&)
REM Проверка числа на простоту
CLS
INPUT "Введите целое число: ", М&
IF prime (M&)=1 THEN
PRINT "Это число - простое " ELSE
PRINT "Это число - составное"
END IF
END
FUNCTION prime(N&)
REM Если N - простое, то prime = 1
DIM j AS LONG
IF N&<4 THEN GOTO Ml : ' Числа 2, 3 - простые
IF N& MOD 2=0 THEN GOTO MO : ' Четные числа - составные
REM Проверка делимости на нечетные числа
FOR j=3 TO SQR(N&)+1 STEP 2
IF N& MOD j=0 THEN GOTO MO NEXT j
Ml: prime=l: EXIT FUNCTION M0: prime=0 END FUNCTION
Программа 2_21.с
/* Проверка числа на простоту */
#include <stdio.h>
#include <math.h>
int prime(long N) ;
main() {
long M;
char *a[]={"составное","простое"};
printf("\n Введите целое число: ");
scanf("%ld",SM);
printf("\n Это число - %s", a[prime(M)]);
getch(); }
int prime(long N)
{
/* Если N - простое, то prime = 1 */
long kmax,j;
if(N<4) return 1; /* Числа 2, 3 - простые */
if(N % 2==0) return 0; /* Четные числа - составные */
/* Проверка делимости на нечетные числа */
for(j=3; j*j<=N; j+=2)
if( N % j==0) return 0; return 1;
Программа 2_21.pas
program prostota;
{ Проверка числа на простоту }
var
М:longint; const
a:array [0..1] of string=('составное','простое');
function prime(N:longint}:byte;
{ Если N - простое, то prime = 1 }
var
j:longint; label ml;
begin
prime:=1;
if N<4 then exit; { Числа 2, 3 - простые }
prime:=0;
if N mod 2=0 then exit; { Четные числа - составные }
j:=3;
{ Проверка делимости на нечетные числа }
ml:
if N mod j=0 then exit;
j:=j+2;
if j*j<=N then goto ml;
prime:=1; end; begin
writeln ('Введите целое число: ');
readln(М);
writeln('Это число - ', a[prime(М)]);
readln; end.
Задание 2.22. Решето Эратосфена
История сохранила память о древнегреческом математике Эратосфене Кипренском, который применил оригинальный алгоритм нахождения всех
простых чисел. На длинном папирусе он выписал числа 2, 3, 4, ... , 1000. Затем, сохранив первое число 2, он проколол каждое второе число, т. е. все четные числа, делящиеся на 2. В следующий раз, пропустив второе, не проколотое число 3, удалил после него каждое третье число, т. е. все числа, делящиеся на 3. Такая же участь постигла и каждое пятое число после 5, каждое седьмое число после 7 и т. д. К концу эксперимента на папирусе остались не проколотыми только простые числа. Возможно, что Эратосфен не догадался вдвое облегчить свою работу и использовать вдвое более короткий папирус, изначально выписав на нем только нечетные, числа. Зато сейчас мы составим программу, которая использует идею Эратосфена и находит все простые числа среди первых maxk нечетных чисел.
Совет 1 (общий)
Сформируем массив is, i-й элемент которого будет представлять нечетное число 2*1+3 и равняться 1, если 1-е число еще не "проколото", и равняться 0 в противном случае. Действуем по алгоритму Эратосфена — принимаем 3 в качестве первого нечетного простого числа и присваиваем нулевые значения каждому третьему элементу is. В качестве второго нечетного простого числа принимается следующая "не проколотая" позиция, соответствующая 5. "Прокалываем" каждую пятую позицию в is и т. д. В Бейсике идентификатор is является служебным словом, поэтому в программе его пришлось заменить на isi.
Совет 2 (Паскаль)
Решето Эратосфена очень изящно выглядит с применением данных типа множество (set). Регулярное множество, содержащее отрезок чисел натурального ряда, легко формируется с помощью интервального набора данных. К множеству можно добавлять новые элементы (операция сложения), исключать ненужные (операция вычитания), проверять принадлежность данного элемента множеству (операция in). Единственный недостаток множеств в Паскале — ограничение на количество элементов (не более 255).
Программа 2_22.bas
DEFINT A-Z CLS
МАХК=500 DIM ISI(MAXK+1)
FOR I=0 ТО МАХК-1: IS1(I)=1: NEXT I
FOR I= 0 TO MAXK IF ISI(I)=l THEN
'если число еще не "проколото"
PRIME=I+I+3: ' сформировали очередное простое число
N=N+1: ' увеличили счетчик простых чисел
PRINT USING "#####";
PRIME; : ' вывод очередного простого числа
K=I+PRIME: ' индекс для первого "прокола"
WHILE К<=МAХК
REM "проколы" чисел, делящихся на
PRIME IS1(K)=0: K=K+PRIME
WEND
END IF
NEXT I
PRINT "Среди первых ";МАХК+1; " нечетных чисел";
PRINT " найдено "; N; " простых"
END
Программа 2_22.с
#include <stdio.h>
#include <conio.h>
#define maxk 500
main() {
int pr_num,n=0;
char is[maxk+1];
register int i,k;
clrscr () ;
for(i=0; i<=maxk; i++) is[i]=l; /* все числа "на папирусе" */
for(i=0; i<=maxk; i++)
{if(is[i]==l) /* если число еще не "проколото" */
{ pr_num=i+i+3; /* сформировали очередное простое число */
n++; /* увеличили счетчик простых чисел */
printf("%5d",pr_num); /* вывод очередного простого имела */
k=i+pr num; /* индекс для первого "прокола" */
while (К<:=mахk)
{
/* "проколы" чисел, делящихся на pr_num */
is[k]=0; k+=pr_num;
}
}
}
printf("\nСреди первых %d нечетных чисел", maxk+1);
printf("найдено %d простых", n);
getch();
}
Программа 2_22.pas
program sievel;
uses Crt;
const
maxk=500;
n:integer=0;
var
is: array [0..maxk] of byte;
i,k,prime:integer;
begin clrscr;
for i:=0 to maxk do is[i]:=1; { все числа "на папирусе" }
for i:=0 to maxk do
begin
if is[i]=l then { если число еще не "проколото" }
begin
prime:=i+i+3; { сформировали очередное простое число }
inc(n); { увеличили счетчик простых чисел }
write(prime:5); { вывод очередного простого числа }
k:=i+prime; { индекс для первого "прокола" }
while k<=maxk do
begin { "проколы" чисел, делящихся на pr_num }
is[k]:=0; inc(k,prime); end end; end;
writeln;
write(' Среди первых ',maxk+l, ' нечетных чисел ');
writeln(' найдено ',n,' простых');
readln;
end.
Программа 2_22a.pas
program sieve; uses Crt; const
maxN=255; (не более 255} var
primes: set of 2..maxN;
i,j:integer;
begin clrscr;
primes:=[2..maxN]; { первоначальная роспись папируса }
for i:=2 to maxN do
if i in primes then { если i принадлежит множеству }
begin
write (i:5); { вывод очередного простого числа }
for j : =1 to maxN div i do
primes:=primes-[i*j]; { удаление чисел, кратных i}
end;
readln;
end.
Задание 2.23. Разложение четного числа на сумму простых чисел
В 1742 г. Христиан Гольдбах высказал предположение, что любое четное число можно представить в виде суммы двух простых чисел, а любое нечетное — в виде суммы трех простых чисел. На самом деле, вторая часть утверждения Гольдбаха является следствием первой. Если из нечетного числа вычесть подходящее простое, то остаток будет четным, и как только его удастся разложить на сумму двух простых, первоначальное нечетное число будет равно сумме трех простых чисел. Несмотря на кажущуюся простоту проблемы Гольдбаха, до сих пор ее справедливость ни доказана, ни опровергнута. Более того, в начале 2000 г. английский книгоиздатель Фейбер предложил награду в размере 1 миллиона долларов тому, кто сумеет доказать или опровергнуть предположение Гольдбаха.
Предлагается провести численный эксперимент, который, к сожалению, не претендует на получение объявленной награды. Составим программу, которая находит все возможные разложения числа п на сумму двух простых чисел.
Совет 1 (общий)
Самый простой вариант поиска нужного разложения заключается в переборе сумм вида k + (n - k). Если оба слагаемых оказались простыми, то искомое
разложение найдено. Анализ слагаемых можно организовать с помощью функции prime, построенной в одном из предыдущих заданий. Естественно, что начать перебор можно с k = 2, а затем в качестве первого слагаемого пробовать все нечетные числа, не превосходящие п/2.
Программа 2_23.bas
DECLARE FUNCTION prime!(N&)
REM Разложение четного числа на сумму двух простых CLS
INPUT "Введите четное число: ",М& IF prime (M&-2)=1 THEN
PRINT M&; "=2-f ";M&-2
FOR j&=l TO M&/2 STEP 2
IF prime(j&)=l AND prime (M&-J&)=1 THEN
PRINT M&;"=";j&;"+";M&-j&
END IF
NEXT j& END
FUNCTION prime (N&)
DIM j AS LONG
IF N&<4 THEN GOTO Ml
IF N& MOD 2=0 THEN GOTO MO
FOR j=3 TO SQR(N&)+1 STEP 2
IF N& MOD j=0 THEN GOTO MO
NEXT j
Ml: prime=l: EXIT FUNCTION MO: prime=0
END FUNCTION
Программа 2_23.с
/* Разложение четного числа на сумму двух простых */ tinclude <stdio.h> linclude <math.h> int prime(long N) ; main()
{
long M,j;
clrscr () ;
printf("\n Введите четное число: ");
scanf("%ld",&M);
if (prime(M-2) ) printf ("%ld== 2+%ld\t",M,M-2) ;
for(j=l; j <= M/2; j+=2)
if(prime(j) && prime(M-j)) printf("!ld=l31d+%ld\t",
M, j, M-j);
getch(); }
int prime(long N) {
long kmax,j;
if(N<4) return 1;
if(N%2==0) return 0;
kmax=sqrt(N)+0.5;
for(j=3; j<=kmax; j+=2)
if(N%j==0) return 0; return 1; }
Программа 2_23.pas
program razlojenie;
{ Разложение четного числа на сумму двух простых }
var
M:longint;
j:integer; label m2;
function prime(N:longint):byte; var
j:longint; label ml; begin
prime:=l;
if N<4 then exit;
prime:=0;
if N mod 2=0 then exit; j:=3;
ml: if N mod j=0 then exit;
J:=J+2;
if j*j<=N then goto ml;
prime:=1; end; begin
write ('Введите четное число: ');
readln(M);
if prime(M-2)=l then writeln(M,'+',M-2);
j:=3; m2:
if (prime(j)=l) and (prime(M-j)=1) then writeln(M,'=',j,'+',M-j);
j:=j+2;
if j< M div 2 then goto m2;
readln; end.
Задание 2.24. Генерация чисел Хэмминга
Числами Хэмминга называются натуральные числа, которые среди своих делителей имеют только степени чисел 2, 3 и 5. Первые 10 упорядоченных по возрастанию чисел Хэмминга образует последовательность 1, 2, 3, 4, 5, 6, 8, 9, 10 и 12. Первому числу этого ряда соответствуют нулевые степени всех сомножителей. Составить программу, которая генерирует первые 1000 чисел Хэмминга.
Совет 1 (общий)
Конечно, можно построить цикл по перебору всех длинных целых чисел (а их — порядка двух миллиардов), в теле которого выделяются все делители, кратные 2, 3 и 5. Если результат деления тестируемого числа оказывается равным 1, т. е. у числа никаких других делителей нет, то найдено очередное число Хэмминга. Однако такая программа будет очень долго работать (для сравнения мы приведем и текой вариант решения).
Гораздо более эффективный алгоритм базируется на определении следующего числа Хэмминга по уже построенной последовательности. При этом приходится запоминать, какое из ранее найденных чисел необходимо домножить на 2, какое — на 3 и какое — на 5. Из трех возможных вариантов следует выбирать минимальное произведение. На первом шаге мы знаем единственное число Хэмминга, равное 1, и именно оно может быть умножено на следующем шаге на один из трех возмож-
ных множителей. Минимальное произведение равно 2, и это — второе число Хэмминга. Теперь умножению на 2 должно быть подвергнуто второе число, а умножению на 3 и 5 — пока еще первое. Поэтому на втором шаге минимальное произведение равно 3. После этого умножению на 2 и 3 может быть подвергнуто второе число, а на 5 — пока еще только первое. Одним словом, когда определено минимальное произведение, использованный множитель должен "переместиться" на следующее, уже найденное число Хэмминга.
Совет 2 (общий)
Обозначим через kl, k2, k3 индексы чисел Хэмминга, которые будут множиться соответственно на 2, 3 и 5. Их начальные значения в программе на Бейсике можно не задавать, т. к. все числовые переменные перед началом работы программы сбрасываются в ноль.
Программа 2_24.bas (оптимальный вариант)
DEFLNG A-Z
DIM Xam(1000)
CLS
Xam(0)=l
PRINT 1,1
FOR J=l TO 999
x2=Xam(k2)*2
x3=Xam(k3)*3
x5=Xam(k5)*5
IF x2<=x3 AND x2<=x5 THEN Xam(J)=x2: k2=k2+l
IF x3<=x2 AND x3<=x5 THEN Xam(J)=x3: k3=k3+l
IF x5<=x2 AND x5<=x3 THEN Xam(J)=x5: k5=k5+l
PRINT USING "###:########## "; J,Xam(J) ;
REM Приостанов после заполнения экрана
IF J MOD 20=0 THEN INPUT A$
NEXT J
END
Программа 2_24.с (оптимальный вариант)
#include <stdio.h>
#include <conio.h>
main() (
long Xam[1000], н2, хЗ, к5;
int j;
int k2=0,k3=0,k5=0;
clrscr(); -
Xam[0]-l,-
printf("%4d %9d",l,l);
for(i=i; j<1000; j++) {
x2=Xam[k2]*2; x3=Xam[k3]*3;
x5=Xam[k5]*5;
if(x2<=x3 && x2<=x5)
{Xam[j]=x2; k2++;}
if(x3<=x2 && x3<=x5)
{Xam[j]=x3; k3++;}
if(x5<=x2 && x5<=x3)
{Xam[j]=x5; k5++;}
printf("\n%4d %91d",j,Xam[j]);
if(j % 20==0)
getch(); }
getch (); }
Программа 2_24.pas (оптимальный вариант)
program Hamming;
uses crt;
var
Xam:array[i..1000] of longint;
x2,x3,x5 : longint;
j:integer; const
k2:integer=l;
k3:integer=l;
k5:integer=l; begin
Xam[l]:=1;
writeln(l:4,' ',1:10);
for j:=2 to 1000 do
begin
x2:=Xam[k2]*2;
x3:=Xam[k3]*3;
x5:=Xam[k5]*5;
if(x2<=x3) and (x2<=x5) then
begin
Xam [ j ] : =x2;
inc(k2);
end;
if(x3<=x2) and (x3<=x5) then
begin Xam[j] := x3; inc(k3);
end;
if (x5 <= x2) and (x5 <= x3) then begin Xam[j] := x5; inc(k5);
end;
writeln(j:4,' ',Xam[j]:10);
if (j mod 20)= 0 then readln; end; readln; end.
Программа 2_24a.pas (полный перебор)
program Hammingl;
uses crt,WinDos;
var
j,Hour,Hourl,min,mini,sec,seel,hsec,hsecl : word; i,k : longint;
begin clrscr;
gettime(Hour,min,sec,hsec); i:=l; j:=0; repeat k:=i;
while (k mod 2)=0 do k:=k div 2;
while (k mod 3)=0 do k:=k div 3;
while (k mod 5)=0 do k:=k div 5;
if k=l then begin
{ write(i:10); } {отключение вывода } inc(j);
end; inc(i);
until j>=1000;
gettime (Hourl,min1,secl,hsecl) ;
writeln;
writeln(Hour,':',min,':',sec,'.',hsec);
writeln(Hourl,':',minl,':',seel,'.',hsecl); readln; end.
Задание 2.25. Генерация неправильно сокращаемых дробей
Существует очень мало правильных дробей вида m/n, которые можно привести к несократимой дроби "незаконным" образом — зачеркнув одинаковые цифры в числителе и знаменателе. Например:
26/65 = 2/5.
Построить программу, которая использует заданное ограничение (п < юо) и выводит все дроби, обладающие указанным выше свойством. Тривиальные дроби типа 10/20, 10/30, ... , 20/30, ... , 80/90, ... желательно не генерировать.
Совет 1 (общий)
Очевидно, что единственным вариантом является полный перебор правильных дробей от 10/11 до 98/99, в процессе которого надо сравнивать исходную дробь с четырьмя возможными отношениями старших и младших цифр. Так как программа оперирует с вещественными числами, необходимо избегать проверок на точное совпадение. Кроме того, следует обходить деление на ноль.
Программа 2_25.bas
RЕМ Генерация неправильно сокращаемых дробей CLS
RЕМ Двойной цикл по перебору дробей от 10/11 до 98/99
FOR N=11 TO 99 FOR M=10 TO N-1
T=M/N : ' Настоящее значение дроби
Nlo=N MOD 10 : ' Младшая цифра знаменателя
Nhi=(N-Nlo)\10 : ' Старшая цифра знаменателя
Mlo=M MOD 10 : ' Младшая цифра числителя
Mhi=(M-Mlo)\10 : ' Старшая цифра числителя
IF Mlo=0 THEN GOTO 100
КЕМ Анализ различных сочетаний "зачеркиваемых" цифр
IF Nlo=Mlo AND ABS(T-Mhi/Nhi)<.001 THEN PRINT M;"/";N;"=";Mhi;"/";Nhi
END IF
IF Nlo=Mhi AND ABS(T-Mlo/Nhi)<.001 THEN
PRINT M;"/";N;"=";Mlo;"/";Nhi
END IF
IF Nlo<>0 THEN
IF Nhi=Mlo AND ABS(T-Mhi/Nlo)<.001 THEN
PRINT M;"/";N;"=";Mhi;"/";Nlo
END IF
IF Nhi=Mhi AND ABS(T-M1o/Nlo)<.001 THEN
PRINT M;"/";N;"=";Mlo;"/";Nlo
END IF
END IF
100 :
NEXT M NEXT N END
Программа 2_25.с
/* Генерация неправильно сокращаемых дробей */
#include <stdio.h>
#include <conio.h>
#include <math.h>
main() !
char n,m,n_lo,n_hi,m_lo,m_hi; float t; clrscr();
/* Двойной цикл до перебору дробей от 10/11 до 98/99 */ for(n=ll; n<100; п++) for(m=10; m<n; m++) {
t=(float)m/n; /* Настоящее значение дроби */
n_lo=n % 10; /* Младшая цифра знаменателя */
n__hi=n/10; /* Старшая цифра знаменателя */
m_lo=m % 10; /* Младшая цифра числителя */
m_hi=m/10; /* Старшая цифра числителя */
if(m % 10 == 0) continue;
/* Анализ различных сочетаний "зачеркиваемых" цифр */
i£(n lo==m lo && n_hi!=0 && fabs (t-m_hi/n_hi) <1.e-3)
printf("\n%d/%d=ld/id",m,n,m_hi,n_hi);
if(n_lo==m_hi && n_hi!=0 && fabs(t-m_lo/n_hi)<le-3)
printf ("\n%d/%d=%d/%d",m,n,m_lo,n__ni) ;
i£(n hi==m_lo ££ n__lo!=0 ££ fabs(t-m_hi/n_lo)<le-3)
printf("\n%d/%d=ld/ld",m,n,m_hi,n_lo);
if(n_hi==m_hi && n_lo!=0 && fabs(t-m_lo/n_lo)<le-3)
printf("\n%d/%d=%d/%d",m,n,m_lo,n_lo); }
getch{); }
Программа 2_25.pas
program drobi;
{Генерация неправильно сокращаемых дробей}
uses crt;
var
n, m, n_lo, n__hi, m_lo, m_hi: byte ; t:real; begin
clrscr;
{ Двойной цикл по перебору дробей от 10/11 до 98/99 } for n:=ll to 99 do for m:=10 to n-1 do begin
t:=m/n; { Настоящее значение дроби }
n_lo:=n mod 10; { Младшая цифра знаменателя }
n_hi:=n div 10; { Старшая цифра знаменателя }
m_lo:=m mod 10; { Младшая цифра числителя }
m_hi:=m div 10; { Старшая цифра числителя }
if m_lo=0 then continue;
{ Анализ различных сочетаний "зачеркиваемых" цифр }
if (n_lo=m_lo) and (n_hi<>0) and (abs(t-m_hi/n_hi)<le-3) then
writeln(m:3, '/',n:2, ' = ',m_hi:l, '/',n_hi) ;
if (n_lo=m_hi) and (n_hi<>0) and (abs(t-m_lo/n_hi)<le-3) then
writeln(m:3,'/',n:2,'=',m_1о:1,'/',njii);
if (n_hi=m_lo) and (n_lo<>0) and (abs(t-m_hi/n_lo)<le-3) then
writeln(m:3,'/',n:2,'=',m_hi:l,'/',n_lo);
if (n_hi=m_hi) and (n__lo<>0) and (abs(t-m_lo/n_lo)<le-3) then
writeln(m:3,'/',n:2,'=',m_lo:1,'/',n_lo); end; readln;
end.
Задание 2.26. Разложение натурального числа на сумму квадратов
Утверждение, принадлежащее Лагранжу, гласит, что любое натуральное число N можно представить в виде суммы не более чем четырех квадратов целых чисел (N = а2 + ь2 + с2 + d2). Составить программу, которая вводит длинное целое число N и находит хотя бы одно такое разложение. О неединственности результата свидетельствует пример: 4 = 22 = 12 + 12 + 12 + 12. Кроме того, существует довольно много прямоугольных треугольников с целочисленными сторонами, которые позволяют заменить сумму квадратов катетов квадратом гипотенузы, а сумму четырех квадратов, следовательно, -суммой трех.
Совет 1 (общий)
Скорее всего, для решения этой задачи придется пойти на полный перебор. Оценку сверху на количество циклов, которое может потребоваться, легко получить, упорядочив слагаемые по величине:
a<b<c<d< sqrt(N).
Совет 2 (общий)
Решение может быть найдено и меньше чем за N2 циклов, если проанализировать четность N и в цикле шагать не через 1, а через 2. Так, например, если N нечетно, то среди искомых слагаемых имеется либо одно, либо три нечетных числа. Если N четно, то искомое разложение может состоять либо из четырех четных, либо из четырех нечетных, либо из двух четных и двух нечетных слагаемых. Цикл перебора можно оформить в виде подпрограммы, которой передаются начальные значения управляющих переменных. Подпрограмма PROBA получает 5 параметров, первые 4 из которых задают четность (параметр = 1) или нечетность (параметр = 0) подбираемых слагаемых а, Ь, с и d.
Программа 2_26.bas
RЕМ Разложение числа на сумму квадратов
DECLARE SUB PROBA (Al AS INTEGER, Bl AS INTEGER,Cl AS INTEGER, Dl AS INTEGER,N AS LONG)
INPUT "Введите натуральное число - ", N& IF (N& MOD 2)=0 THEN
REM Если число N - четное
PROBA 0,0,1,1,N&
PROBA 0, 0,0,0,N&
PROBA 1,1,1,1,N& ELBE
REM Если число N - нечетное
PROBA 0,0,0,1,N&
PROBA 0,1,1,1,N& END IF END
SUB PROBA (Al AS INTEGER,Bl AS INTEGER,Cl AS INTEGER,Dl AS INTEGER,N AS LONG)
'Подпрограмма перебора вариантов
Q=INT(SQR{N)+.5) : ' Определение верхней границы
FOR A%=A1 TO Q STEP 2: S1=A%*A%
FOR B%=B1 TO Q STEP 2: S2=S1+B%*B%
FOR C%=C1 TO Q STEP 2: S3=S2+C%*C%
FOR D%=D1 TO Q STEP 2
IF S3+D%*D%=N THEN
PRINT "Искомые слагаемые :"
PRINT "a=";A%,"b=";B%,"c=";C%,"d=";D% END
END IF
NEXT D%: NEXT C%: NEXT B%: NEXT A% END SUB
Программа 2_26.с
/* Разложение числа на сумму квадратов */
#include <math.h>
#include <stdlib.h>
#include <conio.h>
void proba(int al,int bl,int cl,int dl,long N) ;
main() {
long N;
printf("\n Введите натуральное число - ");
scanf("%ld",&N);
if((N % 2)==0) /* Если число N - четное */
{
proba(0,0,l,l,N);
proba(0,0,0,0,N);
proba(1,1,1,1,N) ;
}
else /* Если число N - нечетное */
{ proba(0,0,0,l,N);
proba(0,l,l,l,N); } }
void proba(int al,int bl,int cl,int dl,long N)
/* Подпрограмма перебора вариантов */
{ int a,b,c,d,q; long sl,s2,s3;
q=floor(sqrt(N)+.5);
/* Определение верхней границы */
for(a=al; a<=q; a+=2)
{
sl=a*a;
for(b=bl; b<=q; b+=2)
{
s2=sl+b*b;
for(c=cl; c<=q; c+=2)
{
s3=s2+c*c;
for(d=dl; d<=q; d+=2)
if (s3+d*d=N)
{
printf("\n Искомые слагаемые :");
printf("\n a=%d b=%d c=%d d=%d",a,b,c,d);
getch();
exit(0);
}
}
}
}
return;
}
Программа 2_26.pas
program razlojenie;
{ Разложение числаг на сумму квадратов }
var
N:longint;
procedure proba(al,bl,cl,d1:integer; N:longint);
{ Подпрограмма перебора вариантов }
var
a,b,с,d,q:integer; s1,s2,s3:longint;
begin
q:=round(sqrt(N)); {Определение верхней границы }
a:=al;
while a<=q do begin
sl:-a*a,- b:-b1;
while b<=q do begin s2:=sl+b*b;
c:=cl;
while c<=q do begin s3:=s2+c*c;
d:=dl; while d<=q do begin
if(s3+d*d=N) then begin
writeln('Искомые слагаемые :');
writeln('а=',а,' b=',b,' c=',c,' d=',d);
readln;
exit;
end;
inc(d,2);
end; inc(c,2);
end;
inc(b,2);
end; inc(a,2);
end;
end;
begin
write('Введите натуральное число - ');
readln(N);
if odd(N) then begin { Если число N - нечетное }
proba(0,0,0,l,N);
proba(0,1,1,1,N);
end else begin { Если число N - четное }
proba (0, 0,1,1,N) ;
proba(0,0,0,0,N);
proba(1,1,1,1,N);
end;
end.
Задание 2.27. Анализ взаимного расположения точки и треугольника
Составить программу, которая определяет, лежит ли точка с заданными координатами внутри или вне треугольника. В реальности возможны три ситуации: либо точка принадлежит хотя бы одной из сторон (то есть находится на границе треугольника), либо она расположена строго внутри, либо -строго снаружи.
Совет 1 (общий)
Воспользуемся известным фактом — любая прямая (А*х+в*у+с=о) делит плоскость на две полуплоскости. Все точки, принадлежащие одной полуплоскости, при подстановке в уравнение .прямой дают значение A*х+B*у+с одного знака, а все точки другой полуплоскости — значение противоположного знака.
Поэтому, если точка р находится строго внутри треугольника две, то точки А и Р находятся в одной полуплоскости по отношению к прямой вс, точки В И Р — в одной полуплоскости по отношению к прямой АС, точки С И Р - в одной полуплоскости по отношению к прямой АВ.
Программа 2_27.bas
RЕМ Анализ взаимного расположения точки и треугольника
DECLARE FUNCTION R!(U!,V!,Ul!,VI!,U2!,V2!)
PRINT "Введи координаты вершин треугольника"
INPUT X1,Y1,X2,Y2,X3,Y3
'XI = 0: Yl = 0: X2 = 0: Y2 = 1: X3 = 1: Y3 = 0
PRINT "Введи координаты точки" INPUT X, Y
IF (R(X,Y,X1,Y1,X2,Y2)*R(X3,Y3,XI,Yl,X2,Y2)>0 AND R(X,Y,X2,Y2,X3,Y3)*R(X1,Y1,X2,Y2,X3,Y3)>0
AND R(X,Y,X1,Y1,X3,Y3)*R(X2,Y2,X1,Y1,X3 Y3)>0) THEN
PRINT "Точка находится внутри треугольника": END
END IF
IF (R(X,Y,X1,Y1,X2,Y2)*R(X3,Y3,X1,Y1,X2,Y2)<0
OR R(X,Y,X2,Y2,X3,Y3)*R(X1,Y1,X2,Y2,X3,Y3)<0
OR R(X,Y,X1,Y1,X3,Y3) *R(X2,Y2,X1.,Y1,X3,Y3)<0) THEN
PRINT "Точка находится вне треугольника": END
END IF
PRINT "Точка принадлежит границе" END
FUNCTION R(U,V,U1,V1,U2,V2)
REM Анализ взаимного расположения точки (U,V) и прямой,
RЕМ проходящей через точки (U1,V1) и (U2,V2)
R=0
IF ((U-U1)*(V2-V1)-(V-V1)*(U2-U1)>0) THENR=1
IF ((U-U1)*(V2-V1)-(V-V1)*(U2-U1)<0) THENR=-1
END FUNCTION
Программа 2_27.c
/* Анализ взаимного расположения точки и треугольника */
#inclucte <stdio.h>
#include <iconic. h>
int r(float u,float v,float ul,float vl, float u2,float v2);
main () {
float X0,y0,xl,yl,x2,y2,x3,y3;
float k012,k012, k023, k123, K312, k213;
printf("\n Введи координаты вершин треугольника\п");
scanf("%f %f %f %f %f %f",&xl,&yl,&x2,&y2,&x3,&y3);
printf{"\n Введи координаты точки\n");
scanf("%f %f",&x0,&y0);
k012=r(x0,y0,xl,yl,x2,y2);
k312=r(x3,y3,xl,yl,x2,y2);
k023=r(x0,y0,x2,y2,x3,y3);
k123=r(xl,yl,x2,y2,x3,y3);
k013=r(x0,y0,xl,yl,x3,y3);
k213=r(x2,y2,xl,yl,x3,y3);
if(k012*k312>0 && k023*k!23>0 SS k013*k213>0)
{
puts("Точка находится внутри треугольника");
getch();
exit(0);
} if(k012*k312<0 || k023*k123<0 || k013*k213<0)
{
puts("Точка находится вне треугольника"); getch(); exit(O);
}
puts("Точка находится на границе"); getch();
}
/*--------------------------------------*/
int r(float u,float v,float ul,float vl, float u2,float v2)
/* Анализ взаимного расположения точки (U,V) и прямой,
проходящей через точки (U1,V1) и (U2,V2) */
{
float s=(u-ul)*(v2-vl)-(v-vl)*(u2-ul);
if(s>0) return 1;
if(s<0) return -1;
return 0;
}
Программа 2_27.pas
program triangle;
( Анализ взаимного расположения точки и треугольника }
var
Xd,y0,xl,yl,x2,y2,x3,y3:real;
k012, k312,k023,k!23,k013,k213:integer;
function r(u, v,ul, vl,u2, v2:real) :integer;
{ Анализ взаимного расположения точки (U,V) и прямой,
проходящей через точки (U1,V1) и (U2,V2) }
var
s:real;
begin
s: = (u-ul) * (v2-vl)- (v-vl) * (u2-ul) ;
r:=0;
if s>0 then r:=l;
if s<0 then r:=-l;
end;
begin
writeln('Введи координаты вершин треугольника');' readln(xl,yl,х2,у2,хЗ,уЗ);
writeln('Введи координаты точки');
readln(x,у);
k012:=r(x0,y0,xl,yl,x2,y2);
k312:=r(x3,y3,xl,yl,x2,y2);
k023:=r(х0,у0,х2,у2,хЗ,уЗ);
k123:=r(xl,yl,x2,y2,x3,y3);
k013:=r(x0,y0,xl,yl,x3,y3);
k213:=r(х2,у2,х!,у!,хЗ,уЗ);
if (k012*k312>0) and (k023*k!23>0) and (k013*k213>0) then begin
write('Точка находится внутри треугольника');
halt;
end;
if (k012*k312<0) or (k023*k!23<0) or (k013*k213<0) then begin
write ('Точка находится вне треугольника'),' halt; end;
write ('Точка в треугольнике' ) ;
readln;
end.
Задание 2.28. Игра на вычитание
И фа заключается в последовательном вычитании партнерами из числа, находящегося на кону. Программа генерирует случайное число от 50 до 100 и передает ход человеку. Ходят по очереди. За один ход можно вычесть из оставшегося числа от 1 до 9 (в общем случае от 1 до м). Побеждает тот, кто сумеет оставить на кону нулевое число.
Совет 1 (общий)
Если на кону находится число N+1, то любой ход приводит к выигрышу партнера. Поэтому выигрышная стратегия программы довольно проста. Если на кону находится число м > N+1, то своим ходом программа должна оставить на кону Ml = k* (N+1), т. е. вычесть из м остаток от деления м на N+I. Это не всегда удается, т. к. остаток может оказаться равным 0. Но если это удалось, то последующие ходы делаются по стандартной схеме: если противник вычитает q, то программа должна вычесть N+1-q. В итоге на кону останется N+1 и любой ход человека приведет к его поражению. В том случае, когда программа не может сделать выигрышный ход, надо вычесть 1 в надежде, что противник ошибется, и тогда можно будет перехватить инициативу.
Программа 2_28.bas
RЕМ Программа игры на вычитание до нуля
DEFINT A-Z
ml:
CLS
RANDOMIZE 32767
N = INT(RND * 25) + 26
PRINT "На кону - "; N; " Брать можно от 1 до 5"
m2:
PRINT "Ваш ход. Сколько берем? - "
m4:
a$ = INKEY$: IF a$ = "" THEN GOTO m4
k = ASC(a$) - 48
IF 1 > k OR k > 5 THEN PRINT "Так ходить нельзя!": GOTO m2
N = N - k
PRINT "После Вашего хода осталось "; N
IF N = О THEN PRINT "Поздравляю! Вы выиграли!": GOTO ex
IF (N MOD 6) = 0 THEN k = 1 ELSE k = N MOD 6
N = N - k
PRINT "Я беру "; k; " остается "; N
IF N = 0 THEN
PRINT "К сожалению, Вы проиграли!": GOTO ex
ELSE
GOTO m2
END IF ex: .,
PRINT "Хотите еще? - (y/n) : "
m3:
a$ = INKEY$: IF a$ = "" THEN GOTO m3
IF a$ = "y" THEN GOTO ml
END
Программа 2_28.с
// Программа игры на вычитание до нуля
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
void main() { int N,k,j; ml:
clrscr();
randomize();
N=random(25)+26;
printf("Ha кону - %d. Брать можно от 1 до 5.\n",N);
m2:
printf("\n Ваш ход. Сколько берем? - ");
k=getche()-48;
if(l>k || k>5) {printf("\n Так ходить нельзя!"); goto m2;}
N=N-k;
printf("\n После Вашего хода осталось %d",N);
if(N==0){printf("\n Поздравляю! Вы выиграли!");
goto ex;}
k=(N%6=0)?l:N%6;
printf ("\n Я беру %d остается %d",k,N);
if(N==0){printf("\n K сожалению, Вы проиграли!");
goto ex;
}
goto m2;
ex:
printf("ХпХотите еще? - (y/n) : ");
if(getch()=='y') goto ml; )
Программа 2_28.pas
{Программа игры на вычитание до нуля}
program game_mun;
uses Crt;
var
N,k,j:integer; label
ml,m2,ex;
begin ml:
clrscr;
randomize;
N:=random(25)+26;
writeln('Ha кону - ',N,'. Брать можно от 1 до 5.');
m2:
writeln('Bain ход. Сколько берем? - ');
k:=Ord(ReadKey)-48;
if(l>k) or (k>5) then
begin
writeln('TaK ходить нельзя!1); goto m2; end;
N:=N-k;
writeln('После Вашего хода осталось ',N);
if N=0 then
begin writeln('Поздравляю! Вы выиграли!'}; goto ex; end;
if (N mod 6)=0 then k:=l else k:=N mod 6;
N:=N-k;
writeln('Я беру ',k,' остается ',N);
if N=0 then
begin writeln('К сожалению, Вы проиграли!'); goto ex; end;
goto m2; ex:
writeln('Хотите еще? - (y/n) : ');
if ReadKey ='y' then goto ml; end.
Задания 2.29—2.31. Путешествия по узлам целочисленной решетки
Существует довольно много задач, связанных с нумерацией узлов целочисленной решетки. Одна из них известна под названием "скатерти Улама" (Ulam S. М.). Развлекаясь на одном из скучных собраний, Улам пронумеровал по спирали клетки тетрадного листа. Когда он обвел номера клеток, соответствующие простым числам, на листе возник довольно занятный узор, который мог оказаться полезным для изучения закономерностей в числовых последовательностях.
В отличие от Улама мы будем нумеровать не клеточки, а узлы целочисленной решетки, начав эту процедуру с нулевого узла в начале системы координат (рис. 2.1).
Рис. 2.1. Нумерация точек на скатерти Улама
Одна из наиболее естественных задач заключается в определении координат точки по ее номеру-ы. Конечно, ее решение можно построить на базе прямого перебора всех точек спирали до узла с заданным номером. При этом движение, начинающееся из точки с номером о, состоит сначала из одного шага вправо (x=x+1) и вверх (у=у+1), затем из двух последовательных шагов влево (x=x-1) и вниз (y=y-l), из трех последовательных шагов вправо и вверх, из четырех последовательных шагов влево и вниз и т. д. Однако при достаточно больших значениях N такая процедура требует слишком много времени.
Гораздо интереснее построить более эффективный алгоритм решения задачи. Одна из идей заключается в определении координат угловой точки, расположенной на общей с нашей точкой горизонтальной или вертикальной ветви спирали. Заметим, что на луче, проведенном из начала координат через узлы с координатами (-п,п), располагаются точки с номерами (2*n)2.
На аналогичном луче, начинающемся из первой точки и проходящем через точки с координатами (n,-n+l), расположены точки с номерами (2*n-1)2. Поэтому нам достаточно обнаружить ближайший к N квадрат числа q, чтобы сообразить, на какой из четырех ветвей "спирали" находится точка с номером N. Если q -- четное, то в зависимости от разности qz-N интересующая нас точка находится на верхней или левой ветви. И для вычисления координат достаточно откорректировать одну из координат угловой точки (-q,q) на величину разности. Если q оказалось нечетным, то ветвь, содержащая точку с номером N, находится либо внизу, либо справа. И тогда коррекции надо подвергнуть одну из координат точки (q, -q+1).
Аналогичная идея может быть использована и для обратного преобразования координат заданной точки (х,у) в ее порядковый номер на спирали. Достаточно определить ближайшую угловую точку с коррдинатами (-q, q) или (q,-q+i) и произвести соответствующую коррекцию номера выбранной угловой точки. Несмотря на кажущуюся простоту идеи, ее реализация требует тщательной проверки многочисленных условий.
Обратим внимание на то, что диагональ у=х делит узлы нашей решетки на два множества. Для всех узлов, расположенных слева от диагонали, выполняется условие х > у. Пусть точка с координатами (х,у) расположена либо на горизонтальном витке спирали, находящемся в левой полуплоскости, либо на вертикальном витке, спускающемся до диагонали. Разобьем каждый из этих витков на две части и пронумеруем их следующим образом:
1 — горизонтальный участок от диагонали до оси у;
2 — горизонтальный участок от оси у до угла поворота;
3 — вертикальный участок от угла поворота до оси х;
4 — вертикальный участок от оси х до очередного поворота.
На каждом из этих участков справедливы следующие соотношения:
1 : х > 0, у > 0
2 : х < 0, у > 0, у > |х|
3 : х < 0, у > 0, у < |х|
4 : х < 0, у < 0
Определив принадлежность точки тому или иному участку, мы можем вычислить координаты левого верхнего угла поворота (х0,у0) и найти порядковый номер этого узла N =(2*х0)2. Далее, в зависимости от номера участка, уже не сложно найти номер точки с координатами (х, у).
Аналогичные рассуждения можно повторить и для участков горизонтальной и вертикальной ветвей, принадлежащих правой полуплоскости. В программе для обозначения соответствующих четырех частей использованы буквенные обозначения — а, b, с, d. Для вычисления номера нашей точки здесь используется правый нижний угол поворота.
Ниже приводятся тексты программ, демонстрирующие работу процедуры
coord_to_n.
Программа 2_29.bas
DECiARF, FUNCTION CoordToN! (Х&, Y&)
REM Вычисление номера узла по его координатам
INPUT "Введите координаты узла : ", Х&, Y&
М& = CoordToN(X&, Y&)
PRINT "Его номер = "; М&
END
FUNCTION CoordToN (XS, Y&)
IF X& <= Y& THEN 'если узел в левой полуплоскости
IF Х& >= 0 AND Y& > 0 THEN GOTO m12 'случай 1
IF Х& < 0 AND Y& >= 0 AND Y& >= ABS(X&) THEN 'случай 2
'обработка случаев 1 и 2
m12:
х0 = -Y&: у0 = Y&: N& = (2 * х0)^2'координаты и номер угла
CoordToN = N& + х0 - Х&: EXIT FUNCTION
ELSE
'обработка случаев 3 и 4
х0 = Х&: у0 = -XS: N& = (2 * х0)^2 CoordToN = N& + у0 - Y&:
EXIT FUNCTION
END IF
END IF ' если узел в правой полуплоскости
IF X& <= 0 AND Y& < 0 THEN GOTO mab' случай а
IF x& >= 0 AND'YS < 0 AND ABS<Y&) >= х& THEN ' обработка случаев 2, 3
mab: х0 = -Y& +1: Y0 = -Y&: N& = (2 * хО - 1) ^2
CoordToN = N& - хО + Х&: EXIT
FUNCTION ELSE
'обработка случаев с, d
х0 = Х&: у0 = -Х& + 1: N& = (2 * х0 - 1) ^ 2
CoordToN = N& + YS - у0
END IF
END FUNCTION
Программа 2_29.с
#include <stdio.h>
#include <conio.h>
#include <math.h>
long sqr(long x);
long coord_to_n(long x,long y);
void main() { long x,y,M;
printf("\n Введите координаты узла : ");
scanf("%ld %ld",Sx,&y);
M=coord to n(x,y); .
printf("\nEro номер = %ld",M);
getch(); }
long coord_to_n(long x,long y) { long N,x0,y0;
if(x<=y) //если узел в левой полуплоскости
{
if(х>=0 && у>0) goto m12; //случай 1
if(x<0 && y>=0 && y>=labs(x)) //случай 2 {//обработка случаев 1 и 2
ml2:
х0=-у;
у0=у;
N=sqr(2*хО); //координаты и номер угла
return N+x0-x; } //обработка случаев 3 и 4
х0=х; у0=-х; N=sqr(2*х0); return N+y0-y; }
//если узел в правой полуплоскости
if(х<=0 && у<0) goto mab; //случай 2
if(x>=0 && у<0 && labs(y)>=x) //случай 3
{//обработка случаев 1, 2
mab:
x0=-y+l;
y0=-y;
N=sqr(2*х0-1);
return N-x0+x;
} //обработка случаев 3, 4
х0=х;
у0=-х+1;
N=sqr(2*x0-l);
return N+y-y0;
long sqr(long x)
{ return x*x; }
Программа 2_29.pas
program CoordToN;
var
M,x,y:longint;
function coord_to_n(x,y:longint}:longint;
var
x0,y0,N:longint;
label ml2,mab;
begin
if x<=y then { если узел в левой полуплоскости }
begin if (x>=0) and(y>0) then goto m!2; { случай 1 }
if (x<0) and (y>=0) and (y>=abs(x)) then { случай 2 }
begin { обработка случаев 1 и 2 }
m12: x0:=-y; y0:=y; N:=sqr(2*x0); { координаты и номер угла }
coord__to_n: = N+xO-x; exit;
end;
{ обработка случаев З и 4 }
x0:=x; y0:=-x; N:=sqr(2*x0);
coord_to_n:=N+yO-y; exit;
end;
{ если узел в правой полуплоскости }
if(x<=0) and (y<0) then goto mab; { случай а }
if(x>=0) and (y<0) and (abs(y)>=x) then { случай b }
begin { обработка случаев a, b }
mab: x0:=-y+l; y0:=-y; N:=sqr(2*x0-l);
coord_to_n:= N-xO+x; exit;
end;
{ обработка случаев с, d }
x0:=x; y0:=-x+l; N:=sqr(2*xO-l);
coord_to_n:= N+y-y0;
end;
begin
writeln('Введите координаты узла : ');
readln(x,у);
м.=toord_to_a(x,y);
writeln ('Его номер = \М);
readln;
end.
На базе процедур n_to_coord и coord_to_N можно построить Программы определения расстояния между точками с номерами NI и N2, нахождения номеров ближайших соседей к точке с номером N и т. п.
Программа 2_30.bas
DECLARE SUB NtoCoord (N&, X&, YS)
REM Определение расстояния между двумя точками
INPUT "Введи номер первой точки : ", N&
NtoCoord N&, xl&, yl&
INPUT "Введи номер второй точки : ", N&
NtoCoord N&, х2&, у2&
dx = xl& - x2&: dy = yl& - у2&
PRINT "Расстояние = ", SQR(dx * dx + dy * dy)
END
SUB NtoCoord (N&, X&, Y&)
DIM k AS LONG, j AS LONG, d AS LONG
j = INT(SQR(N&) + .5)
d = N& - j * j
k = j \ 2
PRINT "j="; j, "d="; d, "k="; k
IF (j MOD 2) О О THEN
IF d > 0 THEN
X& = k + 1: Y& = -k + d
ELSE
X& = k + 1 + d: Y& = -k
END IF
ELSE
IF d > 0 THEN
X& = -k: Y& = k - d
ELSE
X& = -k - d: Y& = k
END IF
END IF
END SUB
Программа 2_30.с
//Определение расстояния между двумя точками
#include <stdio.h>
#include <conio.h>
#include <math.h>
void n_to_coord(long N, long *x, long *y) ;
void main()
{ long N,xl,yl,x2,y2;
double dx,dy;
printf("\ n Введи номер первой точки : ");
scanf("%ld",&N);
n_to_coord(N,Sxl, &yl);
printf("\n Введи номер второй точки : ");
scanf("%ld",&N);
n_to_coord(N,&x2,&y2) ;
dx=xl-x2; dy=yl-y2;
printf("ХпРасстояние = %f",sqrt(dx*dx + dy*dy));
getch();
}
void n_to_coord(long N,long *x,long *y)
( long k,j,d;
j=floor(sqrt(N)+0.5);
d=N-j*j; k=j / 2;
if (j % 2)
{ if (d>0) { *x=k+l; *y=-k+d; }
else { *x=k+l+d;*y=-k; }
}
else
{ if (d>0) { *x=-k; *y=k-d; }
else { *x=-k-d; *y=k; )
}
}
Программа 2_30.pas
program spiral;
var
xl, yl, x2,y2,N:longint;
dx, dy: double;
procedure n_to_coord(N:longint;var x,y:longint);
var
k,j,d:longint;
begin
}
j:=trunc(sqrt(NJ+0.5) ;
d:=N-sqr(j);
k:=j div 2; if odd(j) then begin
if d>0 then begin x:=k+l; y:=-k+d;end else begin x:=k+l+d; y:=-k; end; end
else if d>0 then begin x:=-k; y:=k-d; end else begin x:=-k-d; y:=k; end; end; begin
writeln('Введи номер первой точки'); readln(N);
n_to_coord(N,xl,yl); writeln('Введи номер второй точки'); readln(N);
n_to_coord(N,x2, у2); dx:=xl-x2; dy:=yl-y2;
writeln('Расстояние = ',sqrt(dx*dx + dy*dy)); readln; end.
Вторая схема нумерации точек целочисленной решетки, расположенной в первом квадранте, заключается в движении по раскручивающейся диагонали (рис. 2.2).
Рис. 2.2. Нумерация точек по диагоналям
Базовые процедуры по-прежнему сводятся к установлению прямых и обратных связей между координатами точек и их номерами. Пронумеруем диагонали в порядке их удаления от начала координат — 0, 1, 2, ... В качестве нулевой "диагонали" будем рассматривать вырожденный отрезок, содержащий единственную точку с номером о. Очевидно, что на диагонали с номером k находится k+1 точек, удовлетворяющих уравнению соответствующей прямой:
X + у = b(k) .
Заметим, что на диагоналях с нечетными номерами номера точек возрастают при движении снизу вверх, а на диагоналях с четными номерами — при движении сверху вниз. Осталось только подметить закономерность между номером диагонали, ее уравнением и координатами начальной и конечной точек. В этом нам поможет анализ табл. 2.1.
Таблица 2.1. Закономерность между номером диагонали, ее уравнением
и координатами начальной и конечной точек
Номер Номера начальной диагонали и конечной точек |
Координаты начальной и конечной точек |
|||||
nDiag |
nBeg |
nEnd |
(хВеg, уВеg) |
(xEnd, yEnd) |
||
0 |
0 |
0 |
(0,0) |
(0,0) |
||
1 |
1 |
2 |
(1,0) |
(0,1) |
||
2 |
3 |
5 |
(0,2) |
(2,0) |
||
3 |
6 |
9 |
(3,0) |
(0,3) |
||
4 |
10 |
14 |
(0,4) |
(4,0) |
||
5 |
15 |
20 |
(5,0) |
(0,5) |
||
6 |
21 |
27 |
(0,6) |
(6,0) |
||
7 |
28 |
35 |
(7,0) |
(0,7) |
||
8 |
36 |
44 |
(0,8) |
(8,0) |
||
9 |
45 |
54 |
(9,0) |
(0,9) |
||
10 |
55 |
65 |
(0,10) |
(10,0) |
||
По содержимому табл. 2.1 можно установить следующие факты:
nEnd = nBeg + nDiag nBeg(nDiag-t-l) = nEnd(nDiag) + 1
Если nDiag -- четное, то координаты начальной точки — (0, nDiag) и при перемещении по диагонали х уменьшается, а у возрастает. В противном случае начальная точка имеет координаты (noiag, 0) и при перемещении по диагонали х возрастает, а у уменьшается.
Ниже приводятся тексты программ, определяющие номера смежных точек для точки с заданным номером. Для уменьшения количества передаваемых параметров характеристики диагонали вынесены в глобальные переменные.
Массивы dx и dy предназначены для определения координат смежных точек, количество которых может варьироваться от трех (для точки с номером 0) или пяти (для начальной и конечной точек диагонали) до восьми (в случае внутренних точек). Анализ того, какая из восьми возможных точек является допустимой, вынесен в процедуру nextpoint. Она проверяет принадлежность точки первому квадранту, восстанавливает по координатам порядковый номер допустимой точки и выводит его на экран.
Программа 2_31.bas
DECLARE SUB NEXTPOINT (X&, Y&)
DIM nDiag AS LONG 'номер диагонали
DIM xBeg AS LONG, yBeg AS LONG 'начальная точка на диагонали
DIM nPoints AS LONG 'количество точек на диагонали
DIM nBeg AS LONG 'номер начальной точки на диагонали
DIM dx(8), dy(S)
DATA -1,-1,-1,0,1,1/ 1/ 0
FOR J = 0 TO 7: READ dx(J): NEXT J
DATA -1, 0, 1,1,1,0,-1,-1
FOR J = 0 TO 7: READ dy(J): NEXT J
ml:
CLS
INPUT "Введите номер точки: ", N КЕМ Определение параметров диагонали
nBeg = 0: М = N: хВед = 0: уВеg = 0
FOR k = 0 ТО 200000000
М = М - k
IF М < 0 THEN EXIT FOR
nBeg = nBeg + k NEXT k
nDiag = k - 1: nPoints = k
IF (nDiag MOD 2) = 0 THEN 'диагональ с четным номером
yBeg = nDiag xN = xBeg + (N - nBeg) yN = yBeg - (N - nBeg)
ELSE 'диагональ с нечетным номером
xBeg = nDiag
xN = xBeg - (N - nBeg)
yN = yBeg + (N - nBeg) END IF
PRINT "Номера смежных точек: "
FOR k = 0 ТО 7
X& = xN + dx(k)
Y& = yN + dy(k)
NEXTPOINT X&, Y& NEXT k PRINT
PRINT "Хотите повторить - (y/n) : ";
m2:
A$ = INKEY$: IF A$ = "" THEN GOTO m2
IF A$ = "y" THEN GOTO m1 END
SUB NEXTPOINT (X&, Y&)
SHARED nDiag AS LONG, xBeg AS LONG, yBeg AS LONG, nPoints AS LONG
DIM N AS LONG, J AS INTEGER
IF X& < 0 OR Y& < 0 THEN EXIT SUB
nBeg = 0: nDiag = X& + Y&: xBeg = yBeg = 0
nPoints = nDiag + 1
FOR J = 0 TO nPoints - 1: nBeg = nBeg + J: NEXT J
IF (nDiag MOD 2) = 0 THEN
yBeg = nDiag: N = nBeg + yBeg - Y&
ELSE
xBeg = nDiag: N = nBeg + xBeg - X&
END IF
PRINT N; ",";
END SUB
Программа 2_31.с
#include <stdio.h>
#include <conio.h>
void nextpoint(long x,long y) ;
int dx[8]={-l,-l,-l,0,l,l, 1, 0};
int dy[8]={-l, 0, 1,1,1,0,-!,-!};
long nDiag, //номер диагонали
xBeg,yBeg, //координаты начальной точки на диагонали nPoints, //количество точек на диагонали nBeg; //номер начальной точки на диагонали
void main() { long N,M,
xN,yN, //координаты введенной точки x,y,k; clrscr (); m:
printf("\n\n Введите номер точки: "} ; scanf("%ld",&N);
//Определение параметров диагонали nBeg=0; M=N;
хВед=уВед=0;
for(k=0;;k++)
{ M=M-k;
if(M<0) break; nBeg=nBeg+k; }
nDiag=k-l; nPoints=k;
if((nDiag % 2)=0) //диагональ с четным номером { yBeg= nDiag; xN=xBeg+(N-nBeg); yN=yBeg-(N-nBeg);
)
else //диагональ с нечетным номером
{ xBeg=nDiag;
xN=xBeg-(N-nBeg); yN=yBeg+(N-nBeg); }
printf("Номера смежных точек:\n" ); for(k=0;k<8;k++) { x = xN + dx[k]; у = yN + dy [k] ; nextpoint(x,y); }
printf("ХпХотите повторить - (y/n) : ");
if(getch(}=='у'} goto m; }
void nextpoint(long x,long y) { long N;
if(x<0||y<0) return;
nBeg=0;
nDiag=x+y;
xBeg=yBeg=0;
nPoints=nDiag+l;
for fint j=0;j<nPoints;j++) nBeg+=j;
if((nDiag % 2)==0) { yBeg=nDiag; N=nBeg+yBeg-y;}
else { xBeg=nDiag; N=nBeg+xBeg-x; }
printf("%ld ",N);
}
Программа 2_31.pas
program spirall;
uses Crt;
label ml;
var
nDiag, {номер диагонали}
xBeg,yBeg, ( координаты начальной точки на диагонали}
nPoints, {количество точек на диагонали}
nBeg, {номер начальной точки на диагонали}
N,M,
xN,yN, {координаты введенной точки}
х,у,k: longint; const
dx : array [0..7] of integer =(-1,-1,-1,0,1,1, 1, 0);
dy : array [0..7] of integer =(-1, 0, 1,1,1,0,-1,-1);
procedure nextpoint(x,y:longint);
var
N: longint ,-
j. integer/" begin
if (x>=0)and(y>=0) then
bey in nBeg:=0; nDiag;=x+y; xseg:=0;
yBeg:=0;
nPoints:=nDiag+l;
for j:=0 to nPoints-1 do nBeg:=nBeg+j;
if ((nDiag mod 2)=0) then begin
yBeg:=nDiag; N:=nBeg+yBeg-y; end
else begin
xBeg:=nDiag; N:=nBeg+xBeg-x; end;
write(N,', ') ; end; end; begin
clrscr; ml:
write('Введите номер точки: ');
readln(N);
{ Определение параметров диагонали }
nBeg:=0;
M:=N;
xBeg:=0;
yBeg:=0;
k:=0;
repeat
M:=M-k;
if M<0 then break; nBeg:---nBeg+k;
k:=k+l;
until k<0; nDiag:=k-l;
nPoints:=k;
if(nDiag mod 2)=0 then {диагональ с четным номером} begin
yBeg:=nDiag;
xN:=xBeg+(N-nBeg) ;
yN:=yBeg-(N-nBeg) ; end
else {диагональ с нечетным номером}
begin
xBeg:=nDiag; xN:=xBeg-(N-nBeg) ;
yN:=yBeg+(N-nBeg) ; end;
writeln('Номера смежных точек : ');
for k:=0 to 7 do begin
x := xN + dx[k]; у := yN + dy[k]; nextpoint(x,y);
end;
writeln;
writeln('Хотите повторить - (y/n) : ');
if ReadKey='y' then goto ml;
end.