430 likes | 581 Views
第四章 数组. 数组: (1)整体 —— 数组;个体 —— 元素。 (2)所有元素的值类型相同。 (3)数组的标识:数组名;元素的标识:数组名与一组下标。 (4)作为线性表的推广,元素之间的关系由下标来决定。. 第四章 数组. 4.1 数组的顺序存储 4.2 稀疏矩阵. 4.1 数组的顺序存储. 将数组中的各元素的值按一定的次序存放在计算机的一组连续存储单元中。根据元素的下标,可计算出该元素存放的地址,便于对其操作。 一、一维数组 double a[t 1 ], *p; int i;
E N D
第四章 数组 数组: (1)整体——数组;个体——元素。 (2)所有元素的值类型相同。 (3)数组的标识:数组名;元素的标识:数组名与一组下标。 (4)作为线性表的推广,元素之间的关系由下标来决定。
第四章 数组 4.1 数组的顺序存储 4.2 稀疏矩阵
4.1 数组的顺序存储 将数组中的各元素的值按一定的次序存放在计算机的一组连续存储单元中。根据元素的下标,可计算出该元素存放的地址,便于对其操作。 一、一维数组 double a[t1], *p; int i; 那么a[i]的地址(&a[i])为: &a[0]+i*sizeof(double) 若p=a; 那么a[i]值另一表示为 *(p+i)。
4.1 数组的顺序存储 二、二维数组 double a[t1][t2], *p; int i, j; 那么a[i][j]的地址(&a[i][j])为: &a[0][0]+(i*t2+j)*sizeof(double) 或 &a[0][0]+(j*t1+i)*sizeof(double) 若p=a;那么a[i][j]可表示为 *(p+i*t2+j) 或 *(p+j*t1+i)。
4.1 数组的顺序存储 三、三维数组 double a[t1][t2][t3], *p; int i, j, k; 那么a[i][j][k]的地址(& a[i][j][k])为: &a[0][0][0]+(i*t2*t3+j*t3+k)*sizeof(double) 若p=a; 那么a[i][j][k]可表示为: *(p+i*t2*t3+j*t3+k)
4.1 数组的顺序存储 四、n维数组 double a[t1][t2] …[tn], *p; int i1, i2, …, in; 那么a[i1][i2] …[in]的地址(& a[i1][i2] …[in]) 为:&a[0][0] …[0]+(i1*t2*t3*…*tn +i2*t3*…*tn +…… +in-1*tn +in)*sizeof(double)
4.1 数组的顺序存储 四、n维数组 若p=a; 那么a[i1][i2] …[in]可表示为: *(p+ i1*t2*t3*…*tn+i2*t3*…*tn+……+in-1*tn+in)
4.2 特殊矩阵的压缩存储 一、三角矩阵 double a[n][n], *p; // 方阵 int i, j; 若对于一切 j<i 的元素a[i][j]均为零,称为上三角矩阵;若对于一切 j>i 的元素a[i][j]均为零,称为下三角矩阵。 以下三角矩阵为例,范围外的零元素不存储,那么只需存储n(n+1)/2个元素。按行优先可排列为: a[0][0], a[1][0], a[1][1], a[2][0], a[2][1], a[2][2], a[3][0], …, a[n-1][0], a[n-1][1], …, a[n-1][n-1]
一、三角矩阵 那么元素a[i][j](i>j)的地址为: &a[0][0]+[i*(i+1)/2+j]*sizeof(double) 若p=a; 那么a[i][j](i>j)可表示为: *(p+ i*(i+1)/2+j)
4.2 特殊矩阵的压缩存储 二、带状矩阵 double a[n][n], *p; // 方阵 int i, j; 若对于一切 |i-j|>b的元素a[i][j]均为零,即主对角线及两侧b条平行线之外的元素均为零,称为带状矩阵,b为半带宽。 同样用压缩存储,带状范围外的零元素不存储。为了便于计算元素a[i][j](|i-j|≤b)的地址,除了第一行和最后一行外,每行都当作有(2*b+1)个元素且相邻两行主对角线元素的地址的间隔为(2*b+1)。
二、带状矩阵 a[0][0] a[0][1] a[0][2] a[0][3] [1] [2] a[1][0] a[1][1] a[1][2] a[1][3] a[1][4] [3] a[2][0] a[2][1] a[2][2] a[2][3] a[2][4] a[2][5] a[3][0] a[3][1] a[3][2] a[3][3] a[3][4] a[3][5] [4] a[4][1] a[4][2] a[4][3] a[4][4] a[4][5] [5] [6] a[5][2] a[5][3] a[5][4] a[5][5]
二、带状矩阵 那么元素a[i][j] (|i-j|≤b)的地址为: &a[0][0]+[i*(2*b+1)+(j-i)]*sizeof(double) 若p=a; 则元素a[i][j] (|i-j|≤b)可表示为: *(p+i*(2*b+1)+(j-i))
4.3 稀疏矩阵 稀疏矩阵:矩阵中的大多数元素的值为零。 在存储稀疏矩阵时,采用压缩存储的方法,不存储零元素。 具体方法: 1. 三元组数组 2. 十字链表
1. 三元组数组 #define MAXN 100 int a[MAXN][3]; int k; 规定: (1)a[0][0]: 矩阵行数 a[0][1]: 矩阵列数 a[0][2]: 非零元素个数 (2)当1≤k≤a[0][2]时,a[k][0]:元素行号; a[k][1]:元素列号;a[k][2]: 元素的值。 (3)矩阵非零元素要求按行号递增次序,同一行按列号递增次序逐一存放在上面定义的三元组数组中。
1. 三元组数组 0 1 2 0 5 4 7 1 0 0 8 2 0 3 -15 3 1 1 6 4 1 3 4 5 2 0 7 6 2 1 13 7 4 1 8 0 1 2 3 0 8 0 0 -15 1 0 6 0 4 2 7 13 0 0 3 0 0 0 0 4 0 8 0 0
1. 三元组数组 应用举例: 三元组数组表示的稀疏矩阵如何实现矩阵的转置? (1)行列交换,再按行排序 (2)按列反复扫描,逐一置于各行 将数组中置于矩阵第一列的元素逐一取出,为新矩阵的第一行元素,存放在新数组中。再第二列、第三列、…、直到最后一列。
1. 三元组数组 Void mat_trans( int a[ ][3] , int b[ ][3] ) { int m , n , t , p ,q , col ; m=a[0][0] ; // m矩阵A的行数 n=a[0][1] ; // n矩阵A的列数 t=a[0][2] ; // 非零元素个数 b[0][0]=n ; // 转置矩阵B的行数 b[0][1]=m ; // 转置矩阵B的列数 b[0][2]=t ; // B非零元素个数
1. 三元组数组 if (t>0) { q=1 ; // 数组b的行号 for (col =0 ; col <n ; col++ ) // 矩阵A的列号 for ( p=1 ; p <=t ; p++ ) // 数组a的行号 if ( a[p][1] ==col ) // 扫描A中该列元素 { b[q][0]= a[p][1] ; // 从a写到b中去 b[q][1]=a[p][0] ; b[q][2]=a[p][2] ; q++ ; // b的下一个空行 } } } // 时间复杂度为O(n*t)
1. 三元组数组 (3)快速转置 先统计数组表示的矩阵的各列元素的个数。 再计算出表示转置后的新矩阵的新数组中各行开始接纳元素的下标。 然后逐一扫描原数组,根据其列号,知道其转置后的行号,从而确定其在新数组中的存储位置。
1. 三元组数组 0 1 2 0 5 4 7 1 0 0 8 2 0 3 -15 3 1 1 6 4 1 3 4 5 2 0 7 6 2 1 13 7 4 1 8 col 0 1 2 3 x[i] 2 3 0 2 Y[i] 1 3 6 6
1. 三元组数组 #define MAXN 100 Void mat_fast_trans ( int a[ ][3], int b[ ][3] ) { int m , n , t , i , j ; int x [MAXN] , y[MAXN] ; m = a[0][0] ; n = a[0][1] ; t =a [0][2] ; b[0][0] = n ; b[0][1] = m ; b[0][2] = t ;
1. 三元组数组 if ( t>0 ) { for ( i = 0 ; i< n ; i++ ) x[i] = 0 ; for ( i = 1 ; i<= t ; i ++ ) x[ a[i][1]] += 1 ; y[0] =1 ; for ( i=1 ; i<n ; i++ ) y[i]= y[i-1] + x[i-1] ; for ( i=1 ; i<=t ; i++ ) { j = y[a[i][1]] ; b[j][0] = a[i][1] ; b[j][1] = a[i][0] ; b[j][2] = a[i][2] ; y[a[i][1]] = j+1 ; } } } // 时间复杂度为O(n+t)
2. 十字链表 数组表示稀疏矩阵,操作时移动过多。为了便于插入和删除,采用链表是明智之举。 定义存放元素结点的数据类型MNODE: typedef struct node { int row, col;//行号、列号 struct node *right, *down; //行列链表指针 union { datatype v; //元素值 struct node *next; //索引指针 } v_next; } MNODE;
2. 十字链表 MNODE类型的结点表示稀疏矩阵的一个非零元素。在结点中,row和col字段分别存放元素的行号和列号;指针right指向表示同一行中下一个非零元素的结点;指针down指向表示同一列中下一个非零元素的结点。v_next.v存放元素的值。 矩阵的每一行(或列)元素的结点构成一个环形链表。一个结点处于行链表和列链表的十字交点上。故称为十字链表。
2. 十字链表 为了能够尽快找到某个行(列),使用一个辅助的索引数组hd[ ],元素是指向行(列)表头结点的指针。为了定义辅助数组的元素能使用MNODE类型,我们将内中存放数值的项改造成联合类型的变量v_next。 union { datatype v; //元素值 struct node *next; //索引指针 } v_next; MNODE hd[ ];// 辅助的索引数组
2. 十字链表 行号(或列号)相同的非零元素串联在该行(或该列)的链表中.索引数组中下标等于行号(或列号)元素的v_next.next指向的一个表头结点作为它们两个链表的头结点,即hd[i] ->v_next .next指向第i行(包括第i列)的表头结点。 各个表头结点自身使用v_next.next指针串联成一个环形链表。 某行的非零元素由right指针串联成循环行链表;某列的非零元素由down指针串联成循环列链表。 索引数组的hd[0]指针所指的头结点的row和 col分别存放矩阵的行数、列数。
2. 十字链表 (1)创建十字链表的算法思想: 将一个二维矩阵来创建一个十字链表,首先建立十字链表表头结点的组成循环链表,然后将二维矩阵A的非零元素,以三元组数组形式依此插入到初始化的十字链中。 具体插入操作步骤如下: 创建一个新结点*p; 根据行号i,找到在行表中的插入位置,并在行表中插入该结点; 同时根据列号j,找到在行表中的插入位置,并在行表中插入该结点; 在算法中将利用一个辅助索引数组MNode *hd[s+1]; 其中 s=Man(M , N) , hd [i]指向第i行(第i列)链表的头结点。
2. 十字链表 #define M 5 #define N 4 #define Man(M, N) ((M)>(N)?(M):(N)) MNODE *CreatMLink( ) // 返回十字链表的头指针 { MNode *H; int s=Man(M, N); MNode *p, *q, *hd[s+1]; int i, j, m, n, t; datatype v; cin >> m >> n >>t; H=new MNode;//申请总头结点 H->row=m; H->col=n; hd[0]=H;
2. 十字链表 for(i=1; i<=s; i++) { p=new MNode; //申请第i个头结点 p->row=0; p->col=0; p->right=p; p->down=p; hd[i]=p; hd[i-1]->v_next.next=p; } hd[s]->v_next.next=H; //将头结点形成循环链表
2. 十字链表 //以下是将*p插入到第i行链表中去,且按列号有序 for (k=1; k<=t; k++) { cin >> i >> j >> v ; //输入一个三元组 p=new MNode; p->row=i ; p->col=j; p->v_next.v=v; q=hd[i]; while ( q->right!=hd[i] && (q->right->col)<j ) q=q->right; // 按列号找位置 p->right=q->right;// 插入 q->right=p;
2. 十字链表 //以下是将*p插入到第j行链表中去,且按行号有序 q=hd[i]; while ( q->down!=hd[j] && (q->down->row)<i ) q=q->down; // 按行号找位置 p-> down =q-> down; // 插入 q-> down =p; } return H; }
2. 十字链表 (2) 十字链表表示矩阵的加法运算 已知两个稀疏矩阵A和B,分别采用十字链表存储,计算C=A+B,C也采用十字链表方式存储,并且在A的基础上形成C。 由矩阵的加法规则得知,只有A和B对应行和列相等时,二者才能相加。C中的非零元素cij有下列四种情况: (1)改变结点的值(aij+bij≠0); (2)不变(bij=0); (3)插入一个新结点(aij=0); (4)删除一个结点(aij+bij=0)。
2. 十字链表 整个运算从矩阵的第一行起逐行进行。对每一行都从行表的头结点出发,分别找到A和B在该行中的第一个非零元素结点后开始比较,用指针pa和pb分别指向A和B的十字链表中行号相同的两个结点,按下列4种情况处理: (1) 若pa->col=pb->col且pa->v+pb->v ≠0,则将aij+bij的结果值改写pa所指结点的值域即可。
2. 十字链表 (2) 若pa->col=pb->col且pa->v+pb->v=0,则需要在矩阵A的十字链表中删除pa所指结点,此时需改变该行链表中前趋结点的right域,以及该列链表中前趋结点的down域。 (3) 若pa->col < pb->col且pa->col≠0(即不是表头结点),则只需要将pa指针向右推进一步,而pb不动,并继续进行比较。 (4) 若pa->col > pb->col或pa->col=0(即是表头结点),则需要在矩阵A的十字链表中插入一个pb所指结点,然后 pb指针向右推进一步,而pa不动,并继续进行比较。
2. 十字链表 十字链表表示的稀疏矩阵相加算法 MNode *AddMat (MNode *Ha, MNode *Hb) { Mnode *p,*q,*pa,*pb,*ca,*cb,*qa; if (Ha->row!=Hb->row || Ha->col!=Hb->col) return NULL; ca=Ha->v_next.next; //指向A第一行表头结点 cb=Hb->v_next.next; //指向B第一行表头结点 do { pa=ca->right; //指向A当前行中第一个结点 qa=ca; // qa是pa的前驱 pb=cb->right; // 指向B当前行中第一个结点
while (pb->col!=0) //当前行没有处理完{ if (pa->col < pb->col && pa->col !=0 ) //第三种情况{ qa=pa; pa=pa->right;} else if (pa->col > pb->col || pa->col ==0 ) //第四种{ p=new MNode; p->row=pb->row; p->col=pb->col; p->v=pb->v; p->right=pa;qa->right=p; // 插入*pa的前面 pa=p; q=Find_JH(Ha,p->col); //从列的头结点找起while(q->down->row!=0 && q->down->row<p->row) q=q->down; p->down=q->down; //插在*q的后面q->down=p; pb=pb->right; }
else // 第一、二种情况 { x= pa->v_next.v+ pb->v_next.v; if (x==0) // 第二种情况 { qa->right=pa->right; .// 从行链中删除 q= Find_JH (Ha,pa->col); while ( q->down->row < pa->row ) q=q->down; q->down=pa->down; delete pa; pa=qa; } else // 第一种情况 { pa->v_next.v=x; qa=pa; }
pa=pa->right; pb=pb->right; } } // ca指向A中下一行的表头结点 ca=ca->v_next.next; // cb指向B中下一行的表头结点 cb=cb->v_next.next; } while (ca->row==0) return Ha; }
MNode *Find_JH(MNode *H, int j) //返回H中第j列链表的头结点指针 { MNode *p=H; int i = 1 ; while (i++ < j) p=p-> v_next.next; return p; }
作业: 87 1 2 3 4 5 6 7