Часто для работы с множеством однотипных данных (целочисленными значениями, строками, датами и т.п.) оказывается удобным использовать массивы. Например, можно создать массив для хранения фамилий студентов, обучающихся в одной группе. Вместо создания переменных для каждого студента, например Студент1, Студент2 и т.д., достаточно создать один массив, где каждой фамилии из списка будет присвоен порядковый номер. Таким образом, можно дать следующее определение. Массив — структурированный тип данных, состоящий из фиксированного числа элементов одного типа.
Массив в табл. 5.1 имеет 8 элементов, каждый элемент сохраняет число вещественного типа. Элементы в массиве пронумерованы (нумерация массивов начинается с нуля). Такого рода массив, представляющий собой просто набор данных одного и того же типа, называют простым или одномерным массивом. Для доступа к данным, хранящимся в определённом элементе массива, необходимо указать имя массива и порядковый номер этого элемента, называемый индексом.
| № элемента массива | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| Значение | 13.65 | -0.95 | 16.78 | 8.09 | -11.76 | 9.07 | 5.13 | -25.64 |
Если возникает необходимость хранения данных в виде матриц, в формате строк и столбцов, то необходимо использовать двумерные массивы. В табл. 5.2 приведён пример массива, состоящего из четырёх строк и пяти столбцов. Это двумерный массив. Строки в нём можно считать первым измерением, а столбцы вторым. Для доступа к данным, хранящимся в этом массиве, необходимо указать имя массива и два индекса, первый должен соответствовать номеру строки, а второй номеру столбца, где хранится необходимый элемент.
| 1.5 | -0.9 | 1.8 | 7.09 | -1.76 |
| 3.6 | 0.5 | 6.7 | 0.09 | -1.33 |
| 13.65 | 0.95 | 16.78 | 8.09 | -11.76 |
| 7.5 | 0.95 | 7.3 | 8.9 | 0.11 |
Если при описании массива определён его размер, то массив называют статическим. Рассмотрим работу с одномерными статическими массивами в языке С(С++). Двумерные массивы подробно описаны в следующей главе.
Описать статический массив в С(С++) можно так:
тип имя_переменной [размерность];
размерность — количество элементов в массиве. Например:
int x[10]; //Описание массива из 10 целых чисел. Первый //элемент массива имеет индекс 0, последний 9. float a[20]; //Описание массива из 20 вещественных чисел. //Первый элемент массива имеет индекс 0, последний 19.
Размерность массива и тип его элементов определяют объём памяти, который необходим для хранения массива. Рассмотрим ещё один пример описания массива:
const int n=15; //Определена целая положительная константа. double B[n]; //Описан массив из 15 вещественных чисел.
При описании статического массива в качестве размерности можно использовать целое положительное число или предопределённую константу.
Элементы массива в С(С++) нумеруются с нуля. Первый элемент всегда имеет номер ноль, а номер последнего элемента на единицу меньше заданной при его описании размерности:
char C[5]; //Описан массив из 5 символов, нумерация от 0 до 4.
Доступ к каждому элементу массива осуществляется с помощью индекса — порядкового номера элемента. Для обращения к элементу массива указывают его имя, а затем в квадратных скобках индекс:
имя_массива [индекс]
Например:
const int n=15; double C[n], S; S=C[0]+C[n-1]; //Сумма первого и последнего элементов массива С.
Массиву, как и любой другой переменной, можно присвоить начальное значение (инициализировать). Для этого значения элементов массива нужно перечислить в фигурных скобках через запятую:
тип имя_переменной[размерность]={элемент_0, элемент_1, ...};
Например:
float a[6] = { 1.2, ( float ) 3/4, 5./6, 6.1 };
//Формируется массив из шести вещественных чисел, значения элементам присваиваются по
//порядку. Элементы, значения которых не указаны (в данном случае a[4], a[5]), обнуляются:
//a[0]=1.2, a[1]=(float)3/4, a[2]=5./6, a[3]=6.1, a[4]=0, a[5]=0,
//для элементов a[1] и a[2] выполняется преобразование типов.
Рассмотрим, как хранится массив в памяти компьютера. Предположим, была описана переменная:
double x[30];
это значит, что в памяти компьютера выделяется место для хранения 30 элементов типа double. При этом адрес выделенного участка памяти хранится в переменной x. Таким образом, к значению нулевого элемента массива можно обратится двумя способами:
x[0].*x, так как адрес начала массива хранится в переменной x (по существу x — указатель на double).Если к значению x добавить единицу (число 1), то мы сместимся на один элемент типа double. Таким образом, x+1 — адрес элемента массива x с индексом 1. К первому элементу массива x также можно обратиться двумя способами: x[1] или *(x+1). Аналогично, к элементу с индексом 2 можно обращаться либо x[2], либо *(x+2). Таким образом, получается, что к элементу с индексом i можно обращаться x[i] или *(x+i).
При обработке массива (независимо от способа обращения x[i] или *(x+i))
программист сам должен контролировать, существует ли элемент массива x[i]
(или *(x+i)) и не вышла ли программа за границы массива.
Особенностью статических массивов является определение размера при написании текста программы. При необходимости увеличить размер массива, необходимо изменить текст программы и перекомпилировать её. При динамическом выделении памяти для массивов в С(С++) можно использовать указатели и операторы (функции) выделения памяти.
Для создания динамического массива необходимо [1, 8]:
тип * указатель;);Для выделения памяти в С++ можно воспользоваться оператором new или функциями языка С — calloc, malloc, realloc. Все функции находятся в библиотеке
stdlib.h.
Функция malloc выделяет непрерывный участок памяти размером size байт и возвращает указатель на первый байт этого участка. Обращение к функции имеет вид:
void* malloc ( size_t size );
где size — целое беззнаковое size_t — базовый беззнаковый целочисленный тип языка С/С++, который выбирается таким образом, чтобы в него можно было записать максимальный размер теоретически возможного массива любого типа. В 32-битной операционной системе size_t является беззнаковым 32-битным числом (максимальное значение 232- 1), в 64-битной — 64-битным беззнаковым числом (максимальное значение 264- 1).void*, которую можно преобразовать к любому необходимому типу указателя. Если выделить память невозможно, то функция вернёт пустой указатель NULL.
Например,
double *h; //Описываем указатель на double. int k; cin>>k; //Ввод целого числа k. //Выделение участка памяти для хранения k элементов типа double. //Адрес этого участка хранится в переменной h. h=(double *) malloc ( k* sizeof ( double ) ); //h — адрес начала участка памяти, //h + 1, h + 2, h + 3 и т. д. — адреса последующих элементов типа double
Функция calloc предназначена для выделения и обнуления памяти.
void * calloc ( size_t num, size_t size );
С помощью функции будет выделен участок памяти, в котором будет храниться num элементов по size байт каждый. Все элементы выделенного участка обнуляются. Функция возвращает указатель на выделенный участок или NULL при невозможности выделить память.
Например,
float *h; //Описываем указатель на float. int k; cin>>k; //Ввод целого числа k. //Выделение участка памяти для хранения k элементов типа float. //Адрес этого участка хранится в переменной h . h=( float *) calloc ( k, sizeof ( float ) ); //h — адрес начала участка памяти, //h + 1, h + 2, h + 3 и т. д. — адреса последующих элементов типа float.
Функция realloc изменяет размер ранее выделенного участка памяти. Обращаются к функции так:
void *realloc ( void *p, size_t size );
где p — указатель на область памяти, размер которой нужно изменить на size. Если в результате работы функции меняется адрес области памяти, то новый адрес вернётся в качестве результата. Если фактическое значение первого параметра NULL, то функция realloc работает так же, как и функция malloc, то есть выделяет участок памяти размером size байт.
Для освобождения выделенной памяти используется функция free. Обращаются к ней так:
void free ( void *p );
где p — указатель на участок памяти, ранее выделенный функциями malloc, calloc или realloc.
В языке С++ есть операторы new для выделения и delete для освобождения участка памяти.
Для выделения памяти для хранения n элементов одного типа оператор new имеет вид [5]:
x=new type [ n ];
type — тип элементов, для которых выделяется участок памяти;
n — количество элементов;
x — указатель на тип данных type, в котором будет храниться адрес выделенного участка памяти.
При выделении памяти для одного элемента оператор new имеет вид:
x=new type;
Например,
float *x; //Указатель на тип данных float . int n; cin>>n; //Ввод n //Выделение участка памяти для хранения n элементов типа float. Адрес этого участка хранится //в переменной x; x+1, x+2, x+3 и т. д. — адреса последующих элементов типа float.
Освобождение выделенного с помощью new участка памяти осуществляется с помощью оператора delete следующей структуры:
delete [ ] p;
p — указатель (адрес участка памяти, ранее выделенного с помощью оператора
new).
В чём же отличие статического и динамического массива?
Предположим, описан статический массив: double x[75];
Это означает, что выделен участок памяти для хранения 75 элементов типа double (массив из 75 элементов типа double). Адрес начала массива хранится в переменной x. Для обращения к $$i$$-му элементу можно использовать конструкции x[i] или *(x+i). Если понадобится обрабатывать массив более, чем из 75 элементов, то придётся изменить описание и перекомпилировать программу. При работе с массивами небольшой размерности, большая часть памяти, выделенной под статический массив, будет использоваться вхолостую.
Допустим, задан динамический массив, например
double *x; //Указатель на double int k; cin>>k; //Вводим размер массива k. //Выделение памяти для хранения динамического массива из k чисел. x=new double [ k ]; //Адрес начала массива хранится в переменной x. x=(double *) calloc ( k, sizeof ( float ) ); //Память можно будет выделить так x=(double *) malloc ( k* sizeof ( float ) ); //или так
В этом случае, мы имеем указатель на тип данных double, вводим $$k$$ — размер динамического массива, выделяем участок памяти для хранения $$k$$ элементов типа double (массив из $$k$$ элементов типа double). Адрес начала массива хранится в переменной x. Для обращения к $$i$$-му элементу можно использовать конструкции x[i] или *(x+i). В случае динамического массива мы сначала определяем его размер (в простейшем случае просто вводим размер массива с клавиатуры), а потом выделяем память для хранения реального количества элементов. Основное отличие статического и динамического массивов состоит в том, что в динамическом массиве выделяется столько элементов, сколько необходимо.
Имя массива (статического или динамического) это адрес начала выделенного для него участка памяти, значит обращаться к элементам массива можно двумя способами — x[i] или *(x+i).
Все манипуляции с массивами в С++ осуществляются поэлементно. Организовывается цикл, в котором происходит последовательное обращение к нулевому, первому, второму и т.д. элементам массива. В общем виде алгоритм обработки массива выглядит так, как показано на рис. 5.1.
Алгоритмы, с помощью которых обрабатывают одномерные массивы, похожи на обработку последовательностей (вычисление суммы, произведения, поиск элементов по определённому признаку, выборки и т. д.). Отличие заключается в том, что в массиве одновременно доступны все его компоненты, поэтому становится возможной, например, сортировка его элементов и другие, более сложные преобразования.
(рис 5.1) Алгоритм обработки элементов массива
Ввод и вывод массивов также осуществляется поэлементно. Блок-схемы алгоритмов ввода и вывода элементов массива X[N] изображены на рис. 5.2,рис. 5.3.
Рассмотрим несколько вариантов ввода массива:
//Вариант 1. Ввод массива с помощью функции scanf.
//При организации ввода используются специальные символы: табуляция — \t
//и переход на новую строку — \n.
#include <stdio.h>
int main ( )
{
float x [ 10 ]; int i, n;
printf ( " \n N = " ); scanf ( " % d ",n ); //Ввод размерности массива.
printf ( " \n Введите элементы массива X \n " );
for ( i =0; i<n; i++)
scanf ( " % f ", x+ i ); //Ввод элементов массива в цикле.
//Обратите внимание, x + i — адрес i-го элемента массива.
return 0;
}
(рис 5.2) Алгоритм ввода массива X[N]
Результат работы программы:
N=3 Введите элементы массива X 1.2 -3.8 0.49
(рис 5.3) Алгоритм вывода массива X[N]
//Вариант 2. Ввод массива с помощью функции scanf и вспомогательной переменной b.
#include <stdio.h>
int main ( )
{
float x [ 10 ], b; int i, n;
printf ( " \n N = " ); scanf ( " % d ",n ); //Ввод размерности массива.
printf ( " \n Массив X \n " );
for ( i =0; i<n; i++)
{
printf ( " \n Элемент % d \t ", i ); //Сообщение о вводе элемента.
scanf ( " % f ",b ); //Ввод переменной b.
x [ i ]=b; //Присваивание элементу массива значения переменной b .
}
return 0;
}
Результат работы программы:
N=4 Массив X Элемент 0 8.7 Элемент 1 0.74 Элемент 2 -9 Элемент 3 78
//Вариант 3. Ввод динамического массива с помощью cin.
#include <iostream>
using namespace std;
int main ( )
{
int *X,N, i;
cout<<" \n N = "; cin>>N; //Ввод размерности массива.
X=new int [N ]; //Выделение памяти для динамического массива из N элементов.
for ( i =0; i<N; i++)
{
cout<<" \n X [ "<<i<<" ]= "; //Сообщение о вводе элемента.
cin>>X [ i ]; //Ввод элементов массива в цикле.
}
delete [ ] X;
return 0;
}
Результат работы программы:
N=4 X[0]=1 X[1]=2 X[2]=4 X[3]=5
Вывод статического или динамического массива можно осуществить несколькими способами:
//Вариант 1. Вывод массива в виде строки. for ( i =0; i<n; i++) printf ( " % f \t ",X [ i ] ); //Вариант 2. Вывод массива в виде столбца. for ( i =0; i<n; i++) printf ( " \n % f ",X [ i ] ); //Вариант 3. Вывод массива в виде строки. for ( i =0; i<N; i++) cout <<" \t X [ "<<i<<" ]= "<<X [ i ]; //Вариант 4. Вывод массива в виде столбца. for ( i =0; i<N; i++) cout <<" \n X [ "<<i<<" ]= "<<X [ i ];
Дан массив $$X$$, состоящий из $$N$$ элементов. Найти сумму элементов этого массива. Процесс накапливания суммы элементов массива достаточно прост и практически ничем не отличается от суммирования значений некоторой числовой последовательности. Переменной $$S$$ присваивается значение, равное нулю, затем к переменной $$S$$ последовательно добавляются элементы массива $$X$$. Блок-схема алгоритма расчёта суммы приведена на рис. 5.4.
(рис 5.4) Алгоритм вычисления суммы элементов массива
Соответствующий алгоритму фрагмент программы будет иметь вид:
for ( S= i =0; i<N; i++) S+=X [ i ]; cout<<" S = "<<S<<" \n ";
Дан массив $$X$$, состоящий из $$N$$ элементов. Найти произведение элементов этого массива. Решение этой задачи сводится к тому, что значение переменной $$P$$, в которую предварительно была записана единица, последовательно умножается на значение $$i$$–го элемента массива. Блок-схема алгоритма приведена на рис. 5.5.
Соответствующий фрагмент программы будет иметь вид:
for (P=1, i =0; i<N; i++) P*=X [ i ]; cout<<" P = "<<P<<" \n ";
Задача 5.1. Задан массив целых чисел. Найти сумму простых чисел и произведение отрицательных элементов массива.
Алгоритм решения задачи состоит из следующих этапов.
(рис 5.5) Вычисление произведения элементов массива
Блок-схема решения задачи представлена на рис. 5.6. Для решения задачи применим функцию (prostoe) проверки, является ли число простым. Текст программы с подробными комментариями приведён далее.
#include <iostream>
using namespace std;
//Текст функции prostoe.
bool prostoe ( int N)
{
int i;
bool pr;
if (N<2) pr=false;
else
for ( pr=true, i =2; i<=N/ 2; i++)
if (N%i ==0)
{
pr=false;
break;
}
return pr;
}
int main ( )
{
int *X, i,N, S, P;
cout<<"Введите размер массива "; cin>>N; //Ввод размерности массива.
X=new int [N ]; //Выделение памяти для хранения динамического массива X.
cout<<"Ведите массив X \n "; //Ввод массива X.
for ( i =0; i<N; i++)
{ cout<<" X ( "<<i<<" ) = "; cin>>X [ i ]; }
for (P=1,S= i =0; i<N; i++) //В цикле перебираем все элементы массива
{
//Если очередной элемент массива — простое число, добавляем его к сумме.
if ( prostoe (X [ i ] ) ) S+=X [ i ];
//Если очередной элемент массива отрицателен, умножаем его на P.
if (X [ i ] <0) P*=X [ i ];
}
cout << " S = " <<S<<" \t P = "<<P<< endl; //Вывод S и P на экран.
delete [ ] X; //Освобождение занимаемой массивом X памяти.
return 0;
}
(рис 5.6) Блок-схема алгоритма решения задачи 5.1
Результаты работы программы представлены ниже.
Введите размер массива 10 Ведите массив Х X(0)=-7 X(1)=-9 X(2)=5 X(3)=7 X(4)=2 X(5)=4 X(6)=6 X(7)=8 X(8)=10 X(9)=12 S=14 P=63
Задача 5.2. Дан массив $$A$$, состоящий из $$k$$ целых положительных чисел. Записать все чётные по значению элементы массива $$A$$ в массив $$B$$.
На рис. 5.7 представлен фрагмент алгоритма решения данной задачи. Здесь индексы массива $$A$$ хранятся в переменной $$i$$, а для номеров массива $$B$$ зарезервирована переменная $$m$$. Операция, выполняемая в блоке 1, означает, что в массиве $$A$$ может не быть искомых элементов. Далее организован цикл (блок 2), с помощью которого можно обращаться к элементам массива $$A$$. Если условие в блоке 3 выполняется, то переменная $$m$$ увеличивается на единицу, а значение соответствующего элемента массива $$A$$ записывается в массив $$В$$ под номером $$m$$ (блок 4). Условный блок 5 необходим для того, чтобы проверить, выполнилось ли хотя бы раз условие поиска (блок 2). Если массив $$B$$ сформирован, то он выводится на экран (блоки 6, 7), в противном случае выдаётся соответствующее сообщение (блок 8).
(рис 5.7) Алгоритм решения задачи 5.2
Приведённый ниже фрагмент программы реализует описанный алгоритм:
for (m=-1, i =0; i<k; i++)
{
if (A [ i ]%2==0) //Если элемент чётный, то
{
m++; //увеличить значение индекса массива В
B [m]=A [ i ]; //и записать элемент в массив В.
}
}
if (m>-1) //Если чётные элементы найдены, то распечатать сформированный массив.
for ( i =0; i<=m; cout<<B [ i ]<<" \t ", i++);
else //иначе, выдать сообщение,
cout<<"Массив B не сформирован!"<<endl;
Дан массив X, состоящий из n элементов. Найти максимальный элемент массива и номер, под которым он хранится в массиве.
Алгоритм решения задачи следующий. Пусть в переменной с именем Max хранится значение максимального элемента массива, а в переменной с именем Nmax – его номер. Предположим, что нулевой элемент массива является максимальным, и запишем его в переменную Max, а в Nmax — его номер (то есть ноль). Затем все элементы, начиная с первого, сравниваем в цикле с максимальным. Если текущий элемент массива оказывается больше максимального, то записываем его в переменную Max, а в переменную Nmax – текущее значение индекса i. Процесс определения максимального элемента в массиве приведён в табл. 5.3 и изображён при помощи блок-схемы на рис. 5.8. Соответствующий фрагмент программы имеет вид:
for (Max=X [ 0 ], Nmax= i =0; i<n; i++)
if (Max<X [ i ] )
{
Max=X [ i ];
Nmax= i;
}
cout<<" Max = "<<Max<<" \n ";
cout<<" Nmax = "<<Nmax<<" \n ";
| Номера элементов | 0 | 1 | 2 | 3 | 4 | 5 |
| Исходный массив | 4 | 7 | 3 | 8 | 9 | 2 |
| Значение переменной $$Max$$ | 4 | 7 | 7 | 8 | 9 | 9 |
| Значение переменной $$Nmax$$ | 1 | 2 | 2 | 4 | 5 | 5 |
(рис 5.8) Поиск максимального элемента и его номера в массиве
При поиске максимального элемента и его номера, можно найти только номер максимального элемента, а потом по номеру извлечь значение максимального элемента из массива.
Текст программы поиска номера максимального элемента:
#include <iostream>
using namespace std;
int main ( )
{
float *X;
int i,N, nom;
cout<<"Введите размер массива "; cin>>N; //Ввод размерности динамического массива
X=new float [N ]; //Выделение памяти для хранения динамического массива X.
cout<<"Введите элементы массива X \n "; //Ввод динамического массива X.
for ( i =0; i<N; i++)
cin>>X [ i ];
//В переменной nom будем хранить номер максимального элемента.
nom=0; //Предположим, что максимальным элементом является элемент с номером 0.
for ( i =1; i<N; i++)
//Если очередной элемент больше X[nom], значит nom не является номером максимального
//элемента, элемент с номером i больше элемента X[nom], поэтому переписываем
//число i в переменную nom.
if (X [ i ]>X [ nom ] ) nom= i;
cout << "Максимальный элемент= "<<X [ nom]<<", его номер= " << nom << endl;
return 0;
}
Совет. Алгоритм поиска минимального элемента в массиве будет отличаться от приведённого выше лишь тем, что в условном блоке и, соответственно, в конструкции if текста программы знак поменяется с "<" на ">".
Рассмотрим несколько задач.
Задача 5.3. Найти минимальное простое число в целочисленном массиве x[N].
Эта задача относится к классу задач поиска минимума (максимума) среди элементов, удовлетворяющих условию. Подобные задачи рассматривались в задачах на обработку последовательности чисел. Здесь поступим аналогично. Блок-схема приведена на рис. 5.9.
Необходимо первое простое число объявить минимумом, а все последующие простые элементы массива сравнивать с минимумом. Будем в цикле последовательно проверять, является ли элемент массива простым числом (функция prostoe). Если X[i] является простым числом, то количество простых чисел (k) увеличиваем на 1 (k++), далее, проверяем, если k равен 1 (if (k==1)), то этот элемент объявляем минимальным (min=x[i]; nom=i;), иначе сравниваем его с минимальным (if (x[i]<min) {min=x[i];nom=i;}).
Текст программы:
#include <iostream>
using namespace std;
bool prostoe ( int N)
{ int i; bool pr;
if (N<2) pr=false;
else
for ( pr=true, i =2; i<=N/ 2; i++)
if (N%i ==0)
{
pr=false;
break;
}
return pr;
}
int main ( int argc, char **argv )
{
int i, k, n, nom, min, *x;
cout<<" n = "; cin>>n; //Ввод количества элементов в массиве.
x=new int [ n ]; //Выделяем память для динамического массива x.
cout<<"Введите элементы массива X "; //Ввод элементов массива.
for ( i =0; i<n; i++)
cin>>x [ i ];
//С помощью цикла по переменной i, перебираем все элементы в массиве x,
//k — количество простых чисел в массиве.
for ( i=k=0; i<n; i++)
//Проверяем, является ли очередной элемент массива простым числом.
if ( prostoe ( x [ i ] ) ) //Если x[i] — простое число.
{
k++; //Увеличиваем счётчик количества простых чисел в массиве.
//Если текущий элемент является первым простым числом в массиве,
// объявляем его минимумом, а его номер сохраняем в перемнной nom.
if ( k==1) {min=x [ i ]; nom= i; }
else
//Все последующие простые числа в массиве сравниваем с минимальным простым числом.
//Если текущее число меньше min, перезаписываем его в переменную min,
//а его номер — в переменную nom.
if ( x [ i ]<min ) {min=x [ i ]; nom= i; }
}
//Если в массиве были простые числа, выводим значение и номер минимального простого числа.
if ( k>0)
cout<<" min = "<<min<<" \ tnom = "<<nom<<endl;
//Иначе выводим сообщение о том, что в массиве нет простых чисел.
else cout<<"Нет простых чисел в массиве"<<endl;
return 0;
}
(рис 5.9) Блок-схема решения задачи 5.3
Аналогичным образом можно написать программу любой задачи поиска минимума (максимума) среди элементов, удовлетворяющих какому-либо условию (минимум среди положительных элементов, среди чётных и т.д.).
Задача 5.4. Найти $$k$$ минимальных чисел в вещественном массиве.
Перед решением этой довольно сложной задачи рассмотрим более простую задачу.
Найти два наименьших элемента в массиве. Фактически надо найти номера (nmin1, nmin2) двух наименьших элементов массива. На первом этапе надо найти номер минимального (nmin1) элемента массива. На втором этапе надо искать номер минимального элемента, при условии, что он не равен nmin1. Вторая часть очень похожа на предыдущую задачу (минимум среди элементов, удовлетворяющих условию, в этом случае условие имеет вид i!=nmin1).
Решение задачи с комментариями:
#include <iostream>
using namespace std;
int main ( int argc, char **argv )
{
int kvo, i, n, nmin1, nmin2;
double *X;
cout<<" n = "; cin>>n;
X=new double [ n ];
cout<<"Введите элементы массива X \n ";
for ( i =0; i<n; i++)
cin>>X [ i ];
//Стандартный алгоритм поиска номера первого минимального элемента (nmin1).
for ( nmin1=0, i =1; i<n; i++)
if (X [ i ]<X [ nmin1 ] ) nmin1= i;
//Второй этап — поиск номера минимального элемента среди элементов, номер
//которых не совпадает nmin1. kvo — количество таких элементов.
for ( kvo= i =0; i<n; i++)
if ( i !=nmin1 ) //Если номер текущего элемента не совпадает с nmin1,
{
kvo++; //увеличиваем количество таких элементов на 1.
//Номер первого элемента, индекс которого не равен nmin1,
//объявляем номером второго минимального элемента.
if ( kvo==1) nmin2= i;
else
//очередной элемент, индекс которого не равен nmin1, сравниваем с минимальным,
//если он меньше, номер перезаписываем в переменную nmin2.
if (X [ i ]<X [ nmin2 ] ) nmin2= i;
}
//Вывод двух минимальных элементов и их индексов.
cout<<" nmin1 = "<<nmin1<<" \ tmin1 = "<<X [ nmin1]<< endl;
cout<<" nmin2 = "<<nmin2<<" \ tmin2 = "<<X [ nmin2]<< endl;
return 0;
}
По образу и подобию этой задачи можно написать задачу поиска трёх минимальных элементов в массиве. Первые два этапа (поиск номеров двух минимальных элементов в массиве) будут полным повторением кода, приведённого выше. На третьем этапе нужен цикл, в котором будем искать номер минимального элемента, при условии, что его номер не равен nmin1 и nmin2. Авторы настоятельно рекомендуют читателям самостоятельно написать подобную программу. Аналогично можно написать программу поиска четырёх минимальных элементов. Однако при этом усложняется и увеличивается код программы. К тому же, рассмотренный приём не позволит решить задачу в общем случае (найти k минимумов).
Для поиска $$k$$ минимумов в массиве можно поступить следующим образом. Будем формировать массив
#include <iostream>
using namespace std;
int main ( int argc, char **argv )
{
int p, j, i, n, *nmin, k, kvo, nmin_temp;
bool pr;
double *x;
cout<<" n = "; cin>>n;
x=new double [ n ];
cout<<"Введите элементы массива Х\n ";
for ( i =0; i<n; i++)
cin>>x [ i ];
cout<<"Введите количество минимумов\n "; cin>>k;
nmin=new int [ k ];
for ( j =0; j<k; j++) //Цикл по переменной j для поиска номера j + 1 минимального элемента
{
kvo=0;
for ( i =0; i<n; i++) //Перебираем все элементы массива.
{
//Цикл по переменной p проверяет, содержится ли номер i в массиве nmin.
pr=false;
for ( p=0;p<j; p++)
if ( i==nmin [ p ] ) pr=true;
if ( ! pr ) //Если не содержится, то количество элементов увеличить на 1.
{
kvo++;
//Если kvo=1, то найден первый элемент, который не содержится в массиве
//nmin, его номер объявляем номером минимального элемента массива
if ( kvo==1) nmin_temp= i;
else
//Если kvo>1, сравниваем текущий элемент x[i] с минимальным.
if ( x [ i ]<x [ nmin_temp ] ) nmin_temp= i;
}
}
nmin [ j ]=nmin_temp; //Номер очередного минимального элемента записываем в массив.
}
for ( j =0; j<k; j++) //Вывод номеров и значений k минимальных элементов массива.
cout<<" nmin1 = "<<nmin [ j ]<<" \ tmin1 = "<<x [ nmin [ j ]]<< endl;
return 0;
}
Проверку, содержится ли число i в массиве nmin, можно оформить в виде функции, тогда программа может быть записана следующим образом:
(рис 5.10) Блок-схема алгоритма поиска k минимальных элементов в массиве x.
#include <iostream>
using namespace std;
//Функция проверяет, содержится ли число i в массиве x из n элементов.
//Функция возвращает true, если содержится, и false, если не содержится.
bool proverka ( int i, int *x, int n )
{
bool pr;
int p;
pr=false;
for ( p=0;p<n; p++)
if ( i==x [ p ] ) pr=true;
return pr;
}
int main ( int argc, char **argv )
{
int j, i, n, *nmin, k, kvo, nmin_temp;
double *x;
cout<<" n = "; cin>>n;
x=new double [ n ];
cout<<"Введите элементы массива Х\n ";
for ( i =0; i<n; i++)
cin>>x [ i ];
cout<<"Введите количество минимумов\n "; cin>>k;
nmin=new int [ k ];
for ( j =0; j<k; j++) //Цикл по переменной j для поиска номера j + 1 минимального элемента
{
kvo=0;
for ( i =0; i<n; i++) //Перебираем все элементы массива.
{
//Вызов функции proverka, определяем, содержится ли число i в массиве nmin из j элементов
if ( ! proverka ( i, nmin, j ) )
{
kvo++;
if ( kvo==1) nmin_temp= i;
else
if ( x [ i ]<x [ nmin_temp ] ) nmin_temp= i;
}
}
nmin [ j ]=nmin_temp;
}
for ( j =0; j<k; j++) //Вывод номеров и значений k минимальных элементов массива.
cout<<" nmin1 = "<<nmin [ j ]<<" \ tmin1 = "<<x [ nmin [ j ]]<< endl;
return 0;
}
Авторы настоятельно рекомендуют читателю разобрать все версии решения задачи 5.4.
Задача 5.5. Поменять местами максимальный и минимальный элементы в массиве X.
Алгоритм решения задачи можно разбить на следующие этапы.
nmax) и минимального (nmin) элементов массива.X[nmax]=X [nmin]; X[nmin]=X[nmax];). При таком присваивании мы сразу же теряем максимальный элемент. Поэтому нам понадобится временная (буферная) переменная temp. Обмен элементов местами должен быть таким: temp=X[nmax]; X[nmax]=X[nmin]; X[nmin]=temp;Далее приведён текст программы с комментариями.
#include <iostream>
using namespace std;
int main ( int argc, char **argv )
{
int i,N, nmax, nmin;
float temp;
cout<<" N = "; cin>>N;
float X [N ];
cout<<"Введите элементы массива Х\n ";
for ( i =0; i<N; i++)
cin>>X [ i ];
//Поиск номеров максимального и минимального элементов массива.
for (nmax=nmin=0, i =1; i<N; i++)
{
if (X [ i ]<X [ nmin ] ) nmin= i;
if (X [ i ]>X [ nmax ] ) nmax= i;
}
//Обмен максимального и минимального элементов местами.
temp=X [ nmax ]; X [ nmax]=X [ nmin ]; X [ nmin ]=temp;
cout<<"Преобразованный массив Х\n "; //Вывод преобразованного массива.
for ( i =0; i<N; i++)
cout<<X [ i ]<<" ";
cout<<endl;
return 0;
}
Задача 5.6. Найти среднее геометрическое среди простых чисел, расположенных между максимальным и минимальным элементами массива.
Среднее геометрическое $$k$$ элементов $$(SG)$$ можно вычислить по формуле $$SG=\sqrt[{k}]{P},\ P$$ — произведение $$k$$ элементов. При решении этой задачи необходимо найти произведение и количество простых чисел, расположенных между максимальным и минимальным элементами.
Алгоритм решения задачи состоит из следующих этапов:
nmax) и минимального (nmin) элементов массива.При решении этой задачи следует учитывать, что неизвестно, какой элемент расположен раньше — максимальный или минимальный.
Текст программы с комментариями приведён ниже.
#include <iostream>
#include <math.h>
using namespace std;
bool prostoe ( int N)
{
int i;
bool pr;
if (N<2) pr=false;
else
for ( pr=true, i =2; i<=N/ 2; i++)
if (N%i ==0)
{
pr=false;
break;
}
return pr;
}
int main ( int argc, char **argv )
{
int i, k, n, nmax, nmin, p, *x;
cout<<" n = "; cin>>n; //Ввод количества элементов в массиве.
x=new int [ n ]; //Выделяем память для динамического массива x.
cout<<"Введите элементы массива X"; //Ввод элементов массива.
for ( i =0; i<n; i++)
cin>>x [ i ];
//Поиск номеров максимального и минимального элементов в массиве.
for (nmax=nmin= i =0; i<n; i++)
{
if ( x [ i ]<x [ nmin ] ) nmin= i;
if ( x [ i ]>x [ nmax ] ) nmax= i;
}
if ( nmin<nmax )
for ( p=1,k=0, i=nmin+1; i<nmax; i++)
//Обратите особое внимание на использование в следующей строке фигурной скобки
//(составного оператора). В цикле всего один оператор!!! При этом, при отсутствии
//составного оператора, программа начинает считать с ошибками!!!
{
//Проверяем, является ли очередной элемент массива простым числом.
if ( prostoe ( x [ i ] ) ) //Если x[i] — простое число.
{
//Домножаем y[i] на p, а также увеличиваем счётчик количества простых чисел в массиве.
k++;p*=x [ i ];
}
}
else
for ( p=1,k=0, i=nmax+1; i<nmin; i++)
//Проверяем, является ли очередной элемент массива простым числом.
if ( prostoe ( x [ i ] ) ) //Если x[i] — простое число.
{//Домножаем y[i] на p, а также увеличиваем счётчик количества простых чисел в массиве.
k++;p*=x [ i ];
}
//Если в массиве были простые числа, выводим среднее геометрическое этих чисел на экран
if ( k>0)
cout<<" SG "<<pow ( p, 1./ k )<<endl;
//Иначе выводим сообщение о том, что в массиве нет простых чисел.
else cout<<"Нет простых чисел в массиве"<<endl;
return 0;
}
Для удаления элемента с индексом $$m$$ из массива $$X$$, состоящего из $$n$$ элементов, нужно записать ($$m + 1$$)-й элемент на место элемента $$m, (m + 2)$$-й — на место ($$m+1$$)-го и т.д., ($$n-1$$)-й — на место ($$n-2$$)-го. После удаления количество элементов в массиве уменьшилось на 1 (рис. 5.11).
(рис 5.11) Алгоритм удаления элемента из массива
Фрагмент программы на С++:
cout<<" \n m = "; cin>>m; //Ввод номера элемента, подлежащего удалению. for ( i=m; i<n-1;X [ i ]=X [ i +1 ], i++); //Удаление m-го элемента. n--; for ( i =0; i<n-1; i++)cout<<X [ i ]<<" \t "; //Вывод изменённого массива.
При написании программ, в которых удаляются элементы из массива, следует учитывать тот факт, что после удаления элемента все элементы, расположенные после удалённого, изменяют свои номера (индексы уменьшаются на один). Это особенно важно при удалении нескольких элементов из массива. Рассмотрим несколько задач.
Задача 5.7. Удалить из массива x[20] все элементы с пятого по десятый.
При решении задач, связанных с удалением подряд идущих элементов, следует понимать, что после удаления очередного элемента следующий переместился на место удалённого. Поэтому далее нужно будет удалять элемент с тем же самым номером. В нашем случае подлежит удалению 6 элементов с пятого по десятый. Однако, реально надо будет 6 раз удалить элемент с номером 5. Блок-схема алгоритма представлена на рис 5.12, текст программы приведён далее.
#include <iostream>
#include <math.h>
using namespace std;
int main ( int argc, char **argv )
{
int i, j, n=20;
float x [ n ]; //Выделяем память для динамического массива x.
cout<<"Введите элементы массива X \n "; //Ввод элементов массива.
for ( i =0; i<n; i++)
cin>>x [ i ];
for ( j =1; j <=6; j++) //Шесть раз повторяем алгоритм удаления элемента с индексом 5.
for ( i =5; i<n-j; i++) //Удаление элемента с индексом 5.
x [ i ]=x [ i + 1 ];
cout<<"Преобразованный массив X \n "; //Вывод элементов массива.
for ( i =0; i<n-6; i++)
cout<<x [ i ]<<" \t ";
cout<<endl;
return 0;
}
(рис 5.12) Алгоритм решения задачи 5.7
Задача 5.8. Удалить из массива X[n] все положительные элементы.
При удалении отдельных элементов из массива следует учитывать: при удалении элемента (сдвиге элементов влево и уменьшении $$n$$) не надо переходить к следующему, а если элемент не удалялся, то, наоборот, надо переходить к следующему.
Далее приведён текст программы решения задачи 5.8.
#include <iostream>
#include <math.h>
using namespace std;
int main ( int argc, char **argv )
{
int i, j, n;
cout<<" n = "; cin>>n;
float x [ n ]; //Выделяем память для динамического массива x.
cout<<"Введите элементы массива X \n "; //Ввод элементов массива.
for ( i =0; i<n; i++)
cin>>x [ i ];
for ( i =0; i<n; )
if ( x [ i ] >0) //Если текущий элемент положителен,
{ //то удаляем элемент с индексом i.
for ( j= i; j<n-1; j++)
x [ j ]=x [ j + 1 ];
n--;
}
else i ++; //иначе — переходим к следующему элементу массива.
cout<<"Преобразованный массив X \n "; //Вывод элементов массива.
for ( i =0; i<n; i++)
cout<<x [ i ]<<" \t ";
cout<<endl;
return 0;
}
Задача 5.9. Удалить из массива все отрицательные элементы, расположенные между максимальным и минимальным элементами массива X[n].
Решение этой задачи можно разделить на следующие этапы:
nmax) и минимального (nmin) элементов массива.a) и большего (b) из чисел nmax и nmin.a и b. Если число окажется отрицательным, то его необходимо удалить. Однако на этом этапе нужно учитывать тонкий момент. Если просто организовать цикл от a+1 до b-1, то при удалении элемента изменяется количество элементов, расположенных между a и b, и номер последнего удаляемого элемента. Это может привести к тому, что не всегда корректно будут удаляться отрицательные элементы, расположенные между a и b. Поэтому этот цикл для удаления организован несколько иначе.Текст программы:
#include <iostream>
#include <math.h>
using namespace std;
int main ( int argc, char **argv )
{
int i, j, k, n, nmax, nmin, *x, a, b;
cout<<" n = "; cin>>n; //Ввод количества элементов в массиве.
x=new int [ n ]; //Выделяем память для динамического массива x.
cout<<"Введите элементы массива X \n "; //Ввод элементов массива.
for ( i =0; i<n; i++)
cin>>x [ i ];
//Поиск номеров максимального и минимального элементов в массиве.
for (nmax=nmin= i =0; i<n; i++)
{
if ( x [ i ]<x [ nmin ] ) nmin= i;
if ( x [ i ]>x [ nmax ] ) nmax= i;
}
//Проверяем, что раньше расположено, минимум или максимум
if ( nmin<nmax )
{
a=nmin;
b=nmax;
}
else
{
a=nmax;
b=nmin;
}
//Перебираем все элементы, расположенные между максимумом и минимумом
for ( i=a+1,k=1;k<=b-a-1;k++)
if ( x [ i ] <0) //Проверяем, является ли очередной элемент массива отрицательным.
{//Если текущий элемент массива является отрицательным числом, удаляем его
for ( j= i; j<n-1; j++)
x [ j ]=x [ j + 1 ];
n--;
}
else i ++; //Если x[i]>=0, переходим к следующему элементу.
cout<<"Преобразованный массив X\n ";
for ( i =0; i<n; i++)
cout<<x [ i ]<<" \t ";
cout<<endl;
return 0;
}
В качестве тестового можно использовать следующий массив: 34, 4, -7, -8, -10, 7, -100, -200, -300, 1. Здесь приведённая выше программа работает корректно, а вариант
for ( i=a+1; i<b; )
if ( x [ i ] <0)
{
for ( j= i; j<n-1; j++)
x [ j ]=x [ j + 1 ];
n--;
}
else i ++;
приводит к неправильным результатам. Рекомендуем читателю самостоятельно разобраться в особенностях подобных алгоритмов удаления.
Задача 5.10. В массиве X[n] найти группу наибольшей длины, которая состоит из знакочередующихся чисел.
Если будут вычислены следующие значения:
nach — номер первого элемента в группе;kon — номер последнего элемента в группе;k — количество элементов в группе.то, зная любые два из них, можно однозначно определить группу внутри массива.
Вначале количество элементов в знакочередующейся группе равно 1. Дело в том, что если мы встретим первую пару знакочередующихся элементов, то количество их в группе сразу станет равным 2. Однако все последующие пары элементов будут увеличивать k на 1. И чтобы не решать проблему построения последовательности значений k 0,2,3,4,5,..., первоначальное значение k примем равным 1. Когда будем встречать очередную пару подряд идущих соседних элементов, то k необходимо будет увеличить на 1.
Алгоритм поиска очередной группы состоит в следующем: попарно ($$x_i, x_{i+1}$$) перебираем все элементы массива (параметр цикла $$i$$ изменяется от 0 до n - 2).
Если произведение соседних элементов отрицательно ($$xi \cdot x_i+1 < 0$$), то это означает, что они имеют разные знаки и являются элементами группы. В этом случае количество ($$k$$) элементов в группе увеличиваем на 1 (k++). Если же произведение соседних элементов положительно ($$x_i\cdot x_{i+1} > 0$$), то эти элементы не являются членами группы. В этом случае возможны два варианта:
kon=i -номер последнего элемента в группе, $$k$$ — количество элементов в только что закончившейся группе.После того, как закончилась очередная группа знакочередующихся элементов, необходимо количество групп (kon_max=i). Если это не первая группа (kgr=1), то сравниваем max и длину текущей группы (k). Если k>max, то в переменную max записываем длину этой группы (max=k), а в переменную kon_max номер последнего элемента группы (kon_max=i).
После этого в переменную k опять записываем 1 для формирования новой группы элементов.
По окончанию цикла значение k может быть больше 1. Это означает, что в самом конце массива встретилась ещё одна группа. Для неё надо будет провести все те же действия, что и для любой другой группы. Далее приведён текст программы.
#include <iostream>
using namespace std;
int main ( int argc, char **argv )
{ float *x;
int i, k, n, max, kgr, kon_max;
cout<<" n = "; cin>>n; //Ввод размера массива.
x=new float [ n ]; //Выделение памяти для массива.
cout<<"Введите массив x\n "; //Ввод элементов массива.
for ( i =0; i<n; i++)
cin>>x [ i ];
//Попарно перебираем элементы массива. Количество знакочередующихся
//групп в массиве kgr=0, количество элементов в текущей группе — 1.
for ( kgr= i =0,k=1; i<n-1; i++)
//Если соседние элементы имеют разные знаки, то количество (k)
//элементов в группе увеличиваем на 1.
if ( x [ i ] * x [ i +1]<0) k++;
else
if ( k>1) //Если k>1, то только что закончилась группа, i — номер последнего элемента
{//в группе, k — количество элементов в группе. Увеличиваем kgr на 1.
kgr++;
if ( kgr==1) //Если это первая группа (kgr=1) знакочередующихся элементов,
{
max=k; //то max — длина группы (max=k),
kon_max= i; //kon_max — номер последнего элемента группы.
}
else //это не первая группа (kgr 6= 1), сравниваем max и длину текущей группы.
if ( k>max) //Если k>max,
{
max=k; //max — длина группы,
kon_max= i; //kon_max — номер последнего элемента группы.
}
k=1; //В переменную k записываем 1 для формирования новой группы элементов.
}
if ( k>1) //Если в конце массива была группа.
{
kgr++; //Количество групп увеличиваем на 1.
if ( kgr==1) //Если это первая группа,
{
max=k; //то max — длина группы,
kon_max=n-1; //группа закончилась на последнем элементе массива.
}
else
if ( k>max) //Если длина очередной группы больше max.
{
max=k; //то в max записываем длину последней группы,
kon_max=n-1; //группа закончилась на последнем элементе массива.
}
}
if ( kgr >0) //Если знакочередующиеся группы были,
{ //то выводим информацию о группе наибольшей длины,
cout<<"В массиве "<<kgr<<" групп знакочередующихся элементов\n ";
cout<<"Группа максимальной длины начинается с элемента Номер
"<<kon_max-max+1<<", её длина "<<max<<",номер последнего элемента группы
" <<kon_max<<endl;
for ( i=kon_max-max+1; i<=kon_max; i++) //а также саму группу.
cout<<x [ i ]<<" ";
cout<<endl;
}
else //Если знакочередующихся групп не было, то выводим сообщение об этом.
cout<<"В массиве нет групп знакочередующихся элементов\n ";
return 0;
}
Сортировка представляет собой процесс упорядочения элементов в массиве в порядке возрастания или убывания их значений. Например, массив $$Y$$ из $$n$$ элементов будет отсортирован в порядке возрастания значений его элементов, если
$$Y [0] < Y [1] < ... < Y [n - 1],$$и в порядке убывания, если
$$Y [0] > Y [1] > ... > Y [n - 1].$$Существует большое количество алгоритмов сортировки, но все они базируются на трёх основных:
Представим, что нам необходимо разложить по порядку карты в колоде. Для сортировки карт обменом можно разложить карты на столе лицевой стороной вверх и менять местами те карты, которые расположены в неправильном порядке, делая это до тех пор, пока колода карт не станет упорядоченной.
Для сортировки выбором из разложенных на столе карт выбирают самую младшую (старшую) карту и держат её в руках. Затем из оставшихся карт вновь выбирают наименьшую (наибольшую) по значению карту и помещают её позади той карты, которая была выбрана первой. Этот процесс повторяется до тех пор, пока вся колода не окажется в руках. Поскольку каждый раз выбирается наименьшая (наибольшая) по значению карта из оставшихся на столе карт, по завершению такого процесса карты будут отсортированы по возрастанию (убыванию).
Для сортировки вставкой из колоды берут две карты и располагают их в необходимом порядке по отношению друг к другу. Каждая следующая карта, взятая из колоды, должна быть установлена на соответствующее место по отношению к уже упорядоченным картам.
Итак, решим следующую задачу. Задан массив $$Y$$ из $$n$$ целых чисел. Расположить элементы массива в порядке возрастания их значений.
Сортировка пузырьковым методом является наиболее известной. Её популярность объясняется запоминающимся названием, которое происходит из-за подобия процессу движения пузырьков в резервуаре с водой, когда каждый пузырёк находит свой собственный уровень, и простотой алгоритма. Сортировка методом "пузырька" использует метод обменной сортировки и основана на выполнении в цикле операций сравнения и при необходимости обмена соседних элементов. Рассмотрим алгоритм пузырьковой сортировки более подробно.
Сравним нулевой элемент массива с первым, если нулевой окажется больше первого, то поменяем их местами. Те же действия выполним для первого и второго, второго и третьего, $$i$$–го и ($$i + 1$$)–го, предпоследнего и последнего элементов. В результате этих действий самый большой элемент станет на последнее ($$n-1$$)-е место. Теперь повторим данный алгоритм сначала, но последний $$(n - 1)$$-й элемент рассматривать не будем, так как он уже занял своё место. После проведения данной операции самый большой элемент оставшегося массива станет на $$(n-2)$$-е место. Так повторяем до тех пор, пока не упорядочим весь массив.
В табл. 5.4 представлен процесс упорядочивания элементов в массиве.
| Номер элемента | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| Исходный массив | 7 | 3 | 5 | 4 | 2 |
| Первый просмотр | 3 | 5 | 4 | 2 | 7 |
| Второй просмотр | 3 | 4 | 2 | 5 | 7 |
| Третий просмотр | 3 | 2 | 4 | 5 | 7 |
| Четвёртый просмотр | 2 | 3 | 4 | 5 | 7 |
Нетрудно заметить, что для преобразования массива, состоящего из $$n$$ элементов, необходимо просмотреть его $$n$$-раз, каждый раз уменьшая диапазон просмотра на один элемент. Блок–схема описанного алгоритма приведена на рис. 5.13.
(рис 5.13) Сортировка массива пузырьковым методом
Обратите внимание на то, что для перестановки элементов (рис. 5.13, блок 4) используется буферная переменная $$b$$, в которой временно хранится значение элемента, подлежащего замене. Текст программы, сортирующей элементы в массиве по возрастанию методом "пузырька", приведён далее.
#include <iostream>
using namespace std;
int main ( )
{
int n, i, b, j;
cout<<" n = "; cin>>n;
float y [ n ];
for ( i =0; i<n; i++) //Ввод массива.
{
cout<<" \n Y [ "<<i<<" ]= ";
cin>>y [ i ];
}
for ( j =1; j<n; j++) //Упорядочивание элементов в массиве по возрастанию их значений.
for ( i =0; i<n-j; i++)
if ( y [ i ]>y [ i +1 ]) //Если текущий элемент больше следующего
{
b=y [ i ]; //Сохранить значение текущего элемента
y [ i ]=y [ i + 1 ]; //Заменить текущий элемент следующим
y [ i +1]=b; //Заменить следующий элемент на сохранённый в b
}
for ( i =0; i<n; i++) cout<<y [ i ]<<" \t "; //Вывод упорядоченного массива
return 0;
}
Для перестановки элементов в массиве по убыванию их значений необходимо в программе и блок-схеме при сравнении элементов массива заменить знак ">" на "<".
Однако, в этом и во всех далее рассмотренных алгоритмах не учитывается то факт, что на каком-то этапе (или даже в начале) массив уже может оказаться отсортированным. При большом количестве элементов (сотни и даже тысячи чисел) на сортировку "вхолостую" массива тратится достаточно много времени. Ниже приведены тексты двух вариантов программы сортировки по убыванию методом пузырька, в которых алгоритм прерывается, если массив уже отсортирован.
Вариант 1.
#include <iostream>
using namespace std;
int main ( int argc, char **argv )
{
int n, i, b, j;
bool pr;
cout<<" n = "; cin>>n;
float y [ n ];
for ( i =0; i<n; i++) //Ввод массива.
{
cout<<" \n Y [ "<<i<<" ]= ";
cin>>y [ i ];
}
for ( j =1; j<n; j++) //Упорядочивание элементов массива по убыванию их значений.
{
for ( pr=false, i =0; i<n-j; i++) //Предполагаем, что массив уже отсортирован
// ( pr=false ) .
if ( y [ i ]<y [ i +1 ]) //Если текущий элемент меньше следующего
{
b=y [ i ]; //Сохранить значение текущего элемента
y [ i ]=y [ i + 1 ]; //Заменить текущий элемент следующим
y [ i +1]=b; //Заменить следующий элемент текущим
pr=true; //Если элемент менялись местами, массив ещё не отсортирован (pr=true);
}
cout<<" j = "<<j<<endl;
//Если на j-м шаге соседние элементы не менялись, то массив уже отсортирован,
if ( ! pr ) break; //повторять смысла нет;
}
for ( i =0; i<n; i++) cout<<y [ i ]<<" \t "; //Вывод упорядоченного массива
return 0;
}
Вариант 2.
#include <iostream>
using namespace std;
int main ( int argc, char **argv )
{
int n, i, b, j;
bool pr=true;
cout<<" n = "; cin>>n;
float y [ n ];
for ( i =0; i<n; i++) //Ввод массива.
{
cout<<" \n Y [ "<<i<<" ]= ";
cin>>y [ i ];
}
for ( j =1; pr; j++) //Упорядочивание элементов массива по убыванию их значений.
{ //Вход в цикл, если массив не отсортирован (pr=true).
for ( pr=false, i =0; i<n-j; i++) //Предполагаем, что массив уже отсортирован
// ( pr=false ) .
if ( y [ i ]<y [ i +1 ]) //Если текущий элемент меньше следующего
{
b=y [ i ]; //Сохранить значение текущего элемента
y [ i ]=y [ i + 1 ]; //Заменить текущий элемент следующим
y [ i +1]=b; //Заменить следующий элемент текущим
pr=true; //Элементы менялись местами, массив ещё не отсортирован ( pr=true )
}
}
for ( i =0; i<n; i++) cout<<y [ i ]<<" \t "; //Вывод упорядоченного массива
return 0;
}
Алгоритм сортировки выбором приведён в виде блок-схемы на рис. 5.14. Идея алгоритма заключается в следующем. В массиве $$Y$$, состоящем из $$n$$ элементов, ищем самый большой элемент (блоки 2–5) и меняем его местами с последним элементом (блок 7). Повторяем алгоритм поиска максимального элемента, но последний $$(n - 1)$$-й элемент не рассматриваем, так как он уже занял свою позицию.
(рис 5.14) Сортировка массива выбором наибольшего элемента
Найденный максимум ставим на $$(n - 2)$$-ю позицию. Описанную выше операцию поиска проводим $$n-1$$ раз, до полного упорядочивания элементов в массиве. Фрагмент программы выполняет сортировку массива по возрастанию методом выбора:
for ( j =1; j<n; b=y [ n-j ], y [ n-j ]=y [ nom ], y [ nom]=b, j++)
for (max=y [ 0 ], nom=0, i =1; i<=n-j; i++)
if ( y [ i ]>max) {max=y [ i ]; nom= i; }
Для упорядочивания массива по убыванию необходимо менять минимальный элемент с последним элементом.
Сортировка вставкой заключается в том, что сначала упорядочиваются два элемента массива. Затем делается вставка третьего элемента в соответствующее место по отношению к первым двум элементам. Четвёртый элемент помещают в список из уже упорядоченных трёх элементов. Этот процесс повторяется до тех пор, пока все элементы не будут упорядочены.
Прежде чем приступить к составлению блок–схемы, рассмотрим следующий пример. Пусть известно, что в массиве из десяти элементов первые шесть уже упорядочены (с нулевого по пятый), а шестой элемент нужно вставить между вторым и четвёртым. Сохраним шестой элемент во вспомогательной переменной, а на его место запишем пятый. Далее четвёртый переместим на место пятого, а третий на место четвёртого, тем самым выполнив сдвиг элементов массива на одну позицию вправо. Записав содержимое вспомогательной переменной в третью позицию, достигнем нужного результата.
Составим блок–схему алгоритма (рис. 5.15), учитывая, что возможно описанные выше действия придётся выполнить неоднократно.
(рис 5.15) Сортировка массива вставкой
Организуем цикл для просмотра всех элементов массива, начиная с первого (блок 1). Сохраним значение текущего $$i$$–го элемента во вспомогательной переменной b, так как оно может быть потеряно при сдвиге элементов (блок 2), и присвоим переменной $$j$$ значение индекса предыдущего $$(i - 1)$$–го элемента массива (блок 3). Далее движемся по массиву влево в поисках элемента, меньшего чем текущий, и, пока он не найден, сдвигаем элементы вправо на одну позицию. Для этого организуем цикл (блок 4), который прекратиться, как только будет найден элемент меньше текущего. Если такого элемента в массиве не найдётся и переменная $$j$$ станет равной (-1), то это будет означать, что достигнута левая граница массива, и текущий элемент необходимо установить в первую позицию. Смещение элементов массива вправо на одну позицию выполняется в блоке 5, а изменение счётчика $$j$$ в блоке 6. Блок 7 выполняет вставку текущего элемента в соответствующую позицию. Далее приведён фрагмент программы, реализующей сортировку массива методом вставки.
for ( i =1; i<n; y [ j +1]=b, i++) for ( b=y [ i ], j=i -1;( j >-1 b<y [ j ] ); y [ j +1]=y [ j ], j --);
Рассмотрим несколько несложных задач, связанных с упорядочиванием.
Задача 5.11. Задан массив $$a[n]$$, упорядоченный по убыванию, вставить в него некоторое число $$b$$, не нарушив упорядоченности массива.
Массив является упорядоченным по убыванию, если каждое последующий элемент массива не больше предыдущего, т. е. при выполнении следующей совокупности неравенств $$a_0 \ge a_1 \ge a_2 \ge ... \ge a_{n-3} \ge a_{n-2} \ge a_{n-1}$$.
Для вставки в подобный массив некоторого числа без нарушений упорядоченности, необходимо:
a_k b.Текст программы с комментариями приведён ниже.
#include <iostream>
2 using namespace std;
int main ( int argc, char **argv )
4 {
int i, k, n;
6 float b;
cout<<" n = "; cin>>n; //Ввод размера исходного массива.
8 float a [ n + 1 ]; //Выделение памяти с учётом предстоящей вставки одного числа в массив.
cout<<"Введите массив a \n "; //Ввод исходного упорядоченного по убыванию массива.
10 for ( i =0; i<n; i++)
cin>>a [ i ];
12 cout<<"Введите число b = "; cin>>b; //Ввод вставляемого в массив числа b .
//Если число b меньше всех элементов массива, записываем b в последний элемент массива.
14 if ( a [ n-1]>=b ) a [ n ]=b;
else //Иначе
16 {
for ( i =0; i<n; i++) //Ищем первое число, меньшее b .
18 if ( a [ i ]<=b )
{
20 k= i; //Запоминаем его номер в переменной k .
break;
22 }
for ( i=n-1; i>=k; i --) //Все элементы массива от n -1-го до k-го сдвигаем на один вправо.
24 a [ i +1]=a [ i ];
a [ k ]=b; //Вставляем число b в массив.
26 }
cout<<"Преобразованный массив a \n ";
28 for ( i =0; i<=n; i++)
cout<<a [ i ]<<" \t ";
30 return 0;
}
Обратите внимание, при решении задачи с массивом, упорядоченным по возрастанию необходимо во фрагменте со строки 14 по строку 22 заменить все операции отношения на противоположные.
Задача 5.12. Проверить, является ли массив упорядоченным по возрастанию.
Для проверки упорядоченности по возрастанию pr=true). Если хотя бы для одной пары соседних элементов выполняется условие $$a_i > a_{i+1}$$, то массив не упорядочен по возрастанию (pr=false). Текст программы с комментариями приведён ниже. Читателю предлагается преобразовать программу таким образом, чтобы осуществлялась проверка, упорядочен ли массив по убыванию.
#include <iostream> namespace std;
int main ( int argc, char **argv )
{
int i, n;
bool pr;
cout<<" n = "; cin>>n; //Ввод размера исходного массива.
float *a=new float [ n ]; //Выделение памяти для массива.
cout<<"Введите массив a \n "; //Ввод исходного массива.
for ( i =0; i<n; i++)
cin>>a [ i ];
//Предполагаем, что массив упорядочен (pr=true), перебираем все пары соседних значений
//(i — номер пары), при i равном n - 2 будем сравнивать последнюю пару a[n-2] и a[n-1].
for ( pr=true, i =0; i<n-1; i++)
//Если для очередной пары соседних элементов выяснилось, что предыдущий элемент больше
//последующего, то массив неупорядочен по возрастанию (pr=false), остальные пары соседних
//значений, можно не проверять (оператор break)
if ( a [ i ]>a [ i +1 ]) { pr=false; break; }
if ( pr ) cout<<"Массив упорядочен по возрастанию";
else cout<<"Массив не упорядочен по возрастанию";
return 0;
}
При решении некоторых задач возникает необходимость передавать имя функции как параметр. В этом случае формальным параметром является указатель на передаваемую функцию. В общем виде прототип указателя на функцию можно записать так.
type (*name_f ) ( type1, type2, type3,...)
Здесь
name_f — имя функцииtype — тип, возвращаемый функцией,type1, type2, type3,... — типы формальных параметров функции.В качестве примера рассмотрим решение широко известной математической задачи.
Задача 5.13. Вычислить $$\int\limits_a^b f(x)dx$$методами Гаусса и Чебышёва.
Кратко напомним читателю методы численного интегрирования.
Метод Гаусса состоит в следующем. Определённый интеграл непрерывной функции на интервале от -1 до 1 можно заменить суммой и вычислить по формуле $$\int\limits_{-1}^1 f(x)dx=\sum\limits_{i=1}^n A_if(t_i),\t_i$$ — точки из интервала [-1, 1], $$A_i$$ — рассчитываемые коэффициенты. Методика определения $$A_i, t_i$$ представлена в [3]. Для практического использования значения коэффициентов при $$n = 2, 3, 4, 5, 6, 7, 8$$ представлены в табл. 5.5.
| n | Массив t | Массив A |
|---|---|---|
| 2 | -0.57735027, 0.57735027 | 1,1 |
| 3 | -0.77459667, 0, 0.77459667 | 5/9, 8/9, 5/9 |
| 4 | -0.86113631, -0.33998104, 0.33998104,0.86113631 | 0.34785484, 0.65214516, 0.65214516, 0.34785484 |
| 5 | -0.90617985, -0.53846931, 0, 0.53846931,0.90617985 | 0.23692688, 0.47862868, 0.568888889,0.47862868, 0.23692688 |
| 6 | -0.93246951, -0.66120939, -0.23861919, 0.23861919, 0.66120939, 0.93246951 | 0.17132450, 0.36076158, 0.46791394, 0.46791394, 0.36076158, 0.17132450 |
| 7 | -0.94910791, -0.74153119, -0.40584515, 0, 0.40584515, 0.74153119, 0.94910791 | 0.12948496, 0.27970540, 0.38183006, 0.41795918, 0.38183006, 0.27970540, 0.12948496 |
| 8 | -0.96028986, -0.79666648, -0.52553242, -0.18343464, 0.18343464, 0.52553242, 0.79666648, 0.96028986 | 0.10122854, 0.22238104, 0.31370664, 0.36268378, 0.36268378, 0.31370664, 0.22238104, 0.10122854 |
Для вычисления интеграла непрерывной функции на интервале от $$a$$ до $$b$$ квадратурная формула Гаусса может быть записана следующим образом $$\int\limits_a^bf(x)dx=\frac{b-a}{2}\sum\limits_{i=1}^nA_if\left(\frac{b+a}{2}\cdot {\frac{b-a}{2}}t_i\right)$$, значения коэффициентов $$A_i$$ и $$t_i$$ приведены в табл. 5.5
При использовании квадратурной формулы Чебышёва, определённый интеграл непрерывной функции на интервале от -1 до 1 записывается в виде следующей формулы $$\int\limits_{-1}^1f(x)dx=\frac{2}{n}\sum\limits_{i=1}^nf(t_{i}),\t_{i}$$— точки из интервала [-1, 1]. Формула Чебышёва для вычисления интеграла на интервале от $$a$$ до $$b$$ может быть записана так $$\int\limits_{a}^{b}f(x)dx=\frac{b-a}{n}\sum\limits_{i=1}^{n}f\left(\frac{b+a}{2}\cdot {\frac{b-a}{2}}t_{i}\right)$$ Методика определения $$t_i$$ представлена в [3]. Рассмотренные формулы имеют смысл при $$n = 2, 3, 4, 5, 6, 7, 9$$, коэффициенты $$t_i$$ представлены в табл. 5.6.
| n | Массив t |
|---|---|
| 2 | -0.577350, 0.577350 |
| 3 | -0.707107, 0, -0.707107 |
| 4 | -0.794654, -0.187592, 0.187592, 0.794654 |
| 5 | -0.832498, -0.374541, 0, 0.374541, 0.832498 |
| 6 | -0.866247, -0.422519, -0.266635, 0.266635, 0.422519, 0.866247 |
| 7 | -0.883862, -0.529657, -0.323912, 0, 0.323912, 0.529657, 0.883862 |
| 9 | -0.911589, -0.601019, -0.528762, -0.167906, 0, 0.167906, 0.528762, 0.601019, 0.911589 |
Осталось написать функции вычисления определённого интеграла $$\int\limits_{a}^{b}f(x)dx$$ методами Гаусса и Чебышёва. Далее приведены тексты функций и функция main(). В качестве тестовых использовались интегралы $$\int\limits_{0}^{2}\sin ^{4}xdx\approx 0.9701,\ \int\limits_{5}^{13}\sqrt{2x-1}dx\approx 32.667$$.
#include <iostream>
#include <math.h>
using namespace std;
//Функция вычисления определённого интеграла методом Чебышёва.
//(a, b) — интервал интегрирования, *fn — указатель на функцию типа double f (double).
double int_chebishev ( double a, double b,
double (*fn ) ( double ) )
{
int i, n=9;
double s,
t [ 9 ]= {-0.911589, -0.601019, -0.528762, -0.167906, 0, 0.167906, 0.528762,
0.601019, 0.911589 };
for ( s= i =0; i<n; i++)
s+=fn ( ( b+a ) /2+(b-a ) /2*t [ i ] );
s *=(b-a ) /n;
return s;
}
//Функция вычисления определённого интеграла методом Гаусса.
//(a, b) — интервал интегрирования, *fn — указатель на функцию типа double f (double)
double int_gauss ( double a, double b, double (*fn ) ( double ) )
{
int i, n=8;
double s,
t [8]= { -0.96028986, -0.79666648, -0.52553242, -0.18343464, 0.18343464,
0.52553242, 0.79666648, 0.96028986 },
A[8]= { 0.10122854, 0.22238104, 0.31370664, 0.36268378, 0.36268378,
0.31370664, 0.22238104, 0.10122854 };
for ( s= i =0; i<n; i++)
s+=A [ i ] *fn ( ( b+a ) /2+(b-a ) /2* t [ i ] );
s *=(b-a ) / 2;
reurn s;
}
//Функции f1 и f2 типа double f(double), указатели на которые будут передаваться
//в int_gauss и int_chebishev.
double f1 ( double y )
{
return sin(y) *sin(y) *sin(y) *sin( );
}
double f2 ( double y )
{
return pow ( 2*y -1, 0.5 );
}
int main ( int argc, char **argv )
{
double a, b;
cout<<"Интеграл sin(x)^4 = \n ";
cout<<"Введите интервал интегрирования\n ";
cin>>a>>b;
//Вызов функции int_gauss(a, b, f1), f1 — имя функции, интеграл от которой надо посчитать.
cout<<"Метод Гаусса:"<<int_gauss ( a, b, f1 )<<endl;
//Вызов функции int_chebishev(a, b, f1),
//f1 — имя функции, интеграл от которой надо посчитать.
cout<<"Метод Чебышёва:"<<int_chebishev( a, b, f1 )<<endl;
cout<<"Интеграл sqrt ( 2*x-1 ) =\n ";
cout<<"Введите интервалы интегрирования\n ";
cin>>a>>b;
//Вызов функции int_gauss(a, b, f2), f2 — имя функции, интеграл от которой надо посчитать.
cout<<"Метод Гаусса:"<<int_gauss ( a, b, f2 )<<endl;
//Вызов функции int_chebishev(a, b, f2),
//f2 — имя функции, интеграл от которой надо посчитать.
cout<<"Метод Чебышёва:"<<int_chebishev ( a, b, f2 )<<endl;
return 0;
}
Результаты работы программы приведены ниже
Интеграл sin(x)^4= Введите интервалы интегрирования 0 2 Метод Гаусса:0.970118 Метод Чебышёва:0.970082 Интеграл sqrt(2*x-1)= Введите интервалы интегрирования 5 13 Метод Гаусса:32.6667 Метод Чебышёва:32.6667
Функции в С(С++) могут возвращать только одно скалярное значение, однако использование указателей в качестве аргументов функций позволяет обойти это ограничение и писать сложные функции, которые могут возвращать несколько значений.
Если в качестве аргумента в функцию передаётся указатель (адрес), то нужно иметь в виду следующее. При изменении в функции значений, хранящегося по этому адресу, будет происходить глобальное изменение значений, хранящихся по данному адресу в памяти компьютера. Таким образом, получаем механизм, с помощью которого можно возвращать множество значений. Для этого надо передавать их как адреса (указатели). В литературе по программированию подобный механизм зачастую называют передачей параметров по адресу. При этом не следует забывать о том, что этот механизм работает без всяких исключений. Любое изменение значений, переданных в функцию по адресу, приводит к глобальному изменению.
В качестве примера рассмотрим задачу удаления положительных элементов из массива (см. задачу 5.8). Пользуясь тем, что задача несложная, напишем несколько вариантов функции удаления элемента с заданным номером из массива.
Назовём функцию udal. Её входными параметрами будут:
x),n),k).Функция возвращает:
x),n).При передаче массива с помощью указателя исчезает проблема возврата в главную программу модифицированного массива, размер массива будем возвращать с помощью обычного оператора return.
Заголовок (прототип) функции udal может быть таким:
int udal( float *x, int k, int n )
Здесь x — массив, k — номер удаляемого элемента, n — размер массива.
Весь текст функции можно записать так:
int udal ( float *x, int k, int n )
{
int i;
if ( k>n-1) return n;
else
{
for ( i=k; i<n-1; i++)
x [ i ]=x [ i + 1 ];
n--;
return n;
}
}
Ниже приведён весь текст программы удаления положительных элементов из массива x c использованием функции udal и комментарии к нему.
#include <iostream>
#include <math.h>
using namespace std;
int udal ( float *x, int k, int n )
{
int i;
//Если номер удаляемого элемента больше номера последнего элемента,
//то удалять нечего, в этом случае возвращается неизменённый размер массива
if ( k>n-1) return n;
else
{
for ( i=k; i<n-1; i++) //Удаляем элемент с номером k.
x [ i ]=x [ i + 1 ];
n--;
return n; //Возвращаем изменённый размер массива.
}
}
int main ( int argc, char **argv )
{
int i, n;
cout<<" n = "; cin>>n;
float x [ n ]; //Выделяем память для динамического массива x.
cout<<"Введите элементы массива X \n "; //Ввод элементов массива.
for ( i =0; i<n; i++)
cin>>x [ i ];
for ( i =0; i<n; )
if ( x [ i ] >0)
//Если текущий элемент положителен, то для удаления элемента с индексом i вызываем
//функцию udal, которая изменяет элементы, хранящиеся по адресу x,
n=udal ( x, i, n ); //и возвращает размер массива.
else i ++; //иначе (x[i]<=0) — переходим к следующему элементу массива.
cout<<"Преобразованный массив X \n "; //Вывод элементов массива после удаления.
for ( i =0; i<n; i++)
cout<<x [ i ]<<" \t ";
cout<<endl;
return 0;
}
Эту функцию можно переписать и по-другому, передавая и массив и его размер как указатели, в этом случае функция будет такой:
void udal ( float *x, int k, int *n )
{
int i;
for ( i=k; i <*n-1; i++)
x [ i ]=x [ i + 1 ];
if ( k<*n ) --*n;
}
В этом случае изменится и обращение к udal в функции main.
Ниже приведён модифицированный текст программы удаления положительных элементов из массива x c использованием функции udal(float *x, int k, int *n) и комментарии к нему.
#include <iostream>
#include <math.h>
using namespace std;
void udal ( float *x, int k, int*n )
{
int i;
//Если номер удаляемого элемента больше номера последнего элемента, то удалять нечего,
//в этом случае возвращается неизменённый размер массива. Удаляем элемент с номером k.
for ( i=k; i <-n-1; i++)
x [ i ]=x [ i + 1 ];
//Уменьшаем на 1 значение, хранящееся по адресу n.
//Обратите внимание, что надо писать именно --*n, *n-- — НЕПРАВИЛЬНО!!!!!!!!!!!!!!!
if ( k<*n ) --*n;
}
int main ( int argc, char **argv )
{
int i, n;
cout<<" n = "; cin>>n;
float x [ n ]; //Выделяем память для динамического массива x.
cout<<"Введите элементы массива X \n "; //Ввод элементов массива.
for ( i =0; i<n; i++)
cin>>x [ i ];
for ( i =0; i<n; )
if ( x [ i ] >0) //Если текущий элемент положителен, то удаление элемента с индексом i,
//Вызываем функцию udal, которая изменяет элементы, хранящиеся по адресу x,
//и изменяет значение переменной n.
udal ( x, i,n );
else i ++; //иначе (x[i]<=0) — переходим к следующему элементу массива.
cout<<"Преобразованный массив X \n "; //Вывод элементов массива после удаления.
for ( i =0; i<n; i++)
cout<<x [ i ]<<" \t ";
cout<<endl;
return 0;
}
Авторы рекомендуют разобраться с этими примерами для понимания механизма передачи параметров по адресу.
Задача 5.14. Из массива целых чисел удалить все простые числа, значение которых меньше среднего арифметического элементов массива. Полученный массив упорядочить по возрастанию.
Алгоритм решения этой задачи без применения функций будет очень громоздким, а текст программы малопонятным. Поэтому разобьём задачу на подзадачи:
Прототипы функций, которые предназначены для решения подзадач, могут выглядеть так:
float sr_arifm(int *x, int n) — вычисляет среднее арифметическое массива x из n элементов;bool prostoe(int n) — проверяет, является ли целое число n простым, результат — логическое значение true, если число простое, и false в противном случае;void udal(int *x, int m, int *n) — удаляет элемент с номером m в массиве x из n элементов (рис. 6.4);void upor(int *x, int N, bool pr=true) — сортирует массив x из n элементов по возрастанию или по убыванию, направление сортировки зависит от значения параметра pr, если pr=true, то выполняется сортировка по возрастанию, если pr=false, то по убыванию.Текст программы с комментариями:
#include <iostream>
using namespace std;
float sr_arifm ( int *x, int n ) //Функция вычисления среднего значения.
{
int i; float s =0;
for ( i =0; i<n; s+=x [ i ], i++);
if ( n>0) return ( s /n );
else return 0;
}
bool prostoe ( int n ) //Функция для проверки, является ли число n простым.
{
bool pr; int i;
for ( pr=true, i =2; i<=n / 2; i++)
if ( n%i ==0) { pr=false; break; }
return ( pr );
}
void ud a l ( int *x, int m, int *n ) //Функция удаления элемента из массива.
{
int i;
for ( i=m; i <*n-1;*( x+ i ) =*(x+ i +1), i++);
--*n;
realloc ( ( int *) x, *n*sizeof ( int ) );
}
void upor ( int *x, int n, bool pr=true ) //Функция сортировки массива.
{
int i, j, b;
if ( pr )
{
for ( j =1; j<=n-1; j++)
for ( i =0; i<=n-1-j; i++)
if ( *( x+ i ) >*(x+ i +1) )
{
b=*(x+ i );
*( x+ i ) =*(x+ i +1);
*( x+ i +1)=b;
}
}
else
for ( j =1; j<=n-1; j++)
for ( i =0; i<=n-1-j; i++)
if ( * ( x+ i ) <*(x+ i +1) )
{
b=*(x+ i );
*( x+ i ) =*(x+ i +1);
*( x+ i +1)=b;
}
}
int main ( )
{
int *a, n, i; float sr;
cout<<" n = "; cin>>n; //Ввод размерности массива.
a=( int *) calloc ( n, sizeof ( int ) ); //Выделение памяти.
cout << "Введите массив A \n ";
for ( i =0; i<n; i++) cin >>*(a+ i ); //Ввод массива.
sr=sr_arifm ( a, n ); //Вычисление среднего арифметического.
cout<<" sr = "<<s r<<" \n "; //Вывод среднего арифметического.
for ( i =0; i<n; )
{
if ( prostoe ( * ( a+ i ) ) *( a+ i )<sr ) //Если число простое и меньше среднего,
udal ( a, i,n ); //удалить его из массива,
else i ++; //иначе, перейти к следующему элементу.
}
cout << "Массив A \n "; //Вывод модифицированного массива.
for ( i =0; i<n; i++) cout <<*(a+ i )<<" \t ";
cout<<" \n ";
upor ( a, n ); //Сортировка массива.
cout<<"Упорядоченный массив A \n "; //Вывод упорядоченного массива.
for ( i =0; i<n; i++) cout <<*(a+ i )<<" \t ";
cout<<" \n ";
free ( a ); //Освобождение памяти.
return 0;
}
Задача 5.15. Все положительные элементы целочисленного массива $$G$$ переписать в массив $$W$$. В массиве $$W$$ упорядочить по убыванию элементы, которые расположены между наибольшим и наименьшим числами-палиндромами.
Для создания этой программы напишем следующие функции:
int form (int *a, int n, int *b) — из массива целых чисел $$a$$ формирует массив положительных чисел $$b, n$$ — количество чисел в массиве $$a$$, функция возвращает число элементов в массиве $$b$$.bool palindrom (int n) — проверяет, является ли число $$n$$ палиндромом.sort (int *x, int n, int k, int p) — сортирует по возрастанию элементы массива x[n], расположенные между $$k$$-м и $$p$$-м элементами массива.Рекомендуем читателю самостоятельно разобрать текст программы, реализующей решение задачи 5.15.
#include <iostream>
#include <stdlib .h>
#include <math.h>
using namespace std;
int kvo_razryad ( int M)
{
long int k;
for ( k=1;M>9;M/=10,k++);
return k;
}
bool palindrom ( int n )
{
int k=kvo_razryad ( n ), s, p=n;
for ( s =0;p != 0; p/=10,k--)
s+=(p%10)*pow ( 10, k-1);
if ( s==n ) return true; else return false;
}
int form ( int *a, int n, int *b )
{
int i, k;
for ( i=k=0; i<n; i++)
if ( a [ i ] >0)
b [ k++]=a [ i ];
return k;
}
void sort ( int *x, int n, int k, int p )
{
int i, nom, j;
int b;
for ( i=k+1; i<p; )
{
nom= i;
for ( j= i +1; j<p; j++)
if ( x [ j ]<x [ nom ] ) nom=j;
b=x [ p -1 ]; x [ p-1]=x [ nom ]; x [ nom]=b;
p--;
}
}
int main ( int argc, char **argv )
{
int *G, *W;
int nmax, nmin, kp, i,N, k;
cout<<" N = ";
cin>>N;
G=( int *) calloc (N, sizeof ( int ) );
W=( int *) calloc (N, sizeof ( int ) );
cout<<"Ввод массива G \n ";
for ( i =0; i<N; i++)
cin>>G[ i ];
k=form (G,N,W);
cout<<"Вывод массива W \n ";
for ( i =0; i<k; i++)
cout<<W[ i ]<<" ";
cout<<endl;
for ( kp= i =0; i<k; i++)
if ( palindrom (W[ i ] ) )
{
kp++;
if ( kp==1) {nmax= i; nmin= i; }
else
{
if (W[ i ]<W[ nmin ] ) nmin= i;
if (W[ i ]>W[ nmax ] ) nmax= i;
}
}
if (nmax<nmin )
sort (W, k, nmax, nmin );
else
sort (W, k, nmin, nmax );
cout<<"Вывод преобразованного массива W \n ";
for ( i =0; i<k; i++)
cout<<W[ i ]<<" ";
cout<<endl;
return 0;
}
Результаты работы программы представлены ниже.
N=17 Ввод массива G -5 -6 191 121 12 -13 14 15 -5 100 666 -666 15251 16261 16262 991 -724 Вывод массива W 191 121 12 14 15 100 666 15251 16261 16262 991 Вывод преобразованного массива W 191 121 15251 666 100 15 14 12 16261 16262 991
Разработать программу на языке C++ для решения следующей задачи.
Разработать программу на языке C++ для решения следующей задачи.
Разработать программу на языке C++ для решения следующей задачи.
Разработать программу на языке C++ для решения следующей задачи.
Часто для работы с множеством однотипных данных (целочисленными значениями, строками, датами и т.п.) оказывается удобным использовать массивы. Например, можно создать массив для хранения фамилий студентов, обучающихся в одной группе. Вместо создания переменных для каждого студента, например Студент1, Студент2 и т.д., достаточно создать один массив, где каждой фамилии из списка будет присвоен порядковый номер. Таким образом, можно дать следующее определение. Массив — структурированный тип данных, состоящий из фиксированного числа элементов одного типа.
Массив в табл. 5.1 имеет 8 элементов, каждый элемент сохраняет число вещественного типа. Элементы в массиве пронумерованы (нумерация массивов начинается с нуля). Такого рода массив, представляющий собой просто набор данных одного и того же типа, называют простым или одномерным массивом. Для доступа к данным, хранящимся в определённом элементе массива, необходимо указать имя массива и порядковый номер этого элемента, называемый индексом.
| № элемента массива | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| Значение | 13.65 | -0.95 | 16.78 | 8.09 | -11.76 | 9.07 | 5.13 | -25.64 |
Если возникает необходимость хранения данных в виде матриц, в формате строк и столбцов, то необходимо использовать двумерные массивы. В табл. 5.2 приведён пример массива, состоящего из четырёх строк и пяти столбцов. Это двумерный массив. Строки в нём можно считать первым измерением, а столбцы вторым. Для доступа к данным, хранящимся в этом массиве, необходимо указать имя массива и два индекса, первый должен соответствовать номеру строки, а второй номеру столбца, где хранится необходимый элемент.
| 1.5 | -0.9 | 1.8 | 7.09 | -1.76 |
| 3.6 | 0.5 | 6.7 | 0.09 | -1.33 |
| 13.65 | 0.95 | 16.78 | 8.09 | -11.76 |
| 7.5 | 0.95 | 7.3 | 8.9 | 0.11 |
Если при описании массива определён его размер, то массив называют статическим. Рассмотрим работу с одномерными статическими массивами в языке С(С++). Двумерные массивы подробно описаны в следующей главе.
Описать статический массив в С(С++) можно так:
тип имя_переменной [размерность];
размерность — количество элементов в массиве. Например:
int x[10]; //Описание массива из 10 целых чисел. Первый //элемент массива имеет индекс 0, последний 9. float a[20]; //Описание массива из 20 вещественных чисел. //Первый элемент массива имеет индекс 0, последний 19.
Размерность массива и тип его элементов определяют объём памяти, который необходим для хранения массива. Рассмотрим ещё один пример описания массива:
const int n=15; //Определена целая положительная константа. double B[n]; //Описан массив из 15 вещественных чисел.
При описании статического массива в качестве размерности можно использовать целое положительное число или предопределённую константу.
Элементы массива в С(С++) нумеруются с нуля. Первый элемент всегда имеет номер ноль, а номер последнего элемента на единицу меньше заданной при его описании размерности:
char C[5]; //Описан массив из 5 символов, нумерация от 0 до 4.
Доступ к каждому элементу массива осуществляется с помощью индекса — порядкового номера элемента. Для обращения к элементу массива указывают его имя, а затем в квадратных скобках индекс:
имя_массива [индекс]
Например:
const int n=15; double C[n], S; S=C[0]+C[n-1]; //Сумма первого и последнего элементов массива С.
Массиву, как и любой другой переменной, можно присвоить начальное значение (инициализировать). Для этого значения элементов массива нужно перечислить в фигурных скобках через запятую:
тип имя_переменной[размерность]={элемент_0, элемент_1, ...};
Например:
float a[6] = { 1.2, ( float ) 3/4, 5./6, 6.1 };
//Формируется массив из шести вещественных чисел, значения элементам присваиваются по
//порядку. Элементы, значения которых не указаны (в данном случае a[4], a[5]), обнуляются:
//a[0]=1.2, a[1]=(float)3/4, a[2]=5./6, a[3]=6.1, a[4]=0, a[5]=0,
//для элементов a[1] и a[2] выполняется преобразование типов.
Рассмотрим, как хранится массив в памяти компьютера. Предположим, была описана переменная:
double x[30];
это значит, что в памяти компьютера выделяется место для хранения 30 элементов типа double. При этом адрес выделенного участка памяти хранится в переменной x. Таким образом, к значению нулевого элемента массива можно обратится двумя способами:
x[0].*x, так как адрес начала массива хранится в переменной x (по существу x — указатель на double).Если к значению x добавить единицу (число 1), то мы сместимся на один элемент типа double. Таким образом, x+1 — адрес элемента массива x с индексом 1. К первому элементу массива x также можно обратиться двумя способами: x[1] или *(x+1). Аналогично, к элементу с индексом 2 можно обращаться либо x[2], либо *(x+2). Таким образом, получается, что к элементу с индексом i можно обращаться x[i] или *(x+i).
При обработке массива (независимо от способа обращения x[i] или *(x+i))
программист сам должен контролировать, существует ли элемент массива x[i]
(или *(x+i)) и не вышла ли программа за границы массива.
Особенностью статических массивов является определение размера при написании текста программы. При необходимости увеличить размер массива, необходимо изменить текст программы и перекомпилировать её. При динамическом выделении памяти для массивов в С(С++) можно использовать указатели и операторы (функции) выделения памяти.
Для создания динамического массива необходимо [1, 8]:
тип * указатель;);Для выделения памяти в С++ можно воспользоваться оператором new или функциями языка С — calloc, malloc, realloc. Все функции находятся в библиотеке
stdlib.h.
Функция malloc выделяет непрерывный участок памяти размером size байт и возвращает указатель на первый байт этого участка. Обращение к функции имеет вид:
void* malloc ( size_t size );
где size — целое беззнаковое size_t — базовый беззнаковый целочисленный тип языка С/С++, который выбирается таким образом, чтобы в него можно было записать максимальный размер теоретически возможного массива любого типа. В 32-битной операционной системе size_t является беззнаковым 32-битным числом (максимальное значение 232- 1), в 64-битной — 64-битным беззнаковым числом (максимальное значение 264- 1).void*, которую можно преобразовать к любому необходимому типу указателя. Если выделить память невозможно, то функция вернёт пустой указатель NULL.
Например,
double *h; //Описываем указатель на double. int k; cin>>k; //Ввод целого числа k. //Выделение участка памяти для хранения k элементов типа double. //Адрес этого участка хранится в переменной h. h=(double *) malloc ( k* sizeof ( double ) ); //h — адрес начала участка памяти, //h + 1, h + 2, h + 3 и т. д. — адреса последующих элементов типа double
Функция calloc предназначена для выделения и обнуления памяти.
void * calloc ( size_t num, size_t size );
С помощью функции будет выделен участок памяти, в котором будет храниться num элементов по size байт каждый. Все элементы выделенного участка обнуляются. Функция возвращает указатель на выделенный участок или NULL при невозможности выделить память.
Например,
float *h; //Описываем указатель на float. int k; cin>>k; //Ввод целого числа k. //Выделение участка памяти для хранения k элементов типа float. //Адрес этого участка хранится в переменной h . h=( float *) calloc ( k, sizeof ( float ) ); //h — адрес начала участка памяти, //h + 1, h + 2, h + 3 и т. д. — адреса последующих элементов типа float.
Функция realloc изменяет размер ранее выделенного участка памяти. Обращаются к функции так:
void *realloc ( void *p, size_t size );
где p — указатель на область памяти, размер которой нужно изменить на size. Если в результате работы функции меняется адрес области памяти, то новый адрес вернётся в качестве результата. Если фактическое значение первого параметра NULL, то функция realloc работает так же, как и функция malloc, то есть выделяет участок памяти размером size байт.
Для освобождения выделенной памяти используется функция free. Обращаются к ней так:
void free ( void *p );
где p — указатель на участок памяти, ранее выделенный функциями malloc, calloc или realloc.
В языке С++ есть операторы new для выделения и delete для освобождения участка памяти.
Для выделения памяти для хранения n элементов одного типа оператор new имеет вид [5]:
x=new type [ n ];
type — тип элементов, для которых выделяется участок памяти;
n — количество элементов;
x — указатель на тип данных type, в котором будет храниться адрес выделенного участка памяти.
При выделении памяти для одного элемента оператор new имеет вид:
x=new type;
Например,
float *x; //Указатель на тип данных float . int n; cin>>n; //Ввод n //Выделение участка памяти для хранения n элементов типа float. Адрес этого участка хранится //в переменной x; x+1, x+2, x+3 и т. д. — адреса последующих элементов типа float.
Освобождение выделенного с помощью new участка памяти осуществляется с помощью оператора delete следующей структуры:
delete [ ] p;
p — указатель (адрес участка памяти, ранее выделенного с помощью оператора
new).
В чём же отличие статического и динамического массива?
Предположим, описан статический массив: double x[75];
Это означает, что выделен участок памяти для хранения 75 элементов типа double (массив из 75 элементов типа double). Адрес начала массива хранится в переменной x. Для обращения к $$i$$-му элементу можно использовать конструкции x[i] или *(x+i). Если понадобится обрабатывать массив более, чем из 75 элементов, то придётся изменить описание и перекомпилировать программу. При работе с массивами небольшой размерности, большая часть памяти, выделенной под статический массив, будет использоваться вхолостую.
Допустим, задан динамический массив, например
double *x; //Указатель на double int k; cin>>k; //Вводим размер массива k. //Выделение памяти для хранения динамического массива из k чисел. x=new double [ k ]; //Адрес начала массива хранится в переменной x. x=(double *) calloc ( k, sizeof ( float ) ); //Память можно будет выделить так x=(double *) malloc ( k* sizeof ( float ) ); //или так
В этом случае, мы имеем указатель на тип данных double, вводим $$k$$ — размер динамического массива, выделяем участок памяти для хранения $$k$$ элементов типа double (массив из $$k$$ элементов типа double). Адрес начала массива хранится в переменной x. Для обращения к $$i$$-му элементу можно использовать конструкции x[i] или *(x+i). В случае динамического массива мы сначала определяем его размер (в простейшем случае просто вводим размер массива с клавиатуры), а потом выделяем память для хранения реального количества элементов. Основное отличие статического и динамического массивов состоит в том, что в динамическом массиве выделяется столько элементов, сколько необходимо.
Имя массива (статического или динамического) это адрес начала выделенного для него участка памяти, значит обращаться к элементам массива можно двумя способами — x[i] или *(x+i).
Все манипуляции с массивами в С++ осуществляются поэлементно. Организовывается цикл, в котором происходит последовательное обращение к нулевому, первому, второму и т.д. элементам массива. В общем виде алгоритм обработки массива выглядит так, как показано на рис. 5.1.
Алгоритмы, с помощью которых обрабатывают одномерные массивы, похожи на обработку последовательностей (вычисление суммы, произведения, поиск элементов по определённому признаку, выборки и т. д.). Отличие заключается в том, что в массиве одновременно доступны все его компоненты, поэтому становится возможной, например, сортировка его элементов и другие, более сложные преобразования.
(рис 5.1) Алгоритм обработки элементов массива
Ввод и вывод массивов также осуществляется поэлементно. Блок-схемы алгоритмов ввода и вывода элементов массива X[N] изображены на рис. 5.2,рис. 5.3.
Рассмотрим несколько вариантов ввода массива:
//Вариант 1. Ввод массива с помощью функции scanf.
//При организации ввода используются специальные символы: табуляция — \t
//и переход на новую строку — \n.
#include <stdio.h>
int main ( )
{
float x [ 10 ]; int i, n;
printf ( " \n N = " ); scanf ( " % d ",n ); //Ввод размерности массива.
printf ( " \n Введите элементы массива X \n " );
for ( i =0; i<n; i++)
scanf ( " % f ", x+ i ); //Ввод элементов массива в цикле.
//Обратите внимание, x + i — адрес i-го элемента массива.
return 0;
}
(рис 5.2) Алгоритм ввода массива X[N]
Результат работы программы:
N=3 Введите элементы массива X 1.2 -3.8 0.49
(рис 5.3) Алгоритм вывода массива X[N]
//Вариант 2. Ввод массива с помощью функции scanf и вспомогательной переменной b.
#include <stdio.h>
int main ( )
{
float x [ 10 ], b; int i, n;
printf ( " \n N = " ); scanf ( " % d ",n ); //Ввод размерности массива.
printf ( " \n Массив X \n " );
for ( i =0; i<n; i++)
{
printf ( " \n Элемент % d \t ", i ); //Сообщение о вводе элемента.
scanf ( " % f ",b ); //Ввод переменной b.
x [ i ]=b; //Присваивание элементу массива значения переменной b .
}
return 0;
}
Результат работы программы:
N=4 Массив X Элемент 0 8.7 Элемент 1 0.74 Элемент 2 -9 Элемент 3 78
//Вариант 3. Ввод динамического массива с помощью cin.
#include <iostream>
using namespace std;
int main ( )
{
int *X,N, i;
cout<<" \n N = "; cin>>N; //Ввод размерности массива.
X=new int [N ]; //Выделение памяти для динамического массива из N элементов.
for ( i =0; i<N; i++)
{
cout<<" \n X [ "<<i<<" ]= "; //Сообщение о вводе элемента.
cin>>X [ i ]; //Ввод элементов массива в цикле.
}
delete [ ] X;
return 0;
}
Результат работы программы:
N=4 X[0]=1 X[1]=2 X[2]=4 X[3]=5
Вывод статического или динамического массива можно осуществить несколькими способами:
//Вариант 1. Вывод массива в виде строки. for ( i =0; i<n; i++) printf ( " % f \t ",X [ i ] ); //Вариант 2. Вывод массива в виде столбца. for ( i =0; i<n; i++) printf ( " \n % f ",X [ i ] ); //Вариант 3. Вывод массива в виде строки. for ( i =0; i<N; i++) cout <<" \t X [ "<<i<<" ]= "<<X [ i ]; //Вариант 4. Вывод массива в виде столбца. for ( i =0; i<N; i++) cout <<" \n X [ "<<i<<" ]= "<<X [ i ];
Дан массив $$X$$, состоящий из $$N$$ элементов. Найти сумму элементов этого массива. Процесс накапливания суммы элементов массива достаточно прост и практически ничем не отличается от суммирования значений некоторой числовой последовательности. Переменной $$S$$ присваивается значение, равное нулю, затем к переменной $$S$$ последовательно добавляются элементы массива $$X$$. Блок-схема алгоритма расчёта суммы приведена на рис. 5.4.
(рис 5.4) Алгоритм вычисления суммы элементов массива
Соответствующий алгоритму фрагмент программы будет иметь вид:
for ( S= i =0; i<N; i++) S+=X [ i ]; cout<<" S = "<<S<<" \n ";
Дан массив $$X$$, состоящий из $$N$$ элементов. Найти произведение элементов этого массива. Решение этой задачи сводится к тому, что значение переменной $$P$$, в которую предварительно была записана единица, последовательно умножается на значение $$i$$–го элемента массива. Блок-схема алгоритма приведена на рис. 5.5.
Соответствующий фрагмент программы будет иметь вид:
for (P=1, i =0; i<N; i++) P*=X [ i ]; cout<<" P = "<<P<<" \n ";
Задача 5.1. Задан массив целых чисел. Найти сумму простых чисел и произведение отрицательных элементов массива.
Алгоритм решения задачи состоит из следующих этапов.
(рис 5.5) Вычисление произведения элементов массива
Блок-схема решения задачи представлена на рис. 5.6. Для решения задачи применим функцию (prostoe) проверки, является ли число простым. Текст программы с подробными комментариями приведён далее.
#include <iostream>
using namespace std;
//Текст функции prostoe.
bool prostoe ( int N)
{
int i;
bool pr;
if (N<2) pr=false;
else
for ( pr=true, i =2; i<=N/ 2; i++)
if (N%i ==0)
{
pr=false;
break;
}
return pr;
}
int main ( )
{
int *X, i,N, S, P;
cout<<"Введите размер массива "; cin>>N; //Ввод размерности массива.
X=new int [N ]; //Выделение памяти для хранения динамического массива X.
cout<<"Ведите массив X \n "; //Ввод массива X.
for ( i =0; i<N; i++)
{ cout<<" X ( "<<i<<" ) = "; cin>>X [ i ]; }
for (P=1,S= i =0; i<N; i++) //В цикле перебираем все элементы массива
{
//Если очередной элемент массива — простое число, добавляем его к сумме.
if ( prostoe (X [ i ] ) ) S+=X [ i ];
//Если очередной элемент массива отрицателен, умножаем его на P.
if (X [ i ] <0) P*=X [ i ];
}
cout << " S = " <<S<<" \t P = "<<P<< endl; //Вывод S и P на экран.
delete [ ] X; //Освобождение занимаемой массивом X памяти.
return 0;
}
(рис 5.6) Блок-схема алгоритма решения задачи 5.1
Результаты работы программы представлены ниже.
Введите размер массива 10 Ведите массив Х X(0)=-7 X(1)=-9 X(2)=5 X(3)=7 X(4)=2 X(5)=4 X(6)=6 X(7)=8 X(8)=10 X(9)=12 S=14 P=63
Задача 5.2. Дан массив $$A$$, состоящий из $$k$$ целых положительных чисел. Записать все чётные по значению элементы массива $$A$$ в массив $$B$$.
На рис. 5.7 представлен фрагмент алгоритма решения данной задачи. Здесь индексы массива $$A$$ хранятся в переменной $$i$$, а для номеров массива $$B$$ зарезервирована переменная $$m$$. Операция, выполняемая в блоке 1, означает, что в массиве $$A$$ может не быть искомых элементов. Далее организован цикл (блок 2), с помощью которого можно обращаться к элементам массива $$A$$. Если условие в блоке 3 выполняется, то переменная $$m$$ увеличивается на единицу, а значение соответствующего элемента массива $$A$$ записывается в массив $$В$$ под номером $$m$$ (блок 4). Условный блок 5 необходим для того, чтобы проверить, выполнилось ли хотя бы раз условие поиска (блок 2). Если массив $$B$$ сформирован, то он выводится на экран (блоки 6, 7), в противном случае выдаётся соответствующее сообщение (блок 8).
(рис 5.7) Алгоритм решения задачи 5.2
Приведённый ниже фрагмент программы реализует описанный алгоритм:
for (m=-1, i =0; i<k; i++)
{
if (A [ i ]%2==0) //Если элемент чётный, то
{
m++; //увеличить значение индекса массива В
B [m]=A [ i ]; //и записать элемент в массив В.
}
}
if (m>-1) //Если чётные элементы найдены, то распечатать сформированный массив.
for ( i =0; i<=m; cout<<B [ i ]<<" \t ", i++);
else //иначе, выдать сообщение,
cout<<"Массив B не сформирован!"<<endl;
Дан массив X, состоящий из n элементов. Найти максимальный элемент массива и номер, под которым он хранится в массиве.
Алгоритм решения задачи следующий. Пусть в переменной с именем Max хранится значение максимального элемента массива, а в переменной с именем Nmax – его номер. Предположим, что нулевой элемент массива является максимальным, и запишем его в переменную Max, а в Nmax — его номер (то есть ноль). Затем все элементы, начиная с первого, сравниваем в цикле с максимальным. Если текущий элемент массива оказывается больше максимального, то записываем его в переменную Max, а в переменную Nmax – текущее значение индекса i. Процесс определения максимального элемента в массиве приведён в табл. 5.3 и изображён при помощи блок-схемы на рис. 5.8. Соответствующий фрагмент программы имеет вид:
for (Max=X [ 0 ], Nmax= i =0; i<n; i++)
if (Max<X [ i ] )
{
Max=X [ i ];
Nmax= i;
}
cout<<" Max = "<<Max<<" \n ";
cout<<" Nmax = "<<Nmax<<" \n ";
| Номера элементов | 0 | 1 | 2 | 3 | 4 | 5 |
| Исходный массив | 4 | 7 | 3 | 8 | 9 | 2 |
| Значение переменной $$Max$$ | 4 | 7 | 7 | 8 | 9 | 9 |
| Значение переменной $$Nmax$$ | 1 | 2 | 2 | 4 | 5 | 5 |
(рис 5.8) Поиск максимального элемента и его номера в массиве
При поиске максимального элемента и его номера, можно найти только номер максимального элемента, а потом по номеру извлечь значение максимального элемента из массива.
Текст программы поиска номера максимального элемента:
#include <iostream>
using namespace std;
int main ( )
{
float *X;
int i,N, nom;
cout<<"Введите размер массива "; cin>>N; //Ввод размерности динамического массива
X=new float [N ]; //Выделение памяти для хранения динамического массива X.
cout<<"Введите элементы массива X \n "; //Ввод динамического массива X.
for ( i =0; i<N; i++)
cin>>X [ i ];
//В переменной nom будем хранить номер максимального элемента.
nom=0; //Предположим, что максимальным элементом является элемент с номером 0.
for ( i =1; i<N; i++)
//Если очередной элемент больше X[nom], значит nom не является номером максимального
//элемента, элемент с номером i больше элемента X[nom], поэтому переписываем
//число i в переменную nom.
if (X [ i ]>X [ nom ] ) nom= i;
cout << "Максимальный элемент= "<<X [ nom]<<", его номер= " << nom << endl;
return 0;
}
Совет. Алгоритм поиска минимального элемента в массиве будет отличаться от приведённого выше лишь тем, что в условном блоке и, соответственно, в конструкции if текста программы знак поменяется с "<" на ">".
Рассмотрим несколько задач.
Задача 5.3. Найти минимальное простое число в целочисленном массиве x[N].
Эта задача относится к классу задач поиска минимума (максимума) среди элементов, удовлетворяющих условию. Подобные задачи рассматривались в задачах на обработку последовательности чисел. Здесь поступим аналогично. Блок-схема приведена на рис. 5.9.
Необходимо первое простое число объявить минимумом, а все последующие простые элементы массива сравнивать с минимумом. Будем в цикле последовательно проверять, является ли элемент массива простым числом (функция prostoe). Если X[i] является простым числом, то количество простых чисел (k) увеличиваем на 1 (k++), далее, проверяем, если k равен 1 (if (k==1)), то этот элемент объявляем минимальным (min=x[i]; nom=i;), иначе сравниваем его с минимальным (if (x[i]<min) {min=x[i];nom=i;}).
Текст программы:
#include <iostream>
using namespace std;
bool prostoe ( int N)
{ int i; bool pr;
if (N<2) pr=false;
else
for ( pr=true, i =2; i<=N/ 2; i++)
if (N%i ==0)
{
pr=false;
break;
}
return pr;
}
int main ( int argc, char **argv )
{
int i, k, n, nom, min, *x;
cout<<" n = "; cin>>n; //Ввод количества элементов в массиве.
x=new int [ n ]; //Выделяем память для динамического массива x.
cout<<"Введите элементы массива X "; //Ввод элементов массива.
for ( i =0; i<n; i++)
cin>>x [ i ];
//С помощью цикла по переменной i, перебираем все элементы в массиве x,
//k — количество простых чисел в массиве.
for ( i=k=0; i<n; i++)
//Проверяем, является ли очередной элемент массива простым числом.
if ( prostoe ( x [ i ] ) ) //Если x[i] — простое число.
{
k++; //Увеличиваем счётчик количества простых чисел в массиве.
//Если текущий элемент является первым простым числом в массиве,
// объявляем его минимумом, а его номер сохраняем в перемнной nom.
if ( k==1) {min=x [ i ]; nom= i; }
else
//Все последующие простые числа в массиве сравниваем с минимальным простым числом.
//Если текущее число меньше min, перезаписываем его в переменную min,
//а его номер — в переменную nom.
if ( x [ i ]<min ) {min=x [ i ]; nom= i; }
}
//Если в массиве были простые числа, выводим значение и номер минимального простого числа.
if ( k>0)
cout<<" min = "<<min<<" \ tnom = "<<nom<<endl;
//Иначе выводим сообщение о том, что в массиве нет простых чисел.
else cout<<"Нет простых чисел в массиве"<<endl;
return 0;
}
(рис 5.9) Блок-схема решения задачи 5.3
Аналогичным образом можно написать программу любой задачи поиска минимума (максимума) среди элементов, удовлетворяющих какому-либо условию (минимум среди положительных элементов, среди чётных и т.д.).
Задача 5.4. Найти $$k$$ минимальных чисел в вещественном массиве.
Перед решением этой довольно сложной задачи рассмотрим более простую задачу.
Найти два наименьших элемента в массиве. Фактически надо найти номера (nmin1, nmin2) двух наименьших элементов массива. На первом этапе надо найти номер минимального (nmin1) элемента массива. На втором этапе надо искать номер минимального элемента, при условии, что он не равен nmin1. Вторая часть очень похожа на предыдущую задачу (минимум среди элементов, удовлетворяющих условию, в этом случае условие имеет вид i!=nmin1).
Решение задачи с комментариями:
#include <iostream>
using namespace std;
int main ( int argc, char **argv )
{
int kvo, i, n, nmin1, nmin2;
double *X;
cout<<" n = "; cin>>n;
X=new double [ n ];
cout<<"Введите элементы массива X \n ";
for ( i =0; i<n; i++)
cin>>X [ i ];
//Стандартный алгоритм поиска номера первого минимального элемента (nmin1).
for ( nmin1=0, i =1; i<n; i++)
if (X [ i ]<X [ nmin1 ] ) nmin1= i;
//Второй этап — поиск номера минимального элемента среди элементов, номер
//которых не совпадает nmin1. kvo — количество таких элементов.
for ( kvo= i =0; i<n; i++)
if ( i !=nmin1 ) //Если номер текущего элемента не совпадает с nmin1,
{
kvo++; //увеличиваем количество таких элементов на 1.
//Номер первого элемента, индекс которого не равен nmin1,
//объявляем номером второго минимального элемента.
if ( kvo==1) nmin2= i;
else
//очередной элемент, индекс которого не равен nmin1, сравниваем с минимальным,
//если он меньше, номер перезаписываем в переменную nmin2.
if (X [ i ]<X [ nmin2 ] ) nmin2= i;
}
//Вывод двух минимальных элементов и их индексов.
cout<<" nmin1 = "<<nmin1<<" \ tmin1 = "<<X [ nmin1]<< endl;
cout<<" nmin2 = "<<nmin2<<" \ tmin2 = "<<X [ nmin2]<< endl;
return 0;
}
По образу и подобию этой задачи можно написать задачу поиска трёх минимальных элементов в массиве. Первые два этапа (поиск номеров двух минимальных элементов в массиве) будут полным повторением кода, приведённого выше. На третьем этапе нужен цикл, в котором будем искать номер минимального элемента, при условии, что его номер не равен nmin1 и nmin2. Авторы настоятельно рекомендуют читателям самостоятельно написать подобную программу. Аналогично можно написать программу поиска четырёх минимальных элементов. Однако при этом усложняется и увеличивается код программы. К тому же, рассмотренный приём не позволит решить задачу в общем случае (найти k минимумов).
Для поиска $$k$$ минимумов в массиве можно поступить следующим образом. Будем формировать массив
#include <iostream>
using namespace std;
int main ( int argc, char **argv )
{
int p, j, i, n, *nmin, k, kvo, nmin_temp;
bool pr;
double *x;
cout<<" n = "; cin>>n;
x=new double [ n ];
cout<<"Введите элементы массива Х\n ";
for ( i =0; i<n; i++)
cin>>x [ i ];
cout<<"Введите количество минимумов\n "; cin>>k;
nmin=new int [ k ];
for ( j =0; j<k; j++) //Цикл по переменной j для поиска номера j + 1 минимального элемента
{
kvo=0;
for ( i =0; i<n; i++) //Перебираем все элементы массива.
{
//Цикл по переменной p проверяет, содержится ли номер i в массиве nmin.
pr=false;
for ( p=0;p<j; p++)
if ( i==nmin [ p ] ) pr=true;
if ( ! pr ) //Если не содержится, то количество элементов увеличить на 1.
{
kvo++;
//Если kvo=1, то найден первый элемент, который не содержится в массиве
//nmin, его номер объявляем номером минимального элемента массива
if ( kvo==1) nmin_temp= i;
else
//Если kvo>1, сравниваем текущий элемент x[i] с минимальным.
if ( x [ i ]<x [ nmin_temp ] ) nmin_temp= i;
}
}
nmin [ j ]=nmin_temp; //Номер очередного минимального элемента записываем в массив.
}
for ( j =0; j<k; j++) //Вывод номеров и значений k минимальных элементов массива.
cout<<" nmin1 = "<<nmin [ j ]<<" \ tmin1 = "<<x [ nmin [ j ]]<< endl;
return 0;
}
Проверку, содержится ли число i в массиве nmin, можно оформить в виде функции, тогда программа может быть записана следующим образом:
(рис 5.10) Блок-схема алгоритма поиска k минимальных элементов в массиве x.
#include <iostream>
using namespace std;
//Функция проверяет, содержится ли число i в массиве x из n элементов.
//Функция возвращает true, если содержится, и false, если не содержится.
bool proverka ( int i, int *x, int n )
{
bool pr;
int p;
pr=false;
for ( p=0;p<n; p++)
if ( i==x [ p ] ) pr=true;
return pr;
}
int main ( int argc, char **argv )
{
int j, i, n, *nmin, k, kvo, nmin_temp;
double *x;
cout<<" n = "; cin>>n;
x=new double [ n ];
cout<<"Введите элементы массива Х\n ";
for ( i =0; i<n; i++)
cin>>x [ i ];
cout<<"Введите количество минимумов\n "; cin>>k;
nmin=new int [ k ];
for ( j =0; j<k; j++) //Цикл по переменной j для поиска номера j + 1 минимального элемента
{
kvo=0;
for ( i =0; i<n; i++) //Перебираем все элементы массива.
{
//Вызов функции proverka, определяем, содержится ли число i в массиве nmin из j элементов
if ( ! proverka ( i, nmin, j ) )
{
kvo++;
if ( kvo==1) nmin_temp= i;
else
if ( x [ i ]<x [ nmin_temp ] ) nmin_temp= i;
}
}
nmin [ j ]=nmin_temp;
}
for ( j =0; j<k; j++) //Вывод номеров и значений k минимальных элементов массива.
cout<<" nmin1 = "<<nmin [ j ]<<" \ tmin1 = "<<x [ nmin [ j ]]<< endl;
return 0;
}
Авторы настоятельно рекомендуют читателю разобрать все версии решения задачи 5.4.
Задача 5.5. Поменять местами максимальный и минимальный элементы в массиве X.
Алгоритм решения задачи можно разбить на следующие этапы.
nmax) и минимального (nmin) элементов массива.X[nmax]=X [nmin]; X[nmin]=X[nmax];). При таком присваивании мы сразу же теряем максимальный элемент. Поэтому нам понадобится временная (буферная) переменная temp. Обмен элементов местами должен быть таким: temp=X[nmax]; X[nmax]=X[nmin]; X[nmin]=temp;Далее приведён текст программы с комментариями.
#include <iostream>
using namespace std;
int main ( int argc, char **argv )
{
int i,N, nmax, nmin;
float temp;
cout<<" N = "; cin>>N;
float X [N ];
cout<<"Введите элементы массива Х\n ";
for ( i =0; i<N; i++)
cin>>X [ i ];
//Поиск номеров максимального и минимального элементов массива.
for (nmax=nmin=0, i =1; i<N; i++)
{
if (X [ i ]<X [ nmin ] ) nmin= i;
if (X [ i ]>X [ nmax ] ) nmax= i;
}
//Обмен максимального и минимального элементов местами.
temp=X [ nmax ]; X [ nmax]=X [ nmin ]; X [ nmin ]=temp;
cout<<"Преобразованный массив Х\n "; //Вывод преобразованного массива.
for ( i =0; i<N; i++)
cout<<X [ i ]<<" ";
cout<<endl;
return 0;
}
Задача 5.6. Найти среднее геометрическое среди простых чисел, расположенных между максимальным и минимальным элементами массива.
Среднее геометрическое $$k$$ элементов $$(SG)$$ можно вычислить по формуле $$SG=\sqrt[{k}]{P},\ P$$ — произведение $$k$$ элементов. При решении этой задачи необходимо найти произведение и количество простых чисел, расположенных между максимальным и минимальным элементами.
Алгоритм решения задачи состоит из следующих этапов:
nmax) и минимального (nmin) элементов массива.При решении этой задачи следует учитывать, что неизвестно, какой элемент расположен раньше — максимальный или минимальный.
Текст программы с комментариями приведён ниже.
#include <iostream>
#include <math.h>
using namespace std;
bool prostoe ( int N)
{
int i;
bool pr;
if (N<2) pr=false;
else
for ( pr=true, i =2; i<=N/ 2; i++)
if (N%i ==0)
{
pr=false;
break;
}
return pr;
}
int main ( int argc, char **argv )
{
int i, k, n, nmax, nmin, p, *x;
cout<<" n = "; cin>>n; //Ввод количества элементов в массиве.
x=new int [ n ]; //Выделяем память для динамического массива x.
cout<<"Введите элементы массива X"; //Ввод элементов массива.
for ( i =0; i<n; i++)
cin>>x [ i ];
//Поиск номеров максимального и минимального элементов в массиве.
for (nmax=nmin= i =0; i<n; i++)
{
if ( x [ i ]<x [ nmin ] ) nmin= i;
if ( x [ i ]>x [ nmax ] ) nmax= i;
}
if ( nmin<nmax )
for ( p=1,k=0, i=nmin+1; i<nmax; i++)
//Обратите особое внимание на использование в следующей строке фигурной скобки
//(составного оператора). В цикле всего один оператор!!! При этом, при отсутствии
//составного оператора, программа начинает считать с ошибками!!!
{
//Проверяем, является ли очередной элемент массива простым числом.
if ( prostoe ( x [ i ] ) ) //Если x[i] — простое число.
{
//Домножаем y[i] на p, а также увеличиваем счётчик количества простых чисел в массиве.
k++;p*=x [ i ];
}
}
else
for ( p=1,k=0, i=nmax+1; i<nmin; i++)
//Проверяем, является ли очередной элемент массива простым числом.
if ( prostoe ( x [ i ] ) ) //Если x[i] — простое число.
{//Домножаем y[i] на p, а также увеличиваем счётчик количества простых чисел в массиве.
k++;p*=x [ i ];
}
//Если в массиве были простые числа, выводим среднее геометрическое этих чисел на экран
if ( k>0)
cout<<" SG "<<pow ( p, 1./ k )<<endl;
//Иначе выводим сообщение о том, что в массиве нет простых чисел.
else cout<<"Нет простых чисел в массиве"<<endl;
return 0;
}
Для удаления элемента с индексом $$m$$ из массива $$X$$, состоящего из $$n$$ элементов, нужно записать ($$m + 1$$)-й элемент на место элемента $$m, (m + 2)$$-й — на место ($$m+1$$)-го и т.д., ($$n-1$$)-й — на место ($$n-2$$)-го. После удаления количество элементов в массиве уменьшилось на 1 (рис. 5.11).
(рис 5.11) Алгоритм удаления элемента из массива
Фрагмент программы на С++:
cout<<" \n m = "; cin>>m; //Ввод номера элемента, подлежащего удалению. for ( i=m; i<n-1;X [ i ]=X [ i +1 ], i++); //Удаление m-го элемента. n--; for ( i =0; i<n-1; i++)cout<<X [ i ]<<" \t "; //Вывод изменённого массива.
При написании программ, в которых удаляются элементы из массива, следует учитывать тот факт, что после удаления элемента все элементы, расположенные после удалённого, изменяют свои номера (индексы уменьшаются на один). Это особенно важно при удалении нескольких элементов из массива. Рассмотрим несколько задач.
Задача 5.7. Удалить из массива x[20] все элементы с пятого по десятый.
При решении задач, связанных с удалением подряд идущих элементов, следует понимать, что после удаления очередного элемента следующий переместился на место удалённого. Поэтому далее нужно будет удалять элемент с тем же самым номером. В нашем случае подлежит удалению 6 элементов с пятого по десятый. Однако, реально надо будет 6 раз удалить элемент с номером 5. Блок-схема алгоритма представлена на рис 5.12, текст программы приведён далее.
#include <iostream>
#include <math.h>
using namespace std;
int main ( int argc, char **argv )
{
int i, j, n=20;
float x [ n ]; //Выделяем память для динамического массива x.
cout<<"Введите элементы массива X \n "; //Ввод элементов массива.
for ( i =0; i<n; i++)
cin>>x [ i ];
for ( j =1; j <=6; j++) //Шесть раз повторяем алгоритм удаления элемента с индексом 5.
for ( i =5; i<n-j; i++) //Удаление элемента с индексом 5.
x [ i ]=x [ i + 1 ];
cout<<"Преобразованный массив X \n "; //Вывод элементов массива.
for ( i =0; i<n-6; i++)
cout<<x [ i ]<<" \t ";
cout<<endl;
return 0;
}
(рис 5.12) Алгоритм решения задачи 5.7
Задача 5.8. Удалить из массива X[n] все положительные элементы.
При удалении отдельных элементов из массива следует учитывать: при удалении элемента (сдвиге элементов влево и уменьшении $$n$$) не надо переходить к следующему, а если элемент не удалялся, то, наоборот, надо переходить к следующему.
Далее приведён текст программы решения задачи 5.8.
#include <iostream>
#include <math.h>
using namespace std;
int main ( int argc, char **argv )
{
int i, j, n;
cout<<" n = "; cin>>n;
float x [ n ]; //Выделяем память для динамического массива x.
cout<<"Введите элементы массива X \n "; //Ввод элементов массива.
for ( i =0; i<n; i++)
cin>>x [ i ];
for ( i =0; i<n; )
if ( x [ i ] >0) //Если текущий элемент положителен,
{ //то удаляем элемент с индексом i.
for ( j= i; j<n-1; j++)
x [ j ]=x [ j + 1 ];
n--;
}
else i ++; //иначе — переходим к следующему элементу массива.
cout<<"Преобразованный массив X \n "; //Вывод элементов массива.
for ( i =0; i<n; i++)
cout<<x [ i ]<<" \t ";
cout<<endl;
return 0;
}
Задача 5.9. Удалить из массива все отрицательные элементы, расположенные между максимальным и минимальным элементами массива X[n].
Решение этой задачи можно разделить на следующие этапы:
nmax) и минимального (nmin) элементов массива.a) и большего (b) из чисел nmax и nmin.a и b. Если число окажется отрицательным, то его необходимо удалить. Однако на этом этапе нужно учитывать тонкий момент. Если просто организовать цикл от a+1 до b-1, то при удалении элемента изменяется количество элементов, расположенных между a и b, и номер последнего удаляемого элемента. Это может привести к тому, что не всегда корректно будут удаляться отрицательные элементы, расположенные между a и b. Поэтому этот цикл для удаления организован несколько иначе.Текст программы:
#include <iostream>
#include <math.h>
using namespace std;
int main ( int argc, char **argv )
{
int i, j, k, n, nmax, nmin, *x, a, b;
cout<<" n = "; cin>>n; //Ввод количества элементов в массиве.
x=new int [ n ]; //Выделяем память для динамического массива x.
cout<<"Введите элементы массива X \n "; //Ввод элементов массива.
for ( i =0; i<n; i++)
cin>>x [ i ];
//Поиск номеров максимального и минимального элементов в массиве.
for (nmax=nmin= i =0; i<n; i++)
{
if ( x [ i ]<x [ nmin ] ) nmin= i;
if ( x [ i ]>x [ nmax ] ) nmax= i;
}
//Проверяем, что раньше расположено, минимум или максимум
if ( nmin<nmax )
{
a=nmin;
b=nmax;
}
else
{
a=nmax;
b=nmin;
}
//Перебираем все элементы, расположенные между максимумом и минимумом
for ( i=a+1,k=1;k<=b-a-1;k++)
if ( x [ i ] <0) //Проверяем, является ли очередной элемент массива отрицательным.
{//Если текущий элемент массива является отрицательным числом, удаляем его
for ( j= i; j<n-1; j++)
x [ j ]=x [ j + 1 ];
n--;
}
else i ++; //Если x[i]>=0, переходим к следующему элементу.
cout<<"Преобразованный массив X\n ";
for ( i =0; i<n; i++)
cout<<x [ i ]<<" \t ";
cout<<endl;
return 0;
}
В качестве тестового можно использовать следующий массив: 34, 4, -7, -8, -10, 7, -100, -200, -300, 1. Здесь приведённая выше программа работает корректно, а вариант
for ( i=a+1; i<b; )
if ( x [ i ] <0)
{
for ( j= i; j<n-1; j++)
x [ j ]=x [ j + 1 ];
n--;
}
else i ++;
приводит к неправильным результатам. Рекомендуем читателю самостоятельно разобраться в особенностях подобных алгоритмов удаления.
Задача 5.10. В массиве X[n] найти группу наибольшей длины, которая состоит из знакочередующихся чисел.
Если будут вычислены следующие значения:
nach — номер первого элемента в группе;kon — номер последнего элемента в группе;k — количество элементов в группе.то, зная любые два из них, можно однозначно определить группу внутри массива.
Вначале количество элементов в знакочередующейся группе равно 1. Дело в том, что если мы встретим первую пару знакочередующихся элементов, то количество их в группе сразу станет равным 2. Однако все последующие пары элементов будут увеличивать k на 1. И чтобы не решать проблему построения последовательности значений k 0,2,3,4,5,..., первоначальное значение k примем равным 1. Когда будем встречать очередную пару подряд идущих соседних элементов, то k необходимо будет увеличить на 1.
Алгоритм поиска очередной группы состоит в следующем: попарно ($$x_i, x_{i+1}$$) перебираем все элементы массива (параметр цикла $$i$$ изменяется от 0 до n - 2).
Если произведение соседних элементов отрицательно ($$xi \cdot x_i+1 < 0$$), то это означает, что они имеют разные знаки и являются элементами группы. В этом случае количество ($$k$$) элементов в группе увеличиваем на 1 (k++). Если же произведение соседних элементов положительно ($$x_i\cdot x_{i+1} > 0$$), то эти элементы не являются членами группы. В этом случае возможны два варианта:
kon=i -номер последнего элемента в группе, $$k$$ — количество элементов в только что закончившейся группе.После того, как закончилась очередная группа знакочередующихся элементов, необходимо количество групп (kon_max=i). Если это не первая группа (kgr=1), то сравниваем max и длину текущей группы (k). Если k>max, то в переменную max записываем длину этой группы (max=k), а в переменную kon_max номер последнего элемента группы (kon_max=i).
После этого в переменную k опять записываем 1 для формирования новой группы элементов.
По окончанию цикла значение k может быть больше 1. Это означает, что в самом конце массива встретилась ещё одна группа. Для неё надо будет провести все те же действия, что и для любой другой группы. Далее приведён текст программы.
#include <iostream>
using namespace std;
int main ( int argc, char **argv )
{ float *x;
int i, k, n, max, kgr, kon_max;
cout<<" n = "; cin>>n; //Ввод размера массива.
x=new float [ n ]; //Выделение памяти для массива.
cout<<"Введите массив x\n "; //Ввод элементов массива.
for ( i =0; i<n; i++)
cin>>x [ i ];
//Попарно перебираем элементы массива. Количество знакочередующихся
//групп в массиве kgr=0, количество элементов в текущей группе — 1.
for ( kgr= i =0,k=1; i<n-1; i++)
//Если соседние элементы имеют разные знаки, то количество (k)
//элементов в группе увеличиваем на 1.
if ( x [ i ] * x [ i +1]<0) k++;
else
if ( k>1) //Если k>1, то только что закончилась группа, i — номер последнего элемента
{//в группе, k — количество элементов в группе. Увеличиваем kgr на 1.
kgr++;
if ( kgr==1) //Если это первая группа (kgr=1) знакочередующихся элементов,
{
max=k; //то max — длина группы (max=k),
kon_max= i; //kon_max — номер последнего элемента группы.
}
else //это не первая группа (kgr 6= 1), сравниваем max и длину текущей группы.
if ( k>max) //Если k>max,
{
max=k; //max — длина группы,
kon_max= i; //kon_max — номер последнего элемента группы.
}
k=1; //В переменную k записываем 1 для формирования новой группы элементов.
}
if ( k>1) //Если в конце массива была группа.
{
kgr++; //Количество групп увеличиваем на 1.
if ( kgr==1) //Если это первая группа,
{
max=k; //то max — длина группы,
kon_max=n-1; //группа закончилась на последнем элементе массива.
}
else
if ( k>max) //Если длина очередной группы больше max.
{
max=k; //то в max записываем длину последней группы,
kon_max=n-1; //группа закончилась на последнем элементе массива.
}
}
if ( kgr >0) //Если знакочередующиеся группы были,
{ //то выводим информацию о группе наибольшей длины,
cout<<"В массиве "<<kgr<<" групп знакочередующихся элементов\n ";
cout<<"Группа максимальной длины начинается с элемента Номер
"<<kon_max-max+1<<", её длина "<<max<<",номер последнего элемента группы
" <<kon_max<<endl;
for ( i=kon_max-max+1; i<=kon_max; i++) //а также саму группу.
cout<<x [ i ]<<" ";
cout<<endl;
}
else //Если знакочередующихся групп не было, то выводим сообщение об этом.
cout<<"В массиве нет групп знакочередующихся элементов\n ";
return 0;
}
Сортировка представляет собой процесс упорядочения элементов в массиве в порядке возрастания или убывания их значений. Например, массив $$Y$$ из $$n$$ элементов будет отсортирован в порядке возрастания значений его элементов, если
$$Y [0] < Y [1] < ... < Y [n - 1],$$и в порядке убывания, если
$$Y [0] > Y [1] > ... > Y [n - 1].$$Существует большое количество алгоритмов сортировки, но все они базируются на трёх основных:
Представим, что нам необходимо разложить по порядку карты в колоде. Для сортировки карт обменом можно разложить карты на столе лицевой стороной вверх и менять местами те карты, которые расположены в неправильном порядке, делая это до тех пор, пока колода карт не станет упорядоченной.
Для сортировки выбором из разложенных на столе карт выбирают самую младшую (старшую) карту и держат её в руках. Затем из оставшихся карт вновь выбирают наименьшую (наибольшую) по значению карту и помещают её позади той карты, которая была выбрана первой. Этот процесс повторяется до тех пор, пока вся колода не окажется в руках. Поскольку каждый раз выбирается наименьшая (наибольшая) по значению карта из оставшихся на столе карт, по завершению такого процесса карты будут отсортированы по возрастанию (убыванию).
Для сортировки вставкой из колоды берут две карты и располагают их в необходимом порядке по отношению друг к другу. Каждая следующая карта, взятая из колоды, должна быть установлена на соответствующее место по отношению к уже упорядоченным картам.
Итак, решим следующую задачу. Задан массив $$Y$$ из $$n$$ целых чисел. Расположить элементы массива в порядке возрастания их значений.
Сортировка пузырьковым методом является наиболее известной. Её популярность объясняется запоминающимся названием, которое происходит из-за подобия процессу движения пузырьков в резервуаре с водой, когда каждый пузырёк находит свой собственный уровень, и простотой алгоритма. Сортировка методом "пузырька" использует метод обменной сортировки и основана на выполнении в цикле операций сравнения и при необходимости обмена соседних элементов. Рассмотрим алгоритм пузырьковой сортировки более подробно.
Сравним нулевой элемент массива с первым, если нулевой окажется больше первого, то поменяем их местами. Те же действия выполним для первого и второго, второго и третьего, $$i$$–го и ($$i + 1$$)–го, предпоследнего и последнего элементов. В результате этих действий самый большой элемент станет на последнее ($$n-1$$)-е место. Теперь повторим данный алгоритм сначала, но последний $$(n - 1)$$-й элемент рассматривать не будем, так как он уже занял своё место. После проведения данной операции самый большой элемент оставшегося массива станет на $$(n-2)$$-е место. Так повторяем до тех пор, пока не упорядочим весь массив.
В табл. 5.4 представлен процесс упорядочивания элементов в массиве.
| Номер элемента | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| Исходный массив | 7 | 3 | 5 | 4 | 2 |
| Первый просмотр | 3 | 5 | 4 | 2 | 7 |
| Второй просмотр | 3 | 4 | 2 | 5 | 7 |
| Третий просмотр | 3 | 2 | 4 | 5 | 7 |
| Четвёртый просмотр | 2 | 3 | 4 | 5 | 7 |
Нетрудно заметить, что для преобразования массива, состоящего из $$n$$ элементов, необходимо просмотреть его $$n$$-раз, каждый раз уменьшая диапазон просмотра на один элемент. Блок–схема описанного алгоритма приведена на рис. 5.13.
(рис 5.13) Сортировка массива пузырьковым методом
Обратите внимание на то, что для перестановки элементов (рис. 5.13, блок 4) используется буферная переменная $$b$$, в которой временно хранится значение элемента, подлежащего замене. Текст программы, сортирующей элементы в массиве по возрастанию методом "пузырька", приведён далее.
#include <iostream>
using namespace std;
int main ( )
{
int n, i, b, j;
cout<<" n = "; cin>>n;
float y [ n ];
for ( i =0; i<n; i++) //Ввод массива.
{
cout<<" \n Y [ "<<i<<" ]= ";
cin>>y [ i ];
}
for ( j =1; j<n; j++) //Упорядочивание элементов в массиве по возрастанию их значений.
for ( i =0; i<n-j; i++)
if ( y [ i ]>y [ i +1 ]) //Если текущий элемент больше следующего
{
b=y [ i ]; //Сохранить значение текущего элемента
y [ i ]=y [ i + 1 ]; //Заменить текущий элемент следующим
y [ i +1]=b; //Заменить следующий элемент на сохранённый в b
}
for ( i =0; i<n; i++) cout<<y [ i ]<<" \t "; //Вывод упорядоченного массива
return 0;
}
Для перестановки элементов в массиве по убыванию их значений необходимо в программе и блок-схеме при сравнении элементов массива заменить знак ">" на "<".
Однако, в этом и во всех далее рассмотренных алгоритмах не учитывается то факт, что на каком-то этапе (или даже в начале) массив уже может оказаться отсортированным. При большом количестве элементов (сотни и даже тысячи чисел) на сортировку "вхолостую" массива тратится достаточно много времени. Ниже приведены тексты двух вариантов программы сортировки по убыванию методом пузырька, в которых алгоритм прерывается, если массив уже отсортирован.
Вариант 1.
#include <iostream>
using namespace std;
int main ( int argc, char **argv )
{
int n, i, b, j;
bool pr;
cout<<" n = "; cin>>n;
float y [ n ];
for ( i =0; i<n; i++) //Ввод массива.
{
cout<<" \n Y [ "<<i<<" ]= ";
cin>>y [ i ];
}
for ( j =1; j<n; j++) //Упорядочивание элементов массива по убыванию их значений.
{
for ( pr=false, i =0; i<n-j; i++) //Предполагаем, что массив уже отсортирован
// ( pr=false ) .
if ( y [ i ]<y [ i +1 ]) //Если текущий элемент меньше следующего
{
b=y [ i ]; //Сохранить значение текущего элемента
y [ i ]=y [ i + 1 ]; //Заменить текущий элемент следующим
y [ i +1]=b; //Заменить следующий элемент текущим
pr=true; //Если элемент менялись местами, массив ещё не отсортирован (pr=true);
}
cout<<" j = "<<j<<endl;
//Если на j-м шаге соседние элементы не менялись, то массив уже отсортирован,
if ( ! pr ) break; //повторять смысла нет;
}
for ( i =0; i<n; i++) cout<<y [ i ]<<" \t "; //Вывод упорядоченного массива
return 0;
}
Вариант 2.
#include <iostream>
using namespace std;
int main ( int argc, char **argv )
{
int n, i, b, j;
bool pr=true;
cout<<" n = "; cin>>n;
float y [ n ];
for ( i =0; i<n; i++) //Ввод массива.
{
cout<<" \n Y [ "<<i<<" ]= ";
cin>>y [ i ];
}
for ( j =1; pr; j++) //Упорядочивание элементов массива по убыванию их значений.
{ //Вход в цикл, если массив не отсортирован (pr=true).
for ( pr=false, i =0; i<n-j; i++) //Предполагаем, что массив уже отсортирован
// ( pr=false ) .
if ( y [ i ]<y [ i +1 ]) //Если текущий элемент меньше следующего
{
b=y [ i ]; //Сохранить значение текущего элемента
y [ i ]=y [ i + 1 ]; //Заменить текущий элемент следующим
y [ i +1]=b; //Заменить следующий элемент текущим
pr=true; //Элементы менялись местами, массив ещё не отсортирован ( pr=true )
}
}
for ( i =0; i<n; i++) cout<<y [ i ]<<" \t "; //Вывод упорядоченного массива
return 0;
}
Алгоритм сортировки выбором приведён в виде блок-схемы на рис. 5.14. Идея алгоритма заключается в следующем. В массиве $$Y$$, состоящем из $$n$$ элементов, ищем самый большой элемент (блоки 2–5) и меняем его местами с последним элементом (блок 7). Повторяем алгоритм поиска максимального элемента, но последний $$(n - 1)$$-й элемент не рассматриваем, так как он уже занял свою позицию.
(рис 5.14) Сортировка массива выбором наибольшего элемента
Найденный максимум ставим на $$(n - 2)$$-ю позицию. Описанную выше операцию поиска проводим $$n-1$$ раз, до полного упорядочивания элементов в массиве. Фрагмент программы выполняет сортировку массива по возрастанию методом выбора:
for ( j =1; j<n; b=y [ n-j ], y [ n-j ]=y [ nom ], y [ nom]=b, j++)
for (max=y [ 0 ], nom=0, i =1; i<=n-j; i++)
if ( y [ i ]>max) {max=y [ i ]; nom= i; }
Для упорядочивания массива по убыванию необходимо менять минимальный элемент с последним элементом.
Сортировка вставкой заключается в том, что сначала упорядочиваются два элемента массива. Затем делается вставка третьего элемента в соответствующее место по отношению к первым двум элементам. Четвёртый элемент помещают в список из уже упорядоченных трёх элементов. Этот процесс повторяется до тех пор, пока все элементы не будут упорядочены.
Прежде чем приступить к составлению блок–схемы, рассмотрим следующий пример. Пусть известно, что в массиве из десяти элементов первые шесть уже упорядочены (с нулевого по пятый), а шестой элемент нужно вставить между вторым и четвёртым. Сохраним шестой элемент во вспомогательной переменной, а на его место запишем пятый. Далее четвёртый переместим на место пятого, а третий на место четвёртого, тем самым выполнив сдвиг элементов массива на одну позицию вправо. Записав содержимое вспомогательной переменной в третью позицию, достигнем нужного результата.
Составим блок–схему алгоритма (рис. 5.15), учитывая, что возможно описанные выше действия придётся выполнить неоднократно.
(рис 5.15) Сортировка массива вставкой
Организуем цикл для просмотра всех элементов массива, начиная с первого (блок 1). Сохраним значение текущего $$i$$–го элемента во вспомогательной переменной b, так как оно может быть потеряно при сдвиге элементов (блок 2), и присвоим переменной $$j$$ значение индекса предыдущего $$(i - 1)$$–го элемента массива (блок 3). Далее движемся по массиву влево в поисках элемента, меньшего чем текущий, и, пока он не найден, сдвигаем элементы вправо на одну позицию. Для этого организуем цикл (блок 4), который прекратиться, как только будет найден элемент меньше текущего. Если такого элемента в массиве не найдётся и переменная $$j$$ станет равной (-1), то это будет означать, что достигнута левая граница массива, и текущий элемент необходимо установить в первую позицию. Смещение элементов массива вправо на одну позицию выполняется в блоке 5, а изменение счётчика $$j$$ в блоке 6. Блок 7 выполняет вставку текущего элемента в соответствующую позицию. Далее приведён фрагмент программы, реализующей сортировку массива методом вставки.
for ( i =1; i<n; y [ j +1]=b, i++) for ( b=y [ i ], j=i -1;( j >-1 b<y [ j ] ); y [ j +1]=y [ j ], j --);
Рассмотрим несколько несложных задач, связанных с упорядочиванием.
Задача 5.11. Задан массив $$a[n]$$, упорядоченный по убыванию, вставить в него некоторое число $$b$$, не нарушив упорядоченности массива.
Массив является упорядоченным по убыванию, если каждое последующий элемент массива не больше предыдущего, т. е. при выполнении следующей совокупности неравенств $$a_0 \ge a_1 \ge a_2 \ge ... \ge a_{n-3} \ge a_{n-2} \ge a_{n-1}$$.
Для вставки в подобный массив некоторого числа без нарушений упорядоченности, необходимо:
a_k b.Текст программы с комментариями приведён ниже.
#include <iostream>
2 using namespace std;
int main ( int argc, char **argv )
4 {
int i, k, n;
6 float b;
cout<<" n = "; cin>>n; //Ввод размера исходного массива.
8 float a [ n + 1 ]; //Выделение памяти с учётом предстоящей вставки одного числа в массив.
cout<<"Введите массив a \n "; //Ввод исходного упорядоченного по убыванию массива.
10 for ( i =0; i<n; i++)
cin>>a [ i ];
12 cout<<"Введите число b = "; cin>>b; //Ввод вставляемого в массив числа b .
//Если число b меньше всех элементов массива, записываем b в последний элемент массива.
14 if ( a [ n-1]>=b ) a [ n ]=b;
else //Иначе
16 {
for ( i =0; i<n; i++) //Ищем первое число, меньшее b .
18 if ( a [ i ]<=b )
{
20 k= i; //Запоминаем его номер в переменной k .
break;
22 }
for ( i=n-1; i>=k; i --) //Все элементы массива от n -1-го до k-го сдвигаем на один вправо.
24 a [ i +1]=a [ i ];
a [ k ]=b; //Вставляем число b в массив.
26 }
cout<<"Преобразованный массив a \n ";
28 for ( i =0; i<=n; i++)
cout<<a [ i ]<<" \t ";
30 return 0;
}
Обратите внимание, при решении задачи с массивом, упорядоченным по возрастанию необходимо во фрагменте со строки 14 по строку 22 заменить все операции отношения на противоположные.
Задача 5.12. Проверить, является ли массив упорядоченным по возрастанию.
Для проверки упорядоченности по возрастанию pr=true). Если хотя бы для одной пары соседних элементов выполняется условие $$a_i > a_{i+1}$$, то массив не упорядочен по возрастанию (pr=false). Текст программы с комментариями приведён ниже. Читателю предлагается преобразовать программу таким образом, чтобы осуществлялась проверка, упорядочен ли массив по убыванию.
#include <iostream> namespace std;
int main ( int argc, char **argv )
{
int i, n;
bool pr;
cout<<" n = "; cin>>n; //Ввод размера исходного массива.
float *a=new float [ n ]; //Выделение памяти для массива.
cout<<"Введите массив a \n "; //Ввод исходного массива.
for ( i =0; i<n; i++)
cin>>a [ i ];
//Предполагаем, что массив упорядочен (pr=true), перебираем все пары соседних значений
//(i — номер пары), при i равном n - 2 будем сравнивать последнюю пару a[n-2] и a[n-1].
for ( pr=true, i =0; i<n-1; i++)
//Если для очередной пары соседних элементов выяснилось, что предыдущий элемент больше
//последующего, то массив неупорядочен по возрастанию (pr=false), остальные пары соседних
//значений, можно не проверять (оператор break)
if ( a [ i ]>a [ i +1 ]) { pr=false; break; }
if ( pr ) cout<<"Массив упорядочен по возрастанию";
else cout<<"Массив не упорядочен по возрастанию";
return 0;
}
При решении некоторых задач возникает необходимость передавать имя функции как параметр. В этом случае формальным параметром является указатель на передаваемую функцию. В общем виде прототип указателя на функцию можно записать так.
type (*name_f ) ( type1, type2, type3,...)
Здесь
name_f — имя функцииtype — тип, возвращаемый функцией,type1, type2, type3,... — типы формальных параметров функции.В качестве примера рассмотрим решение широко известной математической задачи.
Задача 5.13. Вычислить $$\int\limits_a^b f(x)dx$$методами Гаусса и Чебышёва.
Кратко напомним читателю методы численного интегрирования.
Метод Гаусса состоит в следующем. Определённый интеграл непрерывной функции на интервале от -1 до 1 можно заменить суммой и вычислить по формуле $$\int\limits_{-1}^1 f(x)dx=\sum\limits_{i=1}^n A_if(t_i),\t_i$$ — точки из интервала [-1, 1], $$A_i$$ — рассчитываемые коэффициенты. Методика определения $$A_i, t_i$$ представлена в [3]. Для практического использования значения коэффициентов при $$n = 2, 3, 4, 5, 6, 7, 8$$ представлены в табл. 5.5.
| n | Массив t | Массив A |
|---|---|---|
| 2 | -0.57735027, 0.57735027 | 1,1 |
| 3 | -0.77459667, 0, 0.77459667 | 5/9, 8/9, 5/9 |
| 4 | -0.86113631, -0.33998104, 0.33998104,0.86113631 | 0.34785484, 0.65214516, 0.65214516, 0.34785484 |
| 5 | -0.90617985, -0.53846931, 0, 0.53846931,0.90617985 | 0.23692688, 0.47862868, 0.568888889,0.47862868, 0.23692688 |
| 6 | -0.93246951, -0.66120939, -0.23861919, 0.23861919, 0.66120939, 0.93246951 | 0.17132450, 0.36076158, 0.46791394, 0.46791394, 0.36076158, 0.17132450 |
| 7 | -0.94910791, -0.74153119, -0.40584515, 0, 0.40584515, 0.74153119, 0.94910791 | 0.12948496, 0.27970540, 0.38183006, 0.41795918, 0.38183006, 0.27970540, 0.12948496 |
| 8 | -0.96028986, -0.79666648, -0.52553242, -0.18343464, 0.18343464, 0.52553242, 0.79666648, 0.96028986 | 0.10122854, 0.22238104, 0.31370664, 0.36268378, 0.36268378, 0.31370664, 0.22238104, 0.10122854 |
Для вычисления интеграла непрерывной функции на интервале от $$a$$ до $$b$$ квадратурная формула Гаусса может быть записана следующим образом $$\int\limits_a^bf(x)dx=\frac{b-a}{2}\sum\limits_{i=1}^nA_if\left(\frac{b+a}{2}\cdot {\frac{b-a}{2}}t_i\right)$$, значения коэффициентов $$A_i$$ и $$t_i$$ приведены в табл. 5.5
При использовании квадратурной формулы Чебышёва, определённый интеграл непрерывной функции на интервале от -1 до 1 записывается в виде следующей формулы $$\int\limits_{-1}^1f(x)dx=\frac{2}{n}\sum\limits_{i=1}^nf(t_{i}),\t_{i}$$— точки из интервала [-1, 1]. Формула Чебышёва для вычисления интеграла на интервале от $$a$$ до $$b$$ может быть записана так $$\int\limits_{a}^{b}f(x)dx=\frac{b-a}{n}\sum\limits_{i=1}^{n}f\left(\frac{b+a}{2}\cdot {\frac{b-a}{2}}t_{i}\right)$$ Методика определения $$t_i$$ представлена в [3]. Рассмотренные формулы имеют смысл при $$n = 2, 3, 4, 5, 6, 7, 9$$, коэффициенты $$t_i$$ представлены в табл. 5.6.
| n | Массив t |
|---|---|
| 2 | -0.577350, 0.577350 |
| 3 | -0.707107, 0, -0.707107 |
| 4 | -0.794654, -0.187592, 0.187592, 0.794654 |
| 5 | -0.832498, -0.374541, 0, 0.374541, 0.832498 |
| 6 | -0.866247, -0.422519, -0.266635, 0.266635, 0.422519, 0.866247 |
| 7 | -0.883862, -0.529657, -0.323912, 0, 0.323912, 0.529657, 0.883862 |
| 9 | -0.911589, -0.601019, -0.528762, -0.167906, 0, 0.167906, 0.528762, 0.601019, 0.911589 |
Осталось написать функции вычисления определённого интеграла $$\int\limits_{a}^{b}f(x)dx$$ методами Гаусса и Чебышёва. Далее приведены тексты функций и функция main(). В качестве тестовых использовались интегралы $$\int\limits_{0}^{2}\sin ^{4}xdx\approx 0.9701,\ \int\limits_{5}^{13}\sqrt{2x-1}dx\approx 32.667$$.
#include <iostream>
#include <math.h>
using namespace std;
//Функция вычисления определённого интеграла методом Чебышёва.
//(a, b) — интервал интегрирования, *fn — указатель на функцию типа double f (double).
double int_chebishev ( double a, double b,
double (*fn ) ( double ) )
{
int i, n=9;
double s,
t [ 9 ]= {-0.911589, -0.601019, -0.528762, -0.167906, 0, 0.167906, 0.528762,
0.601019, 0.911589 };
for ( s= i =0; i<n; i++)
s+=fn ( ( b+a ) /2+(b-a ) /2*t [ i ] );
s *=(b-a ) /n;
return s;
}
//Функция вычисления определённого интеграла методом Гаусса.
//(a, b) — интервал интегрирования, *fn — указатель на функцию типа double f (double)
double int_gauss ( double a, double b, double (*fn ) ( double ) )
{
int i, n=8;
double s,
t [8]= { -0.96028986, -0.79666648, -0.52553242, -0.18343464, 0.18343464,
0.52553242, 0.79666648, 0.96028986 },
A[8]= { 0.10122854, 0.22238104, 0.31370664, 0.36268378, 0.36268378,
0.31370664, 0.22238104, 0.10122854 };
for ( s= i =0; i<n; i++)
s+=A [ i ] *fn ( ( b+a ) /2+(b-a ) /2* t [ i ] );
s *=(b-a ) / 2;
reurn s;
}
//Функции f1 и f2 типа double f(double), указатели на которые будут передаваться
//в int_gauss и int_chebishev.
double f1 ( double y )
{
return sin(y) *sin(y) *sin(y) *sin( );
}
double f2 ( double y )
{
return pow ( 2*y -1, 0.5 );
}
int main ( int argc, char **argv )
{
double a, b;
cout<<"Интеграл sin(x)^4 = \n ";
cout<<"Введите интервал интегрирования\n ";
cin>>a>>b;
//Вызов функции int_gauss(a, b, f1), f1 — имя функции, интеграл от которой надо посчитать.
cout<<"Метод Гаусса:"<<int_gauss ( a, b, f1 )<<endl;
//Вызов функции int_chebishev(a, b, f1),
//f1 — имя функции, интеграл от которой надо посчитать.
cout<<"Метод Чебышёва:"<<int_chebishev( a, b, f1 )<<endl;
cout<<"Интеграл sqrt ( 2*x-1 ) =\n ";
cout<<"Введите интервалы интегрирования\n ";
cin>>a>>b;
//Вызов функции int_gauss(a, b, f2), f2 — имя функции, интеграл от которой надо посчитать.
cout<<"Метод Гаусса:"<<int_gauss ( a, b, f2 )<<endl;
//Вызов функции int_chebishev(a, b, f2),
//f2 — имя функции, интеграл от которой надо посчитать.
cout<<"Метод Чебышёва:"<<int_chebishev ( a, b, f2 )<<endl;
return 0;
}
Результаты работы программы приведены ниже
Интеграл sin(x)^4= Введите интервалы интегрирования 0 2 Метод Гаусса:0.970118 Метод Чебышёва:0.970082 Интеграл sqrt(2*x-1)= Введите интервалы интегрирования 5 13 Метод Гаусса:32.6667 Метод Чебышёва:32.6667
Функции в С(С++) могут возвращать только одно скалярное значение, однако использование указателей в качестве аргументов функций позволяет обойти это ограничение и писать сложные функции, которые могут возвращать несколько значений.
Если в качестве аргумента в функцию передаётся указатель (адрес), то нужно иметь в виду следующее. При изменении в функции значений, хранящегося по этому адресу, будет происходить глобальное изменение значений, хранящихся по данному адресу в памяти компьютера. Таким образом, получаем механизм, с помощью которого можно возвращать множество значений. Для этого надо передавать их как адреса (указатели). В литературе по программированию подобный механизм зачастую называют передачей параметров по адресу. При этом не следует забывать о том, что этот механизм работает без всяких исключений. Любое изменение значений, переданных в функцию по адресу, приводит к глобальному изменению.
В качестве примера рассмотрим задачу удаления положительных элементов из массива (см. задачу 5.8). Пользуясь тем, что задача несложная, напишем несколько вариантов функции удаления элемента с заданным номером из массива.
Назовём функцию udal. Её входными параметрами будут:
x),n),k).Функция возвращает:
x),n).При передаче массива с помощью указателя исчезает проблема возврата в главную программу модифицированного массива, размер массива будем возвращать с помощью обычного оператора return.
Заголовок (прототип) функции udal может быть таким:
int udal( float *x, int k, int n )
Здесь x — массив, k — номер удаляемого элемента, n — размер массива.
Весь текст функции можно записать так:
int udal ( float *x, int k, int n )
{
int i;
if ( k>n-1) return n;
else
{
for ( i=k; i<n-1; i++)
x [ i ]=x [ i + 1 ];
n--;
return n;
}
}
Ниже приведён весь текст программы удаления положительных элементов из массива x c использованием функции udal и комментарии к нему.
#include <iostream>
#include <math.h>
using namespace std;
int udal ( float *x, int k, int n )
{
int i;
//Если номер удаляемого элемента больше номера последнего элемента,
//то удалять нечего, в этом случае возвращается неизменённый размер массива
if ( k>n-1) return n;
else
{
for ( i=k; i<n-1; i++) //Удаляем элемент с номером k.
x [ i ]=x [ i + 1 ];
n--;
return n; //Возвращаем изменённый размер массива.
}
}
int main ( int argc, char **argv )
{
int i, n;
cout<<" n = "; cin>>n;
float x [ n ]; //Выделяем память для динамического массива x.
cout<<"Введите элементы массива X \n "; //Ввод элементов массива.
for ( i =0; i<n; i++)
cin>>x [ i ];
for ( i =0; i<n; )
if ( x [ i ] >0)
//Если текущий элемент положителен, то для удаления элемента с индексом i вызываем
//функцию udal, которая изменяет элементы, хранящиеся по адресу x,
n=udal ( x, i, n ); //и возвращает размер массива.
else i ++; //иначе (x[i]<=0) — переходим к следующему элементу массива.
cout<<"Преобразованный массив X \n "; //Вывод элементов массива после удаления.
for ( i =0; i<n; i++)
cout<<x [ i ]<<" \t ";
cout<<endl;
return 0;
}
Эту функцию можно переписать и по-другому, передавая и массив и его размер как указатели, в этом случае функция будет такой:
void udal ( float *x, int k, int *n )
{
int i;
for ( i=k; i <*n-1; i++)
x [ i ]=x [ i + 1 ];
if ( k<*n ) --*n;
}
В этом случае изменится и обращение к udal в функции main.
Ниже приведён модифицированный текст программы удаления положительных элементов из массива x c использованием функции udal(float *x, int k, int *n) и комментарии к нему.
#include <iostream>
#include <math.h>
using namespace std;
void udal ( float *x, int k, int*n )
{
int i;
//Если номер удаляемого элемента больше номера последнего элемента, то удалять нечего,
//в этом случае возвращается неизменённый размер массива. Удаляем элемент с номером k.
for ( i=k; i <-n-1; i++)
x [ i ]=x [ i + 1 ];
//Уменьшаем на 1 значение, хранящееся по адресу n.
//Обратите внимание, что надо писать именно --*n, *n-- — НЕПРАВИЛЬНО!!!!!!!!!!!!!!!
if ( k<*n ) --*n;
}
int main ( int argc, char **argv )
{
int i, n;
cout<<" n = "; cin>>n;
float x [ n ]; //Выделяем память для динамического массива x.
cout<<"Введите элементы массива X \n "; //Ввод элементов массива.
for ( i =0; i<n; i++)
cin>>x [ i ];
for ( i =0; i<n; )
if ( x [ i ] >0) //Если текущий элемент положителен, то удаление элемента с индексом i,
//Вызываем функцию udal, которая изменяет элементы, хранящиеся по адресу x,
//и изменяет значение переменной n.
udal ( x, i,n );
else i ++; //иначе (x[i]<=0) — переходим к следующему элементу массива.
cout<<"Преобразованный массив X \n "; //Вывод элементов массива после удаления.
for ( i =0; i<n; i++)
cout<<x [ i ]<<" \t ";
cout<<endl;
return 0;
}
Авторы рекомендуют разобраться с этими примерами для понимания механизма передачи параметров по адресу.
Задача 5.14. Из массива целых чисел удалить все простые числа, значение которых меньше среднего арифметического элементов массива. Полученный массив упорядочить по возрастанию.
Алгоритм решения этой задачи без применения функций будет очень громоздким, а текст программы малопонятным. Поэтому разобьём задачу на подзадачи:
Прототипы функций, которые предназначены для решения подзадач, могут выглядеть так:
float sr_arifm(int *x, int n) — вычисляет среднее арифметическое массива x из n элементов;bool prostoe(int n) — проверяет, является ли целое число n простым, результат — логическое значение true, если число простое, и false в противном случае;void udal(int *x, int m, int *n) — удаляет элемент с номером m в массиве x из n элементов (рис. 6.4);void upor(int *x, int N, bool pr=true) — сортирует массив x из n элементов по возрастанию или по убыванию, направление сортировки зависит от значения параметра pr, если pr=true, то выполняется сортировка по возрастанию, если pr=false, то по убыванию.Текст программы с комментариями:
#include <iostream>
using namespace std;
float sr_arifm ( int *x, int n ) //Функция вычисления среднего значения.
{
int i; float s =0;
for ( i =0; i<n; s+=x [ i ], i++);
if ( n>0) return ( s /n );
else return 0;
}
bool prostoe ( int n ) //Функция для проверки, является ли число n простым.
{
bool pr; int i;
for ( pr=true, i =2; i<=n / 2; i++)
if ( n%i ==0) { pr=false; break; }
return ( pr );
}
void ud a l ( int *x, int m, int *n ) //Функция удаления элемента из массива.
{
int i;
for ( i=m; i <*n-1;*( x+ i ) =*(x+ i +1), i++);
--*n;
realloc ( ( int *) x, *n*sizeof ( int ) );
}
void upor ( int *x, int n, bool pr=true ) //Функция сортировки массива.
{
int i, j, b;
if ( pr )
{
for ( j =1; j<=n-1; j++)
for ( i =0; i<=n-1-j; i++)
if ( *( x+ i ) >*(x+ i +1) )
{
b=*(x+ i );
*( x+ i ) =*(x+ i +1);
*( x+ i +1)=b;
}
}
else
for ( j =1; j<=n-1; j++)
for ( i =0; i<=n-1-j; i++)
if ( * ( x+ i ) <*(x+ i +1) )
{
b=*(x+ i );
*( x+ i ) =*(x+ i +1);
*( x+ i +1)=b;
}
}
int main ( )
{
int *a, n, i; float sr;
cout<<" n = "; cin>>n; //Ввод размерности массива.
a=( int *) calloc ( n, sizeof ( int ) ); //Выделение памяти.
cout << "Введите массив A \n ";
for ( i =0; i<n; i++) cin >>*(a+ i ); //Ввод массива.
sr=sr_arifm ( a, n ); //Вычисление среднего арифметического.
cout<<" sr = "<<s r<<" \n "; //Вывод среднего арифметического.
for ( i =0; i<n; )
{
if ( prostoe ( * ( a+ i ) ) *( a+ i )<sr ) //Если число простое и меньше среднего,
udal ( a, i,n ); //удалить его из массива,
else i ++; //иначе, перейти к следующему элементу.
}
cout << "Массив A \n "; //Вывод модифицированного массива.
for ( i =0; i<n; i++) cout <<*(a+ i )<<" \t ";
cout<<" \n ";
upor ( a, n ); //Сортировка массива.
cout<<"Упорядоченный массив A \n "; //Вывод упорядоченного массива.
for ( i =0; i<n; i++) cout <<*(a+ i )<<" \t ";
cout<<" \n ";
free ( a ); //Освобождение памяти.
return 0;
}
Задача 5.15. Все положительные элементы целочисленного массива $$G$$ переписать в массив $$W$$. В массиве $$W$$ упорядочить по убыванию элементы, которые расположены между наибольшим и наименьшим числами-палиндромами.
Для создания этой программы напишем следующие функции:
int form (int *a, int n, int *b) — из массива целых чисел $$a$$ формирует массив положительных чисел $$b, n$$ — количество чисел в массиве $$a$$, функция возвращает число элементов в массиве $$b$$.bool palindrom (int n) — проверяет, является ли число $$n$$ палиндромом.sort (int *x, int n, int k, int p) — сортирует по возрастанию элементы массива x[n], расположенные между $$k$$-м и $$p$$-м элементами массива.Рекомендуем читателю самостоятельно разобрать текст программы, реализующей решение задачи 5.15.
#include <iostream>
#include <stdlib .h>
#include <math.h>
using namespace std;
int kvo_razryad ( int M)
{
long int k;
for ( k=1;M>9;M/=10,k++);
return k;
}
bool palindrom ( int n )
{
int k=kvo_razryad ( n ), s, p=n;
for ( s =0;p != 0; p/=10,k--)
s+=(p%10)*pow ( 10, k-1);
if ( s==n ) return true; else return false;
}
int form ( int *a, int n, int *b )
{
int i, k;
for ( i=k=0; i<n; i++)
if ( a [ i ] >0)
b [ k++]=a [ i ];
return k;
}
void sort ( int *x, int n, int k, int p )
{
int i, nom, j;
int b;
for ( i=k+1; i<p; )
{
nom= i;
for ( j= i +1; j<p; j++)
if ( x [ j ]<x [ nom ] ) nom=j;
b=x [ p -1 ]; x [ p-1]=x [ nom ]; x [ nom]=b;
p--;
}
}
int main ( int argc, char **argv )
{
int *G, *W;
int nmax, nmin, kp, i,N, k;
cout<<" N = ";
cin>>N;
G=( int *) calloc (N, sizeof ( int ) );
W=( int *) calloc (N, sizeof ( int ) );
cout<<"Ввод массива G \n ";
for ( i =0; i<N; i++)
cin>>G[ i ];
k=form (G,N,W);
cout<<"Вывод массива W \n ";
for ( i =0; i<k; i++)
cout<<W[ i ]<<" ";
cout<<endl;
for ( kp= i =0; i<k; i++)
if ( palindrom (W[ i ] ) )
{
kp++;
if ( kp==1) {nmax= i; nmin= i; }
else
{
if (W[ i ]<W[ nmin ] ) nmin= i;
if (W[ i ]>W[ nmax ] ) nmax= i;
}
}
if (nmax<nmin )
sort (W, k, nmax, nmin );
else
sort (W, k, nmin, nmax );
cout<<"Вывод преобразованного массива W \n ";
for ( i =0; i<k; i++)
cout<<W[ i ]<<" ";
cout<<endl;
return 0;
}
Результаты работы программы представлены ниже.
N=17 Ввод массива G -5 -6 191 121 12 -13 14 15 -5 100 666 -666 15251 16261 16262 991 -724 Вывод массива W 191 121 12 14 15 100 666 15251 16261 16262 991 Вывод преобразованного массива W 191 121 15251 666 100 15 14 12 16261 16262 991
Разработать программу на языке C++ для решения следующей задачи.
Разработать программу на языке C++ для решения следующей задачи.
Разработать программу на языке C++ для решения следующей задачи.
Разработать программу на языке C++ для решения следующей задачи.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.