设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间17 现在时间17 缓存数据 你们知道一个听电音的推到古风是什么感受吗[呆]

你们知道一个听电音的推到古风是什么感受吗[呆] -- 红昭愿

查看: 1133|回复: 2

C++实现特殊矩阵的压缩存储算法

[复制链接]

  离线 

TA的专栏

  • 打卡等级:即来则安
  • 打卡总天数:21
  • 打卡月天数:0
  • 打卡总奖励:320
  • 最近打卡:2025-03-28 03:04:30
等级头衔

等級:晓枫资讯-上等兵

在线时间
0 小时

积分成就
威望
0
贡献
401
主题
365
精华
0
金钱
1493
积分
810
注册时间
2023-2-10
最后登录
2025-3-28

发表于 2023-2-13 10:46:20 | 显示全部楼层 |阅读模式
1. 前言

什么是特殊矩阵?
  1. C++
复制代码
,一般使用二维数组存储矩阵数据。
在实际存储时,会发现矩阵中有许多值相同的数据或有许多零数据,且分布呈现出一定的规律,称这类型的矩阵为特殊矩阵。
为了节省存储空间,可以设计算法,对这类特殊矩阵进行压缩存储,让多个相同的非零数据只分配一个存储空间;对零数据不分配空间。
本文将讲解如何压缩这类特殊矩阵,以及压缩后如何保证矩阵的常规操作不受影响。

2. 压缩对称矩阵

什么是对称矩阵?
在一个
  1. n
复制代码
阶矩阵
  1. A
复制代码
中,若所有数据满足如下述特性,则可称
  1. A
复制代码
为对称矩阵。
  1. a[i][j]==a[j][i]
复制代码
i是数据在矩阵中的行号。
j是数据在矩阵中的列号。
  1. 0<<i,j<<n-1
复制代码
  1. n
复制代码
阶对称矩阵
  1. a[i][j]
复制代码
中,当i==j(行号和列号相同)时所有元素所构建成的集合称为主对角线。
如下图所示:
114715lb6md1c8bmfj7c4b.png

对称矩阵以主对角线为分界线,把整个矩阵分成 2 个三角区域,主对角线之上的称为上三角,主对角线之下的区域称为下三角。
对称矩阵的上三角和下三角区域中的元素是相同的,以n行n列的二维数组存储时,会浪费近一半的空间,可以采压缩机制,将 二维数组中的数据压缩存储在一个一维数组中,这个过程也称为数据线性化。
线性过程时,一维数组的空间需要多大?
  1. n
复制代码
阶矩阵,使用二维数组存储,理论上所需要的存储单元应该是 n2。
对称矩阵以主对角线为分界线,上三角和下三角区域中的数据是相同的。注意,主对角线上的元素是需要单独存储的,主对角线上的数据个数为 n。
真正需要的存储单元应该:(理论上所需要的存储单元-主对角线上的数据所需单元) / 2 +主对角线上的数据所需单元。
如下表达式所述:
(n2-n)/2+n=n(n+1)/2
所以,可以把
  1. n
复制代码
阶矩阵中的数据可以全部压缩在长度为
  1. n(n+1)/2
复制代码
的一维数组中,能节约近一半的存储空间。并且
  1. n
复制代码
阶矩阵和一维数组之间满足如下的位置对应关系:
114715tkbsz77ppzos0sfj.jpeg
  1. i>=j
复制代码
表示矩阵中的下三角区域(包含主对角线上数据)。
  1. i<j
复制代码
表示矩阵中的上三角区域。
转存实现:
  1. #include <iostream>using namespace std;int main(int argc, char** argv) {        //对称矩阵        int nums[4][4]= { {3,5,6,8},{5,4,7,9},{6,7,12,10},{8,9,10,13} };        //一维数组,根据上述公式,一维数组长度为 4*(4+1)/2=10        int zipNums[10]= {0};        for(int i=0; i<4; i++) {                for(int j=0; j<4; j++) {                        if (i>=j) {                                zipNums[ i*(i+1)/2+j]=nums[i][j];                        } else {                                zipNums[ j*(j+1)/2+i]=nums[i][j];                        }                }        }        for(int i=0; i<10; i++) {                cout<<zipNums[i]<<"\t";        }        return 0;}
复制代码
114716xoamw9vfoozadks3.png

如上是二维数组压缩到一维数组后的结果。

3. 压缩稀疏矩阵

什么是稀疏矩阵?
如果矩阵A中的有效数据的数量远远小于矩阵实际能描述的数据的总数,则称A为稀疏矩阵。
现假设有
  1. m
复制代码
  1. n
复制代码
列的矩阵,其中所保存的元素个数为
  1. c
复制代码
,则稀疏因子为:
  1. e=c/(m*n)
复制代码
。当用二维数组存储稀疏矩阵中数据时,仅有少部分空间被利用,可以采用压缩机制来进行存储。
稀疏因子越小,表示有效数据越少。
稀疏矩阵中的非零数据的存储位置是没有规律的,在压缩存储时,除了需要记录非零数据本身外还需要记录其位置信息。所以需要一个三元组对象(i,j,a[j])对数据进行唯一性确定。

3.1 三元组表

为了便于描述,压缩前的矩阵称为原稀疏矩阵,压缩后的稀疏矩阵称三元组表矩阵。
原稀疏矩阵也好,三元组表矩阵也好。只要顶着矩阵这个概念,就应该能进行矩阵相应的操作。矩阵的内置操作有很多,本文选择矩阵的转置操作来对比压缩前和压缩后的算法差异性。
什么是矩阵转置?
如有
  1. m
复制代码
  1. n
复制代码
列的
  1. A
复制代码
矩阵,所谓转置,指把
  1. A
复制代码
变成
  1. n
复制代码
  1. m
复制代码
列的
  1. B
复制代码
矩阵。
  1. A
复制代码
  1. B
复制代码
满足
  1. A[i][j]=B[j][i]
复制代码
。即
  1. A
复制代码
的行变成
  1. B
复制代码
的列。如下图所示:
114716pe92yd5pd999dgyi.png
  1. A
复制代码
稀疏矩阵转置成
  1. B
复制代码
稀疏矩阵的原生实现:
  1. //原矩阵
  2. int aArray[4][5]= {{0,5,0,1,0},{0,0,3,0,0},{0,7,0,0,0},{0,0,9,0,0}};
  3. //转置后矩阵
  4. int bArray[5][4];
  5. //转置算法
  6. for(int row=0; row<4; row++) {
  7.         for(int col=0; col<5; col++) {
  8.                 bArray[col][row]=aArray[row][col];
  9.         }
  10. }
复制代码
基于原生矩阵上的转置算法,其时间复杂度为
  1. O(m*n)
复制代码
,即O(n2)。
从存储角度而言,
  1. aArray
复制代码
矩阵和其转置后的
  1. bArray
复制代码
矩阵都是稀疏矩阵,使用二维数组存储会浪费大量的空间。有必要对其以三元组表的形式进行压缩存储。
三元组表是一个一维数组,因其中的每一个存储位置需要存储原稀疏矩阵中非零数据的
  1. 3
复制代码
个信息(行,列,值)。三元组表名由此而来,也就是说数组中存储的是对象。
先来一个图示,直观上了解一下A稀疏矩阵压缩前后的差异性。
114716i89ot92alt9tt889.png

压缩算法实现:
  1. #include <iostream>
  2. using namespace std;
  3. typedef int DataType;
  4. #define maxSize 100
  5. //三元组结构
  6. struct Node {
  7.         //行号
  8.         int row=-1;
  9.         //列号
  10.         int col=-1;
  11.         //非零元素的值
  12.         DataType val=0;
  13. } ;

  14. //维护三元组表的类
  15. class Matrix {
  16.         private:
  17.                 //位置编号
  18.                 int idx=0;
  19.                 //压缩前稀疏矩阵的行数
  20.                 int rows;
  21.                 //压缩前稀疏矩阵的列数
  22.                 int cols;
  23.                 //原稀疏矩阵中非零数据的个数
  24.                 int terms;
  25.                 //压缩存储的一维数组,初始化
  26.                 Node node;
  27.                 Node data[maxSize]= {node};
  28.         public:
  29.                 //构造函数
  30.                 Matrix(int row,int col) {
  31.                         this->rows=row;
  32.                         this->cols=col;
  33.              this->terms=0;
  34.                 }
  35.                 //存储三元结点
  36.                 void setData(int row ,int col,int val) {
  37.                         Node n;
  38.                         n.row=row;
  39.                         n.col=col;
  40.                         n.val=val;
  41.                         this->data[idx++]=n;
  42.              //记录非零数据的数量
  43.              this->terms++;
  44.                 }
  45.         //重载上面函数
  46.             void setData(int index,int row ,int col,int val) {
  47.                         Node n;
  48.                         n.row=row;
  49.                         n.col=col;
  50.                         n.val=val;
  51.                         this->data[index]=n;
  52.                         this->terms++;
  53.                 }
  54.                 //显示三无组表中的数据
  55.                 void showInfo() {
  56.                         for(int i=0; i<maxSize; i++ ) {
  57.                                 if(data[i].val==0)break;
  58.                                 cout<<data[i].row<<"\t"<<data[i].col<<"\t"<<data[i].val<<endl;
  59.                         }
  60.                 }
  61.                 //基于三元组表的转置算法
  62.                 Matrix transMatrix();
  63. };

  64. int main(int argc, char** argv) {
  65.         //原稀疏矩阵
  66.         int aArray[4][5]= {{0,5,0,1,0},{0,0,3,0,0},{0,7,0,0,0},{0,0,9,0,0}};
  67.         //实例化
  68.         Matrix matrix(4,5);
  69.         //压缩矩阵
  70.         for(int row=0; row<4; row++) {
  71.                 for(int col=0; col<5; col++) {
  72.                         if (aArray[row][col]!=0) {
  73.                  //转存至三元组表中
  74.                                 matrix.setData(row,col,aArray[row][col]);
  75.                         }
  76.                 }
  77.         }
  78.         matrix.showInfo();
  79.         return 0;
  80. }
复制代码
代码执行后的结果和直观图示结果一致:
114716zdr66naqnzjq8uln.png

压缩之后,则要思考,如何在A三元组表的基础上直接实现矩阵的转置。或者说 ,转置后的矩阵还是使用三元组表方式描述。
先从直观上了解一下,转置后的
  1. B
复制代码
矩稀疏阵的三元组表的结构应该是什么样子。
114717tpf7gqgq36qg3jjg.png

是否可以通过直接交换A的三元组表中行和列位置中的值?至于可不可以,可以先用演示图推演一下:
114717n3zc02eynnz78342.png

从图示可知,如果仅是交换A三元组表的行和列位置后得到的新三元组表并不和前面所推演出现的B三元组表一致。
如果仔细观察,可发现得到的新三元组表的是对原B稀疏表以列优先遍历后的结果。
B稀疏矩阵的三元组表显然应该是以行优先遍历的结果。

3.2 以列优先搜索

经过转置后,A稀疏矩阵的行会变成B稀疏矩阵的列,也可以说A的列变成B的行。如果在A中以列优先搜索,则相当于在B中以行优先进行搜索。可利用这个简单而又令人兴奋的道理实现基于三元组表的转置。
  1. Matrix Matrix::transMatrix(){
  2.         //转置后的三元组表对象
  3.         Matrix bMatrix(this->cols,this->rows);
  4.         //对原稀疏矩阵以列优先搜索
  5.         for(int c=0;c<this->cols;c++){
  6.                 //在对应的三元组表上查找此列上是否有非零数据
  7.                  for(int j=0;j<this->terms;j++ ){
  8.                          if(this->data[j].col==c){
  9.                                  //如果此列上有数据,则转置并保存
  10.                                 bMatrix.setData(this->data[j].col,this->data[j].row,this->data[j].val);  
  11.                          }
  12.                  }
  13.         }
  14.         return  bMatrix;
  15. }
复制代码
测试代码:
  1. int main(int argc, char** argv) {
  2. //原稀疏矩阵
  3. int aArray[4][5]= {{0,5,0,1,0},{0,0,3,0,0},{0,7,0,0,0},{0,0,9,0,0}};
  4. //实例化压缩矩阵
  5. Matrix matrix(4,5);
  6. //压缩矩阵
  7. for(int row=0; row<4; row++) {
  8.         for(int col=0; col<5; col++) {
  9.                 if (aArray[row][col]!=0) {
  10.                         matrix.setData(row,col,aArray[row][col]);
  11.                 }
  12.         }
  13. }
  14. cout<<"显示 A 稀疏矩阵压缩后的结果:"<<endl;
  15. matrix.showInfo();
  16. cout<<"在A的三元组表的基础上转置后的结果:"<<endl;
  17. Matrix bMatrix= matrix.transMatrix();
  18. bMatrix.showInfo();          
  19. return 0;
  20. }
复制代码
输出结果:
114717gawwz7azgxfqzq89.png

代码执行后输出的结果,和前文推演出来的结果是一样的。
前文可知,基于原生稀疏矩阵上的转置时间复杂度为
  1. O(m*n)
复制代码
。基于三元组表的 时间复杂度=稀疏矩阵的列数乘以稀疏矩阵中非零数据的个数。当稀疏矩阵中的元素个数为
  1. n*m
复制代码
时,则上述的时间复杂度会变成 O(m*n2)。

3.3 找出存储位置

上述算法适合于当稀疏因子较小时,当矩阵中的非零数据较多时,时间复杂度会较高。可以在上述列优先搜索的算法基础上进行优化。
其核心思路如下所述:

  • 在原A稀疏矩阵中按列优先进行搜索。
  • 统计每一列中非零数据的个数。
  • 记录每一列中第一个非零数据在
    1. B
    复制代码
    三元组表中的位置。
114717lnk7s2jmzkmzl5jz.png

对A稀疏矩阵按列遍历时,可以发现,扫描时,数据出现的顺序和其在B三元组表中的存储顺序是一致的。
如果在遍历时,能记录每列非零数据在B三元组表中应该存储的位置,则可以实现A三元组表中的数据直接以转置要求存储在B三元组表中。
114718m894k4j4v99zvni8.png

重写上述的转置函数。
  1. Matrix Matrix::transMatrix() {
  2.         //保存转置后数据的压缩矩阵
  3.         Matrix bMatrix(this->cols,this->rows);
  4.         //初始化数组,用来保存A稀疏矩阵中第一列中非零数据的个数
  5.         int counts[this->cols]= {0};
  6.         //计算每一列中非零数据个数
  7.         for(int i=0; i<this->terms; i++)
  8.                 counts[this->data[i].col]++;
  9.         //初始化数组,用来保存A稀疏矩阵每列中非零数据在B三元组表中的起始位置
  10.         int position[this->cols]= {0};       
  11.         for(int i=1;i<this->cols;i++ ){
  12.         //上一列的起始位置加上上一列非零数据的个数
  13.                 position[i]=position[i-1]+counts[i-1];
  14.         }
  15.     //转置A三元组表
  16.     for(int i=0;i<this->terms;i++){
  17.             int col=this->data[i].col;
  18.             int row=this->data[i].row;
  19.             int val=this->data[i].val;
  20.             //找到在B三元组中的起始存储位置
  21.                 int pos=position[col];
  22.                 bMatrix.setData(pos,col,row,val);
  23.                 position[col]++;       
  24.         }
  25.         return  bMatrix;
  26. }
复制代码
测试代码不需要任何变化:
  1. int main(int argc, char** argv) {
  2.         //原稀疏矩阵
  3.         int aArray[4][5]= {{0,5,0,1,0},{0,0,3,0,0},{0,7,0,0,0},{0,0,9,0,0}};
  4.         //实例化压缩矩阵
  5.         Matrix matrix(4,5);
  6.         //压缩矩阵
  7.         for(int row=0; row<4; row++) {
  8.                 for(int col=0; col<5; col++) {
  9.                         if (aArray[row][col]!=0) {
  10.                                 matrix.setData(row,col,aArray[row][col]);
  11.                         }
  12.                 }
  13.         }
  14.         cout<<"显示 A 稀疏矩阵压缩后的结果:"<<endl;
  15.         matrix.showInfo();
  16.         cout<<"在A的三元组表的基础上转置后的结果:"<<endl;
  17.         Matrix bMatrix= matrix.transMatrix();
  18.         bMatrix.showInfo();
  19.         return 0;
  20. }
复制代码
输出结果:
114718r3edee3nurfzpfnr.png

上述 2 种转置算法,其本质是一样的,第一种方案更容易理解,第二种方案在第一种方案的基础上用空间换取了时间上性能的提升。

4. 总结

使用二维数组存储矩阵中数据时,如果矩阵中的有效数据较小时,可以采用压缩的方式对其进行存储。
本文着重讲解如何使用三元组表方式压缩存储稀疏矩阵。转存过程并不难,难点在于转存为三元组表后,如何在三元组表的基础上正常进行矩阵相关操作。
以上就是C++实现特殊矩阵的压缩存储算法的详细内容,更多关于C++矩阵压缩存储的资料请关注晓枫资讯其它相关文章!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
20
积分
20
注册时间
2022-12-28
最后登录
2022-12-28

发表于 2024-9-23 21:14:44 | 显示全部楼层
顶顶更健康!!!
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
19
积分
18
注册时间
2022-12-27
最后登录
2022-12-27

发表于 7 天前 | 显示全部楼层
路过,支持一下
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼
3楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表