稀疏矩阵之基于数组形式实现COO和CSR
前言
- 语言:Java
- 环境:IntelliJ IDEA
- JDK版本:1.8
- 源码:GitHub
- 参考文章:https://www.cnblogs.com/xbinworld/p/4273506.html
什么是稀疏矩阵
在矩阵中,若数值为0的元素数目远远多于非0元素的数目,并且非0元素分布没有规律时,则称该矩阵为稀疏矩阵;与之相反,若非0元素数目占大多数时,则称该矩阵为稠密矩阵。定义非零元素的总数比上矩阵所有元素的总数为矩阵的稠密度。
例如下面的矩阵:
由于稀疏矩阵中重复的0非常多,因此在存储稀疏矩阵时非常有必要对其压缩,下面介绍了几种常用的稀疏矩阵存储格式。
存储格式
COO格式
COO(Coordinate):仅存储非0数据的行、列、值 这种存储方式虽然简单,但会存在行或列重复出现的情况,这些重复值也需要被压缩。
编码流程:
- 压缩
- 遍历矩阵,获取所有非0数据个数
- 记录原矩阵的总行数、总列数、非0数据个数
- 通过获取的非0数据个数初始化压缩后的数据行数,列数为3,分别为row、column、value
- 再次遍历矩阵,将非0数据所在的行、列、值进行存储
- 解压
- 用记录的总行数、总列数初始化原矩阵
- 用非0数据个数为循环次数逐行还原矩阵
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;
}
}
CSR/CSC格式
CSR(Compressed Sparse Row):仅存储非0数据的列、值、行偏移。行偏移是指每行的第一个非0数在值(value)中的位置
CSC(Compressed Sparse Column):仅存储非0数据的行、值、列偏移。列偏移是指每列的第一个非0数在值(value)中的位置
当使用COO时,若重复行过多,则可以使用CSR来忽略行;若重复列过多,则可以使用CSC忽略列
编码流程:
- 压缩
- 遍历矩阵,获取所有非0数据个数
- 记录原矩阵的总行数、总列数、非0数据个数
- 通过获取的非0数据个数初始化压缩后的数据行数,列数为2,分别为column、value
- 再次遍历矩阵,将非0数据所在的行、列、值进行存储
- 通过value计算出行偏移并存储
- 解压
- 用记录的总行数、总列数初始化原矩阵
- 用非0数据个数为循环次数逐行还原矩阵,还原同时需要通过记录的行偏移控制行
行偏移在解压该过程中可以理解为:忽略行的存在,顺序还原数据,行偏移的值就是当循环到这个值时进行换行
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;
}
}