Обмен местами строк матрицы

108
12 августа 2019, 02:50

Необходимо поменять местами две строки матрицы. Нашел в интернете несколько примеров в которых используется поэлементный обмен. Мне такой код не очень нравится, поэтому я написал свой вариант обмена строк матрицы с использованием ссылочных переменных и указателей, но вот беда - я сам не до конца понимаю как он работает ( он работает, проверял на произвольных матрицах ).

Объясните, пожалуйста.

(В данном конкретном случае меняю местами строки в зависимости от четности индекса)

template <typename T>
void swapRows(T **matrix, int rowsQuantity) {
    for ( int i = 0; i < rowsQuantity - 1; i++) {
        if ( i % 2 == 0 ) {
            T &temp = *matrix[i];
            matrix[i] = matrix[i+1];
            matrix[i+1] = &temp;
        }
    }
}

Практическим путем выяснил что " *matrix[i] " возвращает значение первого элемента i-ой строки. Честно говоря, не понятно почему так. Знаю, что имя массива это указатель на его первый элемент, видимо, это как-то связано, но точную логическую цепочку не получается провести.

Ссылочная переменная " temp " получает адрес этого первого элемента i-ой строки.

Как работает " matrix[i+1] = &temp; " не очень понятно.

Answer 1

Указатель matrix содержит адрес указателья(является указателем на указатель). matrix[i] это указатель (matrix + i), *matrix[i] это первый элемент в массиве(строке) matrix + i.

T &temp = *matrix[i]; 

означает, что первому элементу i - той строки придаем имя temp

matrix[i] = matrix[i+1];

теперь matrix[i] указывает на начало того же массива, что и matrix[i + 1]

matrix[i+1] = &temp; теперь i + 1 - тый указатель получает значение: адрес первого элемента i - той строки, равно, указывает на строку i

Практически вы могли бы написать функцию проще:

template <typename T>
void swapRows(T **matrix, int rowsQuantity) {
    for ( int i = 0; i < rowsQuantity - 1; i += 2) 
      std::iter_swap(matrix[i], matrix[i + 1]);
}
Answer 2

Необходимо поменять местами две строки матрицы.

Во-первых, надо определиться, каким образом объявляется матрица.

Самый простой подход - это объявить матрицу в виде двумерного массива. Например,

const size_t N = 3;
int a[N][N] = 
{
    { 1, 2, 3 },
    { 4, 5, 6 },
    { 7, 8, 9 }
};

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

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

Например,

const size_t N = 3;
int **a = new int *[N];
int value = 1;
for ( size_t i = 0; i < N; i++ )
{
    a[i] = new int[N] { value++, value++, value++ };
}

Теперь указатель a можно передать в вашу функцию в качестве первого аргумента.

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

int **a = new int *[N];

следующим образом

for ( int i = 0; i < rowsQuantity - 1; i++) {
    if ( i % 2 == 0 ) {
        T &temp = *matrix[i];
        matrix[i] = matrix[i+1];
        matrix[i+1] = &temp;
    }
}

Здесь выражение matrix[i] дает значение элемента, то есть значение указателя, в i-ом элементе массива matrix. Этот указатель содержит адрес первого элемента i-го динамически выделенного массива.

Объявление

T &temp = *matrix[i];

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

matrix[i+1] = &temp;

то значение выражения &item будет равно значению, хранящемся в выражении matrix[i].

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

if ( i % 2 == 0 ) {
    T *temp = matrix[i];
    matrix[i] = matrix[i+1];
    matrix[i+1] = temp;
}

Имейте в виду, что есть стандартная функция std::swap, объявленная в заголовке <utility>. которая выполняет данную операцию. С помощью этой функции вы могли бы просто написать

if ( i % 2 == 0 ) {
    std::swap( matrix[i], matrix[i+1] );
}

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

#include <iostream>
#include <utility>
template <typename T, size_t N>
void swapRows( T ( &a )[N][N] )
{
    for ( size_t i = 0; i + 1 < N; i += 2 )
    {
        std::swap( a[i], a[i + 1] );
    }
}
template <typename T>
void swapRows( T **a, size_t n )
{
    for ( size_t i = 0; i + 1 < n; i += 2 )
    {
        std::swap( a[i], a[i + 1] );
    }
}
int main() 
{
{
    const size_t N = 3;
    int **a = new int *[N];
    int value = 1;
    for ( size_t i = 0; i < N; i++ )
    {
        a[i] = new int[N] { value++, value++, value++ };
    }
    for ( size_t i = 0; i < N; i++ )
    {
        for ( size_t j = 0; j < N; j++ )
        {
            std::cout <<  a[i][j] << ' ';
        }
        std::cout << '\n';
    }
    std::cout << '\n';
    swapRows( a, N );

    for ( size_t i = 0; i < N; i++ )
    {
        for ( size_t j = 0; j < N; j++ )
        {
            std::cout <<  a[i][j] << ' ';
        }
        std::cout << '\n';
    }
    std::cout << '\n';
}
{
    const size_t N = 3;
    int a[N][N] = 
    {
        { 1, 2, 3 },
        { 4, 5, 6 },
        { 7, 8, 9 }
    };
    for ( const auto &row : a )
    {
        for ( const auto &item : row )
        {
            std::cout << item << ' ';
        }
        std::cout << '\n';
    }
    std::cout << '\n';
    swapRows( a );
    for ( const auto &row : a )
    {
        for ( const auto &item : row )
        {
            std::cout << item << ' ';
        }
        std::cout << '\n';
    }
    std::cout << '\n';
}
    return 0;
}

Вывод программы на консоль:

1 2 3 
4 5 6 
7 8 9 
4 5 6 
1 2 3 
7 8 9 
1 2 3 
4 5 6 
7 8 9 
4 5 6 
1 2 3 
7 8 9 
Answer 3

В представленном коде происходит следующее:

1)

template <typename T>
void swapRows(T **matrix, int rowsQuantity) {

Эта шаблонная функция первым аргументом принимает указатель на указатель на какой-то тип. Судя по всему, предполагается, что этот тип является элементарным. Например, int, float, double.

Исходя из того, что matrix - это указатель на указатель, ваша матрица представлена как массив, в котором хранятся указатели на массивы (строки), в которых хранятся сами элементы:

Где matrix - это указатель, в котором хранится адрес массива ABC;

2)

for (int i = 0; i < rowsQuantity - 1; i++) {

Происходит обход всей матрицы по строкам. Здесь сразу видны две проблемы:

  1. Нет контроля значения переменной rowsQuantity;
  2. Переменная типа int не вполне подходит для адресации элементов массива. Переменная типа size_t подойдет гораздо лучше.

3)

if ( i % 2 == 0 ) {

Действия выполняются при посещении каждой строки, чей индекс кратен двум (0, 2, 4, и пр.). И действия, соответственно, выполняются попарно над строками 0-1, 2-3, 4-5.Это не совсем рационально, потому что такой подход выполняет в два раза больше инкрементов переменной i, в два раза больше делений с остатком и в два раза больше сравнений, чем реально необходимо.

Гораздо рациональнее было бы выполнять i += 2, вообще отбросив деление с остатком и проверку. Но такой подход потребует выполнение проверки целочисленного переполнения переменной i.

4)

T &temp = *matrix[i];
matrix[i] = matrix[i+1];
matrix[i+1] = &temp;

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

temp = x;
x = y;
y = temp;

Но не совсем. Я не уверен до конца, что происходит в недрах компилятора, и что по этому поводу говорит Стандарт.

matrix[i] - получает значение указателя на строку матрицы (адрес массива со значениями строки). * - разыменовывает данный адрес, получая значение (значения) всей строки. По всей видимости, значение строки помещается в ссылочную переменную temp.

Я не специалист по C++, но мне данный код кажется очень странным. По логике, ссылка является сущностью времени компиляции, иначе говоря - это просто всевдоним для чего-то. То есть, эти три странные строчки можно свести к двум:

x = y;
y = x;

В результате чего и в x, и в y будет находится изначальное значение y. Но, по-видимому, компилятор в принципе понимает, что вы от него хотите. А может быть, это неопределенное поведение. Точно сказать не могу, потому что текущий стандарт C++ занимает почти 2000 страниц.

5)

matrix[i+1] = &temp;

Здесь происходит использование адреса данных, связанных со ссылкой temp.

PS. Я бы рекомендовал как можно реже и меньше смешивать C++ с его C подмножеством. Не потому, что C плохо, а потому что такое смешивание имеет ряд существенных минусов.

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

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

Answer 4

matrix имеет тип указатель на указатель на T, что можно также трактовать как массив указателей на T. Аргумент rowQuantity, по всей видимости, содержит количество элементов этого массива. Немного дополню ващ код:

T &temp = *(matrix[i]);    
matrix[i] = matrix[i + 1];
matrix[i + 1] = &temp;

matrix[i] возвращает итый элемент массива указателей на T, далее *(итый указатель) разыменовывает итый указатель и возвращает ссылку на первый элемент итой строки, полученная ссылка получает имя temp. matrix[i] = matrix[i + 1] итому элементу присваивается итый плюс один элемент, теперь в них содержатся одинаковые указатели. matrix[i + 1] = &temp, здесь &temp возвращает указатель на то на что ссылается temp, а именно на первый элемент итой строки, полученный адрес помещается в итый плюс один элемент массива.

Вот эквивалентный и более понятный код:

T * temp = matrix[i];
matrix[i] = matrix[i + 1];
matrix[i + 1] = temp;
READ ALSO
CryptoAPI генерация RSA ключей

CryptoAPI генерация RSA ключей

Необходимо генерировать закрытый и открытый ключ

116
Конвертировать черно-белое изображение малого размера в матрицу того же размера на C++

Конвертировать черно-белое изображение малого размера в матрицу того же размера на C++

Допустим есть черно-белое изображение размера 10x10 пикселей, то его нужно перевести в матрицу 10x10 в которой черному цвету будет соответствовать...

101
Вывести все минимальные числа

Вывести все минимальные числа

Есть такое задание: в массиве найти минимальное числоЕсли минимальных чисел несколько, то присвоить им среднее арифметическое исходного...

105
Создать список функций

Создать список функций

И если есть советы как запомнить, буду благодарен)

116