320 likes | 413 Views
数组 数据元素可为结构类型的线性表. 数组. 几乎所有的高级程序设计语言中都将数组类型作为固有类型,在此我们以抽象数据类型的形式来讨论其定义和实现. ADT Array{ 数据对象: j i = 0,…,b i -1, i=1,2,…,n D = {a j 1 j 2 …j n | n 称为数组的维数 , b i 是数组第 i 维的长度 , j i 是数组元素的第 i 维下标, a j 1 j 2 …j n ElemSet
E N D
数组 • 几乎所有的高级程序设计语言中都将数组类型作为固有类型,在此我们以抽象数据类型的形式来讨论其定义和实现
ADT Array{ 数据对象: ji = 0,…,bi-1, i=1,2,…,n D = {aj1j2…jn | n称为数组的维数, bi是数组第i维的长度, ji是数组元素的第i维下标, aj1j2…jn ElemSet R = {R1, R2, …, Rn} //每个元素受到n个关系的约束 Ri = {<aj1…ji…jn, aj1,,,ji+1…jn> | 0 jk bk-1, 1 k n 且 k i 0 ji bi-2, aj1…ji…jn, aj1,,,ji+1…jn D, i = 2,…n} P: InitArray(&A, n, bound1, …, boundn) DestoryArray(&A) Value(A, &e, index1, …, indexn) //取出元素值 Assign(&A, e, index1, …, indexn) //给元素赋值 }ADT Array 数组的ADT定义
数组的ADT定义 • n维数组中含有 个数据元素,每个元素受到n个关系的约束,且这n个关系都是线性关系。当n=1时,n维数组就退化为定长的线性表。反之,n维数组也可以看成是线性表的推广 • 数组中的每个元素都对应于一组下标(j1,…,jk),每个下表的取值范围是0 ji bi-1, bi称为第i维的长度 • 数组一旦被定义,它的维数和维界就不再改变 • 数组结构操作 • 初始化、销毁、元素的存取和修改 • 不过,数组多用于静态数据处理,一般不作插入和删除操作
数组特点: • 数组中各元素都具有统一的类型 • d维数组的非边界元素具有d个直接前趋和d个直接后继.数组维数确定后,数据元素个数和元素之间的关系不再发生改变,适合于顺序存储 • 每组有定义的下标都存在一个与其相对应的值
a11 0 0 1 • a12 按行序为主序存放 • a11 按列序为主序存放 1 • ……. • a21 • a1n n-1 • ……. • a21 m-1 n • am1 m • a22 a11 a12 …….. a1n a11 a12……..a1n • a12 • …….. a21 a22 …….. a2n a21 a22 ……..a2n • a22 • a2n • …….. …………………. …………………. • ………. • am2 • am1 am1 am2 …….. amn am1am2…….. amn • ………. • am2 • a1n Loc( aij)=Loc(a11)+[(i-1)n+(j-1)]*l • …….. Loc(aij)=Loc(a11)+[(j-1)m+(i-1)]*l • a2n m*n-1 • amn • …….. m*n-1 • amn 数组的顺序表示 • 数组通常采用顺序存储方式来实现 • n维数组的数据元素的存储问题 • 必须约定存放次序 • 因为存储单元是一维的,而数组是多维的 • 存储方案 • 以行序为主序,如C, Pascal, Basic等语言采用 • 以列序为主序,如Fortran语言采用 • 数组一旦定义了维数和各维长度,便可为其分配存储空间 • 只要给出一组下标便可求得相应元素的存储位置
A Loc(0) A[0] 0 L 1 A[1] i A[i] b1-1 A[b1-1] 数据元素的存储问题 • 一维数组为例 • 如 int A[b1], 共占用b1个整型存储单元 • 给定下标值i,求对应元素的存储位置 • Loc(i) = Loc(0) + i * L 数组基址 元素所占存储单元大小
数据元素的存储问题 • 二维数组为例 • 如 int A[b1,b2],共占用b1*b2个整型存储单元 • 如 行序为主序的存储方式(图a) • 给定下标值i, j,求对应元素的存储位置 Loc(i, j) = Loc(0, 0) + (b2 * i + j) * L A Loc(0,0) A[0,0] A[0,1] A[0,b2-1] A[1,0] A[1,1] A[1,b2-1] A[b1-1,0] A[b1-1,1] A[b1-1, b2-1]
数据元素的存储问题 • n维数组为例 • 如 int A[b1,b2 ,…,bn],共占用b1*b2 *…*bn个整型存储单元 • 行序为主序的存储方式 • 给定下标值j1, j2,…, jn , 求对应元素的存储位置 Loc(j1, j2,…, jn) = Loc(0, 0,…,0) + L * (b2* …..*bn* j1 + b3* …*bn* j2 + … … bn* jn-1 + jn ) • 考虑3维数组的情形[?]
矩阵 • 通常使用二维数组来存储矩阵元素 • 矩阵的常见操作 • 转置、相乘等 void TransposeMatrix(int T[][], int M[][], mu, nu) { //矩阵转置 for (col = 1; col <= nu; ++col) for (row = 1; row <= mu; ++row) T[col][row] = M[row][col]; }
矩阵 void ProductMartrix(int Q[][], int M[][], int N[][], m1, n1, n2) { //矩阵相乘 Qm1*n2=Mm1*n1*Nm2*n2, n1=m2 for (i = 1; i <= m1; ++i) for (j = 1; j <= n2; ++j) { Q[i][j] = 0; for (k = 1; k <= n1; ++k) Q[i][j] += M[i][k] * N[k][j]; } }
矩阵的压缩存储 • 特殊矩阵 • 值相同的元素或零元素在矩阵中的分布有规律 • 如对称矩阵,三角矩阵等 • 稀疏矩阵 • 值相同的元素或零元素在矩阵中的分布无规律,且 • 非零元素个数/矩阵所有元素个数 <= 0.05 • 矩阵的压缩存储 • 多个值相同的元素只分配一个存储空间 • 只存储非零元素
a11 a12 … a1n a21 a22 … a2n … … an1 an2 … ann 特殊矩阵的压缩存储 • n阶对称矩阵 • 矩阵中的元素满足性质:aij = aji • 对称矩阵压缩存储 • 为每一对对称元素分配一个存储空间,这样可将n*n个元素压缩存储到n*(n-1)/2个存储空间中 • 可选择存储其上三角(包括对角线)中的元素或其下三角(包括对角线)中的元素
a11 a12 … a1n a21 a22 … a2n … … an1 an2 … ann A sa a11 a11 0 a12 a21 a22 a1n a31 a21 a32 a22 a33 aij aij k an1 ann an2 n*(n-1)/2 - 1 ann 特殊矩阵的压缩存储 • 讨论以行序为主序的下三角矩阵的存储 • 若不采用压缩存储,矩阵需用二维数组A[n][n]存储 • 若采用压缩存储,可采用一维数组sa[n*(n-1)/2]存储 • 元素sa[k]和矩阵元素aij之间有如下对应关系 i(i-1)/2 + j – 1 当 i >= j k = j(j-1)/2 + i – 1 当 i < j 0 (i-1)*n+j (n-1)*(n-1)
a11 a12 a13 a21 a22 a23 a31 a32 a33 特殊矩阵的压缩存储 • 如3*3对称矩阵 • 未压缩时,用二维数组存放,占用9个单元 • 压缩存放时,用一维数组存放,只需6个单元 • 如a32存放在sa[4]中 • aij = aji sa a11 0 a21 a22 a31 a32 a33 5
特殊矩阵的压缩存储 • 三角矩阵 • 下(上)三角矩阵是指矩阵的上(下)三角(不包括对角线)中的元均为常数c或零的n阶矩阵 • 三角矩阵的压缩存储 • 只存储下(上)三角中的元素,再加一个存储常数c的空间即可
特殊矩阵的压缩存储 • 对角矩阵 • 所有非零元素都集中在以主对角线为中心的带状区域中 • 对角矩阵的压缩存储 • 可按照某原则(或以行为主,或以对角线的顺序)将其压缩到一维数组中
特殊矩阵的压缩存储 • 小结 • 特殊矩阵(如对称矩阵、三角矩阵、对角矩阵等)中,非零元素的分布有明显的规律,因此我们可以将其压缩存储到一维数组中,并找到每个非零元素在一维数组中的对应关系
稀疏矩阵的压缩存储 • 稀疏矩阵 • 值相同的元素或零元素在矩阵中的分布无规律,且 • 非零元素个数/矩阵所有元素个数 <= 0.05 • 原理 • 只需存储矩阵中的非零元素所在的行号、列号和值 • 方法 • 三元组顺序表 (*) • 行逻辑链接顺序表 • 十字链表(*)
稀疏矩阵的压缩存储 • 三元组顺序表 • 以顺序结构存储三元组表 #define MAXSIZE 12500 //假设非零元素个数的最大值 typedef struct{ int i, j; //行号,列号 ElemType e;//元素值 }Triple; //三元组 typedef struct{ Triple data[MAXSIZE + 1]; //非零元素三元组表,data[0]未用 int mu, nu, tu; //行数,列数,非零元素个数 }TSMatrix; //三元组顺序表 TSMatrix M; //矩阵M
稀疏矩阵的压缩存储 • 如稀疏矩阵 • 采用三元组法压缩存储稀疏矩阵 • 按行号排序 • 不支持随机存取,对某行某非零元素访问时,可能需要扫描整个顺序表 0 12 9 0 0 0 0 0 0 0 0 0 0 0 -3 0 0 0 0 14 0 0 0 24 0 0 0 0 0 18 0 0 0 0 0 15 0 0 -7 0 0 0 M.mu = 6 M.nu = 7 M.tu = 8 M = M.data: i j e 1 2 12 1 3 9 3 1 -3 3 6 14 4 3 24 5 2 18 6 1 15 6 4 -7
Status TransposeSMatrix(TSMatrix M, TSMatrix &T) { T.mu = M.nu; T.nu = M.mu; T.tu = M.tu; if (T.tu) { //有非零元素,转置 q = 1; //非零元素计数器 for (col = 1; col <= M.mu; ++col) //按列 for (p = 1; p <= M.tu; ++p) //在三元组中找 if (M.data[p].j = col) { T.data[q].i = M.data[p].j; T.data[q].j = M.data[p].i; T.data[q].e = M.data[p].e; ++q; } } return OK; } • 采用三元组顺序表存储的矩阵的转置算法
M.mu = 6 M.nu = 7 M.tu = 8 T.mu = 7 T.nu = 6 T.tu = 8 M.data: T.data: 转置 i j e i j e 1 2 12 1 3 -3 1 3 9 1 6 15 3 1 -3 2 1 12 3 6 14 2 5 18 4 3 24 3 1 9 5 2 18 3 4 24 4 6 -7 6 1 15 6 4 -7 6 3 14
转置操作:即将A[i][j]改成A[j][i],将NxM矩阵改成MxN矩阵。转置操作:即将A[i][j]改成A[j][i],将NxM矩阵改成MxN矩阵。 0 0 -3 0 0 15 12 0 0 0 18 0 9 0 0 24 0 0 0 0 0 0 0 -7 0 0 0 0 0 0 0 14 0 0 0 0 0 0 0 0 0 0 0 12 9 0 0 0 0 0 0 0 0 0 0 0 -3 0 0 0 0 14 0 0 0 24 0 0 0 0 0 18 0 0 0 0 0 15 0 0 -7 0 0 0 三元组数组存放的稀疏矩阵转置算法
算法思想: (1)扫描整个三元组数组,将每个三元组的i和j互换。 0 12 9 0 0 0 0 0 0 0 0 0 0 0 -3 0 0 0 0 14 0 0 0 24 0 0 0 0 0 18 0 0 0 0 0 15 0 0 -7 0 0 0 三元组数组存放的稀疏矩阵转置算法 0,1,12 0,2, 9 2,0,-3 2,5,14 3,2,24 4,1,18 5,0,15 5,3,-7 1,0,12 2,0, 9 0,2,-3 5,2,14 2,3,24 1,4,18 0,5,15 3,5,-7
算法思想: (2)原来的三元组数组是按照行序存放的,ij互换后也要调整称为行序存放的方式。这需要事先指导转置后每行非零元素的个数,也就是原矩阵每列非零元素的个数。通过单独扫描一次整个三元组数组,就可以计算得到这些数据。有了这些数据,就可以确定转置后的矩阵的每行的非零元素在数组中的位置。 0 1 2 3 4 5 6 num[i] 2 2 2 1 0 1 0 cpot[i] 0 2 4 6 7 7 8 三元组数组存放的稀疏矩阵转置算法 0,1,12 0,2, 9 2,0,-3 2,5,14 3,2,24 4,1,18 5,0,15 5,3,-7
#define N 50 //行数和列数的最大值 void transmatrix(MATRIX M,MATRIX *T) { int num[N]={0}, cpot[N],p,q,col; //计算转置后矩阵的各行的非零元素的个数 for(p=0;p<M.tu;p++)num[M.data[p].j]++; cpot[0]=0;//开始计算各行非零元素在数组中的起始位置 for(col=1;col<M.nu;col++) cpot[col]=cpot[col-1]+num[col-1]; T->mu=M.nu; T->nu=M.mu; T->tu=M.tu; for(p=0;p<M.tu;p++) { col=M.data[p].j; q=cpot[col]; //q成为该单元在转置矩阵数组中的下标 T->data[q].i=M.data[p].j;//开始转置行列号 T->data[q].j=M.data[p].i; T->data[q].e=M.data[p].e;//复制非零元素的值 cpot[col]++;/*修正该行非零元素在三元数组中下标,为存放下一个该行的非零元素作准备*/ }//for } 三元组数组存放的稀疏矩阵转置算法
3 0 0 5 0 -1 0 0 2 0 0 0 0 1 2 3 0 1 2 0 1 2 0 3 0 0 1 -1 3 5 2 2、数组 • (4)稀疏矩阵的压缩存储 ②十字链表:三元组的双链表,结点不但包含三元组,而且还包含两个指针,一个称为行链指针,指向下一个同行的非零元素;另一个称为列链指针,指向下一个同列的非零元素。同一个结点,同时链到行链和列链上。 chead rhead
typedef struct olnode { int i,j; int e; struct olnode *right,*down; }OLNODE, *OLink; typedef struct { OLink *rhead,*chead; int mu,nu,tu; }CROSSLIST; 2、数组 • (4)稀疏矩阵的压缩存储 十字链表的数据类型定义:
void creatm(CROSSLIST *M) {int m,n,t,i,j,e; OLink p,q; printf("请输入矩阵的行数、列数和非零元总数\n"); scanf("%d%d%d",&m,&n,&t); M->mu=m; M->nu=n; M->tu=t; //开始为行链和列链的头指针分配空间 M->rhead=(OLink *)malloc((m+1)*sizeof(OLink)); M->chead=(OLink *)malloc((n+1)*sizeof(OLink)); for(i=0;i<m;i++)M->rhead[i]=NULL; for(i=0;i<n;i++)M->chead[i]=NULL; scanf("%d%d%d",&i,&j,&e); //没有处理非法数据 while(i!=-1) //如果i是-1,表示输入结束 {p=(OLNODEPTR)malloc(sizeof(OLNODE)); p->i=i; p->j=j; p->e=e; //下面将p按序插入行链 if(M->rhead[i]==NULL)//行链空,直接插入 {M->rhead[i]=p;p->right=NULL;} else {if(j<M->rhead[i]->j) {p->right=M->rhead[i]; M->rhead[i]=p;} else {for(q=M->rhead[i];q->right&&q->right->j<j;q=q->right); p->right=q->right; q->right=p; //插到q的后面 }//else 寻找插入位置 }//else 行链不空 //下面将p按序插入列链 if(M->chead[j]==NULL)//列链空,直接插入 {M->chead[j]=p;p->down=NULL;} else {if(i<M->chead[j]->i) {p->down=M->chead[j]; M->chead[j]=p;} else {for(q=M->chead[j];q->down&&q->down->i<i;q=q->down); p->down=q->down; q->down=p; //插到q的后面 }//else 寻找插入位置 }//else 列链不空 scanf(“%d%d%d”,&i,&j,&e); }//while } 十字链表的创建算法
作业 • 用以行序为主序和以列序为主序分别写出三维数组A[2][3][4]的元素在内存中的存储次序 • 对上(下)三角矩阵,若采用以行序为主序的原则用一维数组顺序存储其所有非零元素,试找出每个非零元素aij在一维数组中的对应关系
思考题 • 若在m*n的矩阵中存在一个元素A[i],并满足:A[i,j]是第i行元素中的最小值,且是第j列元素中的最大值,则称矩阵A有鞍点。试写一个算法,找出矩阵A的一个鞍点,若不存在鞍点,则返回某种信息