【考研必备】稀疏矩阵三元组的创建、逆置、加法乘法

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

矩阵的压缩存储

矩阵

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

矩阵转置

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. 矩阵乘法算法
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容

  • 基础篇NumPy的主要对象是同种元素的多维数组。这是一个所有的元素都是一种类型、通过一个正整数元组索引的元素表格(...
    oyan99阅读 5,124评论 0 18
  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,340评论 0 2
  • 1. 幼儿园自由活动的时候,喜欢躺着看天,就盯着一个地方看,不要分神,集中注意力,最好连眼也不眨。 时间久了,就会...
    寺外丈阅读 268评论 1 0
  • 仁立传媒爱心公益论坛“奉献社会,关爱他人”的价值理念得到了广泛的传播,遵从仁立传媒“中国人、凝聚力、传爱心、扬正气...
    盲人王大文阅读 455评论 4 19
  • 今天和白莞聊天,我说特别想写个故事。我写这个故事不是为了治愈这些年来自己心上的伤痛,有些东西就是想写而已,理由什么...
    深蓝不会写小说阅读 326评论 0 2