Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Практикум по программированию на языке Си

..pdf
Скачиваний:
24
Добавлен:
12.11.2023
Размер:
3.53 Mб
Скачать

printf ("%crow[%d]=%9.2e",i%3?'\t':'\n',i,row[i]);

}

return 0;

}

Результат выполнения программы:

size=6<ENTER>

row[0]= 0.00e+00 row[1]= 6.14e-03 row[2]=-3.41e-01 row[3]=-3.74e-01 row[4]= 2.09e-01 row[5]= 1.13e+00 Result:

row[0]=-3.74e-01 row[1]=-3.41e-01 row[2]= 0.00e+00 row[3]= 6.14e-03 row[4]= 2.09e-01 row[5]= 1.13e+00

Для более компактного размещения выводимой информации вместо спецификации %e использована в функции printf() спецификация %9.2e. В ней явно указано количество значащих цифр (2) после десятичной точки и общая длина поля (9) для вывода чисел.

ЗАДАНИЕ. Внесите изменения в программу 06_10.с, чтобы она подсчитывала общее количество сравнений и общее число перестановок при выполнении упорядочения.

/* 06_10_1.c – статистика сортировки массива */ #include <stdio.h>

#include <math.h> int main ()

{

int size; printf("size="); scanf("%d",&size);

{int i, j, compare=0, rearrange=0; double row[size], temp;

for (i=0; i<size; i++) row[i]=i/(2.0+i)-sin(i*i/(2.0+i));

for (i=0; i<size; i++) printf

("%crow[%d]=%9.2e",i%3?'\t':'\n',i,row[i]); for (j=0; j < size-1; j++)

for (i=j+1; i < size; i++)

181

{ compare++;

if (row[j]>row[i])

{temp=row[j];

row[j]=row[i];

row[i]=temp;

rearrange++;

}

}

printf("\nResult:\n");

printf("compare=%d\n",compare);

printf("rearrange=%d\n",rearrange); for (i=0; i<size; i++)

printf ("%crow[%d]=%9.2e",i%3?'\t':'\n',i,row[i]);

}

return 0;

}

Результат выполнения программы:

size=6<ENTER>

row[0]= 0.00e+00 row[1]= 6.14e-03 row[2]=-3.41e-01 row[3]=-3.74e-01 row[4]= 2.09e-01 row[5]= 1.13e+00 Result:

compare=15

rearrange=5

row[0]=-3.74e-01 row[1]=-3.41e-01 row[2]= 0.00e+00 row[3]= 6.14e-03 row[4]= 2.09e-01 row[5]= 1.13e+00

Результат второго выполнения программы:

size=66<ENTER>

...............

Result:

compare=2211

rearrange=950

...............

ЗАДАНИЕ. Переделайте программу, исключив массив переменных размеров (используйте константное выражение в опре-

182

делении массива). Введите проверку допустимости вводимого значения переменной size.

ЗАДАЧА 06-11. Введя размер n массива, вычислите значения его элементов по следующей формуле

 

i

 

 

i

2

 

 

 

a =

sin

 

, i = 0,n1,

i + 2

i +

2

i

 

 

 

 

 

 

 

 

 

 

 

 

 

упорядочите значения элементов массива, используя алгоритм попарного сравнения (метод пузырька).

Алгоритм попарного сравнения в литературе называют "методом пузырька", проводя аналогию с пузырьком, поднимающимся со дна бокала с газированным напитком. По мере всплытия пузырек сталкивается с другими пузырьками и, сливаясь с ними, увеличивается в объеме. Чтобы аналогия стала очевидной, нужно считать, что элементы массива расположены вертикально друг над другом, и их нужно так упорядочить, чтобы значения увеличивались снизу вверх.

Обозначим элементы массива как x0, x1, x2,…,xn-1. Определим вспомогательную переменную exchange. Присвоим exchange нулевое значение и, изменяя индекс j от 0, найдем такое j, для которого xj>xj+1. Поменяем местами значения xj и xj+1, запомним значение индекса j (exchange=j+1) и продолжим "перебор" и сравнение соседних элементов, начиная с j+1. В результате первого просмотра наибольшее значение переместится на место с индексом (n-1) и в переменной exchange будет запомнено то значение индекса j+1, для которого был выполнен последний обмен. Если исходный массив упорядочен, то ни один обмен не происходит, вспомогательная переменная exchange остается нулевой и сортировка завершена.

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

Алгоритм реализует следующая программа:

// 06_11.c - сортировка массива методом "пузырька" #include <stdio.h>

183

#include <math.h> int main ()

{

int size; printf("size="); scanf("%d",&size);

{int i, j, bound=size-1, exchange; double row[size], temp;

for (i=0; i<size; i++) row[i]=i/(2.0+i)-sin(i*i/(2.0+i));

for (i=0; i<size; i++) printf

("%crow[%d]=%5.2f",i%4?'\t':'\n',i,row[i]);

do {

for (j=0, exchange=0; j < bound; j++) if (row[j]>row[j+1])

{temp=row[j];

row[j]=row[j+1];

row[j+1]=temp;

exchange=j+1;

}

bound=exchange-1;

}

while (bound > 0); printf("\nResult:"); for (i=0; i<size; i++)

printf ("%crow[%d]=%5.2f",i%4?'\t':'\n',i,row[i]);

}

return 0;

}

Результат выполнения программы:

size=11<ENTER>

row[0]= 0.00 row[1]= 0.01 row[2]=-0.34 row[3]=-0.37 row[4]= 0.21 row[5]= 1.13 row[6]= 1.73 row[7]= 1.52 row[8]= 0.68 row[9]=-0.06 row[10]=-0.05

Result:

row[0]=-0.37 row[1]=-0.34 row[2]=-0.06 row[3]=-0.05 row[4]= 0.00 row[5]= 0.01 row[6]= 0.21 row[7]= 0.68 row[8]= 1.13 row[9]= 1.52 row[10]= 1.73

184

Отметим некоторые особенности. При печати значений элементов массива в printf() использована спецификация преобразования %5.2f – вывод вещественного числа в форме с фиксированной точкой в поле из 5 позиций и двумя цифрами после десятичной точки.

Параметр внутреннего цикла j (индекс элементов массива) изменяется от 0 до значения переменной bound, где bound – наибольшее значение индекса, при котором на предыдущем просмотре массива выполнен последний обмен элементов. Переменная exchange обнуляется в начале каждого выполнения внутреннего цикла. При обменах в ней запоминается больший номер (индекс) обмениваемых элементов. Внешний цикл с постусловием (bound>0) выполняется хотя бы один раз, при этом проверяется исходная упорядоченность элементов массива.

ЗАДАНИЕ. Дополните программу так, чтобы она подсчитывала общее количество сравнений и общее количество перестановок при выполнении упорядочения.

Заданию соответствует программа 06_11_1.с.

ЗАДАНИЕ. Переделайте программу 06_11.с, исключив неконстантные выражения в определениях массивов.

ЗАДАНИЕ. Сравните результаты сортировок одинаковых массивов с помощью алгоритма прямого перебора (задача 06-10) и алгоритма попарного сравнения (метод пузырька – задача 06-11), решая задачи при разных значениях размеров массивов.

6.3. Решение задачи с массивом с помощью двух программ

Программы для решения задач с массивами обычно включают два алгоритма – формирования и заполнения массивов (присваивание их элементам значений в соответствии с заданными законами) и собственно обработки массивов по требуемым (задачей) правилам.

185

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

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

Простейшее решение – вычисление и вывод значений элементов будущих массивов на печать, а затем их "ручной" ввод в программе обработки. Этот вариант реален, но трудоемок и ненадежен (опасен появлением ошибок ввода).

Более эффективный подход – запись результатов выполнения программы формирования значений элементов массива в файл и чтение программой обработки данных из этого файла. Как работать с файлами с помощью функций стандартной библиотеки, мы рассмотрим в специальной теме (см. тему 12). Сейчас воспользуемся "переназначением" стандартных потоков ввода-вывода. (Этот механизм мы уже рассматривали в подразделе 5.5).

Пусть для решения одной задачи составлены две программы и после их раздельной компиляции получены два файла prog1.exe и prog2.exe с исполнимыми кодами этих программ. Тогда обмен данными между программами можно осуществить с помощью таких директив операционной системы:

>prog1.exe>data<ENTER>

>prog2.exe<data<ENTER>

Здесь data – произвольно выбранное (нами) имя текстового файла, который будет автоматически создан или обновлен операционной системой при выполнении первой директивы. В этот файл программа prog1.exe запишет всю информацию, которая предназначена для вывода в стандартный выходной поток (stdout). Напомним, что в стандартный выходной поток осуществляют вывод библиотечные функции printf(), puts(), putchar(). Директива "prog1.exe>data" переназначает стандартный выходной поток, настраивая его на запись в текстовый файл с именем data.

186

Программа prog2.exe в соответствии со второй директивой будет читать исходные данные не от клавиатуры, а из файла data, указанного справа от знака <. Тем самым происходит “переназначение” стандартного входного потока stdin. Напомним, что чтение из стандартного входного потока осуществляют библиотечные функции scanf(), getchar(), gets().

Трудности решения одной задачи с помощью двух (а не одной) программ связаны с необходимостью согласования форматов данных, выводимых первой программой, с требованиями второй программы. Например, если первая программа формирует массив заранее неизвестного размера, то в файл результатов полезно помещать не только значения элементов, но и размер массива. Причем размер массива следует помещать до значений элементов. Конечно, вторая программа может “узнать” общее число элементов, подсчитав количество значений в файле, затем создать массив нужных фиксированных размеров и только после этого присвоить его элементам значения из файла. Чтобы такая возможность появилась, требуется дважды прочитать содержимое файла. Первое чтение – для подсчета количества элементов в массиве. Затем определение массива (выделение памяти его элементам). Второе чтение – присвоение элементам массива значений из файла. Применению такой схемы мешает тот факт, что стандартные потоки ввода-вывода при исполнении программы не допускают возврата к началу для повторного “просмотра”. Учитывая сказанное, рассмотрим следующую задачу.

ЗАДАЧА 06-12. Введя значение n и два целых числа x и y (условие x<y), сформируйте целочисленный массив из n элементов

последовательности (ряда) {a}i , i = 1, n , где a1 равно x, a2 равно y,

ai=ai-1+ai-2 для i>2. Выведите в четыре колонки полученные значения элементов ряда в обратном порядке. Решение оформите в виде двух программ – генерации значений элементов массива и собственно обработки (печати) массива. (Если x=y=1, то последовательность называют рядом Фибоначчи.)

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

/* 06_12A.c - формирование последовательности Фибоначчи */

187

#include <stdio.h> #define READI(VARIABLE) \

{printf(#VARIABLE"="); scanf("%d",&VARIABLE);} int main ()

{

int size,x,y,i,element1,element2,element; READI(size);

READI(x);

READI(y);

if (size<2 || x>=y)

{printf("Error!"); return 0;

}

printf("\n%d",size);

printf("\n%d",element1=x);

printf("\n%d",element2=y); for (i=3; i<=size; i++)

{element=element1+element2;

printf("\n%d",element);

element1=element2;

element2=element;

}

return 0;

}

Результаты выполнения программы без переназначения потоков, т.е. при запуске директивой

>test.exe<ENTER>

Первый вариант (с ошибкой в данных):

size=6<ENTER>

x=3<ENTER>

y=1<ENTER>

Error!

Второй вариант:

size=6<ENTER>

x=1<ENTER>

y=3<ENTER>

188

6

1

3

4

7

11

18

Если запустить программу с переназначением выходного потока

>test.exe>result<ENTER>,

то общение с программой станет весьма неудобным – на экране пользователя не появятся подсказки вида “size=”. Придется "на память" набирать значения входных данных, ориентируясь только по исходному тексту программы. Предположим, что пользователь справился с этим затруднением и набрал (но с ошибкой):

6<ENTER>

3<ENTER>

2<ENTER>

На экране – пусто! В файле result одна строка:

size=x=y=Error!

Терпеливое рассматривание исходного текста программы или напоминание условия y<x поможет пользователю, и он (наконец-то!) введет правильные исходные данные:

6<ENTER>

1<ENTER>

3<ENTER>

Окно сеанса закроется – выполнение программы закончено. Все результаты в файле result:

size=x=y=

6

1

3

4

189

7

11

18

Полученные в текстовом файле result данные нужно использовать в качестве исходной информации для второй программы нашей задачи. Анализ содержимого файла result показывает, что при чтении из него необходимо пропустить (игнорировать, т.е. исключить при вводе) первую строку. Для этого можно воспользоваться спецификацией преобразования вида %*s в форматной строке функции scanf(). С учетом сказанного вторую часть задачи можно переформулировать таким образом:

ПОДЗАДАЧА. Сформируйте одномерный массив по данным из стандартного входного потока, "настроенного" на файл result. Выведите значения элементов массива в обратном порядке, размещая их в четыре колонки.

/* 06_12B.c - массив из результатов программы

06_12A.с */ #include <stdio.h>

int main ()

{int sizeArray; scanf("%*s%d",&sizeArray);

{

int i, j;

int array[sizeArray];

printf("The values from stdin:\nsizeArray=%d\n", sizeArray);

for (i=0; i<sizeArray; i++) { scanf("%d",&array[i]);

printf("array[%d]=%d\n",i,array[i]);

}

printf("\nThe solution:");

for (j=0, i=sizeArray-1; i>=0; j++, i--) printf

("%carray[%d]=%d",j%4?'\t':'\n',i,array[i]); return 0;

}

}

При выполнении программы 06_12B.c по команде

190