稀疏矩阵之基于数组形式实现COO和CSR

稀疏矩阵之基于数组形式实现COO和CSR

前言

什么是稀疏矩阵

在矩阵中,若数值为0的元素数目远远多于非0元素的数目,并且非0元素分布没有规律时,则称该矩阵为稀疏矩阵;与之相反,若非0元素数目占大多数时,则称该矩阵为稠密矩阵。定义非零元素的总数比上矩阵所有元素的总数为矩阵的稠密度。

例如下面的矩阵:

稀疏矩阵

由于稀疏矩阵中重复的0非常多,因此在存储稀疏矩阵时非常有必要对其压缩,下面介绍了几种常用的稀疏矩阵存储格式。

存储格式

COO格式

COO(Coordinate):仅存储非0数据的行、列、值 这种存储方式虽然简单,但会存在行或列重复出现的情况,这些重复值也需要被压缩。

COO

编码流程:

  • 压缩
    1. 遍历矩阵,获取所有非0数据个数
    2. 记录原矩阵的总行数、总列数、非0数据个数
    3. 通过获取的非0数据个数初始化压缩后的数据行数,列数为3,分别为row、column、value
    4. 再次遍历矩阵,将非0数据所在的行、列、值进行存储
  • 解压
    1. 用记录的总行数、总列数初始化原矩阵
    2. 用非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)中的位置

CSR&CSC

当使用COO时,若重复行过多,则可以使用CSR来忽略行;若重复列过多,则可以使用CSC忽略列

编码流程:

  • 压缩
    1. 遍历矩阵,获取所有非0数据个数
    2. 记录原矩阵的总行数、总列数、非0数据个数
    3. 通过获取的非0数据个数初始化压缩后的数据行数,列数为2,分别为column、value
    4. 再次遍历矩阵,将非0数据所在的行、列、值进行存储
    5. 通过value计算出行偏移并存储
  • 解压
    1. 用记录的总行数、总列数初始化原矩阵
    2. 用非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;
    }
}

转载于:https://my.oschina.net/rawlins/blog/3101192

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值