Операции с массивами копирование, вставка, удаление и сортировка

Практический курс по C/C++: https://stepik.org/course/193691

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

Первая операция – это копирование данных из одного массива в другой. Давайте представим, что в функции main() объявлены два одномерных массива разной длины с начальной инициализацией первого массива:

#include <stdio.h>
 
int main(void)
{
         float func_1[100] = {2.4, -3.8, 0, 10.2, 11.78, -5.43};
         float func_2[50];
 
         return 0;
}

Наша задача скопировать данные из первого массива func_1 во второй func_2. Как это можно сделать? Начинающие программисты первым делом пробуют применить очевидную для них операцию присваивания:

func_2 = func_1;

И здесь их ждет жестокое разочарование. При компиляции в этой строчке появляется ошибка, что выполнять присваивание одного массива другому нельзя. Действительно, такая операция в языке Си запрещена. Это еще раз нам говорит о том, что массивы в Си – это не полноценная структура данных, а все лишь способ хранения значений в непрерывной области памяти, не более того. Как же тогда решить поставленную задачу? Самый очевидный способ – это перебрать элементы первого массива и поэлементно присвоить их другому массиву. Например, это можно сделать так:

#include <stdio.h>
 
int main(void)
{
         float func_1[100] = {2.4, -3.8, 0, 10.2, 11.78, -5.43};
         float func_2[50];
 
         int size_1 = sizeof(func_1) / sizeof(func_1[0]);
         int size_2 = sizeof(func_2) / sizeof(func_2[0]);
         int size = (size_1 < size_2) ? size_1 : size_2;
 
         for(int i = 0; i < size; ++i)
                   func_2[i] = func_1[i];
 
         return 0;
}

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

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

float func_1[100] = {2.4, -3.8, 0, 10.2, 11.78, -5.43};
double func_2[50];

А вот если вместо double прописать целочисленный тип int:

int func_2[50];

то внутри цикла в момент присваивания:

func_2[i] = func_1[i];

будет происходить потеря данных. В подобных случаях рекомендуется явно прописывать операцию приведения типов:

func_2[i] = (int)func_1[i];

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

Вставка значения в произвольный элемент массива

Следующая частая операция – это вставка нового значения в произвольный элемент массива. О чем здесь идет речь? Давайте представим, что имеется массив marks для хранения оценок учащегося и в нем уже присутствуют первые три оценки:

#include <stdio.h>
 
#define TOTAL_MARKS                10
 
int main(void)
{
         char marks[TOTAL_MARKS] = {3, 2, 5};
 
         for(int i = 0; i < TOTAL_MARKS; ++i)
                   printf("%d ", marks[i]);
 
         return 0;
}

Но, потом оказалось, что учитель забыл поставить одну четверку после двойки, а потому, нам нужно вставить это число 4 после числа 2 с сохранением всех остальных последующих оценок (до конца массива). Это и есть пример операции вставки нового значения.

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

3, 2, x, 5, 0, 0, 0, 0, 0, 0

А, затем, записать новое значение в нужный элемент:

3, 2, 4, 5, 0, 0, 0, 0, 0, 0

На уровне программы это можно сделать следующим образом:

#include <stdio.h>
 
#define TOTAL_MARKS                10
 
int main(void)
{
         char marks[TOTAL_MARKS] = {3, 2, 5};
         int insert_indx = 2;
 
         for(int i = TOTAL_MARKS-1; i > insert_indx; --i) {
                   printf("marks[%d] = marks[%d]\n", i, i-1);
                   marks[i] = marks[i-1];
         }
 
         marks[insert_indx] = 4;
 
         for(int i = 0; i < TOTAL_MARKS; ++i)
                   printf("%d ", marks[i]);
 
         return 0;
}

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

3 2 4 5 0 0 0 0 0 0

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

Удаление значения из произвольной позиции массива

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

Сделать это можно следующим образом:

#include <stdio.h>
 
#define TOTAL_MARKS                10
 
int main(void)
{
         char marks[TOTAL_MARKS] = {3, 2, 4, 5, 2, 4};
         int del_indx = 3;
 
         for(int i = del_indx; i < TOTAL_MARKS-1; ++i) {
                   printf("marks[%d] = marks[%d]\n", i, i+1);
                   marks[i] = marks[i+1];
         }
 
         for(int i = 0; i < TOTAL_MARKS; ++i)
                   printf("%d ", marks[i]);
 
         return 0;
}

Здесь вначале определен массив из 10 элементов со значениями:

3, 2, 4, 5, 2, 4, 0, 0, 0, 0

Затем, в переменной del_indx указывается индекс удаляемого элемента. В данном случае – это элемент с индексом 3, который соответствует числу 5. После этого запускается цикл, в котором выполняется смещение всех последующих элементов (после удаляемого) на одну позицию влево. В результате получаем массив:

3, 2, 4, 2, 4, 0, 0, 0, 0, 0

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

Сортировка выбором элементов массива

Последний алгоритм, который мы разберем на этом занятии – это сортировка значений элементов массива. Что это такое? Давайте представим, что имеется массив, содержащий следующие целые числа:

int a[] = {-3, 5, 0, -8, 1, 10};

Требуется все значения в нем выстроить по возрастанию (точнее, по не убыванию):

-8, -3, 0, 1, 5, 10

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

Суть его работы очень проста. Сначала мы проходим по всем элементам массива и находим минимальное значение. Затем меняем местами это минимальное значение с первым элементом. Получаем массив:

1-й проход: -8, 5, 0, -3, 1, 10

Здесь первый элемент уже отсортирован. Далее, повторяем эту же операцию с оставшимися элементами массива, имеем:

2-й проход: -8, -3, 0, 5, 1, 10
3-й проход: -8, -3, 0, 5, 1, 10
4-й проход: -8, -3, 0, 1, 5, 10
5-й проход: -8, -3, 0, 1, 5, 10

Как видите, после пяти проходов массив a оказывается отсортированным по возрастанию (не убыванию).

Реализация этой идеи на языке Си может выглядеть следующим образом:

#include <stdio.h>
 
int main(void) 
{
         char a[] = {-3, 5, 0, -8, 1, 10};
         int size = sizeof(a) / sizeof(a[0]);
         int pos;
         for(int i = 0; i < size-1; ++i) {
                   pos = i;
                   for(int j = i+1; j < size; ++j) {
                            if(a[pos] > a[j])
                                      pos = j;
                   }
                   if(pos != i) {
                            int t = a[i];
                            a[i] = a[pos];
                            a[pos] = t;
                   }
         }
 
         for(int i = 0; i < size; ++i) 
                   printf("%d ", a[i]);
 
         return 0;
}

После выполнения этой программы увидим на экране результат обработки элементов массива:

-8 -3 0 1 5 10

Кстати, если требуется сортировка по убыванию (не возрастанию), то в операторе «if(min > a[j])» достаточно поменять знак больше на меньше. Получим:

10 5 1 0 -3 -8

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

Практический курс по C/C++: https://stepik.org/course/193691

Видео по теме