C ++에서 행렬을 전치하는 가장 빠른 방법은 무엇입니까?


81

전치해야 할 행렬 (상대적으로 큰)이 있습니다. 예를 들어 내 행렬이

a b c d e f
g h i j k l
m n o p q r 

결과는 다음과 같습니다.

a g m
b h n
c I o
d j p
e k q
f l r

이를 수행하는 가장 빠른 방법은 무엇입니까?


2
이를 "전치"라고합니다. 90도 회전은 완전히 다른 개념입니다.
Andy Prowl 2013 년

35
그리고 가장 빠른 방법은 회전하는 것이 아니라 배열에 액세스 할 때 단순히 인덱스 순서를 바꾸는 것입니다.
고성능 마크

2
아무리 빠르더라도 어쨌든 행렬의 모든 요소에 액세스해야합니다.
taocp

10
@HighPerformanceMark : 행 순서대로 행렬에 반복적으로 액세스하려는 경우 "전치 된"플래그를 사용하면 상황에 따라 달라질 수 있습니다.
Matthieu M.

3
행렬을 전치하는 것은 메모리 캐시로 인해 발생하는 문제로 유명합니다. 배열이 충분히 커서 전치의 성능이 중요하고 단순히 스왑 된 인덱스가있는 인터페이스를 제공하여 전치하는 것을 피할 수없는 경우 가장 좋은 옵션은 기존 라이브러리 루틴을 사용하여 큰 행렬을 전치하는 것입니다. 전문가가 이미이 작업을 수행 했으므로 사용해야합니다.
Eric Postpischil 2013 년

답변:


131

이것은 좋은 질문입니다. 행렬 곱셈 및 가우스 스미어 링과 같이 좌표를 바꾸는 것보다 실제로 행렬을 메모리에서 전치하려는 많은 이유가 있습니다.

먼저 전치에 사용하는 기능 중 하나를 나열하겠습니다 ( 편집 : 훨씬 빠른 솔루션을 찾은 내 대답의 끝 부분을 참조하십시오 )

void transpose(float *src, float *dst, const int N, const int M) {
    #pragma omp parallel for
    for(int n = 0; n<N*M; n++) {
        int i = n/N;
        int j = n%N;
        dst[n] = src[M*j + i];
    }
}

이제 전치가 유용한 이유를 살펴 보겠습니다. 행렬 곱셈 C = A * B를 고려하십시오. 이런 식으로 할 수 있습니다.

for(int i=0; i<N; i++) {
    for(int j=0; j<K; j++) {
        float tmp = 0;
        for(int l=0; l<M; l++) {
            tmp += A[M*i+l]*B[K*l+j];
        }
        C[K*i + j] = tmp;
    }
}

그러나 그렇게하면 캐시 미스가 많이 발생합니다. 훨씬 빠른 해결책은 B의 전치를 먼저 취하는 것입니다.

transpose(B);
for(int i=0; i<N; i++) {
    for(int j=0; j<K; j++) {
        float tmp = 0;
        for(int l=0; l<M; l++) {
            tmp += A[M*i+l]*B[K*j+l];
        }
        C[K*i + j] = tmp;
    }
}
transpose(B);

행렬 곱셈은 O (n ^ 3)이고 전치는 O (n ^ 2)이므로 전치를 수행하면 계산 시간에 무시할 수있는 영향을 미칩니다 (large n). 행렬 곱셈에서 루프 타일링은 전치하는 것보다 훨씬 효과적이지만 훨씬 더 복잡합니다.

조옮김을 수행하는 더 빠른 방법을 알고 싶습니다 ( 편집 : 더 빠른 솔루션을 찾았습니다 . 내 대답 끝 참조 ). Haswell / AVX2가 몇 주 안에 출시되면 수집 기능이 있습니다. 이 경우에 도움이 될지는 모르겠지만 열을 모으고 행을 쓰는 이미지를 만들 수 있습니다. 아마도 조옮김을 불필요하게 만들 것입니다.

가우시안 스미어 링의 경우 수평으로 스미어 한 다음 수직으로 스미어 링합니다. 하지만 수직으로 번지는 것은 캐시 문제가 있으므로

Smear image horizontally
transpose output 
Smear output horizontally
transpose output

다음은 http://software.intel.com/en-us/articles/iir-gaussian-blur-filter-implementation-using-intel-advanced-vector-extensions를 설명하는 Intel의 문서입니다 .

마지막으로 매트릭스 곱셈 (및 가우스 스미어 링)에서 실제로 수행하는 작업은 정확히 전치하는 것이 아니라 특정 벡터 크기 (예 : SSE / AVX의 경우 4 또는 8)의 너비로 전치하는 것입니다. 내가 사용하는 기능은 다음과 같습니다.

void reorder_matrix(const float* A, float* B, const int N, const int M, const int vec_size) {
    #pragma omp parallel for
    for(int n=0; n<M*N; n++) {
        int k = vec_size*(n/N/vec_size);
        int i = (n/vec_size)%N;
        int j = n%vec_size;
        B[n] = A[M*i + k + j];
    }
}

편집하다:

큰 행렬에 대해 가장 빠른 전치를 찾기 위해 여러 기능을 시도했습니다. 결국 가장 빠른 결과는 루프 차단을 사용하는 것입니다 block_size=16( 편집 : SSE 및 루프 차단을 사용하여 더 빠른 솔루션을 찾았습니다-아래 참조 ). 이 코드는 모든 NxM 행렬에서 작동합니다 (즉, 행렬이 정사각형 일 필요는 없음).

inline void transpose_scalar_block(float *A, float *B, const int lda, const int ldb, const int block_size) {
    #pragma omp parallel for
    for(int i=0; i<block_size; i++) {
        for(int j=0; j<block_size; j++) {
            B[j*ldb + i] = A[i*lda +j];
        }
    }
}

inline void transpose_block(float *A, float *B, const int n, const int m, const int lda, const int ldb, const int block_size) {
    #pragma omp parallel for
    for(int i=0; i<n; i+=block_size) {
        for(int j=0; j<m; j+=block_size) {
            transpose_scalar_block(&A[i*lda +j], &B[j*ldb + i], lda, ldb, block_size);
        }
    }
}

ldaldb는 행렬의 너비입니다. 블록 크기의 배수 여야합니다. 값을 찾고 3000x1001 행렬에 대한 메모리를 할당하려면 다음과 같이합니다.

#define ROUND_UP(x, s) (((x)+((s)-1)) & -(s))
const int n = 3000;
const int m = 1001;
int lda = ROUND_UP(m, 16);
int ldb = ROUND_UP(n, 16);

float *A = (float*)_mm_malloc(sizeof(float)*lda*ldb, 64);
float *B = (float*)_mm_malloc(sizeof(float)*lda*ldb, 64);

3000x1001의 경우 다음이 반환 ldb = 3008되고 lda = 1008

편집하다:

SSE 내장 함수를 사용하여 더 빠른 솔루션을 찾았습니다.

inline void transpose4x4_SSE(float *A, float *B, const int lda, const int ldb) {
    __m128 row1 = _mm_load_ps(&A[0*lda]);
    __m128 row2 = _mm_load_ps(&A[1*lda]);
    __m128 row3 = _mm_load_ps(&A[2*lda]);
    __m128 row4 = _mm_load_ps(&A[3*lda]);
     _MM_TRANSPOSE4_PS(row1, row2, row3, row4);
     _mm_store_ps(&B[0*ldb], row1);
     _mm_store_ps(&B[1*ldb], row2);
     _mm_store_ps(&B[2*ldb], row3);
     _mm_store_ps(&B[3*ldb], row4);
}

inline void transpose_block_SSE4x4(float *A, float *B, const int n, const int m, const int lda, const int ldb ,const int block_size) {
    #pragma omp parallel for
    for(int i=0; i<n; i+=block_size) {
        for(int j=0; j<m; j+=block_size) {
            int max_i2 = i+block_size < n ? i + block_size : n;
            int max_j2 = j+block_size < m ? j + block_size : m;
            for(int i2=i; i2<max_i2; i2+=4) {
                for(int j2=j; j2<max_j2; j2+=4) {
                    transpose4x4_SSE(&A[i2*lda +j2], &B[j2*ldb + i2], lda, ldb);
                }
            }
        }
    }
}

1
좋은 샷이지만 '행렬 곱셈이 O (n ^ 3)'인지는 모르겠지만 O (n ^ 2)라고 생각합니다.
ulyssis2

2
@ ulyssis2 Strassen의 Matrix Multiplication (O (n ^ 2.8074))을 사용하지 않는 한 O (n ^ 3)입니다. user2088790 : 아주 잘하셨습니다. 내 개인 컬렉션에 보관합니다. :)
saurabheights

10
만약 누가이 대답을 썼는지 알고 싶어하는 사람이 나였다. 한 번 그만두고 넘어 가서 돌아왔다.
Z 보손

1
@ ulyssis2 순진한 행렬 곱셈은 가장 확실하게 O (n ^ 3)이며, 제가 아는 한, 컴퓨팅 커널은 순진한 알고리즘을 구현합니다 (스트라 센이 결국 더 많은 연산 (추가)을 수행하기 때문이라고 생각합니다. 당신은 빠른 제품을 할 수 있지만 내가 틀릴 수 있습니다). 행렬 곱셈이 O (n ^ 2)가 될 수 있는지 여부는 열린 문제입니다.
étale-cohomology

일반적으로 선형 대수 라이브러리에 의존하여 작업을 수행하는 것이 더 나은 옵션입니다. Intel MKL, OpenBLAS 등과 같은 최신 라이브러리는 하드웨어에 사용할 수있는 최상의 구현을 선택하는 동적 CPU 디스패치를 ​​제공합니다 (예 : SSE보다 더 넓은 벡터 레지스터 : AVX AVX2, AVX512 ...). 빠른 프로그램을 얻기 위해 이식 불가능한 프로그램을 만들 필요가 없습니다.
Jorge Bellon

39

이것은 응용 프로그램에 따라 다르지만 일반적으로 행렬을 전치하는 가장 빠른 방법은 조회 할 때 좌표를 반전하는 것입니다. 그러면 실제로 데이터를 이동할 필요가 없습니다.


32
이것은 작은 행렬이거나 한 번만 읽는 경우에 좋습니다. 그러나 전치 행렬이 크고 여러 번 재사용해야하는 경우에도 더 나은 메모리 액세스 패턴을 얻기 위해 빠른 전치 버전을 저장해야 할 수 있습니다. (+1, btw)
Agentlien 2013 년

2
@Agentlien : A [j] [i]가 A [i] [j]보다 느린 이유는 무엇입니까?
비커

32
@beaker 큰 행렬이있는 경우 다른 행 / 열이 다른 캐시 라인 / 페이지를 차지할 수 있습니다. 이 경우 서로 인접한 요소에 액세스하는 방식으로 요소를 반복하고 싶을 것입니다. 그렇지 않으면 모든 요소 액세스가 캐시 미스가되어 성능이 완전히 저하 될 수 있습니다.
Agentlien 2013 년

10
@beaker : CPU 수준의 캐싱 (행렬이 하나의 큰 메모리 덩어리라고 가정)과 관련이 있으며 캐시 라인은 매트릭스의 유효 라인이되고 프리 페처는 다음 몇 라인을 가져올 수 있습니다. 액세스를 전환하면 CPU 캐시 / 프리 페 처가 여전히 한 줄씩 작동하는 동안 열 단위로 액세스하면 성능이 크게 저하 될 수 있습니다.
Matthieu M.

2
@taocp 기본적으로, 당신은 그것이 조옮김을 나타 내기 위해 어떤 종류의 플래그가 필요할 것입니다. 그리고 나서 say 요청은 (i,j)매핑 될 것입니다(j,i)
Shafik Yaghmour

5

x86 하드웨어를 사용하여 4x4 제곱 부동 (나중에 32 비트 정수에 대해 설명 함) 행렬을 바꾸는 방법에 대한 세부 정보입니다. 8x8 또는 16x16과 같은 더 큰 정사각형 행렬을 전치하려면 여기에서 시작하는 것이 좋습니다.

_MM_TRANSPOSE4_PS(r0, r1, r2, r3)다른 컴파일러에 의해 다르게 구현됩니다. GCC 및 ICC (Clang을 확인하지 않았습니다)는 사용 unpcklps, unpckhps, unpcklpd, unpckhpd하지만 MSVC는 shufps. 우리는 실제로이 두 가지 접근법을 이렇게 결합 할 수 있습니다.

t0 = _mm_unpacklo_ps(r0, r1);
t1 = _mm_unpackhi_ps(r0, r1);
t2 = _mm_unpacklo_ps(r2, r3);
t3 = _mm_unpackhi_ps(r2, r3);

r0 = _mm_shuffle_ps(t0,t2, 0x44);
r1 = _mm_shuffle_ps(t0,t2, 0xEE);
r2 = _mm_shuffle_ps(t1,t3, 0x44);
r3 = _mm_shuffle_ps(t1,t3, 0xEE);

흥미로운 점은 두 개의 셔플이 이와 같이 하나의 셔플과 두 개의 블렌드 (SSE4.1)로 변환 될 수 있다는 것입니다.

t0 = _mm_unpacklo_ps(r0, r1);
t1 = _mm_unpackhi_ps(r0, r1);
t2 = _mm_unpacklo_ps(r2, r3);
t3 = _mm_unpackhi_ps(r2, r3);

v  = _mm_shuffle_ps(t0,t2, 0x4E);
r0 = _mm_blend_ps(t0,v, 0xC);
r1 = _mm_blend_ps(t2,v, 0x3);
v  = _mm_shuffle_ps(t1,t3, 0x4E);
r2 = _mm_blend_ps(t1,v, 0xC);
r3 = _mm_blend_ps(t3,v, 0x3);

이것은 4 개의 셔플을 2 개의 셔플과 4 개의 블렌드로 효과적으로 변환했습니다. 이것은 GCC, ICC 및 MSVC의 구현보다 2 개의 명령어를 더 사용합니다. 장점은 일부 상황에서 이점을 가질 수있는 포트 압력을 감소 시킨다는 것입니다. 현재 모든 셔플 및 압축 해제는 하나의 특정 포트로만 이동할 수 있지만 블렌드는 두 개의 다른 포트 중 하나로 이동할 수 있습니다.

MSVC와 같은 8 개의 셔플을 사용하여 4 개의 셔플 + 8 개의 블렌드로 변환하려고 시도했지만 작동하지 않았습니다. 나는 여전히 4 번의 포장을 풀어야했다.

나는이 동일한 기술을 8x8 float 전치에 사용했습니다 (해답의 끝 부분 참조). https://stackoverflow.com/a/25627536/2542702 . 그 대답에서 나는 여전히 8 개의 압축 풀기를 사용해야했지만 8 개의 셔플을 4 개의 셔플과 8 개의 블렌드로 변환하도록 관리했습니다.

32 비트 정수의 경우 shufps(AVX512를 사용한 128 비트 셔플을 제외하고) 같은 것이 없으므로 블렌드로 변환 할 수 없다고 생각하는 언팩으로 만 구현할 수 있습니다 (효율적으로). AVX512 vshufi32x4shufps32 비트 부동 소수점 대신 4 개의 정수로 구성된 128 비트 레인을 제외하고 는 효과적으로 작동 하므로이 동일한 기술이 경우에 따라 가능할 수 있습니다 vshufi32x4. Knights Landing의 경우 셔플은 혼합보다 4 배 더 느립니다 (처리량).


1
shufps정수 데이터에 사용할 수 있습니다 . 셔플 링을 많이하는 경우, 특히 똑같이 효율적인 AVX2를 사용할 수 없는 경우 shufps+ 에 대해 FP 도메인에서 모든 작업을 수행하는 것이 blendps좋습니다 vpblendd. 또한 Intel SnB 제품군 하드웨어에서는 .NET shufps과 같은 정수 명령어간에 사용하기위한 추가 바이 패스 지연이 없습니다 paddd. (혼합 바이 패스 지연이 blendps와 함께 paddd하지만, Agner 안개의 SNB 테스트에 따르면.)
피터 코르

@PeterCordes, 도메인 변경 사항을 다시 검토해야합니다. Core2-Skylake에 대한 도메인 변경 패널티를 요약 한 표 (아마도 SO에 대한 답변)가 있습니까? 어쨌든 나는 이것에 대해 더 많이 생각했습니다. 나는 이제 왜 wim이 vinsertf64x4아니라 16x16 전치 대답에서 계속 언급했는지 알 수 있습니다 vinserti64x4. 행렬을 읽고 쓰는 경우 전치가 데이터를 이동하기 때문에 부동 소수점 도메인 또는 정수 도메인을 사용하는지 여부는 확실히 중요하지 않습니다.
Z boson

1
Agner의 표에는 Core2 및 Nehalem (및 AMD라고 생각 함)에 대한 명령어 별 도메인이 나열되어 있지만 SnB 제품군은 나열되지 않습니다. Agner의 마이크로 아치 가이드에는 몇 가지 예와 함께 SnB에서 1c로 떨어지고 종종 0이라는 단락이 있습니다. 인텔의 최적화 매뉴얼에는 제 생각에 표가 있지만 그럭저럭 시도하지 않았기 때문에 얼마나 자세한 내용이 있는지 기억이 나지 않습니다. 주어진 명령에 어떻게 될지 카테고리 나는 완전히 명백없는 기억 않습니다.
피터 코르에게

메모리에 다시 쓰는 것이 아니더라도 전체 조옮김에 대해 1 개의 추가 클럭입니다. 각 피연산자에 대한 추가 지연은 전치의 소비자가 셔플 또는 블렌드로 작성된 레지스터를 읽기 시작함에 따라 병렬로 (또는 엇갈린 방식으로) 발생할 수 있습니다. 비 순차적 실행은 처음 몇 개의 FMA 또는 마지막 몇 번의 셔플이 완료되는 동안 시작될 수 있도록 허용하지만 다이 패스 지연 체인은 없으며 최대 1 개만 추가됩니다.
Peter Cordes

1
Nicw 대답! 인텔 64-ia-32-architectures-optimization-manual, 표 2-3에는 Skylake에 대한 우회 지연이 나열되어 있습니다. Haswell에 대한 표 2-8은 상당히 다르게 보입니다.
WIM

1

각 행을 열로, 각 열을 행으로 간주합니다. .. i, j 대신 j, i 사용

데모 : http://ideone.com/lvsxKZ

#include <iostream> 
using namespace std;

int main ()
{
    char A [3][3] =
    {
        { 'a', 'b', 'c' },
        { 'd', 'e', 'f' },
        { 'g', 'h', 'i' }
    };

    cout << "A = " << endl << endl;

    // print matrix A
    for (int i=0; i<3; i++)
    {
        for (int j=0; j<3; j++) cout << A[i][j];
        cout << endl;
    }

    cout << endl << "A transpose = " << endl << endl;

    // print A transpose
    for (int i=0; i<3; i++)
    {
        for (int j=0; j<3; j++) cout << A[j][i];
        cout << endl;
    }

    return 0;
}

1

오버 헤드없이 전치 (클래스가 완료되지 않음) :

class Matrix{
   double *data; //suppose this will point to data
   double _get1(int i, int j){return data[i*M+j];} //used to access normally
   double _get2(int i, int j){return data[j*N+i];} //used when transposed

   public:
   int M, N; //dimensions
   double (*get_p)(int, int); //functor to access elements  
   Matrix(int _M,int _N):M(_M), N(_N){
     //allocate data
     get_p=&Matrix::_get1; // initialised with normal access 
     }

   double get(int i, int j){
     //there should be a way to directly use get_p to call. but i think even this
     //doesnt incur overhead because it is inline and the compiler should be intelligent
     //enough to remove the extra call
     return (this->*get_p)(i,j);
    }
   void transpose(){ //twice transpose gives the original
     if(get_p==&Matrix::get1) get_p=&Matrix::_get2;
     else get_p==&Matrix::_get1; 
     swap(M,N);
     }
}

다음과 같이 사용할 수 있습니다.

Matrix M(100,200);
double x=M.get(17,45);
M.transpose();
x=M.get(17,45); // = original M(45,17)

물론 중요하지만 다른 주제 인 메모리 관리에 대해서는 신경 쓰지 않았습니다.


4
각 요소 액세스에 따라야하는 함수 포인터의 오버 헤드가 있습니다.
user877329 aug

1

배열의 크기를 미리 알고있는 경우 통합을 사용하여 도움을받을 수 있습니다. 이렇게-

#include <bits/stdc++.h>
using namespace std;

union ua{
    int arr[2][3];
    int brr[3][2];
};

int main() {
    union ua uav;
    int karr[2][3] = {{1,2,3},{4,5,6}};
    memcpy(uav.arr,karr,sizeof(karr));
    for (int i=0;i<3;i++)
    {
        for (int j=0;j<2;j++)
            cout<<uav.brr[i][j]<<" ";
        cout<<'\n';
    }

    return 0;
}

저는 C / C ++를 처음 접했지만 천재적으로 보입니다. union은 구성원에 대해 공유 메모리 위치를 사용하기 때문에 해당 메모리를 다르게 읽을 수 있습니다. 따라서 새로운 배열 할당을 수행하지 않고 전치 행렬을 얻습니다. 내가 맞아?
Doğuş

1
template <class T>
void transpose( const std::vector< std::vector<T> > & a,
std::vector< std::vector<T> > & b,
int width, int height)
{
    for (int i = 0; i < width; i++)
    {
        for (int j = 0; j < height; j++)
        {
            b[j][i] = a[i][j];
        }
    }
} 

1
읽는 것보다 쓰기시 캐시 미스 패널티가 더 적기 때문에 두 루프를 교환하면 더 빠를 것이라고 생각합니다.
phoeagon

5
이것은 정사각형 행렬에서만 작동합니다. 직사각형 행렬은 완전히 다른 문제입니다!
NealB 2013 년

2
질문은 가장 빠른 방법을 요구합니다. 이것은 단지 방법입니다. 빠르다고 생각하는 이유는 무엇입니까? 큰 행렬의 경우 캐시를 스 래시하고 성능이 끔찍합니다.
Eric Postpischil

1
@NealB : 어떻게 생각하십니까?
Eric Postpischil 2013 년

@EricPostpischil OP는 상대적으로 큰 행렬에 대해 묻고 있으므로 두 배의 메모리 할당을 피하기 위해 "제자리"에서 수행하기를 원한다고 가정합니다. 이 작업이 완료되면 소스 및 대상 행렬의 기본 주소가 동일합니다. 행 및 열 인덱스를 뒤집어서 전치하는 것은 정사각형 행렬에서만 작동합니다. 직사각형 행렬에 대해 올바른 방법이 있지만 다소 복잡합니다.
NealB 2013 년

0

최신 선형 대수 라이브러리에는 가장 일반적인 작업의 최적화 된 버전이 포함되어 있습니다. 이들 중 다수는 동적 CPU 디스패치를 ​​포함하여 프로그램 실행시 하드웨어에 대한 최상의 구현을 선택합니다 (이동성 저하없이).

이것은 일반적으로 벡터 확장 내장 함수를 통해 functinos의 수동 최적화를 수행하는 것보다 나은 대안입니다. 후자는 구현을 특정 하드웨어 공급 업체 및 모델에 연결합니다. 다른 공급 업체 (예 : Power, ARM) 또는 새로운 벡터 확장 (예 : AVX512)으로 교체하기로 결정한 경우이를 다시 구현해야합니다. 최대한 활용하십시오.

예를 들어 MKL 전치에는 BLAS 확장 기능이 포함됩니다 imatcopy. OpenBLAS와 같은 다른 구현에서도 찾을 수 있습니다.

#include <mkl.h>

void transpose( float* a, int n, int m ) {
    const char row_major = 'R';
    const char transpose = 'T';
    const float alpha = 1.0f;
    mkl_simatcopy (row_major, transpose, n, m, alpha, a, n, n);
}

C ++ 프로젝트의 경우 Armadillo C ++를 사용할 수 있습니다.

#include <armadillo>

void transpose( arma::mat &matrix ) {
    arma::inplace_trans(matrix);
}

0

intel mkl은 in-place 및 out-of-place 전치 / 복사 매트릭스를 제안합니다. 여기 에 문서에 대한 링크가 있습니다 . 더 빠른 10 개의 in-place와 최신 버전의 mkl 문서에 몇 가지 실수가 포함되어 있으므로 out of place 구현을 시도하는 것이 좋습니다.


-1

나는 가장 빠른 방법이 O (n ^ 2)보다 높게 취해서는 안된다고 생각합니다. 이런 식으로도 O (1) 공간 만 사용할 수 있습니다.
그렇게하는 방법은 쌍으로 교환하는 것입니다. 왜냐하면 행렬을 전치 할 때 M [i] [j] = M [j] [i]이므로 M [i] [j]를 temp에 저장 한 다음 M [i] [j] = M [j] [i], 마지막 단계 : M [j] [i] = temp. 이것은 한 번의 패스로 할 수 있으므로 O (n ^ 2)


2
M [i] [j] = M [j] [i]는 정사각형 행렬 인 경우에만 작동합니다. 그렇지 않으면 인덱스 예외가 발생합니다.
Antony Thomas

-6

내 대답은 3x3 행렬로 바뀝니다.

 #include<iostream.h>

#include<math.h>


main()
{
int a[3][3];
int b[3];
cout<<"You must give us an array 3x3 and then we will give you Transposed it "<<endl;
for(int i=0;i<3;i++)
{
    for(int j=0;j<3;j++)
{
cout<<"Enter a["<<i<<"]["<<j<<"]: ";

cin>>a[i][j];

}

}
cout<<"Matrix you entered is :"<<endl;

 for (int e = 0 ; e < 3 ; e++ )

{
    for ( int f = 0 ; f < 3 ; f++ )

        cout << a[e][f] << "\t";


    cout << endl;

    }

 cout<<"\nTransposed of matrix you entered is :"<<endl;
 for (int c = 0 ; c < 3 ; c++ )
{
    for ( int d = 0 ; d < 3 ; d++ )
        cout << a[d][c] << "\t";

    cout << endl;
    }

return 0;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.