定义:一个无效数据量(0)远大于有效数据量的二维数组被称为稀疏数组。
对于一个稀疏数组,我们可以通过特定的方式对其进行压缩,以节省存储空间。
例如:
0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0
0 0 0 2 0 0 0 0 0 0 0
0 0 0 0 0 2 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
就可以转换成如下的形式:
11 11 3
1 2 1
2 3 2
3 5 2
在上面的数组中,第一行的三个数依次表示原稀疏数组的【行数】,【列数】和【非零值数量】,从第二行开始,每行的三个数依次表示原稀疏数组中非零值的【所在行】,【所在列】和【值】。
以下是对稀疏数组压缩的一个实现:
import java.io.*;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author JedeWang
* @Version 1.0
* @Date 2022/2/26-20:16
* @Since jdk1.8
* @Description
*/
public class SparseArray {
/**
* 将一个稀疏数组压缩
*
* @param array 要压缩的稀疏数组
* @return 压缩后的数组
*/
public int[][] compress(int[][] array) {
AtomicInteger sum = new AtomicInteger();
traverse2dArray(array, (i, j, ele) -> {
if (ele != 0) {
sum.getAndIncrement();
}
}
);
int[][] result = new int[sum.get() + 1][3];
result[0][0] = array.length;
result[0][1] = array[0].length;
result[0][2] = sum.get();
AtomicInteger count = new AtomicInteger(1);
traverse2dArray(array, (i, j, ele) -> {
if (ele != 0) {
result[count.get()][0] = i;
result[count.get()][1] = j;
result[count.get()][2] = ele;
count.getAndIncrement();
}
}
);
return result;
}
/**
* 将压缩数组还原为稀疏数组
*
* @param array 压缩数组
* @return 还原后的稀疏数组
*/
public int[][] uncompress(int[][] array) {
int row = array[0][0];
int column = array[0][1];
//创建稀疏数组
int[][] result = new int[row][column];
//遍历压缩数组,依次取每一行的3个值在稀疏数组中给对应位置赋值
for (int i = 1; i < array.length; i++) {
result[array[i][0]][array[i][1]] = array[i][2];
}
return result;
}
/**
* 传入二维数组的持久化
*
* @param array 二维数组
*/
public void persist(int[][] array) {
File file = new File("map.data");
try {
file.createNewFile();
FileWriter fw = new FileWriter(file);
BufferedWriter bw = new BufferedWriter(fw);
traverse2dArray(array, (i, j, ele) -> {
try {
//写入每个元素的值,每写到一组内循环结束(j == array[0].length - 1)就换行
bw.write(ele);
if (j == array[0].length - 1) {
bw.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
);
//必须close,否则无法真正写入文件
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 从文件中恢复压缩数组
*
* @return 恢复后的压缩数组
*/
public int[][] antiPersist() {
File file = new File("map.data");
int[][] result = new int[0][0];
try {
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
String content;
if ((content = br.readLine()) != null) {
result = new int[(int) content.charAt(2) + 1][3];
result[0][0] = content.charAt(0);
result[0][1] = content.charAt(1);
result[0][2] = content.charAt(2);
}
int count = 1;
while ((content = br.readLine()) != null) {
result[count][0] = content.charAt(0);
result[count][1] = content.charAt(1);
result[count++][2] = content.charAt(2);
}
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
/**
* 通用的二维数组遍历,遍历传入的二维数组,并对里面每个元素的行/列/值执行传入的方法
*
* @param array 传入二维数组
* @param action 需要对二维数组的每个元素的行/列/值进行的操作
*/
public static void traverse2dArray(int[][] array, TriConsumer<Integer, Integer, Integer> action) {
Objects.requireNonNull(action);
int row = array.length;
int column = array[0].length;
for (int i = 0; i < row; i++) {
for (int j = 0; j < column; j++) {
action.accept(i, j, array[i][j]);
}
}
}
/**
* 在控制台打印二维数组
*
* @param array 要打印的二维数组
*/
public void show2dArray(int[][] array) {
traverse2dArray(array, (i, j, ele) -> {
System.out.print(ele + "\t");
if (j == array[0].length - 1) {
System.out.println();
}
});
}
public static void main(String[] args) {
SparseArray sparseArray = new SparseArray();
//11行11列的棋盘 1表示黑子 2表示白子
int[][] sparseChessBoard = new int[11][11];
sparseChessBoard[1][2] = 1;
sparseChessBoard[2][3] = 2;
sparseChessBoard[3][5] = 2;
System.out.println("稀疏数组:");
sparseArray.show2dArray(sparseChessBoard);
System.out.println("压缩成压缩数组:");
int[][] compressedChessBoard = sparseArray.compress(sparseChessBoard);
sparseArray.show2dArray(compressedChessBoard);
//持久化存储
sparseArray.persist(compressedChessBoard);
//恢复
System.out.println("持久化又反持久化的压缩数组:");
int[][] antiPersistedCompressedChessBoard = sparseArray.antiPersist();
sparseArray.show2dArray(antiPersistedCompressedChessBoard);
System.out.println("解压后的原稀疏数组");
int[][] uncompressedChessBoard = sparseArray.uncompress(antiPersistedCompressedChessBoard);
sparseArray.show2dArray(uncompressedChessBoard);
}
}
其中compress()实现了对稀疏数组的压缩,persist()将其持久化为磁盘文件,而antiPersist()用于从磁盘读取数据文件并恢复成压缩数组,uncompress()将压缩数组还原成稀疏数组。
至于traverse2dArray(),其实是我掺的一点私货,本质上就是对二维数组的遍历,只不过个人想用一下函数式接口,这个不用理解也没关系,可以代换成两层for循环。