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;
}
}