其他部分

第一章 矩阵

一、矩阵的转置

 

问题描述:

编写函数,把给定的任意一个二维整型矩阵转换为其转置矩阵。

输入:

1 2 3

4 5 6

输出:

1 4

2 5

3 6

分析

题目要求编写一个能转置任意二维矩阵的函数,显然这个函数需要得到一个二维数组的参数,还应该有一个作为输出的二维数组,我们可以将这个数组也传递给函数,然后函数交换行列的下标赋值给输出数组(即b[j][i] = a[i][j])即可。这样看似问题可以得到解决了,但是,二维数组作为参数时必须指定列数,也就是第二维的大小,比如:void transpose ( int a[][3], int b[][2]),那么这个函数就只能将=转置2行3列的矩阵,无法达到题目中转置任意矩阵的要求。

如何让函数具有通用性呢?我们考虑到数组在内存中的存放方式是按行线性连续排列的,比如:

数组

a[2][3] = {

{ 1,2, 3 },

{ 4,5, 6 }

};

b[3][2] = {

{ 1,2 },

{ 3,4 },

{ 5,6 }

};

看起来这两个数组的布局不一样但是它们在内存中的排列方式是相同的:

a:

a[0][0]

a[0][1]

a[0][2]

a[1][0]

a[1][1]

a[1][2]

1

2

3

4

5

6

b:

b[0][0]

b[0][1]

b[1][0]

b[1][1]

b[2][0]

b[2][1]

1

2

3

4

5

6

这样看来,一维数组在某些时候可以代替二维数组,并且最重要的是,一维数组作为参数传递给函数时,不用指定长度,这个正好符合题意。我们能不能使用一维数组完成题目的要求呢?

首先看一下在二维数组中,转置是如何发生的:

1 2 3

4 5 6

转置

1 4

2 5

3 6

很明显,在二维视角下,矩阵的转置就是交换行标和列标,即:b[j][i] =a[i][j],而2X3的二维数组中的元素a[i][j],在一维视角下就变为a[i*3+j],转置后的3X2二维数组中对应的元素b[j][i]变为b[j*2+i]。如此a[i*3+j]= b[j*2+i]就可以实现转置了。

至此,我们已经找到了使用一维数组实现二维数组转置的方法,现在看看如何将一个二维数组变成一维数组参数。由于一维数组在作为函数参数时就是一个指向int类型的指针,而二维数组名a是一个指向以为数组的指针,因此*a就是一个一维数组,使用它作为函数参数时也是一个指向int类型的指针。

下面是完整的程序

/*
* 转置矩阵的通用函数
*/
void transpose ( int *m1, int *m2, int x, int y ); //转置函数
static int m2[3][2]; //自动初始化 int main ( void )
{
int m1[2][3] = {
{1, 2, 3},
{4, 5, 6},
}; int i;
int j; transpose ( *m1, *m2, 2, 3 ); //使用参数*m1、*m2 for ( i=0; i<3; i++ )
{
for ( j=0; j<2; j++ )
printf ( "%3d", m2[i][j] );
printf ( "\n" );
} return 0;
} void transpose ( int *m1, int *m2, int x, int y )
{
int i;
int j; for ( i=0; i<x; i++ )
for ( j=0; j<y; j++ )
m2[j*x+i] = m1[i*y+j];
}

小结:利用二维数组的内存分布特点,可以用一维数组来处理二维数组的问题,并且这种方法还具有通用性。

二、矩阵的乘法

 

问题描述:编写一个实现两个任意矩阵乘法的函数(两个矩阵中一个的行数和另一个的列数相同)

输入:

m1:

2   -6

3    5

1   -1

m2:

4    -2   -4   -5

-7   -3    6    7

输出:

r:

50    14   -44   -52

-23   -21    18    20

11    1    -10   -12

    分析:

    我们首先考虑矩阵乘法的法则:

    就以上这个例子而言,显然有:

    r[0][0] = m1[0][0] * m2[0][0] + m1[0][1] * m2[1][0];

    r[0][1] = m1[0][0] * m2[0][1] + m1[0][1] * m2[1][1];

    ......

    观察发现:以上等式中,所有m1的行标都与r的行标一致,所有m2的列标都与r的列标一致,而m1中和m2中剩下的没有确定的下标都是在遍历,更巧的是,这两个没有确定的下标的遍历范围是一致的。

    我们假设m1的大小为x*y,m2的为y*z,m3自然就应该是x*z,分别用i、j、k遍历x、y、z。那么,对于一个给定了具体下标值的元素r[i][k](假定r中所有元素的初始值为0),在计算此元素的表达式中,所有m1的行标都已经确定为i,所有m2的列标也确定为k,此时只需要使用j来遍历y,并将j作为m1和m2中那个还没有确定的下标值,可以通过遍历i和k来实现遍历所有r的元素,伪代码如下:

    i: 0 → x

        k: 0 → z

            j: 0 → y

            r[i][k] += m1[i][j] * m2[j][k];

    最后,我们仍然要使用一维数组来解决通用性的问题:

    m1[i][j] = m1[i*y+j];

    m2[j][k] = m2[j*z+k];

    r[i][k] = r[i*z+k];

    最终的函数和测试程序如下:

/*
* 矩阵乘法
*/
#include <stdio.h>
void multiply ( int *m1, int *m2, int *r, int x, int y, int z ); static int r[3][4]; int main ( void )
{
int m1[3][2] = {
{2, -6},
{3, 5},
{1, -1}
}; int m2[2][4] = {
{4, -2, -4, -5},
{-7, -3, 6, 7}
}; int i,j; multiply ( *m1, *m2, *r, 3, 2, 4 ); for ( i=0; i<3; i++ )
{
for ( j=0; j<4; j++ )
printf ( "%5d", r[i][j] );
printf ( "\n" );
} return 0;
} void multiply ( int *m1, int *m2, int *r, int x, int y, int z )
{
int i, j, k; for ( i=0; i<x; i++ )
for ( j=0; j<y; j++ )
for ( k=0; k<z; k++ )
r[i*z+k] += m1[i*y+j] * m2[j*z+k];
} /*
* 测试结果:
*
* 50 14 -44 -52
* -23 -21 18 20
* 11 1 -10 -12
*
*/

    小结:注意分析乘法法则,得到下标值之间的关系。

声明

1、此博文版权归断絮所有,如需转载请注明出处http://blog.csdn.net/duanxu_yzc/article/details/12049955,谢谢。

2、在未经博主断絮允许的情况下,任何个人和机构都不得以任何理由出版此文章,否则必将追究法律责任!

[置顶] 【其他部分 第一章 矩阵】The C Programming Language 程序研究 【持续更新】的更多相关文章

  1. 第一章 “我要点爆”微信小程序云开发之项目建立与我的页面功能实现

    第一章 “我要点爆”微信小程序云开发之项目建立与我的页面功能实现 开发环境搭建 使用自己的AppID新建小程序项目,后端服务选择小程序·云开发,点击新建,完成项目新建. 新建成功后跳转到开发者工具界面 ...

  2. 【第一章】 第一个spring boot程序

    环境: jdk:1.8.0_73 maven:3.3.9 spring-boot:1.2.5.RELEASE(在pom.xml中指定了) 注意:关于spring-boot的支持, 最少使用jdk7(j ...

  3. 我们曾经心碎的C#之 第一章.我的第一个C#程序

    第一章.      C#入门 1.1        .NET与C#            001..NET是Microsoft.NET的简称,是基于Windows平台的一种技术            ...

  4. php将数组中某个元素置顶设为第一个元素

    一个数组$a0有N个元素,要将其中第3个元素,排在数组的首位. 第一种做法是: 取出第3个元素,赋值给变量$a unset 第3个元素 array_unshift 将$a添加到数组头部. 如果是数字下 ...

  5. [置顶] CopyU!v2插件合集 [2013年7月18日更新]

    这里提供了所有可供CopyU!v2使用的功能插件,您可以根据自己的需要下载安装使用,需要提醒您的是,安装过多的插件会影响CopyU!的运行性能,请合理的安装使用! 1.打包插件 版本:1.0.12.1 ...

  6. 第一章开发简单的Java应用程序

    1.什么是程序? 程序一词来源于生活,通俗点讲就是把生活的的事用程序编写出来 并执行. 2.为什么要学习Java呢? Java是Sun Microsystems于1995年推出的高级编程语言 Java ...

  7. CLR Via 第一 章 知识点整理(2)程序集和CLR的启动

    这一节先简单的讨论一下程序集以及CLR的初始化 虽然对应的编译器会生成托管模块,但实际上CLR不与托管模块工作,编译器除了编译还有将生成的托管模块转换为程序集的功能,微软还提供了工具AL.exe(程序 ...

  8. 第一章 C#入门 (Windows窗体应用程序)(三)

    [案例] 编写一个Windows窗体应用程序,窗体上有一个文本框和两个按钮([显示]和[清除]按钮). 单击[显示]时,文本框的背景变为蓝色并且居中显示“努力学习C#”: 单击[清除]按钮,文本框的背 ...

  9. 第一章 C#入门(Windows窗体应用程序)(二)

    C#窗体应用程序(二) [案例]设计登录界面,效果如下: [案例实现步骤] 1.新建项目(Windows控制台应用程序 文件→新建→项目:选择“项目类型”为Visual C#,“模板”为Windows ...

随机推荐

  1. WPF与输入法冲突研究之一:百度输入法会导致WPF程序的崩溃!

    在学习和使用了WPF一段时间之后,有点感觉WPF是个不太成熟的框架,不知道是我学的太肤浅,还是WPF得BUG太多! >>>>>>>模拟场景<<&l ...

  2. python glob标准库基础学习

    #glob文件名模式匹配#作用:使用unix shell规则查找与一个模式匹配文件名"""尽管glob api很小,但这个模块很强大,只要程序需要查找文件系统中名字与某种 ...

  3. PHP设计模式笔记五:策略模式 -- Rango韩老师 http://www.imooc.com/learn/236

    策略模式 1.概述:策略模式,将一组特定的行为和算法封装成类,以适应某些特定的上下文环境,这种模式称为策略模式 例如:一个电商网站系统,针对男性女性用户要各自跳转到不同的商品类目,并且所有广告位展示不 ...

  4. LNMP、LAMP、LANMP一键安装脚本(定期更新)[转]

    这个脚本是使用shell编写,为了快速在生产环境上部署LNMP/LAMP/LANMP(Linux.Nginx/Tengine.MySQL/MariaDB/Percona.PHP),适用于CentOS/ ...

  5. webService返回自定义类型的数据处理

    1.自定义一个Student 数据类型: package com.chnic.webservice; import java.io.Serializable; public class Student ...

  6. [Redux] Extracting Presentational Components -- Footer, FilterLink

    Code to be refactored: let nextTodoId = 0; class TodoApp extends Component { render() { const { todo ...

  7. 【足迹C++primer】46、动态存储类

    动态存储类 StrVec Class Design StrVec Class Definition class StrVec { public: //构造函数 StrVec():elements(nu ...

  8. 常用的50条linux 命令

    从今天起,咱开始正式学习python了,于是遍整理了50条linux的常用命令. 1 线上查询帮助命令 :man   遇到什么不会的命令可以 man +你想要查询的命令 (需要有网),因为是英文的所以 ...

  9. bit、byte、位、字节、字符串等概念

    原始文章:http://djt.qq.com/article/view/658 1.古代送信:马车,烽火,信鸽 2.1837年,世界第一条电报诞生, 美国科学家莫尔斯尝试用一些“点”和“划”来表示不同 ...

  10. Linux 统计代码行数命令

    wc -l `find . -name '*.js'` wc -l `find . -regex ".*\.js"`