Вы - -й посетитель этой странички
Основы программирования
С.М. Окулов,
г.Киров
Занятие 12.
План занятия:
- описание функций;
- несколько фактов из комбинаторики;
- экспериментальная работа с программами вычисления числа сочетаний из n по m; получения палиндрома числа путем последовательного его “перевертывания” и сложений; нахождения общей части n отрезков;
- выполнение самостоятельной работы.
Описание функций
Функции, как и процедуры,
используются для структурирования
программ. В некоторых языках вообще нет
разделения подпрограмм на процедуры и
функции. В Паскале такое разделение есть, но
носит оно преимущественно синтаксический
характер. Заголовок функции начинается с
ключевого слова Function и кончается типом
возвращаемого данной функцией значения.
Function <имя функции>
[(<список
параметров>)]:<тип результата>;
В теле функции обязательно должен быть хотя бы один оператор присваивания, где в левой части стоит имя функции, а в правой — ее значение. Иначе значение функции не будет определено.
Несколько фактов из комбинаторики
Перестановки
Перестановкой из n
элементов назовем упорядоченный набор из n
различных натуральных чисел, принадлежащих
отрезку от 1 до n. Пусть n равно 3. Перечислим
все перестановки из 3 элементов: (1 2 3), (1 3 2), (2
1 3), (2 3 1), (3 1 2), (3 2 1). Количество перестановок
из n элементов (обозначается как P(n))
равно n! = 1•2•...•n (факториалу числа
n). Действительно, есть n способов для
выбора элемента на первое место, (n—1)
способ для выбора элемента на второе место
и т.д. Общее количество способов — это
произведение n•(n—1)•(n—2)•...•2•1.
Перечислите все перестановки из 4
элементов. Количество перестановок — 24.
Подсчитайте вручную значения n! для n
из диапазона от 5 до 12.
Размещения с повторениями
Пусть имеется множество
элементов, относящихся к n различным
видам. Из них составляются всевозможные
наборы по m элементов в каждом. При этом
в наборы могут входить и элементы одного
вида. Два набора считаются различными, если
они отличаются друг от друга или входящими
в них элементами, или их порядком. Пусть n
= 3, m = 2 (как обычно, рассматриваем
натуральные числа). Размещения с повторениями:
(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3).
Число таких наборов R(n, m) = nm
. Это доказывается методом математической
индукции.
Перечислите все размещения с
повторениями при n = 4 и m = 2.
Подсчитайте вручную значение R(n, m)
для нескольких значений n и m.
Размещения без повторений
Размещением без повторений из n
элементов по m называется упорядоченный
набор из m различных чисел,
принадлежащих отрезку от 1 до n. Два
набора считаются различными, если они
отличаются составом или порядком элементов.
Пусть n = 4, m = 2. Перечислим все
размещения без повторений: (1, 2), (2, 1), (1, 3), (3,
1), (1, 4), (4, 1), (2, 3), (3, 2), (2, 4), (4, 2), (3, 4), (4, 3).
Количество размещений A(n, m)
вычисляется по формуле:
A(n, m) = n•(n
— 1)•…•(n — m + 1) или
A(n, m) = n!
/ (n-m)!.
Перечислите все размещения из 5 элементов по 2. Подсчитайте вручную значения A(n, m) для фиксированного значения m, например 4, и n из интервала от 8 до 13.
Сочетания
Сочетанием из n элементов по m
называется набор из m различных чисел,
выбранных из диапазона от 1 до n. Наборы
различаются лишь составом элементов,
порядок элементов роли не играет. Пусть n
= 5, m = 3. Перечислим все сочетания: (1, 2, 3),
(1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5), (1, 4, 5), (2, 3, 4), (2, 3, 5), (2,
4, 5), (3, 4, 5). Каждому сочетанию соответствует m!
размещений. Обозначим число сочетаний C(n,
m). Таким образом,
A(n, m) = C(n,
m)•m! или
C(n, m) = A(n,
m) / m! = n! / m!(n - m)!.
Перечислите все сочетания из 7
элементов по 5. Подсчитайте число сочетаний
для различных значений n и m (не очень
больших). При подсчете как числа сочетаний,
так и числа размещений используется операция
деления. Обоснуйте, почему в результате
получаются только целые числа.
Экспериментальный раздел занятия
1. Составить программу
подсчета числа сочетаний — C(n, m).
Пусть у нас есть функция подсчета факториала
числа — Fact(n). Написание основной программы
сводится к программированию формулы
C(n, m) = n!
/ m!(n - m)!.
Program My12_1;
Var n,m: Integer;
c: Longint;
Function Fact(n:Integer):Longint;
Begin
End;
Begin
WriteLn('Введите n и m :');
ReadLn(n,m);
c:=Fact(n) / (Fact(m) * Fact(n-m));
WriteLn(c);
Readln
End.
При компиляции этой программы (функцию Fact
мы пока не написали, но компилироваться
программа должна и без нее) в строке
c:=Fact(n) / (Fact(m) * Fact(n-m));
возникает ошибка — Error 26: Type mismatch. —
несоответствие типов. Вспомним, что типы
данных определяют не только диапазон
значений переменных, но и допустимые над
этим типом операции. Результат операции
деления “/” нельзя присваивать переменным
целого типа. Необходимо использовать
операцию Div, т.е. соответствующая строка
должна выглядеть следующим образом:
c:=Fact(n) Div (Fact(m) * Fact(n-m));
Дополняем программу.
Function Fact(n:Integer):Longint;
Var i: Integer;
rez: Longint;
Begin
rez:=1;
For i:=1 To n Do rez:=rez*i;
Fact:=rez
End;
Запускаем программу для
различных значений n и m. При n = 10, m
= 5 результат 252 не вызывает сомнений. При n
= 13, m = 5 результат 399, что-то не так...
должно получиться большее значение. При n
= 15, m = 7 результат 9, теперь уже видно, что
программа не работает. Объясните причину.
Подсказка содержится в материале этого
занятия.
А можно ли упростить программу?
Попробуйте убрать переменную c.
Сколько лишних вычислений (умножений)
делает наша программа? Подсчитаем
C(13, 5) = (1•2•3•4•5•6•7•8•9•10•11•12•13) :
((1•2•3•4•5)•(1•2•3•4•5•6•7•8)).
Вычисляя значение С(13, 5)
вручную, мы, конечно, не будем выполнять все
эти умножения. Достаточно вычислить 9•11•13 =
1287. Напишите более эффективную функцию
вычисления числа сочетаний. Например, даже
нижеприведенный вариант программы даст
более обнадеживающие результаты.
Правильный результат при C(13, 5), а именно
1287, уже шаг вперед, но это не предел
улучшений программы.
Program My12_1m;
Var n,m:Integer;
Function S(n,m:Integer):LongInt;
Var i:Integer;
rez,cht:LongInt;
Begin
rez:=1;cht:=1;
For i:=1 To m
Do Begin
rez:=rez*i;
cht:=cht*(n-i+1)
end;
S:=cht Div
rez
End;
Begin
WriteLn('Введите
два числа: ');
ReadLn(n,m);
WriteLn(S(n,m));
ReadLn
End.
На восьмом занятии мы познакомились с основной теоремой арифметики. Пусть нам не требуется вычислять C(n, m), а необходимо представить это значение в виде p1a1 p2a2 - pgag, где ai ³ 0, а pi — простые числа. Ограничения: 1 < n £ 100, 1 < m < n. Вычислять “в лоб” — безнадежное занятие. Числа получаются огромные. Рассмотрим идею решения на примере. Вычисляем C(8, 5) = 8! / (5!•3!). Представим 8! как единицы в соответствующем массиве, например, с именем Sn, Sn[i] = 1, если число i есть в произведении. Просматриваем массив Sn, выбираем очередной элемент. Если число i составное, то находим его разложение на простые сомножители. К элементам Sn, соответствующим сомножителям, прибавляем количество единиц, равное числу их вхождений в i, а Sn[i] обнуляем. Выполним эти действия для n!, m! и (n—m)!. После этого останется только провести вычитание степеней одного и того же простого числа.
Числа \ i |
2 | 3 | 4 | 5 | 6 | 7 | 8 | |
Sn | 8! | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
4 | 3 | 1 | 0 | 1 | 1 | 1 | 1 | |
6 | 4 | 2 | 0 | 1 | 0 | 1 | 1 | |
8 | 7 | 2 | 0 | 1 | 0 | 1 | 0 | |
Sm | 5! | 1 | 1 | 1 | 1 | 0 | 0 | 0 |
4 | 3 | 1 | 0 | 1 | 0 | 0 | 0 | |
Snm | 3! | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
Результат вычисления | 3 | 0 | 0 | 0 | 0 | 1 | 0 |
Итак, C(8, 5) = 23•71.
Program My12_2;
Const Q=100;
Type MyArray=Array[1..Q] Of Integer;
Var n,m:Integer;
P:MyArray;
Procedure Solve(sn,sm:Integer; Var Sp:MyArray);
Begin
End;
Procedure Print(pn:Integer;Pp:MyArray);
Var i:Integer;
Begin
For i:=2 To pn Do
If Pp[i]<>0 Then WriteLn(i:5,Pp[i]:5)
End;
Begin
WriteLn('Ввод чисел n и m');
ReadLn(n,m);
Solve(n,m,P);
Print(n,P);
ReadLn
End.
Пока у нас есть только основная программа, содержащая вызовы процедур, и простая процедура Print. Программа компилируется. Уточним процедуру Solve.
Procedure Solve(sn,sm:Integer; Var Sp:MyArray);
Var i:Integer;
Sni,Smi,Snmi:MyArray;
Begin
Calc(sn,Sni);
{Сформировали массив, соответствующий n!}
Calc(sm,Smi);
{Сформировали массив, соответствующий m!}
Calc(sn-sm,Snmi);
{Сформировали массив, соответствующий (n-m)!}
For i:=2 To sn Do Sp[i]:=Sni[i]-Smi[i]-Snmi[i]
End;
Сделав “заглушку” из процедуры Calc, содержащую только Begin и End, вы получите компилируемую программу. Вариант процедуры Calc, приведенный ниже, хотя и работает, но не очень хорошо читается. Выполните трассировку этой процедуры в режиме отладки, определите назначение каждого оператора. Ваша задача — улучшить процедуру. Вспомните о том, что в материале восьмого занятия перечислены все простые числа до 100.
Procedure Calc (n:Integer; Var X:MyArray);
Var i,j,t:Integer;
Begin
FillChar(X,SizeOf(X),0);
{SizeOf – вычисляет размер области памяти,
выделенной для массива X; FillChar записывает нулевые значения
в эту область памяти.}
For i:=1 To n Do X[i]:=1;
{Признаки чисел, задействованных
в вычислении факториала числа.}
For i:=4 To n Do Begin
t:=i;
j:=2;
While (j<=(t div 2)) Do
If t Mod j=0 Then Begin
{j делит t без остатка.}
Inc(X[j]); t:=t div j
If t=j Then Inc(X[j])
End
Else Inc(j);
If t<>i Then Begin
{Найдены делители числа i.}
X[i]:=0;If (t<>j) Then
Inc(X[t])
End
End
End;
Попробуем запустить эту программу при больших значениях n. Пусть n £ 10 000. Даже если изменить тип Integer на LongInt, появится ошибка Error 202: Stack oferflow error. Нам не хватает оперативной памяти для хранения данных программы. Действительно, массивов введено много. Попробуем обойтись одним глобальным массивом.
{$R+}
Program My12_2m;
Const Q=10000;
Type MyArray=Array[1..Q] Of Integer;
Var n,m:LongInt;
P:MyArray;
Procedure Inc_0(n:LongInt);
Var i,j,t:Integer;
Begin
For i:=2 To n Do Begin
t:=i; j:=2;
While (j<=(t div 2)) Do
If t Mod j=0 Then Begin
Inc(P[j]);
t:=t div j;
If t=j Then Inc(P[j])
End
Else Inc(j);
If t<>i Then Begin
If (t<>j) Then Inc(P[t])
End
Else P[i]:=1
End
End;
Procedure Dec_0(n:LongInt);
Var i,j,t:Integer;
Begin
For i:=2 To n Do Begin
t:=i; j:=2;
While (j<=(t Div 2)) Do
If t Mod j=0 Then Begin
Dec(P[j]);
t:=t div j;
If t=j Then Dec(P[j])
End
Else Inc(j);
If t<>i Then Begin
If (t<>j) Then Dec(P[t])
End
Else Dec(P[i])
End
End;
Begin
WriteLn('Ввод чисел n и m'); ReadLn(n,m);
Inc_0(n);
Dec_0(m)
Dec_0(n-m)
End.
Мы видим, что код процедур Inc_0 и Dec_0 почти повторяется. Это плохо. Попробуйте
написать программу изящнее. Кстати, значение константы
Q
можно увеличить, например, до 30 000. Учтите, что для
больших значений n и m программа может работать
довольно долго.
2. В задании № 11 седьмого занятия
требовалось определить, является ли введенное число
палиндромом.
Оформим “перевертывание” числа в виде
функции.
Function Pal(n:LongInt):LongInt;
Var x:LongInt;
Begin
x:=0;
While n<>0 Do Begin
x:=x*10+n Mod 10;
n:=n Div 10
End;
Pal:=x
End;
Пусть n = 59. “Перевернем” его, получим 95. Найдем сумму чисел 59 и 95, получим 154. “Перевернем” это число, получим 451. Находим сумму: 605. Еще раз: 605 + 506 = 1111. Получили палиндром. Найдите для всех натуральных чисел из интервала от 50 до 80 количество шагов, необходимых для “сведения” их к палиндромам, с помощью описанной схемы.
Program My12_3;
Const a=50;b=80;
Var n,t:LongInt;
cnt,i:Integer;
Function Pal(n:LongInt):LongInt;
Begin
…
End;
Begin
For i:=a To b Do Begin
cnt:=0;n:=i;
While Pal(n)<>n Do Begin
t:=Pal(n); n:=n+t;
Write(t,' ',n,' ');
Inc(cnt)
End;
WriteLn(i,' ',cnt)
End
End.
Попробуйте получить палиндром из числа 89. На 16-м шаге результат по-прежнему не является палиндромом, а сумма выходит за пределы типа LongInt. Изменим программу, используя четвертый пример из раздела экспериментальной работы десятого занятия. Повторите материал о работе с большими числами. Для решения этой задачи потребуется их складывать и “перевертывать”.
{$R+}
Program My12_3m;
Uses Crt;
Const MaxN=1000;
Type MyArray=Array[0..MaxN+1] Of Integer;
{Числа храним в массиве, они большие
и типа LongInt не хватает.}
Var A,Z:MyArray;
cnt,n:Integer;
Function Eq:Boolean;
{Является ли число палиндромом?}
Var i:Integer;
Begin
i:=1;
While (i<=A[0] div 2) And (A[i]=A[A[0]-i+1]) Do
Inc(i);
Eq:=(i>A[0] div 2)
End;
Procedure Swap(Var a,b:Integer);
Var c:Integer;
Begin c:=a;a:=b;b:=c End;
Procedure Rev;{ "Перевертываем" число.}
Var i:Integer;
Begin
For i:=1 To A[0] Div 2 Do
Swap(A[i],A[A[0]-i+1])
End;
Procedure Change(n:Integer);
{Записываем число в массив.}
Var i:Integer;
Begin
i:=1;
While n<>0 Do Begin
A[i]:=n Mod 10; n:=n Div 10;
Inc(A[0]);Inc(i)
End
End;
Procedure Add;
{Складываем два больших числа.}
Var i,r,w:Integer;
Begin
i:=1;r:=0;
While (i<=A[0]) Or (r<>0) Do Begin
w:=A[i]+Z[i]+r; A[i]:=w Mod 10;
r:= w Div 10;
If A[A[0]+1]<>0 Then Inc(A[0]);
Inc(i)
End
End;
Begin
ClrScr; n:=100;
While (n<1000) Do Begin
{Исследуем диапазон от 100 до 999.}
FillChar(A,SizeOf(A),0);
Change(n);{Преобразуем число.}
cnt:=0;
{Счетчик количества шагов –
преобразований числа.}
While (Not Eq) And (A[0]<=MaxN) Do Begin
{Пока не палиндром и нет выхода за
пределы массива A, считаем.}
Z:=A;Inc(cnt);Rev;Add
End;
WriteLn(n,' ',cnt);
{Вывод числа и количества шагов.}
If cnt>=MaxN Then ReadLn;
{Фиксируем факт выхода за пределы массива
A.}
Inc(n)
End
End.
Для чисел 196, 295, 394, 493, 592, 689, 691, 788, 790, 879, 887, 978, 986 массива из 1000 элементов недостаточно для хранения результата. А
всегда ли конечен описанный процесс, всегда ли можно
получить палиндром?
&nbs sp; End;
Рассмотрим следующую задачу. Дано n отрезков, длины которых целые числа. Берем два отрезка и из большего вычитаем меньший. Разность — новый отрезок. Если же длины отрезков совпадают, то один из них исключаем из дальнейшего рассмотрения. Продолжаем процесс. В результате получается одно число, равное длине оставшегося отрезка. Пример: n = 4, длины отрезков: 6, 15, 3, 9.
Номер шага | Длины отрезков | |||
6 | 15 | 3 | 9 | |
1-й шаг | 6 | 9 | 3 | 9 |
2-й шаг | 6 | 3 | 3 | 9 |
3-й шаг | 3 | 3 | 3 | 9 |
4-й шаг | - | 3 | 3 | 9 |
5-й шаг | - | - | 3 | 9 |
6-й шаг | - | - | 3 | 6 |
7-й шаг | - | - | 3 | 3 |
8-й шаг | - | - | - | 3 |
Решать задачу “в лоб” по приведенной схеме можно, но для больших значений n это требует значительных временных затрат. На самом деле все сводится к нахождению наибольшего общего делителя n чисел, а вычитания выполнять не требуется. Напишите соответствующую программу.
Задания для самостоятельной работы
1. Дано n целых чисел. Найти среди них число,
у которого приведенная ниже характеристика
имеет максимальное значение:
·
сумма цифр;
·
первая цифра;
·
количество делителей;
·
сумма всех делителей.
2. Дано n целых чисел. Найти среди них пару
чисел, для которых выполняется приведенное ниже
условие:
·
наибольший общий делитель имеет
максимальное значение;
·
наименьшее общее кратное имеет
наименьшее значение.
Есть ли среди заданных чисел “близнецы”,
т.е. простые числа, разность между которыми равна
2?
3. Дано n целых чисел. Написать программу
подсчета количества чисел, в записи которых нет
цифры 8. При этом требуется использовать функцию вида
Function Yes8(x:LongInt):Boolean;
Без использования компьютера решите эту
задачу для всех трехзначных чисел. Ответ: 729.
4. Даны два числа — n и k (k <
n). Число n есть основание системы счисления, а
k —
количество знаков в записи числа. Написать программу
подсчета количества натуральных чисел в n-ичной системе
счисления, записываемых ровно k знаками. Без
использования компьютера решите эту задачу для всех
трехзначных десятичных чисел. Ответ: 900.
5. Объявлен конкурс на получение грантов. В
конкурсе принимают участие n человек. Гранты
выдаются первым пяти участникам, набравшим
наибольшее количество баллов. Один участник не может
одновременно получить два и более грантов. Написать
программу определения количества способов,
которыми могут быть распределены гранты. Исследовать область
ее применимости.
6. Дана шахматная доска размером
n x n и n
ладей. Определить количество способов расположения ладей
на шахматной доске так, чтобы они не могли
бить друг друга. Исследовать, для каких значений
n
можно использовать типы данных Integer, Word, LongInt.
7. Дано n различных бусин. Сколько
различных ожерелий можно составить из них? Исследуйте
область применимости вашей программы.
Примечание. При n = 7 число ожерелий равно
(5040 : 7) : 2 = 360. Делением на 2 учитывается не только поворот (деление на 7), но и
переворот ожерелий.
8. Имеются предметы k различных типов. Количество предметов первого типа
n1 , второго
— n2 , …, k-го — nk . Написать программу подсчета
числа перестановок, которые можно сделать из этих
предметов, и исследовать область ее применимости.
Предварительно попытайтесь обосновать формулу:
Pn = n! / (n1! • n2! • … • nk!).
Есть текст. Его шифруют (кодируют) с
помощью перестановки букв. В результате
получается новый текст, называемый анаграммой (слова “лунка” и “кулан”
— анаграммы). Для механической расшифровки
анаграммы требуется выполнить количество
перестановок, вычисляемых по вышеприведенной формуле, где
ni — количество конкретных букв в анаграмме.
Предположим, что у вас есть компьютер с миллиардным
быстродействием. Он выполняет миллиард умножений в
секунду. Задайте значения величинам n, n1 ,
n2 , …, nk и
подсчитайте, какое время ваш компьютер будет решать
задачу.
9. Черепашка перемещается по клеточному
полю размером n x m. Ей разрешено двигаться вправо и
вверх. В начальный момент времени она находится в
нижней левой клетке (A), и ей необходимо попасть в
верхнюю правую клетку (B). Сколько различных
путей есть у Черепашки? Подсчитайте количество
путей для различных значений n и m. Выясните,
для каких значений n и m можно использовать для хранения
результата переменные типов Integer, Word, LongInt. Предположим, что
для анализа одного пути Черепашки
компьютеру с миллиардным быстродействием (миллиард
операций в секунду) требуется 100 операций. За какое
время он проанализирует все пути для конкретных
значений n и m?
B | ||||||
A |
10. Последовательности длины n составляются из t нулей и k единиц, причем таким образом, что никакие две единицы не находятся рядом. Подсчитать количество таких последовательностей (ответ: C(t + 1, k)). Найти способ перечисления всех таких последовательностей для небольших значений t и k. Например, для t = 3, k = 2 это последовательности 00101, 01001, 01010, 10001, 10010, 10100.
Продолжение следует