其他部分

第一章 矩阵

一、矩阵的转置

 

问题描述:

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

输入:

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. 2015.11.27初识java一集简单的java小程序

    JAVA配置环境变量方法: 1.打开我的电脑--属性--高级--环境变量 2.新建系统变量JAVA_HOME 和CLASSPATH 变量名:JAVA_HOME变量值:D:\Java\jdk1.7.0_ ...

  2. ubuntu 14.04下练习lua

    随着lua越来越成熟,在服务器中应用也越来越广.自己也想向这方面发展,于是便开始lua的学习. 学习新的语言,应该是先编译.安装.部署开发调试环境,然后练习...可是,我现在并没有项目做啊,我只是想先 ...

  3. Map的内容按字母顺序排序

    map有自带的排序功能,但需要重写排序方法,代码如下: package coreJava.com.shindo.corejava.map; import java.util.ArrayList; im ...

  4. POJ 1001 Exponentiation 模拟小数幂

    模拟小数幂 小数点位 pos 非零末位 e 长度 len 只有三种情况 pos > len pos < e e < pos < len #include <iostrea ...

  5. ubuntu终端命令

    整个电脑都划成ubuntu用. 装软件时的一个明显感觉就是很多事情,用终端的命令行去做很容易,用图形界面往往很复杂,而且很多时候还会出现权限的问题,对于ubuntu的用户权限,现在的唯一感觉就是权限在 ...

  6. SMA2SATA、PCIe2SATA转换模块(也有叫:Sata Test Fixtures)

    SMA2SATA.PCIe2SATA测试夹具(Sata Test Fixtures) 去年制作SMA2SATA.PCIe2SATA适配器的过程早就想写出来,但一直没有时间,今天星期六有个空儿,简单整理 ...

  7. C#按键打开文件选择对话框,并把选择好的路径保存/显示到textBox

    1.选择文件夹 FolderBrowserDialog fbd = new FolderBrowserDialog(); fbd.SelectedPath = "D:";//默认路 ...

  8. MFC的初始化过程和消息映射技术

    1.删除#include <windows.h>--win32中的-(使用win32工程编程mfc必须删除) 添加#include <afxwin.h> -- mfc中的- 2 ...

  9. C++ 虚函数详解

    C++ 虚函数详解 这篇文章主要是转载的http://blog.csdn.net/haoel/article/details/1948051这篇文章,其中又加入了自己的理解和难点以及疑问的解决过程,对 ...

  10. [Mac] 使用Mac时的一些技巧

    这篇博客就用来记录自己在使用Mac时学来的一些技巧吧! 1. 如何开启 Sticky key (在屏幕上显示输入的控制键)   就是这个东西啦,就是在视频演示的时候让别人看到自己按了什么控制键. 在s ...