稀疏矩阵三元组的创建、逆置、加法乘法

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

本篇文章的练习题主要是对天勤19数组那章书后题的补充,因为书上没有运行结果,自己重写了一遍。书上的代码有些部分可读性比较差==没买天勤那本书的看这篇就行了。

矩阵的压缩存储

矩阵

在求矩阵行数和列数时遇到一个坑,就是当你把二维数组直接传到函数中,再从函数中求该二维数组的行数和列数会发生越界访问,求的值可能不对,这是因为你传递的是数组首元素的地址,应当在传参之前计算好行数和列数再传入函数中。

矩阵转置

void display(int A[][maxSize],int c,int r){
	for(int i=0;i<r;i++){
		for(int j=0;j<c;j++){
			cout<<setw(2)<<A[i][j];
		}
		cout<<endl;
	}
}

// 矩阵转置 
void matrixReserve(int A[][maxSize],int c,int r){
	int B[c][r]={};
	cout<<"逆转之前的数组"<<endl;
	display(A,c,r);
	cout<<"逆转之后的数组"<<endl;
	for(int i=0;i<r;i++){
		for(int j=0;j<c;j++){
			B[j][i]=A[i][j];
		}
	}
	display(B,c,r);
} 
int main(){
	int A[][3]={1,2,3,4,5,6,7,8,9};
	int c=sizeof(A[0])/sizeof(int);
	int r=(sizeof(A)/sizeof(int))/c;
	matrixReserve(A,c,r);
}
复制代码

矩阵相加

C[i][j]=A[i][j]+B[i][j]

// 矩阵相加
void matrixAdd(int A[][2],int B[][2],int m,int n) {
	int C[m][2]={};
	for(int i=0;i<m;i++){
		for(int j=0;j<n;j++){
			C[i][j]=A[i][j]+B[i][j];		}
	}
	display(C,m,n);
}
int main(){
	int A[2][2]={1,2,3,4};
	int B[2][2]={5,6,7,8};
	int c=sizeof(A[0])/sizeof(int);
	int r=(sizeof(A)/sizeof(int))/c;
	matrixAdd(A,B,c,r);
}
复制代码

矩阵相乘

C[i][j]=A上第i行每个元素与B上第j列每个元素相乘并求和的结果;两矩阵可以相乘的必要条件是:a的列数必须等于b的行数。

// 矩阵相乘
void matrixMuti(int A[][2],int B[][2],int m,int n) {
	int C[m][2]={};
	for(int i=0;i<m;i++){
		for(int j=0;j<n;j++){
			C[i][j]=0;
    /* 注意这里h的区间并不一定是m,应为a的列数或b的行数,这里因为m和n相等所以为m */
			for(int h=0;h<m;h++){ 
				C[i][j]+=A[i][h]*B[h][j];
			}
			
		}
	}
	display(C,m,n);
}
int main(){
	int A[2][2]={1,2,3,4};
	int B[2][2]={5,6,7,8};
	int c=sizeof(A[0])/sizeof(int);
	int r=(sizeof(A)/sizeof(int))/c;
	matrixMuti(A,B,c,r);

    // 19 22
    // 43 50
}
复制代码

习题

1.数组的n个元素中有多个零元素,设计一个算法将数组中所有的非零元素依次移动到a数组的前端。

算法是遍历时用一个指针j记录最后一次出现的非零元素下标,遇见0跳过,出现下一个非0元素时将j+1与该位置的0值交换,j+1。

void moveZeroToBottom(int A[maxSize],int length){
	int j=-1; // 记录最后一个非零元素出现的位置 
	for(int i=0;i<length;i++){
		if(A[i]==0){
			continue;
		}else{
			j++;
			int temp=A[i];
			A[i]=A[j];
			A[j]=temp;
		}
	}
	displayArr(A,length);
}

int main(){
	int A[maxSize]={0,2,1,3,0,0,3,5,7};
	int length=sizeof(A)/sizeof(A[0]);
	moveZeroToBottom(A,length); // 2 1 3 3 5 7 0 0 0 0
}
复制代码

2.关于浮点型数组A[0,...,n-1]分别用递归实现求最大值以及数组中所有数的和、平均值。

很基础的算法,不过多解释了。就是在求平均值时偷懒了,应该s是按照avg=((n-1)*avg+arr[i])/n这样递归。。自己直接将上一步求的和除了总长度==。

float findMax(float arr[],float max,int i){
	if(i==maxSize){
		return max;
	}
	if(arr[i]>max){
		max=arr[i];
	}
	return findMax(arr,max,i+1); 
}

float getsum(float arr[],float sum,int i){
	if(i==maxSize){
		return sum;
	}
	return getsum(arr,sum+arr[i],i+1);
}

float getAverage(float arr[],float sum,int i){
	if(i==maxSize){
		return sum/maxSize;
	}
	return getAverage(arr,sum+arr[i],i+1);
}

int main(){
	float A[maxSize]={0,2,1,3,8,0,3,5,7};
	float max=findMax(A,0,0);
	float sum=getsum(A,0,0);
	float avg=getAverage(A,0,0);
	cout<<max<<endl;; // 8
	cout<<sum<<endl; // 29
	cout<<avg<<endl; // 3.222222
}
复制代码

3.试设计一个算法,将数组中所有的奇数移到偶数之前,要求不另增加存储空间。写空间复杂度为O(n)。

一头一尾两个指针,当前面的指针i停在偶数时,后面的指针j停在奇数时,两数值交换,继续遍历直至两指针重合。

void bubbleOdd(int arr[]){
	int i=0,j=maxSize-1;
	while(i<j){
		while(arr[i]%2==1&&i<j) i++;
		while(arr[j]%2==2&&i<j) j--;
		if(i<j){
			int temp=arr[i];
			arr[i]=arr[j];
			arr[j]=temp;
			i++;
			j--;
		}
		
	}
	display(arr);
}

int main(){
	int A[maxSize]={0,2,1,3,8,0,3,5,7};
	bubbleOdd(A); // 7 5 1 3 3 0 8 2 0
}
复制代码

4.设有一元素为整数的线性表l,放在一维数组中,设计一个算法,以A[n-1]为参考量,将该数组分为左右两个部分,其中左半部分的元素值均小于等于A[n-1]。右半部分的元素指针大于A[n-1],A[n-1]位于这两部分之间。要求结果仍存放在数组a中。

类似于快排,不过是先将首尾元素互换之后,将首元素作为参考值。一头一尾两指针i和j,j先动先从后面找比参考值小的,与i指向的元素交换;再从前面找比参考值大的,放到后面;直至量指针相遇,将该位置设为参考值。

void quickSort(int arr[]){
	int i=0;
	int j=maxSize-1;
	int temp=arr[j];
	arr[j]=arr[i];
	arr[i]=temp;
	while(i!=j){
		while(i<j&&arr[j]>temp) j--; //找到一个小于目标值的数,放在前面 
		if(i<j){
			arr[i]=arr[j];
			i++;
		}
		while(i<j&&arr[i]<=temp) i++; //找到一个大于目标值的数,放在前面 
		if(i<j){
			arr[j]=arr[i];
			j--;
		}
	}
	arr[i]=temp;
} 
复制代码

5.设计一个算法对给定的一个整形矩阵a统计这个矩阵中具有下列特征的元素个数,并输出它们的坐标以及数值他们技术所在行中的最小折又是所在列中的最小值,或者他们技术所在行中的最大制又是所在列中的最大值。

先求出一行中最小值,记录当前列,再看是否为该列中最小值。

void getMin(int A[][3],int c,int r){
	int min=A[0][0];
	int minj=0;
	int isFind=1;
	for(int i=0;i<r;i++){
		for(int j=0;j<c;j++){
			if(A[i][j]<min){
				min=A[i][j];
				minj=j;
			}
		}
		
		for(int k=0;k<r;k++){
			if(min>A[k][minj]){
			isFind=0;
			break;	
			}
		}
		
		if(isFind==1){
			cout<<"找到最小值"<<A[i][minj]<<",坐标为"<<i<<","<<minj<<endl; 
			isFind=0;
		}
	}
} 
// 输出
找到最小值1,坐标为0,0
复制代码

5.怎样介绍稀疏矩阵的三元组存储结构特点,并实现稀疏矩阵的基本操作。

1.给定稀疏矩阵a。创建其三元组存储结构b。

三元组存储结构是一种顺序结构,因此也是一种顺序表表中的每个节点对应稀疏矩阵的一个非零元素,其中包括三个字段,分别为该元素的值、行下标和列下标。另外,用第零行的第一个元素,存储矩阵中非零元素的个数,第零行的第二个元素存储矩阵的行数,第零行的第三个元素存储矩阵的列数。

void create(int A[][3],int c, int r,int B[][3]){
	int k=1;
	for(int i=0;i<r;i++){
		for(int j=0;j<c;j++){
			if(A[i][j]!=0){
				B[k][0]=A[i][j];
				B[k][1]=i;
				B[k][2]=j;
				k++;
			}	
		}
	}
	B[0][0]=k-1;
	B[0][1]=r;
	B[0][2]=c;
	display(B,k,3);
}


int main(){
	int A[3][3]={1,2,3,4,5,6,7,8,9};
	int B[maxSize][3];
	create(A,3,3,B);
}
复制代码

输出

 9 3 3
 1 0 0
 2 0 1
 3 0 2
 4 1 0
 5 1 1
 6 1 2
 7 2 0
 8 2 1
 9 2 2
复制代码

2.查找给定元素x是否在矩阵中。

遍历上面求到的三元组进行查找。

int search(int trimat[][3],int target,int r){
	int isFind=0;
	for(int i=1;i<r;i++){
		if(B[i][0]==target){
			cout<<"坐标为"<<B[i][1]<<","<<B[i][2]<<endl;
			isFind=1;
		}
	}
	return isFind;
}

int main(){
	int A[][3]={1,2,3,4,5,6,7,8,9};
	int B[9][3];
	create(A,3,3,B);
	search(B,5,9); // 坐标为1,1
}
复制代码

6.假设稀疏矩阵a采用三元组表示。编写一个函数,计算其转置矩阵b要求B也采用三元组表示。

矩阵的转置归根结底就是A[m][n]=B[n][m]这个公式。但三元组不仅仅那么简单,三元组内的元素是原矩阵按照行优先存储的结果,所以下面的程序是错的!!!

void reserve(int B[][3],int C[][3],int r){
	for(int i=1;i<=r;i++){
		C[i][0]=B[i][0];
		C[i][1]=B[i][2];
		C[i][2]=B[i][1];
	}
	C[0][0]=B[0][0];
	C[0][1]=B[0][2];
	C[0][2]=B[0][1];
	cout<<"转置后"<<endl;
	display(C,r,3);
}

复制代码

输出

// 原三元组
 9 3 3
 1 0 0
 2 0 1
 3 0 2
 4 1 0
 5 1 1
 6 1 2
 7 2 0
 8 2 1
 9 2 2
----逆置后为-----
 9 3 3
 1 0 0
 2 1 0
 3 2 0
 4 0 1
 5 1 1
 6 2 1
 7 0 2
 8 1 2
 9 2 2
复制代码

正确的写法应该是,找到A中第i列的元素,依次放在B中的第i行中,并将元素中的坐标对调。

void reserve(int B[][3],int C[][3],int r){
   int k=1;
   for(int i=0;i<3;i++){
   	for(int j=1;j<r;j++){
   		if(B[j][2]==i){
   			C[k][0]=B[j][0];
   			C[k][1]=B[j][2];
   			C[k][2]=B[j][1];
   			k++;
   		}
   	}
   }
   C[0][0]=B[0][0];
   C[0][1]=B[0][2];
   C[0][2]=B[0][1];
   cout<<"转置后"<<endl;
   display(C,10,3);
}
复制代码

结果

----逆置后为-----
 9 3 3
 1 0 0
 4 0 1
 7 0 2
 2 1 0
 5 1 1
 8 1 2
 3 2 0
 6 2 1
 9 2 2
复制代码

7.假设稀疏矩阵。A和b都采用三元组表示,编写一个函数计算C=A+B,C也用三元组表示。

想矩阵相加规则为对应位置上的元素相加,对于三元组存储结构下的矩阵a和b,假如当前需要将位置上的元素。a[i][j]和b[i][j]相加需要考虑的情况,即两个元素的相同位置上是否非零。 (王道中算法逻辑比较繁琐,个人建议先将三元组转为矩阵再加,再转为三元组==)

void add(int A[][3],int B[][3],int C[][3],int r,int c){
	int k=1;
	int i=1,j=1;
	while(i<=A[0][0]&&j<=B[0][0]){
		if(A[i][1]==B[j][1]){ // 拿同一行比 ,较小列的先放入c中 
			if(A[i][2]<B[j][2]){ 
				C[k][0]=A[i][0];
				C[k][1]=A[i][1];
				C[k][2]=A[i][2];
				++k;
				++i; 
			}else if(A[i][2]>B[j][2]){
				C[k][0]=B[j][0];
				C[k][1]=B[j][1];
				C[k][2]=B[j][2];
				++k;
				++j; 
			}else{// A、B两矩阵中位置相同 ,则相加存入C中 
				int temp=A[i][0]+B[j][0];
				if(temp!=0){// 注意要是非0数 
					C[k][0]=temp;
					C[k][1]=A[j][1];
					C[k][2]=A[j][2];
					++k;
				}
				++i;
				++j;
			}
		}else if(A[i][1]<B[j][1]){ // 行数小的先存入C中 
				C[k][0]=A[i][0];
				C[k][1]=A[i][1];
				C[k][2]=A[i][2];
				++k;
				++i; 
		}else{ // A的行数大于B的行数 
				C[k][0]=B[j][0];
				C[k][1]=B[j][1];
				C[k][2]=B[j][2];
				++k;
				++j; 
		}
	}
	// 如果 A、B中仍有剩余元素
		while(i<=A[0][0]){
				C[k][0]=A[i][0];
				C[k][1]=A[i][1];
				C[k][2]=A[i][2];
				++k;
				++i; 
		} 
		
		while(j<=B[0][0]){
				C[k][0]=B[j][0];
				C[k][1]=B[j][1];
				C[k][2]=B[j][2];
				++k;
				++j; 
		}
	C[0][0]=k-1;
	C[0][1]=A[0][1];
	C[0][2]=A[0][2];
	cout<<"相加后的三元组"<<endl;
	display(C,k,3);
}
复制代码

两个矩阵都是{1,2,3,4,5,6,7,8,9}时相加的结果输出如下:

第一个矩阵的三元组为
 9 3 3
 1 0 0
 2 0 1
 3 0 2
 4 1 0
 5 1 1
 6 1 2
 7 2 0
 8 2 1
 9 2 2
第二个矩阵的三元组为
 9 3 3
 1 0 0
 2 0 1
 3 0 2
 4 1 0
 5 1 1
 6 1 2
 7 2 0
 8 2 1
 9 2 2
相加后的三元组
 9 3 3
 2 0 0
 4 0 1
 6 0 2
 8 1 0
10 1 1
12 1 2
14 2 0
16 2 1
18 2 2
复制代码

8.假设稀疏矩阵a和b。采用三元组表示编写一个函数,计算C=A*B,要求c也是采用三元组表示的稀疏矩阵。

矩阵的乘法是一种常用的矩阵运算。假设两矩阵a与b相乘,结果为c,c中的第i行和第j列上的元素为a中第i行的元素与b中第j列的元素对应相乘,并且求和的结果。及两向量的点乘。由上述介绍可知,a和b量举证可以相乘的条件是a的列数必须等于b的行数,看下列公式:

两个向量a = [a1, a2,…, an]和b = [b1, b2,…, bn]的点积定义为:
a·b=a1b1+a2b2+……+anbn。
复制代码

还使用刚才的两个(1~9)的矩阵,他们相乘的过程为

// 原矩阵
1 2 3       1 2 3 
4 5 6	x   4 5 6
7 8 9	    7 8 9
所求矩阵中0,0位置的数为:1*1+2*4+3*7=30
0,1位置的数,为:1*2+2*5+3*8=36
。。。博主懒,不一一算了。。口算出两个数用于验证下面三元组算法的结果
复制代码

放在三元组中,这里我们需要定义getValueOfTrimat函数,根据在矩阵中的下标得出在三元组的值。一定要注意验证sum不能为0,因为三元组中不统计0元素。

int getValueOfTrimat(int trimat[][3],int r,int c){
	for(int i=1;i<=trimat[0][0];i++){
		if(trimat[i][1]==r&&trimat[i][2]==c){
			return trimat[i][0]; 
		}
	}
	return 0;
}

void multi(int A[][3],int B[][3],int C[][3]){
	if(A[0][1]!=B[0][2]){
		cout<<"两矩阵不能相乘"<<endl;
		return;
	}
	int i,j,k=1;
	for(i=0;i<A[0][1];i++){
		for(j=0;j<B[0][2];j++){
			int sum=0;
			for(int t=0;t<A[0][1];t++){
				sum+=getValueOfTrimat(A,i,t)*getValueOfTrimat(B,t,j);
			}
			if(sum!=0){
				C[k][0]=sum;
				C[k][1]=i;
				C[k][2]=j;
				k++;
			}	
		}
	}
	C[0][0]=k-1;
	C[0][1]=i;
	C[0][2]=j;
	display(C,k,3);
}
复制代码

结果为:

第一个矩阵的三元组为
 9 3 3
 1 0 0
 2 0 1
 3 0 2
 4 1 0
 5 1 1
 6 1 2
 7 2 0
 8 2 1
 9 2 2
第二个矩阵的三元组为
 9 3 3
 1 0 0
 2 0 1
 3 0 2
 4 1 0
 5 1 1
 6 1 2
 7 2 0
 8 2 1
 9 2 2
两矩阵相乘的结果为
 9 3 3
30 0 0
36 0 1
42 0 2
66 1 0
81 1 1
96 1 2
102 2 0
126 2 1
150 2 2
复制代码

参考文章

  1. C++二维数组讲解、二维数组的声明和初始化
  2. 矩阵乘法算法

Guess you like

Origin juejin.im/post/7035478353490477092