第 5 章 数组

43
第 5 第 第第 第第第第第第第 第第第第第 第第第第 第第第第

Upload: imogene-pennington

Post on 02-Jan-2016

68 views

Category:

Documents


1 download

DESCRIPTION

第 5 章 数组. 数组的基本概念 动态数组类 特殊矩阵 稀疏矩阵. 主要知识点. 5.1 数组的基本概念. 1.数组的定义. 数组 是 n ( n >1) 个相同数据类型的数据元素 a 0 , a 1 , a 2 ,..., a n -1 构成的占用一块地址连续的内存单元的有限序列。. 数组中任意一个元素可以用该元素在数组中的位置来表示,数组元素的位置通常称作 数组的下标 。. 数组符合线性结构的定义。数组和 线性表相比 ,. 相同之处是 它们都是若干个相同数据类型的数据元素 a 0 ,a 1 ,a 2 ,...,a 0-1 构成的有限序列。 不同之处是 : - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第 5 章 数组

第 5章 数组

主要知识点

数组的基本概念

动态数组类

特殊矩阵

稀疏矩阵

Page 2: 第 5 章 数组

5.1 数组的基本概念1. 数组的定义数组是 n(n > 1)个相同数据类型的数据元素a0,a1,a2,...,an-1 构成的占用一块地址连续的内存单元的有限序列。 数组中任意一个元素可以用该元素在数组中的位置来表示,数组元素的位置通常称作数组的下标。

Page 3: 第 5 章 数组

相同之处是它们都是若干个相同数据类型的数据元素a0,a1,a2,...,a0-1 构成的有限序列。不同之处是:( 1)数组要求其元素占用一块地址连续的内存单元空间,而线性表无此要求;( 2)线性表的元素是逻辑意义上不可再分的元素,而数组中的每个元素还可以是一个数组;( 3)数组的操作主要是向某个下标的数组元素中存数据和取某个下标的数组元素,这和线性表的插入、删除操作不同。

数组符合线性结构的定义。数组和线性表相比,

线性结构(包括线性表、堆栈、队列、串)的顺序存储结构实际就是使用数组来存储。可见,数组是其他数据结构实现顺序存储结构的基础,是软件设计中最基础的数据结构。

Page 4: 第 5 章 数组

2. 数组的实现机制1、一维数组( n 个元素)中任一元素 ai 的内存单元地址的内存单元地址   Loc(ai)=LOC(a 0 )+i*k (0≤i <n)

2、一个 m 行 n 列的二维数组   LOC(aij)=LOC(a

00)+(i*n+j)*k (0≤i<m , 0≤j<n)

注: C++语言中数组元素采用行主序 的存放方法,即行优先顺序。

a 0的内存单元地址 每个元素所需的字节个数

每个元素所需的字节个数a 0 0 的内存单元地址

一个 m×n 的二维数组可以看成是 m 行的一维数组,或者 n 列的一维数组。

a0,0 a0,1 … a0,n-1

a1,0 a1,1 … a1,n-1

… … … … am-1,0 am-1,1 … am-1,n-1

Amn=

Page 5: 第 5 章 数组

3. 数组抽象数据类型数据集合:

数组的数据集合可以表示为 a0, a1, a2, ..., an-1 ,每个数据元素的数据类型为抽象数据元素类型 DataType 。

操作集合:

(1) 初始化数组 Initiate(D)

(2) 取数组元素个数 Size(D)

(3) 存数组元素 Storage(D,i,x)

(4) 取数组元素 Get(D, i)

Page 6: 第 5 章 数组

5.2 动态数组类 数组有静态存储结构的数组和动态存储结构的数组两种,它们的区别在于:

静态数组在定义时就必须给出数组个数;

动态数组是在具体申请存储单元空间时才给出数组元素的个数。

C++语言提供

建立动态数组的运算符是 new,

撤消动态数组的运算符是 delete。

动态数组类的定义和实现如下:

Page 7: 第 5 章 数组

template <class T> // 类的定义

class Array

{ private: T *arr; // 数组

int size; // 数组个数

public:

Array(int sz = 100); // 构造函数

Array(const Array<T>& a); // 拷贝构造函数

~Array(void); // 析构函数

int Size(void)const; // 取数组个数

void operator=(const Array<T>& a); // 赋值

T& operator[](int i); // 下标

void Resize(int sz); // 重置数组

};

Page 8: 第 5 章 数组

template <class T>

Array<T>::Array(int sz) // 构造函数

{

if(sz <= 0)

{

cout << " 无效的数组个数 " << endl;

exit(0);

}

arr = new T[sz];// 申请内存空间

size = sz;// 置数组个数

}

Page 9: 第 5 章 数组

template <class T>

Array<T>::Array(const Array<T>& a) // 拷贝构造函数

{

arr = new T[a.size];// 申请内存空间

// 数组元素赋值

for(int i = 0; i < a.size; i++)

arr[i] = a.arr[i];

size = a.size;// 置数组个数

}

Page 10: 第 5 章 数组

template <class T>

Array<T>::~Array(void) // 析构函数

{

delete []arr;// 释放内存空间

}

template <class T>

int Array<T>::Size(void)const // 取数组个数

{

return size;

}

Page 11: 第 5 章 数组

template <class T>

void Array<T>::operator=(const Array<T>& a) // 赋值运算符重载

{

delete arr;// 释放原内存空间

arr = new T[a.size];// 申请新内存空间

// 数组元素赋值

for(int i = 0; i < a.size; i++)

arr[i] = a.arr[i];

size = a.size;// 置数组个数

}

Page 12: 第 5 章 数组

template <class T>

T& Array<T>:: operator[](int i) // 下标运算符重载

{

if(i < 0 || i > size-1)

{

cout << " 下标越界 " << endl;

exit(0);

}

return arr[i];

}

Page 13: 第 5 章 数组

template <class T>

void Array<T>::Resize(int sz) // 重置数组

{ if(sz <= 0) {

cout << " 无效的数组个数 " << endl; exit(0); }

if(sz == size) return;T* newArray = new T[sz]; // 申请新数组空间

int n = (sz <= size) ? sz: size; // 原数组元素拷贝

for(int i = 0; i < n; i++)newArray[i] = arr[i];

delete []arr; // 释放原数组空间

arr = newArray; // 新数组指针赋值

size = sz; // 置新数组个数

}

Page 14: 第 5 章 数组

例 5-1 设计一个包含一维动态数组的程序,对动态数组类进行测试。

程序设计如下:#include <iostream.h>

#include <stdlib.h>

#include "Array.h"// 包含动态数组类

Page 15: 第 5 章 数组

void main(void)

{ Array<int> a(10);

int n = 10;

for(int i = 0; i < n; i++) a[i] = i + 1;

cout << "a[6] = " << a[6] << endl;

cout << "Size of a = " << a.Size() << endl;

Array<int> b = a;

cout << "b[6] = " << b[6] << endl;

cout << "Size of b = " << b.Size() << endl;

a.Resize(40);

a[21] = 21;

cout << "a[21] = " << a[21] << endl;

cout << "Size of a = " << a.Size() << endl;

Array<int> c(a);

cout << "c[21] = " << c[21] << endl;

cout << "Size of c = " << c.Size() << endl;

}

Page 16: 第 5 章 数组

5.3 特殊矩阵 特殊矩阵 :指有许多值相同的元素或有许多零元素、且值相同的元素或零元素的分布有一定规律的矩阵。1. 几种特殊矩阵的压缩存储 :(1)n 阶对称矩阵 在一个 n 阶方阵 A 中,若元素满足下述性质: aij=aji ( 1≤i,j≤n ) 则称 A 为 n 阶对称矩阵。如图 5.1 是一个 5阶对称矩阵。 1 5 1 3 7 a11

5 0 8 0 0 a21 a22

1 8 9 2 6 a31 a32 a33

3 0 2 5 1 ……………….. 7 0 6 1 3 an1 an2 an3 …ann

n 阶对称矩阵中的元素关于主对角线对称,故只要存储矩阵中上三角或下三角中的元素,让每两个对称的元素共享一个存储空间,这样,能节约近一半的存储空间。

Page 17: 第 5 章 数组

在这个下三角矩阵中,第 i 行恰有 i 个元素,元素总数为 n(n+1)/2 ,这样就可将 n2 个数据元素压缩存储在 n(n+1)/2 个存储单元中。

假设以一维数组 va 作为 n 阶对称矩阵 A 的压缩存储单元, k 为一维数组 va 的下标序号, aij 为 n 阶对称矩阵 A 中 i 行 j 列的数据元素 (其中1≤i,j≤n ),其数学映射关系为:

i(i-1)/2+j-i 当 i≥j

j(j-1)/2+i-1 当 i<jk=

(2)n 阶三角矩阵

以主对角线 划分, n 阶三角矩阵有 n 阶上三角矩阵和 n 阶下三角矩阵两种。

n 阶上三角矩阵如下图 (a)所示,它的下三角(不包括主对角线)中的元素均为 0(或常数)。 n 阶下三角矩阵正好相反,它的主对角线上方均为 0(或常数),如下图 (b)所示。

注:在大多数情况下, n 阶三角矩阵常数为零。

Page 18: 第 5 章 数组

a11 a12 … a 1n a11 c … c

c a22 … a 2n a21 a22 … c

………………. ………………

c c … a nn an1 an2 … an n

(a)上三角矩阵 (b)下三角矩阵 假设以一维数组 sa 作为 n 阶下三角矩阵 A 的压缩存储单元, k 为一维数组 va 的下标序号, aij 为 n 阶下三角矩阵 A 中 i 行 j 列的数据元素(其中 1≤i,j≤n ),其数学映射关系为:

i(i-1)/2+j-1 当 i≥j

n(n+1)/2 (或空) 当 i<jk=

注:此时一维数组 sa的数据元素个数为 (n(n+1)/2)+1个,其中数组 sa的最后一个位置存储 A 中数值不为 0的那个常数。

Page 19: 第 5 章 数组

2.n 阶对称矩阵顺序表类 这里 SeqSynmeMatrix 类以 public 方式继承 SeqList 类(即 SeqSynmeMatrix 类是 SeqList 类的公有派生类),而SeqList 类中的成员变量 list 、 maxSize 和 size 定义为protected 方式(保护方式),所以, SeqSynmeMatrix 类可以像使用本类中的私有成员变量一样使用 SeqList 类中的成员变量 list 、 maxSize 和 size 。

矩阵运算有矩阵加、矩阵减、矩阵乘、矩阵求逆等,因此需要补充的成员函数应该包括矩阵加、矩阵减、矩阵乘、矩阵求逆等。这里设计的类的成员函数只包括了创建和矩阵加。

Page 20: 第 5 章 数组

其定义和实现代码如下:

#include "SeqList.h" // 包含顺序表类

class SeqSynmeMatrix: public SeqList //n 阶对称矩阵顺序表类

//SeqSynmeMatrix 类以 public 方式继承 SeqList 类

{

//输出流运算符重载

friend ostream& operator<< (ostream& out, const SeqSynmeMatrix& a);

private:

int n; // 矩阵的阶数

public:

SeqSynmeMatrix(int max); // 构造函数

~SeqSynmeMatrix(void) {}; // 析构函数

Page 21: 第 5 章 数组

void CreateMatrix(int nn, DataType item[]); //创建

void Add(SeqSynmeMatrix& a); // 矩阵加

};

SeqSynmeMatrix::SeqSynmeMatrix(int max):SeqList(max) // 构造函数

//首先调用顺序表类的构造函数,然后定义自身的初始值

{

n = 0;

}

Page 22: 第 5 章 数组

void SeqSynmeMatrix::CreateMatrix(int nn, DataType item[])//创建

//创建一个置存储下三角元素的 n 阶对称矩阵顺序表

// 矩阵的元素值来自一维数组 item ,二维矩阵元素以行序优先方式存于 item 中

{ n = nn; // 置矩阵阶数

int i, j, k = 0;

for(i = 0; i < n; i++)

for(j = 0; j < n; j++)

if(i >= j)

{

Insert(item[i*n+j], k);

k++;

}

}

Page 23: 第 5 章 数组

void SeqSynmeMatrix::Add(SeqSynmeMatrix& a) // 矩阵加

//this = this + a 。假设两个矩阵都是对称矩阵

{

for(int i = 0; i < size; i++)

list[i] = list[i] + a.list[i];

}

Page 24: 第 5 章 数组

ostream& operator<< (ostream& out, const SeqSynmeMatrix& a)

//输出流运算符重载

{

cout << " 对称矩阵阶数为: " << a.n << endl;

cout << " 矩阵元素为: " << endl;  

int i, j, k = 0, n = a.n;

for(i = 1; i <= n; i++)

{

for(j = 1; j <= n; j++)

if(i >= j)

{

k = i * (i - 1) / 2 + j - 1;

cout << setw(5) << a.list[k];

}

Page 25: 第 5 章 数组

else

{

k = j * (j - 1) / 2 + i - 1;

cout << setw(5) << a.list[k];

}

cout << endl;

}

return out;

}

Page 26: 第 5 章 数组

5.4 稀疏矩阵 1. 概念1 、稀疏矩阵 矩阵中非零元素的个数远远小于矩阵元素个数。2、稠密矩阵 一个不稀疏的矩阵。3、稀疏矩阵压缩存储方法 只存储稀疏矩阵中的非零元素,实现方法是 :将每个非零元素用一个三元组( i , j , aij )来表示,则每个稀疏矩阵可用一个三元组线性表三元组线性表来表示。

Page 27: 第 5 章 数组

例 1:写出下图 5.3 所示稀疏矩阵的压缩存储形式。 1 2 3 4 5 6 1 2 3 4 5 6

解:用三元组线性表线性表表示: {{1,2,12},{1,3,9},{3,1,-3},{3,5,14}, {4,3,24},{5,2,18},{6,1,15},{6,4,-7}}

说明:稀疏矩阵的压缩存储结构主要有三元组顺序表和三元组链表两大类型。

0 12 9 0 0 00 0 0 0 0 0

-3 0 0 0 14 00 0 24 0 0 00 18 0 0 0 0

15 0 0 -7 0 0

Page 28: 第 5 章 数组

2. 三元组顺序表类(1) 三元组顺序表 指用顺序表存储的三元组线性表。 把三元组定义成顺序表的数据元素: struct DataType

{ int row; // 行号 int col; // 列号 ElemType value; // 元素值 } ;

例 1 中的稀疏矩阵的三元组线性表的存储结构 如下图所示

Page 29: 第 5 章 数组

175 1

1914

3745

5076

25 2 2

113 1

6

7

6

0

1

2

3

4

5

6size=

MaxSize-1… … …… rows

cols

dNum

row col value

Page 30: 第 5 章 数组

三元组顺序表类的定义和实现代码如下:#include "SeqList.h" // 包含顺序表类

class SeqSpaMatrix: public SeqList // 三元组顺序表类

//SeqSpaMatrix 类以 public 方式继承 SeqList 类

{ //输出流重载运算符

friend ostream& operator<< (ostream& out, const SeqSpaMatrix& a);

private:

int rows; // 稀疏矩阵的行数

int cols; // 稀疏矩阵的列数

int dNum; // 稀疏矩阵的非零元个数

public:

SeqSpaMatrix(int max); // 构造函数

~SeqSpaMatrix(void) {}; // 析构函数

void CreateMatrix(int r, int c, int d, DataType item[]); //创建

void Transpose(SeqSpaMatrix& a); //转置

};

Page 31: 第 5 章 数组

SeqSpaMatrix::SeqSpaMatrix(int max):SeqList(max)

// 构造函数。首先调用顺序表类的构造函数,然后定义自身的初始值

{

rows = cols = dNum = 0;

}

Page 32: 第 5 章 数组

void SeqSpaMatrix::CreateMatrix(int r, int c, int d, DataType item[])

//创建。创建一个有 r 行、 c 列和 d 个非零元的三元组顺序表

//创建的数值来自数组 item

{

rows = r;;// 置行数

cols = c; // 置列数

dNum = d;// 置非零元个数

for(int i = 0; i < d; i++) //依次插入三元组元素

Insert(item[i], i);

}

Page 33: 第 5 章 数组

void SeqSpaMatrix::Transpose(SeqSpaMatrix& a)

//转置。把三元组顺序表转置为三元组顺序表 a

{

if(a.maxSize < maxSize) // 内存空间不足时重新申请

{delete []a.list;

a.list = new DataType[maxSize];a.maxSize = maxSize;

}a.cols = rows; // 置列数a.rows = cols; // 置行数a.dNum = dNum; // 置非零元个数for(int i = 0; i < dNum; i++) //依次转置{

a.list[i].row = list[i].col;a.list[i].col = list[i].row;a.list[i].value = list[i].value;

}

}

Page 34: 第 5 章 数组

ostream& operator<< (ostream& out, const SeqSpaMatrix& a)

//输出流运算符重载。

{

out << " 矩阵行数为: " << a.rows;

out << " ,列数为: " << a.cols;

out << " ,非零元个数为: " << a.dNum <<endl;

cout << " 矩阵非零元三元组为: " << endl;

for(int i = 0; i < a.dNum; i++)

cout << "a(" << a.list[i].row << ',' << a.list[i].col << ") = "

<< a.list[i].value << endl;

return out;

}

Page 35: 第 5 章 数组

矩阵的转置运算是把矩阵中每个元素的行号值转为列号值,把列号值转为行号值。因此,一个稀疏矩阵的转置矩阵仍是稀疏矩阵。稀疏矩阵三元组顺序表的转置如下图所示,此转置算法的时间复杂度为 O ( dNum ),其中 dNum 为稀疏矩阵的非零元个数。

Page 36: 第 5 章 数组

3

5

2

1

4

7

...

0

1

2

3

4

5

6

maxSi ze-1

...

1

1

2

4

5

6

...

11

17

25

19

37

50

...

7

6

6

rows

col s

dNum

row col val ue

1

2

3

4

5

7

...

0

1

2

3

4

5

6

maxSi ze-1

4

2

1

5

1

6

...

19

25

11

37

17

50

...

7

6

6

rows

col s

dNum

row col val ue

(a)(b)

...

Page 37: 第 5 章 数组

上图所示的稀疏矩阵三元组顺序表的转置操作,其前提条件是:原稀疏矩阵三元组顺序表按先行序后列序的次序存放,其功能要求是: 转置后的稀疏矩阵三元组顺序表仍然按先行序后列序的次序存放。成员函数可重新设计如下:

Page 38: 第 5 章 数组

if(dNum == 0) return;else{

int i, j, k;i = 0; //i 为 a.list[] 的下标值for(k = 1; k <= cols; k++) //k 为原矩阵的列下标{

for(j = 0; j < a.dNum; j++) //j 为 list[] 的下标值{

if(list[j].col == k) //寻找原矩阵中列下标最小值

{a.list[i].row = list[j].col; // 列号转为行号a.list[i].col = list[j].row; // 行号转为列号a.list[i].value = list[j].value; // 数组元素复制

i++;}

}}

}

Page 39: 第 5 章 数组

3. 稀疏矩阵的三元组链表

(1) 三元组链表 用链表存储的三元组线性表。(2) 行指针数组结构的三元组链表 把每行非零元素三元组组织成一个单链表,再设计一个指

针类型的数组存储所有单链表的头指针。(3) 三元组十字链表 把非零元素三元组按行和按列组织成单链表,这样稀疏矩

阵中的每个非零元素三元组结点都 将既勾链在行单链表上,又勾链在列单链表上,形成十字形状。

Page 40: 第 5 章 数组

三元组链表中每个结点的数据 域由稀疏矩阵非零元的行号、列号和元素值组成。稀疏矩阵三元组线性表的带头结点的三元组链表结构如图所示,其中头结点的行 号域存储了稀疏矩阵的行数,列号域存储了稀疏矩阵的列数。

6 7 1 113 1 175 2 252 4 191 5 374 6 507 ∧ h

行数 列数

头结点

这种三元组链表的缺点是实现矩阵运算操作算法的时间复杂度高,因为算法中若要访问某行某列中的一个元素时,必须从头指针进入后逐个结点查找。为降低矩阵运算操作算法的时间复杂度,提出了行指针数组结构的三元组链表,其结构如图所示:

Page 41: 第 5 章 数组

0

1

2

3

4

5

1

2

3

4

5

6

5 173 11

2 25

1 19

4 37

7 50

列号 非零元素行指针数组

行号 头指针

各单链表均不带头结点。由于每个单链表中的行号域数值均相同,所以单链表中省略了三元组的行号域,而把行号统一放在了指针数组的行号域中。

Page 42: 第 5 章 数组

行指针数组结构的三元组链表对于从某行进入后找某列元素的操作比较容易实现,但对于从某列进入后找某行元素的操作就不容易实现,为此又提出了三元组十字链表结构,结构如下:

Page 43: 第 5 章 数组

1

2

3

4

5

6

行指针数组

行号

11 17

横向头指针

19

37

50

25

∧ ∧

∧ ∧

∧ ∧

∧ ∧

1 2 3 4 5 6 7

列指针数组列号

纵向头指针

纵向指针 横向指针

非零元素