C++实现矩阵原地转置算法

本文实例描述了C++实现矩阵原地转置算法,是一个非常经典的算法,相信对于学习C++算法的朋友有很大的帮助。具体如下:

一、问题描述

微软面试题:将一个MxN的矩阵存储在一个一维数组中,编程实现矩阵的转置。

要求:空间复杂度为O(1)

二、思路分析

下面以一个4x2的矩阵A={1,2,3,4,5,6,7,8}进行分析,转置过程如下图:

图中右下角的红色数字表示在一维数组中的下标。矩阵的转置其实就是数组中元素的移动,具体的移动过程如下图:

第一次处理一个环 肯定是该环中 最小的一个。

若是重复处理 ,则在该下标的后继中一定能发现小于该下标的。

int next = getNext(i, m, n);
     while (next > i) // 对于每个下标,不断求后继,看最后跳出循环时 ,next 与i 的关系,第一次处理肯定是 next==i。重复处理时 肯定是next《i 跳出循环。
       next = getNext(next, m, n);
     if (next == i)  // 处理当前环 
       movedata(mtx, i, m, n);


我们发现,这些移动的元素的下标是一个个环,下标1的元素移动到4,下标4的元素移动到2,下标2的元素移动到1。在编写程序的时候,我们需要解决两个问题:第一个是如何判定环是否重复(已处理过);第二个是如何计算当前元素下标的前驱与后继。

第一个问题:如何判断环是重复已处理过的?因为我们遍历整个数组时下标是从小到大的,所以如果是第一次遍历该环,则第一个下标肯定是这个环中最小的。如果一个环被处理过,那么总能找到一个它的后继是小于它的。从上图可以明显看出来。

第二个问题:如何计算当前元素下标的前驱与后继?假设转置前某个元素的数组下标为i,则它所在行列为(i/N, i%N),转置后所在行列则为(i%N, i/N),可计算转置后数组下标为(i%N)*M+i/N,此为i的后继。假设转置后某个元素的数组下标为i,则它所在行列为(i/M, i%M),则转置前所在行列为(i%M, i/M),可计算此时下标为(i%M)*N+i/M,此为i的前驱。

三、代码实现如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/*************************************************************************
   > File Name: matrix_transpose.cpp
   > Author: SongLee
  ************************************************************************/
#include<iostream>
using namespace std;
  
/* 后继 */
int getNext( int i, int m, int n)
{
   return (i%n)*m + i/n;
}
  
/* 前驱 */
int getPre( int i, int m, int n)
{
   return (i%m)*n + i/m;
}
  
/* 处理以下标i为起点的环 */
void movedata( int *mtx, int i, int m, int n)
{
   int temp = mtx[i]; // 暂存
   int cur = i;    // 当前下标
   int pre = getPre(cur, m, n);
   while (pre != i)
   {
     mtx[cur] = mtx[pre];
     cur = pre;
     pre = getPre(cur, m, n);
   }
   mtx[cur] = temp;
}
  
/* 转置,即循环处理所有环 */
void transpose( int *mtx, int m, int n)
{
   for ( int i=0; i<m*n; ++i)
   {
     int next = getNext(i, m, n);
     while (next > i) // 若存在后继小于i说明重复
       next = getNext(next, m, n);
     if (next == i)  // 处理当前环 
       movedata(mtx, i, m, n);
   }
}
  
/* 输出矩阵 */
void print( int *mtx, int m, int n)
{
   for ( int i=0; i<m*n; ++i)
   {
     if ((i+1)%n == 0)
       cout << mtx[i] << "\n" ;
     else
       cout << mtx[i] << " " ;
   }
}
  
/* 测试 */
int main()
{
   int matrix[4*2] = {1,2,3,4,5,6,7,8};
   cout << "Before matrix transposition:" << endl;
   print(matrix, 4, 2);
   transpose(matrix, 4, 2);
   cout << "After matrix transposition:" << endl;
   print(matrix, 2, 4);
   return 0;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
#include <stdio.h> #define MAX_SIZE 100 typedef struct { int i, j; // 行、列下标 int value; // 元素值 } Triple; typedef struct { Triple data[MAX_SIZE + 1]; // 存放三元组的数组 int m, n, num; // 行数、列数、非零元素个数 } Matrix; // 创建稀疏矩阵 void createMatrix(Matrix *mat) { printf("请输入矩阵的行数和列数:"); scanf("%d %d", &mat->m, &mat->n); printf("请输入矩阵的非零元素个数:"); scanf("%d", &mat->num); printf("请输入矩阵的三元组:\n"); for (int i = 1; i <= mat->num; i++) { scanf("%d %d %d", &mat->data[i].i, &mat->data[i].j, &mat->data[i].value); } } // 打印稀疏矩阵 void printMatrix(Matrix mat) { printf("该矩阵的三元组表示如下:\n"); for (int i = 1; i <= mat.num; i++) { printf("%d %d %d\n", mat.data[i].i, mat.data[i].j, mat.data[i].value); } } // 矩阵转置 void transpose(Matrix mat, Matrix *tMat) { tMat->m = mat.n; // 转置后行数变为原来的列数 tMat->n = mat.m; // 转置后列数变为原来的行数 tMat->num = mat.num; // 非零元素个数不变 int q = 1; // tMat->data[] 的下标 for (int col = 1; col <= mat.n; col++) { // 遍历 mat 的每一列 for (int p = 1; p <= mat.num; p++) { // 遍历 mat 的每一行 if (mat.data[p].j == col) { // 找到 mat 中这一列的非零元素 tMat->data[q].i = mat.data[p].j; // 行下标变为原来的列下标 tMat->data[q].j = mat.data[p].i; // 列下标变为原来的行下标 tMat->data[q].value = mat.data[p].value; // 元素值不变 q++; // tMat->data[] 的下标加 1 } } } } int main() { Matrix mat, tMat; createMatrix(&mat); printMatrix(mat); transpose(mat, &tMat); printf("转置后的矩阵三元组表示如下:\n"); for (int i = 1; i <= tMat.num; i++) { printf("%d %d %d\n", tMat.data[i].i, tMat.data[i].j, tMat.data[i].value); } return 0; }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值