第5章数组和稀疏矩阵 5.1问答题及其参考答案 5.1.1问答题 1. 为什么说数组是线性表的推广或扩展,而不说数组就是一种线性表呢? 2. 为什么数组一般不使用链式存储结构? 3. 如果某个一维整数数组A的元素个数n很大,存在大量重复的元素,且所有值相同的元素紧跟在一起,请设计一种压缩存储方式使得存储空间更节省。 4. 有一个5×6的二维数组a,起始元素a[1][1]的地址是1000,每个元素的长度为4。 (1) 若采用按行优先存储,给出元素a[4][5]的地址。 (2) 若采用按列优先存储,给出元素a[4][5]的地址。 5. 一个n阶对称矩阵存入内存,在采用压缩存储和非压缩存储时占用的内存空间分别是多少? 6. 一个6阶对称矩阵A中主对角线以上部分的元素已按列优先顺序存放于一维数组B中,主对角线上的元素均为0。根据以下B的内容画出A矩阵。 01234567891011121314 B: 250340014263012 7. 设A[0..9,0..9]是一个10阶对称矩阵,采用按行优先将其下三角+主对角线部分的元素压缩存储到一维数组B中。已知每个元素占用两个存储单元,其第一个元素A[0][0]的存储位置为1000,求以下问题的计算过程及结果: (1) 给出A[4][5]的存储位置。 (2) 给出存储位置为1080的元素的下标。 8. 设n阶下三角矩阵A[0..n-1,0..n-1]已压缩存储到一维数组B[1..m]中,若按行为主序存储,则A[i][j](i≥j)元素对应的B中存储位置为多少?给出推导过程。 9. 用十字链表表示一个有k个非零元素的m×n的稀疏矩阵,则其总的结点数为多少? 10. 特殊矩阵和稀疏矩阵哪一种压缩存储后失去随机存取特性?为什么? 第5章数组和稀疏矩阵 数据结构教程(C++语言描述)(第2版)学习与上机实验指导 5.1.2问答题参考答案 1. 答: 从逻辑结构的角度看,一维数组是一种线性表,二维数组可以看成数组元素为一维数组的一维数组,所以二维数组可以看成线性表,三维及以上维的数组亦如此。数组的基本运算不同于线性表,数组的主要操作是存取元素,不含插入和删除等运算,所以数组可看作线性表的推广,但数组不等同于线性表。 2. 答: 因为数组的主要操作是存取元素,通常没有插入和删除操作,在使用链式存储结构时需要额外占用更多的存储空间,而且不具有随机存取特性,使得相关操作更复杂。 3. 答: 采用元素类型形如“{整数,个数}”的结构体数组压缩存储,例如,数组A为{1,1,1,5,5,5,5,3,3,3,3,4,4,4,4,4,4},共有17个元素,对应的压缩存储为{{1,3},{5,4},{3,4},{4,6}}。在压缩数组中只有8个整数。从中看出,重复元素越多,采用这种压缩存储方式越节省存储空间。 4. 答: (1) m×n的二维数组a(行、列下标从c1、c2开始)按行优先存储时元素a[i][j]的地址计算公式是LOC(a[i][j])=d+((i-c1)×(n-c2+1)+(j-c2))×k。这里m=5,n=6,k=4,d=1000,c1=c2=1,所以有LOC(a[4][5])=1000+((4-1)×(6-1+1)+(5-1))×4=1000+88=1088。 (2) m×n的二维数组a(行、列下标从c1、c2开始)按列优先存储时元素a[i][j]的地址计算公式是LOC(a[i][j])=d+((j-c2)×(m-c1+1)+(i-c1))×k。这里m=5,n=6,k=4,d=1000,c1=c2=1,所以有LOC(a[4][5])=1000+((5-1)×(5-1+1)+(4-1))×4=1000+92=1092。 5. 答: 若采用压缩存储,其容量为n(n+1)/2,若不采用压缩存储,其容量为n2。 6. 答: 对应的A对称矩阵如下。 A= 025306 200413 500040 340021 014202 630120 7. 答: (1) A[4][5]元素前面有4行,元素个数为1+2+3+4=10,在第4行中,A[4][5]元素前面有5个元素,则前面元素的总数=10+5=15。 LOC(A[4][5])=LOC(A[0][0])+15×2=1030。 (2) 存储位置为1080,则元素在压缩数组中的序号为(1080-1000)/2=40。设该元素为A[i][j],则有 i(i+1)/2+j=40且0≤i,j≤9,从而得到i=8,j=4,所以对应的元素为A[8][4]或者A[4][8]。 8. 答: A的下标从0开始,对于A[i][j](i≥j)元素,前面有0~i-1共i行,各行的元素个数分别为1、2、…、i,计i(i+1)/2个元素,在第i行中,A[i][j]元素前面的元素有A[i,0..j-1],计j个元素,所以在A中A[i][j]元素之前共存储i(i+1)/2+j个元素。而B的下标从1开始,所以对应的B中存储位置是i(i+1)/2+j+1。 9. 答: 十字链表有一个十字链表表头结点,MAX(m,n)个行列表头结点。另外,每个非零元素对应一个结点,即k个元素结点,所以共有MAX(m,n)+k+1个结点。 10. 答: 特殊矩阵A指值相同的元素或常元素在矩阵中的分布有一定规律,可以将这些特殊值的元素压缩存储在一维数组B中,即将A[i][j]元素值存放在B[k]中,下标k和下标i、j的关系用函数k=f(i,j)表示,该函数的计算时间为O(1),因此对于A[i][j]找到存储值B[k]的时间为O(1),所以仍具有随机存取特性。 而稀疏矩阵是指非零元素个数t和矩阵容量相比很小(tm×n),且非零元素分布没有规律。在用十字链表存储时自然失去了随机存取特性,即便用三元组顺序存储结构存储,存取下标为i和j的元素时也需要扫描整个三元组表,平均时间复杂度为O(t)。因此稀疏矩阵A无论采用三元组还是十字链表存储,查找A[i][j]元素值的时间不再是O(1),所以失去了随机存取特性。 5.2算法设计题及其参考答案 5.2.1算法设计题 1. 设计一个算法,将含有n个整数元素的数组a[0..n-1]循环右移m位,要求算法的空间复杂度为O(1)。 2. 有一个含有n个整数元素的数组a[0..n-1],设计一个算法求其中最后一个最小元素的下标。 3. 设a是一个含有n个元素的double型数组,b是一个含有n个元素的整数数组,其值介于0~n-1,且所有元素不相同。现设计一个算法,要求按b的内容调整a中元素的顺序,比如当b[2]=11时,要求将a[11]元素调整到a[2]中。如n=5,a[]={1,2,3,4,5},b[]={2,3,4,1,0},执行本算法后a[]={3,4,5,1,2}。 4. 设计一个算法,实现m行n列的二维数组a的就地转置,当m≠n时返回false,否则返回true。 5. 设计一个算法,求一个m行n列的二维整型数组a的左上角右下角和右上角左下角两条主对角线元素之和,当m≠n时返回false,否则返回true。 5.2.2算法设计题参考答案 1. 解: 设a中元素为xy(x为前n-m个元素,y为后m个元素)。先将x逆置得到x-1y,再将y逆置得到x-1y-1,最后将整个x-1y-1逆置得到(x-1y-1)-1=yx。对应的算法如下: void Reverse(int a[],int i,int j)   //逆置a[i..j] {int i1=i,j1=j; while (i1n) m=m%n; Reverse(a,0,n-m-1); Reverse(a,n-m,n-1); Reverse(a,0,n-1); } 2. 解: 设最后一个最小元素的下标为mini,初值为0。i从1到n-1循环,当a[i]<=a[mini]时置mini=i,最后返回mini。对应的算法如下: int FindMin(int a[],int n) {int mini=0; for (int i=1;i using namespace std; int addr() {int m,n,k,d,i,j,c1,c2,flag,loc; printf("m n k: "); scanf("%d%d%d",&m,&n,&k); printf("起始地址: "); scanf("%d",&d); printf("1-按行优先2-按列优先: "); scanf("%d",&flag); printf("初始下标: "); scanf("%d",&c1); c2=c1; printf("元素 i j: "); scanf("%d%d",&i,&j); if (flag==1) loc=d+((i-c1)*(n-c2+1)+(j-c2))*k; else loc=d+((j-c2)*(m-c1+1)+(i-c1))*k; printf("元素a[%d][%d]的地址:%d\n",i,j,loc); } int main() {printf("\n计算二维数组元素地址\n"); addr(); return 0; } 图5.1第5章基础实验题1 的执行结果 上述程序的一次执行结果如图5.1所示。 2. 解: 假设n阶对称矩阵A的行、列起始地址为c1,压缩存放的一维数组B的起始地址为c2,分4种情况讨论。 (1) 采用按行优先方式压缩存储下三角+主对角部分元素。 对于元素aij(i≥j),前面存放c1~i-1的行元素,共i-c1行,第c1行有一个元素,第c1+1行有两个元素,…,第i-1行有i-c1个元素,共有(1+i-c1)(i-c1)/2个元素。在第i行中前面存放的元素是a[i][c1..j-1],共j-c1个元素,则k=(1+i-c1)(i-c1)/2+j-c1+c2。 对于元素aij(ij),对应的地址k=(2n-j+c1+1)(j-c1)/2+i-j+c2。 (4) 采用按列优先方式压缩存储上三角+主对角部分元素。 对于元素aij(i≤j),前面存放c1~j-1的列元素,共j-c1列,第c1列有一个元素,第c1+1列有两个元素,…,第j-1列有j-c1个元素,共有(1+j-c1)(j-c1)/2个元素。在第j列中前面存放的元素是a[c1..i-1][j],共i-c1个元素,则 k=(1+j-c1)(j-c1)/2+i-c1+c2。 对于元素aij(i>j),对应的地址k=(1+i-c1)(i-c1)/2+j-c1+c2。 对应的程序如下: #include using namespace std; int addr() {int n,k,i,j,c1,c2,tag,flag; printf("n: "); scanf("%d",&n); printf("1-下三角+主对角 2-上三角+主对角: "); scanf("%d",&tag); printf("1-按行优先2-按列优先: "); scanf("%d",&flag); printf("A的初始下标 B的初始下标: "); scanf("%d%d",&c1,&c2); printf("元素 i j: "); scanf("%d%d",&i,&j); if (tag==1) {if (flag==1)   //(1) {if (i>=j) k=(1+i-c1)*(i-c1)/2+j-c1; else k=(1+j-c1)*(j-c1)/2+i-c1; } else   //(2) {if (i>=j) k=(2*n-j+c1+1)*(j-c1)/2+i-j+c2; else k=(2*n-i+c1+1)*(i-c1)/2+j-i+c2; } } else {if (flag==1)   //(3) {if (i<=j) k=(2*n-i+c1+1)*(i-c1)/2+j-i+c2; else k=(2*n-j+c1+1)*(j-c1)/2+i-j+c2; } else   //(4) {if (i<=j) k=(1+j-c1)*(j-c1)/2+i-c1+c2; else k=(1+i-c1)*(i-c1)/2+j-c1+c2; } } printf("元素a[%d][%d]的压缩存储地址k=%d\n",i,j,k); } int main() {printf("\n计算对称矩阵压缩存储时元素地址\n"); addr(); return 0; } 上述程序执行的4种情况及其结果如图5.2所示。 图5.2第5章基础实验题2的执行结果 3. 解: 稀疏矩阵三元组压缩存储结构及其基本运算算法设计参见《教程》中的第5.3.1节。对应的程序如下: #include using namespace std; const int MAXR=20;   //稀疏矩阵的最大行数 const int MAXC=20;   //稀疏矩阵的最大列数 const int MaxSize=100;   //三元组顺序表的最大元素个数 struct TupElem   //单个三元组元素的类型 {int r;   //行号 int c;   //列号 int d;   //元素值 TupElem() {}   //构造函数 TupElem(int r1,int c1,int d1)   //重载构造函数 r=r1; c=c1; d=d1; } }; class TupClass   //三元组存储结构类 {int rows;   //行数 int cols;   //列数 int nums;   //非零元素的个数 TupElem *data;   //稀疏矩阵对应的三元组顺序表 public: TupClass()   //构造函数 {data=new TupElem[MaxSize];   //分配空间 nums=0; } ~TupClass()   //析构函数 { delete [] data;   //释放空间 } void CreateTup(int A[][MAXC],int m,int n)  //创建三元组 {rows=m; cols=n; nums=0; for (int i=0;i=rows || j<0 || j>=cols) return false;   //下标错误时返回false int k=0,k1; while (kdata[k].r) k++;   //查找第i行的第一个非零元素 while (kdata[k].c) k++;   //在第i行中查找第j列的元素 if (data[k].r==i && data[k].c==j)   //找到了这样的元素 data[k].d=x; else   //不存在这样的元素时插入一个元素 {for (k1=nums-1; k1>=k;k1--)   //后移元素以便插入 {data[k1+1].r=data[k1].r; data[k1+1].c=data[k1].c; data[k1+1].d=data[k1].d; } data[k].r=i; data[k].c=j; data[k].d=x; nums++; } return true;   //赋值成功时返回true } bool GetValue(int i,int j,int &x)   //将指定位置的元素值赋给变量x=A[i][j] {if (i<0 || i>=rows || j<0 || j>=cols) return false;   //下标错误时返回false int k=0; while (k using namespace std; int maxSubSum(int a[],int n) {int *sum= new int[n];   //存放前缀和 sum[0]=a[0]; for(int i=1;i0,则该连续子序列再加上a[i]称为更大连续子序列,即a[i]+a[i-1]。求出最大的a[i]为ans,最后输出ans。对应的程序如下: #include using namespace std; int maxSubSum(int a[],int n) {if (a[0]<0) a[0]=0;   //修改a[i]表示以位置i结尾的最大连续子序列之和 int ans=a[0]; for(int i=1;i0) a[i]+=a[i-1]; else a[i]+=0; ans=max(ans,a[i]); } return ans; } void disp(int a[],int n)   //输出a {for (int i=0;i #include using namespace std; #define MAXM 10 #define MAXN 10 vector > MinMax(int a[MAXM][MAXN],int m,int n)  //求所有马鞍点 {int *mind=new int[m];   //存放每行的最小元素 int *maxd=new int[n];   //存放每列的最大元素 for (int i=0;imaxd[j]) maxd[j]=a[i][j]; } vector > ans; for (int i=0;i e; e.push_back(i); e.push_back(j); e.push_back(a[i][j]); ans.push_back(e); } return ans; } void disp(int a[MAXM][MAXN],int m,int n)  //输出二维数组 {for (int i=0;i > ans; ans=MinMax(a,m,n); for (int i=0;i #include using namespace std; #define MAXM 10 #define MAXN 10 void disp(int A[MAXM][MAXN],int n)   //输出二维数组A {for (int i=0;i