当前位置: 首页 > news >正文

数据结构(超详细讲解!!)第二十一节 特殊矩阵的压缩存储

1.压缩存储的目标

值相同的元素只存储一次

压缩掉对零元的存储,只存储非零元

特殊形状矩阵:

是指非零元(如值相同的元素)或零元素分布具有一定规律性的矩阵。

如: 对称矩阵 上三角矩阵   下三角矩阵 对角矩阵   准对角矩阵

2.三角矩阵

三角矩阵大体分为三类:下三角矩阵、上三角矩阵和对称矩阵。

对于一个n阶矩阵A来说,若当i<j时,有aij=0,则称此矩阵为下三角矩阵;

若当i>j时,有aij=0,则称此矩阵为上三角矩阵;

若矩阵中的所有元素均满足aij=aji,则称此矩阵为对称矩阵。

 对于下三角矩阵的压缩存储,我们只存储下三角的非零元素,对于零元素则不存。我们按“行序为主序”进行存储,得到的序列是a11, a21, a22, a31, a32, a33, …, an1, an2, …, ann。由于下三角矩阵的元素个数为n(n+1)/2,即:

所以可压缩存储到一个大小为n(n+1)/2的一维数组C中

下三角矩阵中元素aij(i>j)在一维数组A中的位置为:        

Loc[i, j]=Loc[1, 1]+前i-1行非零元素个数+第i行中aij前非零元素个数        

前i-1行元素个数=1+2+3+4+…+(i-1)=i(i-1)/2,所以有 Loc[i, j]=Loc[1, 1]+i(i-1)/2+j-1        

同样,对于上三角矩阵,也可以将其压缩存储到一个大小为n(n+1)/2的一维数组C中。其中元素aij(i<j)在数组C中的存储位置为: Loc[i, j]=Loc[1, 1]+j(j-1)/2+i-1      

对于对称矩阵,因其元素满足aij=aji,我们可以为每一对相等的元素分配一个存储空间,即只存下三角(或上三角)矩阵,从而将n2个元素压缩到n(n+1)/2个空间中。

3.带状矩阵

 三对角带状矩阵有如下特点:            

i=1, j=1, 2                  

1<i<n, j=i-1, i, i+1;            

i=n, j=n-1, n;

时,aij非零,其它元素均为零。

(1)确定存储该矩阵所需的一维向量空间的大小        

在这里我们假设每个非零元素所占空间的大小为1个单元。 从图中观察得知,三对角带状矩阵中,除了第一行和最后一行只有2个非零元素外,其余各行均有3个非零元素。由此得到, 所需一维向量空间的大小为 2+2+3(n-2)=3n-2

(2)确定非零元素在一维数组空间中的位置        

Loc[i ,  j] = Loc[1, 1]+前i-1行非零元素个数+第i行中aij前非零元素个数;        

前i-1行元素个数=3×(i-1)-1(因为第1行只有2个非零元素);        

第i行中aij前非零元素个数=j-i+1,其中

由此得到:

Loc[i,  j]=Loc[1, 1]+3(i-1)-1+j-i+1 =Loc[1, 1]+2(i-1)+j-1

4.稀疏矩阵

是指非零元比零元少得多,且非零元在矩阵中的分布不具有一定规律性的矩阵。

假设 m 行 n 列的矩阵含 t 个非零元素,则称

 

为稀疏因子。 通常认为 小于等于0.05 的矩阵为稀疏矩阵。

(1)稀疏矩阵的三元组表表示法

对于矩阵中的每个非零元,可以用三个属性来惟一确定:它所在的行、所在的例以及它的值。因此,可以用一个三元组(行, 列, 值)来惟一确定矩阵中的一个非零元。

   稀疏矩阵的三元组表表示法虽然节约了存储空间, 但比起矩阵正常的存储方式来讲,其实现相同操作要耗费较多的时间, 同时也增加了算法的难度, 即以耗费更多时间为代价来换取空间的节省。

#define MAXSIZE 1000   /*非零元素的个数最多为1000*/ typedef struct {int    row,   col;    /*该非零元素的行下标和列下标*/ElementType  e;   /*该非零元素的值*/ }Triple;  typedef struct {Triple   data[MAXSIZE+1];     /* 非零元素的三元组表,data[0]未用*/int      m,   n,   len;           /*矩阵的行数、 列数和非零元素的个数*/ 
}TSMatrix; 

 1) 用三元组表实现稀疏矩阵的转置运算        

下面首先以稀疏矩阵的转置运算为例,介绍采用三元组表时的实现方法。        

所谓的矩阵转置,是指变换元素的位置,把位于(row,col)位置上的元素换到(col,row)位置上,也就是说, 把元素的行列互换。

采用矩阵的正常存储方式时, 实现矩阵转置的经典算法如下:

void  TransMatrix(ElementType source[n][m],  ElementType dest[m][n])
{/*source和dest分别为被转置的矩阵和转置以后的矩阵(用二维数组表示)*/ int i,  j;  for(i=0; i<m; i++)for (j=0; j< n; j++) dest[i][ j]=source[j] [i] ; }

采用矩阵的三元组存储方式实现转置

① 矩阵source的三元组表A的行、 列互换就可以得到B中的元素

 ② 为了保证转置后的矩阵的三元组表B也是以“行序为主序”进行存放,则需要对行、列互换后的三元组表B按B的行下标(即A的列下标)大小重新排序

方法一:

 我们附设一个位置计数器j,用于指向当前转置后元素应放入三元组表B中的位置。 处理完一个元素后,j加1, j的初值为1。 具体转置算法如下:

Void  TransposeTSMatrix(TSMatrix A,  TSMatrix  *B)
{ /*把矩阵A转置到B所指向的矩阵中去, 矩阵用三元组表表示 */int  i ,  j,  k ;  B->m= A.n ;  B->n= A.m ;  B->len= A.len ; if(B->len>0){ 
j=1;  for(k=1;  k<=A.n;  k++)   for(i=1;  i<=A.len;  i++)  if(A.data[i].col==k)   {   B->data[j].row=A.data[i].col    B->data[j].col=A.data[i].row;     B->data[j].e=A.data[i].e;     j++;    }}
} 

  算法的时间耗费主要是在双重循环中,其时间复杂度为O(A.n×A.len), 最坏情况下,当A.len=A.m×A.n时,时间复杂度为O(A.m×A.n2)。采用正常方式实现矩阵转置的算法时间复杂度为O(A.m×A.n)。

方法二:

 为了能将待转置三元组表A中元素一次定位到三元组表B的正确位置上,需要预先计算以下数据:

  (1) 待转置矩阵source每一列中非零元素的个数(即转置后矩阵dest每一行中非零元素的个数)。

(2) 待转置矩阵source每一列中第一个非零元素在三元组表B中的正确位置(即转置后矩阵dest每一行中第一个非零元素在三元组B中的正确位置)。

 为此, 需要设两个数组num[ ]和position[ ],其中num[col]用来存放三元组表A中第col列中非零元素个数(三元组表B中第col行非零元素的个数),position[col]用来存放转置前三元组表A中第col列(转置后三元组表B中第col行)中第一个非零元素在三元组表B中的正确位置。

num[col]的计算方法: 将三元组表A扫描一遍,对于其中列号为k的元素,给相应的num[k]加1。

position[col]的计算方法: position[1]=1, position[col]=position[col-1]+num[col-1],  其中2≤col≤A.n。

  将三元组表A中所有的非零元素直接放到三元组表B中正确位置上的方法:        

position[col]的初值为三元组表A中第col列(三元组表B的第col行)中第一个非零元素的正确位置,当三元组表A中第col列有一个元素加入到三元组表B时,则position[col]=position[col]+1,即: 使position[col]始终指向三元组表A中第col列中下一个非零元素的正确位置。        

具体算法如下:

FastTransposeTSMatrix (TSMatrix  A,   TSMatrix * B)
{ /*基于矩阵的三元组表示, 采用快速转置法, 将矩阵A转置为B所指的矩阵*/
int col,  t,  p, q; 
int num[MAXSIZE],  position[MAXSIZE]; 
B->len=A.len;  B->n=A.m;  B->m=A.n; 
if(B->len){
for(col=1; col<=A.n; col++) num[col]=0;   for(t=1; t<=A.len; t++) num[A.data[t].col]++;   /*计算每一列的非零元素的个数*/position[1]=1; for(col=2; col<A.n; col++)   /*求col列中第一个非零元素在B.data[ ]中的正
确位置 */position[col]=position[col-1]+num[col-1];  for(p=1; p<A.len.p++) {  col=A.data[p].col;   q=position[col];  B->data[q].row=A.data[p].col;   B->data[q].col=A.data[p].row;   B->data[q].e=A.data[p].eposition[col]++;  } 
}
} 

 快速转置算法的时间主要耗费在四个并列的单循环上,这四个并列的单循环分别执行了A.n,A.len,A.n-1,A.len次,因而总的时间复杂度为O(A.n)+O(A.len)+O(A.n)+O(A.len),即为O(A.n+A.len)。 当待转置矩阵M中非零元素个数接近于A.m×A.n 时,其时间复杂度接近于经典算法的时间复杂度O(A.m×A.n)。          

快速转置算法在空间耗费上除了三元组表所占用的空间外,还需要两个辅助向量空间,即num[1..A.n],position[1..A.n]。可见,算法在时间上的节省,是以更多的存储空间为代价的。

(2)稀疏矩阵的链式存储结构: 十字链表

与用二维数组存储稀疏矩阵比较,用三元组表表示的稀疏矩阵节约了空间,但是在进行矩阵加法、减法和乘法等运算时,有时矩阵中的非零元素的位置和个数会发生很大的变化。如A=A+B, 将矩阵B加到矩阵A上,此时若还用三元组表表示法,势必会为了保持三元组表“以行序为主序”而大量移动元素。

在十字链表中,矩阵的每一个非零元素用一个结点表示, 该结点除了(row,col,value)以外,还要有以下两个链域:       

 right:  用于链接同一行中的下一个非零元素;        

down: 用于链接同一列中的下一个非零元素。

用两个一维的指针数组分别存放各行链表的头指针和各列链表的头指针,从而得到了矩阵的十字链表存储结构。

结构类型:

建十字链表的算法的时间复杂度为O(t×s),s=max(m,n)。

typedef struct OLNode{int                row,   col;           /* 非零元素的行和列下标 */ ElementType     value;  struct OLNode   * right, *down;   /* 非零元素所在行表、列表的后继链域 */}OLNode;  *OLink; typedef struct { OLink  * row-head,   *col-head;    /* 行、 列链表的头指针向量 */ int     m,   n,   len;                   /* 稀疏矩阵的行数、 列数、 非
零元素的个数 */}CrossList; CreateCrossList (CrossList * M){/* 采用十字链表存储结构, 创建稀疏矩阵M */if(M!=NULL) free(M); scanf(&m, &n, &t);    /* 输入M的行数, 列数和非零元素的个数 */M->m=m; M->n=n; M->len=t; If(!(M->row-head=(OLink * )malloc((m+1)sizeof(OLink)))) exit(OVERFLOW); If(!(M->col-head=(OLink * )malloc((n+1)sizeof(OLink)))) exit(OVERFLOW); M->row-head[ ]=M->col-head[ ]=NULL;/* 初始化行、 列头指针向量, 各行、 列链表为空的链表 */for(scanf(&i, &j, &e); i!=0;  scanf(&i, &j, &e)) { if(!(p=(OLNode *) malloc(sizeof(OLNode)))) exit(OVERFLOW);  p->row=i; p->col=j; p->value=e;    /* 生成结点 */ if(M->row-head[i]==NULL)   M->row-head[i]=p; 
else{  /* 寻找行表中的插入位置 */for(q=M->row-head[i];   q->right&&q->right->col<j;   q=q->right)p->right=q->right;  q->right=p;    /* 完成插入 */ } if(M->col-head[j]==NULL)   M->col-head[j]=p;  else{  /*寻找列表中的插入位置*/for(q=M->col-head[j];   q->down&&q->down->row<i;   q=q->down) p->down=q->down;  q->down=p;     /* 完成插入 */         }}} 

相关文章:

数据结构(超详细讲解!!)第二十一节 特殊矩阵的压缩存储

1.压缩存储的目标 值相同的元素只存储一次 压缩掉对零元的存储&#xff0c;只存储非零元 特殊形状矩阵&#xff1a; 是指非零元&#xff08;如值相同的元素&#xff09;或零元素分布具有一定规律性的矩阵。 如&#xff1a; 对称矩阵 上三角矩阵 下三角矩阵 对角矩阵 准…...

Python最强自动化神器Playwright!再也不用为爬虫逆向担忧了!

版权说明:本文禁止抄袭、转载,侵权必究! 目录 一、简介+使用场景二、环境部署(准备)三、代码生成器(优势)四、元素定位器(核心)五、追踪查看器(辅助)六、权限控制与认证(高级)七、其他重要功能(进阶)八、作者Info一、简介+使用场景 Playwright是什么?来自Chat…...

为什么 conda 不能升级 python 到 3.12

为什么 conda 不能升级 python 到 3.12 2023-11-05 23:33:29 ChrisZZ 1. 目的 弄清楚为什么执行了如下升级命令后&#xff0c; python 版本还是 3.11&#xff1f; conda update conda conda update python2. 原因 因为 conda forge 没有完成 migration Migration is the …...

0X02

web9 阐释一波密码&#xff0c;依然没有什么 发现&#xff0c;要不扫一下&#xff0c;或者看一看可不可以去爆破密码 就先扫了看看&#xff0c;发现robots.txt 访问看看,出现不允许被访问的目录 还是继续尝试访问看看 就可以下载源码&#xff0c;看看源码 <?php $fl…...

【手写数据库所需C语言基础】可变结构体,结构体成员计算,类型强制转换为统一类型,数据库中使用C语言方法和技巧

​专栏内容&#xff1a; 手写数据库toadb 本专栏主要介绍如何从零开发&#xff0c;开发的步骤&#xff0c;以及开发过程中的涉及的原理&#xff0c;遇到的问题等&#xff0c;让大家能跟上并且可以一起开发&#xff0c;让每个需要的人成为参与者。 本专栏会定期更新&#xff0c;…...

Android Studio(适配器Adapter)

认识适配器 在学完并且在做了一个自主项目后&#xff0c;我对适配器有了以下认识&#xff1a;1. 适配器的作用&#xff1a; 数据驱动的动态页面列表渲染&#xff0c;所以适配器主要就做了两件事&#xff1a;遍历数据&#xff0c;渲染页面&#xff08;列表项&#xff09;。比…...

携程AI布局:三重创新引领旅游行业智能化升级

2023年10月24日&#xff0c;携程全球合作伙伴峰会在新加坡召开&#xff0c;携程集团联合创始人、董事局主席梁建章做了名为《旅游业是独一无二的最好的行业》的演讲&#xff0c;梁建章在演讲中宣布了携程生成式 AI、内容榜单、ESG 低碳酒店标准三重创新的战略方向。这些创新将为…...

IOS手机耗电量测试

1. 耗电量原始测试方法 1.1 方法原理&#xff1a; 根据iPhone手机右上角的电池百分比变化来计算耗电量。 1.2实际操作&#xff1a; 在iOS通用设置中打开电池百分比数值显示&#xff0c;然后操作30分钟&#xff0c;60分钟&#xff0c;90分钟&#xff0c;看开始时和结束时电池…...

LeetCode.6 N字形变换

一开始想的是真的创建一个数组 去按照题目所给的要求填入数据 最后输出不为空的数组项 但是不仅时间复杂度高 而且错误频繁出现 最终也没有提交成功 查阅题解后发现数组并不重要 假设我们忽略掉数组中的那些空白项 最终输出的结果就是numRows行的字符串的拼接 string conver…...

BlockingQueue实现简易消息队列处理器 可分区顺序消费

...

第一章 03Java入门-编写第一个Java程序HelloWorld以及JVM、JDK和JRE概念

文章目录 前言一、下载和安装JDK二、第一个程序HelloWorld1.用记事本编写程序2.编译文件3.运行程序三、HelloWorld案例常见问题四、环境变量五、Notepad软件的安装和使用六、Java语言的发展七、Java为什么这么火八、JRE和JDK总结前言 上次我们学习了常见的CMD指令以及环境变量…...

windbg的常见调试命令

windbg的常见调试命令 1&#xff09;.break&#xff1a;在指定的条件下停止调试。 2&#xff09;.bt&#xff1a;显示调用堆栈信息。 3&#xff09;.catch&#xff1a;设置异常捕获&#xff0c;可以用来捕获程序中的异常并进行调试。 4&#xff09;.continue&#xff1a;继续执…...

VBA之正则表达式(44)-- 拆分商品和规格

实例需求&#xff1a;商品组清单保存在A列中&#xff0c;现需要将其拆分为商品名称&#xff0c;保存在从B列开始的后续单元格中&#xff0c;部分商品包含规格&#xff0c;并且多种规格属性使用了逗号分隔&#xff0c;因此无法直接使用Excel分列功能完成数据拆分。 示例代码如下…...

听GPT 讲Rust源代码--library/std(13)

题图来自 Decoding Rust: Everything You Need to Know About the Programming Language[1] File: rust/library/std/src/os/horizon/raw.rs 在Rust源代码中&#xff0c;rust/library/std/src/os/horizon/raw.rs这个文件的作用是为Rust的标准库提供与Horizon操作系统相关的原始…...

计算机视觉任务图像预处理之去除图像中的背景区域-------使用连通域分析算法(包含完整代码)

原理 通过连通域分析算法能够找到最大的连通域&#xff0c;即图片的主体部分&#xff0c;然后保存该连通域的最小外接矩阵&#xff0c;即可去除掉无关的背景区域 代码 使用连通域分析算法去除图像中的空白部分 并将图像变为统一大小的正方形 from skimage import measure imp…...

SurfaceFlinger的硬件Vsync深入分析-千里马android framework车机手机系统开发

背景&#xff1a; 学过或者你看过surfaceflinger相关文章同学都知道&#xff0c;vsync其实都是由surfaceflinger软件层面进行模拟的&#xff0c;但是软件模拟有可能会有误差或偏差&#xff0c;这个时候就需要有个硬件vsync帮忙校准。 故才会在surfaceflinger的systrace出现如下…...

力扣160. 相交链表

目录 1.解题思路2.代码实现 1.解题思路 首先分析&#xff0c;如果两个链表的长度不一&#xff0c;假设他们有交点&#xff0c;那么他们的最后一定是相同的&#xff0c;也即是后面为相同的部分&#xff0c;但前面不好说&#xff0c;而又因为长度不一又没法简便的一一对比&#…...

操作系统学习与思考

x86体系架构 x86是因特尔8086代芯片的CPU总线位数以及寄存器种类的规范&#xff0c;大部分操作系统都是以该规范作为基准来生产的 计算机组成 CPU&#xff0c;可以根据程序计数器进行取指令操作&#xff0c;并根据指令执行运算&#xff08;加、减、乘、除&#xff09;。运算所…...

C++笔记之动态数组的申请和手动实现一个简单的vector

C笔记之动态数组的申请和手动实现一个简单的vector code review! 文章目录 C笔记之动态数组的申请和手动实现一个简单的vector1.C语言中动态数组的申请与使用1.动态数组的申请使用new和delete使用std::vector 1.std::vector的底层实现2.手动实现一个简单的vector:使用一个指向…...

答题测评考试小程序的效果如何

在线答题系统是一种在线练习、考试、测评的智能答题系统&#xff0c;适用于企业培训、测评考试、知识竞赛、模拟考试等场景&#xff0c;管理员可任意组题、随机出题&#xff0c;答题者成功提交后&#xff0c;系统自动判分。 多种题目类型&#xff0c;两种答题模式 练习模式&a…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

聊一聊接口测试的意义有哪些?

目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开&#xff0c;首…...

Android第十三次面试总结(四大 组件基础)

Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成&#xff0c;用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机&#xff1a; ​onCreate()​​ ​调用时机​&#xff1a;Activity 首次创建时调用。​…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

省略号和可变参数模板

本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积

1.题目介绍 给定一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O…...

MySQL的pymysql操作

本章是MySQL的最后一章&#xff0c;MySQL到此完结&#xff0c;下一站Hadoop&#xff01;&#xff01;&#xff01; 这章很简单&#xff0c;完整代码在最后&#xff0c;详细讲解之前python课程里面也有&#xff0c;感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...

Monorepo架构: Nx Cloud 扩展能力与缓存加速

借助 Nx Cloud 实现项目协同与加速构建 1 &#xff09; 缓存工作原理分析 在了解了本地缓存和远程缓存之后&#xff0c;我们来探究缓存是如何工作的。以计算文件的哈希串为例&#xff0c;若后续运行任务时文件哈希串未变&#xff0c;系统会直接使用对应的输出和制品文件。 2 …...

怎么开发一个网络协议模块(C语言框架)之(六) ——通用对象池总结(核心)

+---------------------------+ | operEntryTbl[] | ← 操作对象池 (对象数组) +---------------------------+ | 0 | 1 | 2 | ... | N-1 | +---------------------------+↓ 初始化时全部加入 +------------------------+ +-------------------------+ | …...