目录
1.稀疏矩阵介绍
2.COO三元组形式存储
3.CSR/CSC形式存储
1.稀疏矩阵介绍
当一个矩阵中大部分元素为0时(如棋盘),则称之为稀疏矩阵,一般情况下稀疏矩阵非零元素的总数比上矩阵所有元素总数的值小于等于0.05,该比值称为矩阵的稠密度。对于稀疏矩阵而言,重复的0非常多,使用同样的内存来存储这个矩阵显然是对内存的浪费,因此在存储稀疏矩阵时非常有必要对其压缩,我们完全可以将矩阵中所有的0元素或不相关元素剔除:
2.COO三元组形式存储
1.记录矩阵一共有几行几列,有多少个不同的值;
2.仅存储非0数据的行、列、值。
压缩流程:
1.遍历矩阵,获取所有非0数据个数
2.记录原矩阵的总行数、总列数、非0数据个数
3.通过获取的非0数据个数初始化压缩后的数据行数,列数为3,分别为row、column、value
4.再次遍历矩阵,将非0数据所在的行、列、值进行存储
解压流程:
1.用记录的总行数、总列数初始化原矩阵
2.用非0数据个数为循环次数逐行还原矩阵
数据结构(java):
public class COO {
private int rows; //保存原矩阵总行数
private int columns; //保存原矩阵总列数
private int sum; //保存总有效数据个数
private int[][] data; //压缩后的数据
//省略get和set方法
}
public class COOUtils {
/**
* 通过COO将原矩阵压缩,并返回压缩后数据
*/
public static COO process(int rows,int columns,int[][] source){
COO coo = new COO(); //要返回的数据
coo.setRows(rows);
coo.setColumns(columns);
int count = 0;
for (int i = 0;i<rows;i++){ //遍历,获取所有有效数据个数
for (int j = 0;j<columns;j++){
if(source[i][j]!=0){
count++;
}
}
}
coo.setSum(count);
int[][] data = new int[coo.getSum()][3];//初始化压缩后的数据
int c = 0; //移动data的行
for (int i = 0;i<rows;i++){
for (int j = 0;j<columns;j++){
if(source[i][j]!=0){
data[c][0] = i; //为row列赋值
data[c][1] = j; //为column列赋值
data[c][2] = source[i][j]; //为value列赋值
c++;
}
}
}
coo.setData(data);
return coo;
}
/**
* 将COO压缩后的数据解压为原数据
*/
public static int[][] restore(COO coo){
int[][] source = new int[coo.getRows()][coo.getColumns()];
for (int[] row:coo.getData()){
source[row[0]][row[1]] = row[2];
}
return source;
}
/**
* 将COO压缩后的数据格式化显示
*/
public static String formitData(COO coo){
int[][] data = coo.getData();
String str = "row\tcolumn\tvalue\n";
for (int[] row:data){
str += row[0]+"\t"+row[1]+"\t"+row[2]+"\n";
}
return str;
}
/**
* 将原矩阵格式化显示
*/
public static String formitSouce(int rows,int columns,int[][] source){
String str = "";
for (int i = 0;i<rows;i++){
for (int j = 0;j<columns;j++){
str += source[i][j]+"\t";
}
str += "\n";
}
return str;
}
}
3.CSR/CSC形式存储
上面的三元组存储方式中,会存在行或列重复出现的情况,这些重复值也可以被压缩。
CSR(Compressed Sparse Row):仅存储非0数据的列、值、行偏移。行偏移是指每行的第一个非0数在值(value)中的位置。
CSC(Compressed Sparse Column):仅存储非0数据的行、值、列偏移。列偏移是指每列的第一个非0数在值(value)中的位置。
当使用COO时,若重复行过多,则可以使用CSR来忽略行;若重复列过多,则可以使用CSC忽略列。以CSR为例:
对上图的解释是,将所有非0数一字顺序排开,编号下标0~9,在排开的一字阵中,对照着原矩阵,找出需要换行处的下标放入数组中:[0,2,3,4,5]
,如下标0
处开始为第一行,第一行有非零数1、5;下标2
处开始为第二行,有非零数3;以此类推。
压缩流程:
1,遍历矩阵,获取所有非0数据个数
2.记录原矩阵的总行数、总列数、非0数据个数
3.通过获取的非0数据个数初始化压缩后的数据行数,列数为2,分别为column、value
4.再次遍历矩阵,将非0数据所在的行、列、值进行存储
5.通过value计算出行偏移并存储
解压流程:
1.用记录的总行数、总列数初始化原矩阵
2.用非0数据个数为循环次数逐行还原矩阵,还原同时需要通过记录的行偏移控制行
数据结构(java):
public class CSR {
private int rows; //保存原矩阵总行数
private int columns; //保存原矩阵总列数
private int sum; //保存总有效数据个数
private int[] rowOffset; //保存行偏移
private int[][] data; //保存列、值
//get、set方法省略
}
public class CSRUtils {
/**
* 通过CSR将原矩阵压缩,并返回压缩后数据
*/
public static CSR process(int rows,int columns,int[][] source){
CSR csr = new CSR(); //要返回的数据
csr.setRows(rows);
csr.setColumns(columns);
int count = 0;
for (int i = 0;i<rows;i++){ //遍历,获取所有有效数据个数
for (int j = 0;j<columns;j++){
if(source[i][j]!=0){
count++;
}
}
}
csr.setSum(count);
int[] rowOffset = new int[csr.getRows()];//存储行偏移
int[][] data = new int[csr.getSum()][2];//初始化压缩后的数据
boolean isFirst = false; //标记每个第一个数
int f = 0; //移动first数组的索引
int[] first = new int[csr.getRows()]; //存储每行的第一个数值
int[] valueOrder = new int[csr.getSum()]; //存储所有有效数据的顺序值
int c = 0; //移动data的行
for (int i = 0;i<rows;i++){
for (int j = 0;j<columns;j++){
if(source[i][j]!=0){
data[c][0] = j; //为column列赋值
data[c][1] = source[i][j]; //为value列赋值
valueOrder[c] = source[i][j];
c++;
if(!isFirst){ //为每行第一个数赋值
first[f] = source[i][j];
isFirst = true;
}
}
}
f++;
isFirst = false;
}
for (int i = 0;i<csr.getRows();i++){ //计算行偏移
rowOffset[i] = getFirstIndex(first[i],valueOrder);
valueOrder[rowOffset[i]] = 0;
}
csr.setData(data);
csr.setRowOffset(rowOffset);
return csr;
}
/**
* 将CSR压缩后的数据解压为原数据
*/
public static int[][] restore(CSR csr){
int[][] source = new int[csr.getRows()][csr.getColumns()];
int[][] data = csr.getData();
int row = -1; //标记当前行
int j = 0; //移动行偏移数组
int[] rowOffset = csr.getRowOffset();
int nowOffset = rowOffset[j]; //获取当前行偏移量
for(int i = 0;i<csr.getSum();i++){
if(nowOffset == i){ //当行偏移量等于非0数值在value中所在位置时
if(j!=csr.getRows()-1){ //并且行偏移数组索引不能超过总行数
j++;
nowOffset = rowOffset[j]; //当前偏移量增加
}
row++; //当前行增加
}
source[row][data[i][0]] = data[i][1];
}
return source;
}
/**
* 将CSR压缩后的数据格式化显示
*/
public static String formitData(CSR csr){
int[][] data = csr.getData();
int [] rowOffset =csr.getRowOffset();
String str = "row\tcolumn\tvalue\n";
for (int i = 0;i<csr.getSum();i++){
if(i<rowOffset.length){
str += rowOffset[i]+"\t";
}else{
str += "\t";
}
str += data[i][0]+"\t"+data[i][1]+"\n";
}
return str;
}
/**
* 将原矩阵格式化显示
*/
public static String formitSouce(int rows,int columns,int[][] source){
String str = "";
for (int i = 0;i<rows;i++){
for (int j = 0;j<columns;j++){
str += source[i][j]+"\t";
}
str += "\n";
}
return str;
}
/**
* 查找target在数组arr的第一次出现的位置
*/
private static int getFirstIndex(int target,int[] arr){
for (int i = 0;i<arr.length;i++){
if (target == arr[i]){
return i;
}
}
return -1;
}
}