1 / 77

第四章 数组与矩阵

第四章 数组与矩阵. 山东财经大学 管理科学与工程学院 李玲玲 lilingling_77@126.com. 第四章 数组与矩阵. 本章的主要内容: 针对一维数组和二维数组分别设计的类 A r r a y 1 D 和类 A r r a y 2 D 。 矩阵类 Matrix 特殊矩阵:对角矩阵、三对角矩阵、三角矩阵和对称矩阵。 稀疏矩阵. 数组与矩阵. 在实际应用过程中,数据通常以表的形式出现。尽管用数组来描述表数据是最自然的方式,但有时为了减少程序的时间和空间需求,通常会采用自定义的描述方式,比如,当表中大部分数据为 0 时。. 数 组.

eliot
Download Presentation

第四章 数组与矩阵

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第四章 数组与矩阵 山东财经大学 管理科学与工程学院 李玲玲 lilingling_77@126.com

  2. 第四章 数组与矩阵 • 本章的主要内容: • 针对一维数组和二维数组分别设计的类A r r a y 1 D和类A r r a y 2 D。 • 矩阵类Matrix • 特殊矩阵:对角矩阵、三对角矩阵、三角矩阵和对称矩阵。 • 稀疏矩阵

  3. 数组与矩阵 • 在实际应用过程中,数据通常以表的形式出现。尽管用数组来描述表数据是最自然的方式,但有时为了减少程序的时间和空间需求,通常会采用自定义的描述方式,比如,当表中大部分数据为0时。

  4. 数 组

  5. 数组的定义 数组:按一定格式排列起来的 具有相同类型的数据元素的集合。 一维数组:若线性表中的数据元素为非结构的简单元素, 则称为一维数组。 一维数组的逻辑结构:线性结构。定长的线性表。 声明格式: 数据类型 变量名称[长度]; 例:int num[5] = {0,1,2,3,4};

  6. 数组的定义 num[5] = {0,1, 2,3,4}; A=(0, 1, …, p) (p = m-1 or n-1) j=(a0j, a1j,…, am-1,j) 0≤j≤n-1 i=(ai0, ai1, …, ai,n-1) 0≤i≤m-1 二维数组:一维数组中的数据元素又是一维数组结构 每一个数据元素 既在一个行表中, 又在一个列表中。 非线性结构 二维数组的逻辑结构 线性结构 定长的线性表 该线性表的每个数据元素 也是一个定长的线性表。

  7. 数组的定义 三维数组:若二维数组中的元素又是一个一维数组结构, 则称作三维数组。 … n维数组:若 n -1 维数组中的元素又是一个一维数组结构, 则称作 n维数组。 结论 线性表结构是数组结构的一个特例, 而数组结构又是线性表结构的扩展。 数组特点:结构固定——定义后,维数和维界不再改变。 数组基本操作:除了结构的初始化和销毁之外, 只有存取元素和修改元素值的操作。

  8. 数组的抽象数据类型 • ADT Array{ • 数据对象:形如(index,value)的数据对集合,其中任意两对数据的index值都各不相同 • 数据之间的关系:线性关系 • 操作 • Create( ):创建一个空的数组 • Store(index,value):添加数据(index,value),同时删除具有相同index值的数据对(如果存在) • Retrieve(index):返回索引值为index的数据对 • }ADT Array

  9. • 上个星期每天的高温(华氏度数)可用如下的数组来表示: • h i g h= { ( s u n d a y, 82), (monday, 79), (tuesday, 85), (wednesday, 92),(thursday, 88), (friday, 89),( s a t u r d a y, 91)} • 通过执行如下操作,可以将m o n d a y的温度改变为8 3。 • S t o re( m o n d a y, 83) • 通过执行如下操作,还可以确定f r i d a y的温度: • R e t r i e v e( f r i d a y ) • 也可以采用如下的数组来描述每天的温度: • h i g h={(0,82), (1,79), (2,85), (3,92), (4,88), (5,89), (6,91)}

  10. 数组的顺序表示和实现 数组特点:结构固定——维数和维界不变。 因为 数组基本操作:初始化、销毁、存取元素、修改元素值。 一般不做插入和删除操作。 一般都是采用顺序存储结构来表示数组。 所以: 以行序为主序 (低下标优先) 两种顺序存储方式 以列序为主序 (高下标优先)

  11. a00 0 a01 1 ……. a00 a01 …….. a0, n-1 a0, n-1 a10 a11 …….. a1, n-1 n -1 a10 …………………. n am-1, 0am-1, 1 …….. am-1, n-1 a11 …….. a1, n-1 ………. am-1, 0 基地址或基址 am-1, 1 …….. am-1, n-1 m*n -1 以行序为主序存放: 二维数组 A 中任一元素ai,j的存储位置 LOC(i, j) = LOC(0, 0) + (b2×i+j )×L 二维数组的映象函数 某个元素的地址就是它前面所有行 所占的单元加上它所在行前面所有列元 素所占的单元数之和。

  12. 0 a00 1 a00a01……..a0, n-1 a10 a10a11……..a1, n-1 ……. m -1 …………………. am-1, 0 m am-1, 0am-1, 1……..am-1, n-1 a01 a11 …….. am-1, 1 ………. a0, n-1 a1, n-1 m*n -1 …….. am-1, n-1 按列序为主序存放 二维数组 A 中任一元素ai,j的存储位置 LOC(i, j) = LOC(0, 0) + (b1×j+i )×L 某个元素的地址就是它前面所有列 所占的单元加上它所在列前面所有行元 素所占的单元数之和。

  13. a00 a01 … a0, n-1 a10 a11 … a1, n-1 …………… ……… a31, 57 …… …………… am-1, 0 am-1, 1 … am-1, n-1 例 设数组 A[0…59, 0…69] 的基地址为 2048,每个元素占 2 个 存储单元,若以列序为主序顺序存储,则元素 A[31, 57] 的存储 地址为。 8950 解:LOC(i, j) = LOC(31, 57) = LOC(0, 0) + (b1×j+i )×L = 2048 + (60×57+31)×2 = 8950 ▲

  14. 一维数组的实现——Array1D • template<class T> • class Array1D { • public: • Array1D(int size = 0); • Array1D(const Array1D<T>& v); // 复制构造函数 • ~Array1D() {delete [] element;} • T& operator[ ](int i) const; • int Size() {return size;} • Array1D<T>& operator=(const Array1D<T>& v);

  15. 一维数组的实现——Array1D 这个类的共享成员包括:构造函数,复制构造函数,析构函数,下标操作符[ ],返回数组大小的函数S i z e,算术操作符+、-、*和+=。此外还可以添加其他的操作符。 私有成员包括表示数组大小的整数size和存放数组元素的一维数组element 。 • Array1D<T> operator+() const; // 一元加法操作符 • Array1D<T> operator+(const Array1D<T>& v) const; • Array1D<T> operator-() const; // 一元减法操作符 • Array1D<T> operator-(const Array1D<T>& v) const; • Array1D<T> operator*(const Array1D<T>& v) const; • Array1D<T>& operator+=(const T& x); • private : • int size; • T *element; //一维数组 • };

  16. 一维数组的实现——Array1D • // 一维数组的构造函数 • template<class T> • Array1D<T>::Array1D(int sz) • { • if (sz < 0) throw BadInitializers(); • size = sz; • element = new T[sz]; • } 分配一维数组的存储空间

  17. 一维数组的实现——Array1D • // 一维数组的复制构造函数 • template<class T> • Array1D<T>::Array1D(const Array1D<T>& v) • { • size = v.size ; • element = new T[size]; • for (int i = 0; i < size; i++) • element[i] = v.element[ i ] ; • } 申请空间之后,将V的元素依次复制

  18. 一维数组的实现——Array1D 可以引用 x[i]的形式 如: X[1] = 2 * Y[3]; 其中X和Y的类型均为A r r a y 1 D。 Y [ 3 ] 在对象Y上施加操作符[ ],结果返回指向元素3的一个引用,然后将它乘以2。代码X [ 1 ]也调用了操作符[ ],结果返回一个指向X [ 1 ]的引用,之后2 * Y [ 3 ]的结果将存储在这个引用位置内。 • // 返回指向第i个元素的引用,使得语句:X[1] = 2 * Y[3];按照期望的方式进行工作 • template<class T> • T& Array1D<T>::operator[](int i) const { • if (i < 0 || i >= size) throw OutOfBounds(); • return element[i]; • }

  19. 一维数组的实现——Array1D • // 重载赋值操作符= • template<class T> • Array1D<T>& Array1D<T>::operator=(const Array1D<T>& v) { • if (this != &v) {// 不是自我赋值 • size = v.size ; • delete [] element; // 释放原空间 • element = new T[size]; // 申请空间 • for (int i = 0; i < size; i++) • element[i] = v. element [ i ] ; • } • return *this; • } 重新建立一个与源数组相同大小的数组,并依次拷贝

  20. 一维数组的实现——Array1D • //重载减号运算符 • template<class T> • Array1D<T> Array1D<T>:: operator - (const Array1D<T>& v) const • {// 返回w = (*this) - v • if (size != v.size) throw SizeMismatch(); • Array1D<T> w(size); // 创建结果数组w • for (int i = 0; i < size; i++) • w.element[i] = element[i] - v.element[i]; • return w; • } 相同大小的数组的元素对应相减

  21. 一维数组的实现——Array1D • //重载负号运算符 • template<class T> • Array1D<T> Array1D<T>::operator-() const • {// 返回w = -(*this) • // 创建结果数组w • Array1D<T> w(size); • for (int i = 0; i < size; i++) • w.element[i] = -element[i]; • return w; • } 对数组的每一个元素取负值

  22. 一维数组的实现——Array1D • //重载+=运算符,其他运算符代码类似 • template<class T> • Array1D<T>& Array1D<T>::operator+=(const T& x) • { • for (int i = 0; i < size; i++) • element[i] += x; • return *this; • } 把x 加到(* this) 的每个元素上

  23. 一维数组的实现——Array1D • 复杂性 • 构造函数和析构函数的复杂性 • 当T是一个内部C + +数据类型(如int, float和c h a r)时,为O( 1 ), • 当T是一个用户自定义的类时,为O ( s i z e )。 • 之所以存在这种差别,是因为当T是一个用户自定义类时,在用n e w(d e l e t e)创建(删除)数组e l e m e n t的过程中,对于e l e m e n t的每个元素都要调用一次T的构造函数(析构函数)。 • 下标操作符[ ]的复杂性为O( 1 ),其他操作符的复杂性均为O ( s i z e )。

  24. 二维数组的实现——Array2D • 二维数组可被视为一维数组的集合,在Array2D中采用一维数组row来直接存储每个行数组。 • 数组row被设置成标准的C++一维数组而不是Array1D的一个实例,是因为我们能够完全控制对row中每个元素的访问(row是一个私有成员)。 • row[i]是类型为Array1D的一维数组,它代表二维数组的第i行。 • row[ i ]不是指向一维数组的指针

  25. 二维数组的实现——Array2D Array2D的析构函数并未明确释放分配给二维数组每一行元素的空间。不过,在执行delete [ ] row 时,对于数组row的每一个元素, delete都将调用Array1D的析构函数,由Array1D的析构函数来释放每行元素所占用的空间。 • template<class T> • class Array2D { • public : • Array2D(int r = 0, int c = 0); • Array2D(const Array2D<T>& m); // 复制构造函数 • ~Array2D() {delete [] row;} • int Rows() const {return rows;} • int Columns() const {return cols;} • Array1D<T>& operator[](int i) const; • Array2D<T>& operator=(const Array2D<T>& m);

  26. 二维数组的实现——Array2D • Array2D<T> operator+() const; // 一元加法操作符 • Array2D<T> operator+(const Array2D<T>& m) const; • Array2D<T> operator-() const; // 一元减法操作符 • Array2D<T> operator-(const Array2D<T>& m) const; • Array2D<T> operator*(const Array2D<T>& m) const; • Array2D<T>& operator+=(const T& x); • private: • int rows, cols; // 数组维数 • Array1D<T> *row; // 一维数组的数组 • };

  27. 二维数组的实现——Array2D • // 二维数组的构造函数 • template<class T> • Array2D<T>::Array2D(int r, int c){ • if (r < 0 || c < 0) throw BadInitializers(); // 合法的r 和c • if ((!r || !c) && (r || c)) throw BadInitializers(); • rows = r; cols = c; • // 分配r个具有缺省大小的一维数组 • row = new Array1D<T> [r]; • // 调整每个元素的大小 • for (int i = 0; i < r; i++) • row[i] .ReSize ( c ) ; • } Resize是Array1D的一个新的成员函数,通过执行以下代码,它能把一个一维数组的大小变成s z: delete [ ] element; size = sz; element = new T [size];

  28. 二维数组的实现——Array2D • 复制构造函数 • 创建一个具有给定长度的数组row • 利用一维数组的赋值操作符复制二维数组中的每一行元素。 • 赋值操作符的代码类似于复制构造函数的代码,不过,Array2D的赋值操作检查了是否为自我赋值。 // 二维数组的复制构造函数 template<class T> Array2D<T>::Array2D(const Array2D<T>& m) { rows = m.rows; cols = m.cols; row = new Array1D<T> [rows]; for (int i = 0; i < rows; i++) row[i] = m.row[i]; }

  29. 二维数组的实现——Array2D 若X的类型为Array2D<T>,则X[i][j]被分解为(X.operator[i]). operator[ j ]。 因此,对于X[i][j]将首先利用实参i来调用Array2D<T>:: operator[ ] ,如果该操作返回了对象Y,则继续用实参j来调用typeof(Y)::operator[ ]。若typeof(Y)不同于Array2D<T>,则仅对二维数组的第一个索引调用Array2D<T>::operator[ ]。 若执行代码X [ i ] [ j ],则X [ i ]将调用Array2D<T>:: operator[ ],该操作符返回一个指向X.row[i](即X的第i行)的引用。由于这个引用的类型为Array1D<T>,所以接下来将调用Array1D<T>::operator[ ]并返回一个指向相应数组元素的引用。 • / /二维数组的第一维索引 • template<class T> • Array1D<T>& Array2D<T>::operator[](int i) const • { • if (i < 0 || i >= rows) throw OutOfBounds(); • return row[i]; • }

  30. 二维数组的实现——Array2D • template<class T> • Array2D<T> Array2D<T>:: operator-(const Array2D<T>& m) const • {// 返回w = (*this) - m. • if (rows != m.rows || cols != m.cols) • throw SizeMismatch(); • Array2D<T> w(rows,cols); • for (int i = 0; i < rows; i++) • w.row[i] = row[i] - m.row[i]; • return w; • } 二元减法操作符只是简单地对二维数组的每一行调用Array1D<T>::operator-。加法操作符、一元减法操作符、增值操作符和输出操作符的代码与此类似。

  31. 二维数组的实现——Array2D • template<class T> • Array2D<T> Array2D<T>:: operator*(const Array2D<T>& m) const{// 矩阵乘,返回w = (*this) * m. • if (cols != m.rows) throw SizeMismatch(); • Array2D<T> w(rows, m.cols); • for (int i = 0; i < rows; i++) • for (int j = 0; j < m.cols; j++) { • T sum = (*this)[i][0] * m[0][j]; • for (int k = 1; k < cols; k++) • sum += (*this)[i][k] * m[k][j]; • w[i][j] = sum; • } • return w; • }

  32. 复杂性 • 构造函数和析构函数的复杂性 • 当T是一个C++内部数据类型时,均为O(rows) • 当T是一个用户自定义的类时,为O(rows*cols) • 复制构造函数及operator的复杂性为O(rows*cols) • 下标操作符的复杂性为O( 1 ) • 乘法操作符的复杂性O (rows*cols* m.cols)

  33. 作业 • 1、扩充A r r a y 1 D类(见程序4 - 1),重载操作符<<输入一个数组)、+(一元加法)、*=(将每个元素右乘一个类型为T的元素)、/=和-=。并测试代码。 • 2、模仿Array2D<T>设计一个三维数组的类Array3D<T>。

  34. 矩 阵

  35. 矩阵 矩阵定义:一个由 m×n个元素排成的 m行(横向) n列(纵向)的表。 矩阵的常规存储: 将矩阵描述为一个二维数组。 矩阵的常规存储的特点: 可以对其元素进行随机存取; 矩阵运算非常简单;存储的密度为 1。 不适宜常规存储的矩阵:值相同的元素很多且呈某种规律 分布;零元素多。 矩阵的压缩存储:为多个相同的非零元素只分配一个存储 空间;对零元素不分配空间。

  36. 在Matrix类中,使用()来指定每个元素,并且各行和各列的索引值都是从1开始的。 矩阵的常规存储 • template<class T> • class Matrix { • public: • Matrix(int r = 0, int c = 0); • Matrix(const Matrix<T>& m); //复制构造函数 • ~Matrix() {delete [] element;} • int Rows() const {return rows;} • int Columns() const {return cols;} • T& operator()(int i, int j) const; • Matrix<T>& operator=(const Matrix<T>& m); • Matrix<T> operator+() const; // 一元加法

  37. 矩阵的常规存储 • Matrix<T> operator+(const Matrix<T>& m) const; • Matrix<T> operator-() const; // 一元减法 • Matrix<T> operator-(const Matrix<T>& m) const; • Matrix<T> operator*(const Matrix<T>& m) const; • Matrix<T>& operator+=(const T& x); • private: • int rows, cols; // 矩阵维数 • T *element; // 元素数组 • }; 采用一个一维数组element来存储rows ×cols矩阵中的rows * cols个元素。

  38. 矩阵的常规存储 复制构造函数与一维数组的复制构造函数类似 • // 类Matrix的构造函数 • template<class T> • Matrix<T>::Matrix(int r, int c) { • // 验证r和c的合法性 • if (r < 0 || c < 0) throw BadInitializers(); • if ((!r || !c) && (r || c)) • throw BadInitializers(); • // 创建矩阵 • rows = r; cols = c; • element = new T [r * c]; • } 创建一个拥有r*c个元素的一维数组,用于存储矩阵的元素

  39. 矩阵的常规存储 • T& Matrix<T>::operator()(int i, int j) const • {// 返回一个指向元素( i , j )的引用 • if (i < 1 || i > rows || j < 1 || j > cols) throw OutOfBounds(); • return element[(i - 1) * cols + j - 1]; • } 重载矩阵下标操作符(),该操作符可以带任意数量的参数。对于一个矩阵来说,需要两个整数参数。

  40. 矩阵的常规存储 • 重载减法运算符 • template<class T> • Matrix<T> Matrix<T>:: operator-(const Matrix<T>& m) const • {// 返回(*this) - m. • if (rows != m.rows || cols != m.cols) • throw SizeMismatch(); • // 创建结果矩阵w • Matrix<T> w(rows, cols); • for (int i = 0; i < rows * cols; i++) • w.element[i] = element[i] - m.element[i]; • return w; • } 与一维数组的减法相似。矩阵加法操作符、一元减法操作符、增值操作符和输出操作符的代码都比较类似。

  41. // 矩阵乘法, template<class T> Matrix<T> Matrix<T>:: operator*(const Matrix<T>& m) const{ if (cols != m.rows) throw SizeMismatch(); Matrix<T> w(rows, m.cols); int ct = 0, cm = 0, cw = 0; for (int i = 1; i <= rows; i++) { for (int j = 1; j <= m.cols; j++) { T sum = element[ct] * m.element[cm]; for (int k = 2; k <= cols; k++) { ct++; cm += m.cols; sum += element[ct] * m.element[cm]; } w.element[cw++] = sum; ct -= cols - 1; cm = j; } ct += cols; cm = 0; } return w; } } 矩阵的常规存储 矩阵乘法代码有三个嵌套的for 循环。在最内层把*this 的第i 行与m 的第j 列相乘,得到元素( i , j )。进入最内层循环时,element[ct]是第i 行的第一个元素,m.element[cm]是第j列的第一个元素。为了得到第i 行的下一个元素,可将ct 增加1;为了得到第j 列的下一个元素,可将cm增加m.cols。当最内层循环完成时, ct 指向第i 行的最后一个元素,cm 指向第j 列的最后一个元素。对于for j 循环的下一次循环,起始时必须将ct 指向第i 行的第一个元素,而将cm 指向m 的下一列的第一个元素,由语句ct -= cols - 1; cm = j;来完成。 对ct 的调整是在最内层循环完成后进行的。当for j 循环完成时,需要将ct 指向下一行的第一个元素(ct+=cols),而将cm 指向第一列的第一个元素(cm=0),

  42. 复杂性 • 当T是一个内部数据类型时,矩阵构造函数复杂性为O( 1 ),而当T是一个用户自定义的类时,构造函数的复杂性为O(rows*cols)。 • 复制构造函数的复杂性为O(rows*cols) • 下标操作符的复杂性为O( 1 ) • 乘法操作符的复杂性O(rows*cols*m.cols )。 • 所有其他矩阵操作符的渐进复杂性分别与Array2D类中对应操作符的渐进复杂性相同。

  43. 特殊矩阵 特殊矩阵:元素值的排列具有一定规律的矩阵。 对称矩阵、下(上)三角矩阵、对角线矩阵等。 1、对称矩阵 在一个 n阶方阵 A 中,若元素满足下述性质: aij = aji 1 ≤ i, j ≤ n 则称 A 为对称矩阵。

  44. 对称矩阵的存储结构 对称矩阵上下三角中的元素数均 为: n(n + 1)/2 可以行序为主序将元素存放在一 个一维数组sa[n(n+1)/2] 中。 则 aij和 sa[k] 存在着一一对应关系: aij前的 i -1 行有 1 + 2 +…+ (i -1) = i(i -1)/2 个元素,在第 i行上有 j 个元素。 aij前的 j -1 列有 1 + 2 +…+ ( j -1) = j( j -1)/2 个元素,在第 j列上有 i 个元素。

  45. 对称矩阵的存储结构 按行序为主序: k = 0 1 2 3 4 n(n-1)/2 n(n+1)/2-1

  46. 上三角矩阵 下三角矩阵 2、三角矩阵 以主对角线划分,三角矩阵有上(下)三角两种。上(下) 三角矩阵的下(上)三角(不含主对角线)中的元素均为常数。 在大多数情况下,三角矩阵常数为零。 三角矩阵的存储:除了存储上(下)三角中的元素,再加一 个存储常数 c的空间。

  47. 3、 对角矩阵 在对角矩阵中,所有的非零元素集中在以主对角线为中心的 带状区域中,即除了主对角线和主对角线相邻两侧的若干条对角 线上的元素之外,其余元素皆为零。 对角矩阵可按行优先顺序或对角线的顺序,将其压缩存储到 一维数组中,可采用逐行映射、逐列映射和按对角线次序映射三种方法。

  48. 稀疏矩阵 稀疏矩阵:设在 m×n的矩阵中有 t个非零元素。 令 = t /(m×n) 当 ≤0.05时称为稀疏矩阵。 三元组 (i, j, aij) 惟一确定矩阵的一个非零元。 M 由{(1,2,12), (1,3,9), (3,1,-3), (3,6,14), (4,3,24), (5,2,18), (6,1,15), (6,4,-7) } 和矩阵维数(6,7) 唯一确定。 压缩存储原则:存各非零元的值、行列位置和矩阵的行列数。 三元组的不同表示方法可决定稀疏矩阵不同的压缩存储方法。

  49. 稀疏矩阵的 数组实现

  50. 稀疏矩阵的数组实现 • 可以按行主次序把无规则稀疏矩阵映射到一维数组中。 • 把稀疏矩阵的非0元素映射到数组中时必须提供三个域: • r o w(矩阵元素所在行号) • c o l(矩阵元素所在列号) • v a l u e(矩阵元素的值)。 • 为此,定义如下所示的模板类Te r m:

More Related