一个实战来了解学习稀疏数组

一个实战来了解学习稀疏数组

这篇博客是本人在学习了B站尚硅谷韩老师的数据结构课程后所做,在此表示感谢!

稀疏数组

引入需求:编写的五子棋程序中,有存盘退出和续上盘的功能,我们可以使用二维数组记录棋盘,如下:
在这里插入图片描述
但是我们发现使用传统的二维数组,会存在大量的没有意义的无效数据,导致内存的浪费,针对如上的情况,我们可以使用稀疏数组来优化;

稀疏数组的基本介绍

当一个数组中的大部分元素为同一个值时(如上面的大部分元素都是0),我们可以使用稀疏数组来保存该数组;

稀疏数组的处理方法是:

  • 稀疏数组有三列,(有效值个数 + 1)行;
  • 稀疏数组的第一行开始从左至右分别记录原先的二维数组有几行、几列、原数组共有多少个有效值;
  • 稀疏数组从第二行开始从左至右记录依次每个有效值(有效值从左到右从上到下开始计)所在原数组的行号、列号、有效值;
    • 即将具有不同值的元素的行、列、值 记录在一个小规模的数组中,从而缩小程序的规模;

*来看一个例子:*原始的二维数组为:

000220015
011000170
000-6000
00000390
91000000
00280000

可以将该二维数组转换成稀疏数组:

行(row)列(col)值(value)
[0]678
[1]0322
[2]0615
[3]1111
[4]1517
[5]23-6
[6]3539
[7]4091
[8]5228

可以发现:原先的二维数组为:5 * 6,变成稀疏数组之后为 8 * 3,大大的减少了存储的消耗;

那么对上面棋盘问题使用的二维数组可以转换成如下的稀疏数组:

行(row)列(col)值(value)
[0]11112
[1]121
[2]232

可以发现,原先的二维数组为 11 * 11,但是此时的稀疏数组为 3 * 3,大大的减少了存储的消耗;

二维数组的转换成稀疏数组的思路:

  1. 遍历 原始的二维数组,得到有效数据的个数,记为sum
  2. 根据sum和 原始二维数组的行、列,就可以创建稀疏数组,记为sparseArray int[sum + 1][3]
  3. 将原始二维数组的有效数据按照存储稀疏数组的规则存入到稀疏数组中;
  4. 将稀疏数组持久化;

稀疏数组转成原始数组的思路:

  1. 读取持久化的稀疏数组;
  2. 先读取稀疏数组的第一行,根据第一行的数据(行数、列数、有效值个数)创建原始二维数组;
  3. 再从第二行开始读取(有效值的行号、有效值的列号、有效值),将每一个有效值赋给指定的 原始二维数组 的位置;
应用实例
  1. 使用稀疏数组,来保留类似前面的二维数组(如棋盘、地图等);

  2. 把稀疏数组存盘,并且可以重新恢复原来的二维数组;

项目编写

编写工具类

因为牵扯到保存和读取稀疏数组操作(序列化和反序列化操作),数组打印等代码,所以我们先来编写一个工具类Util,以使代码结构更加分明,如下:

package edu.hebeu;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * 这个类是工具类
 * @author 13651
 *
 */
public class Util {
	private static ObjectOutputStream oos = null; // 声明ObjectOutputStream对象
	private static ObjectInputStream ois = null; // 声明一个ObjectInputStream对象
	
	/**
	 * 这个方法用来进行序列化
	 * @param obj
	 */
	public static void toSerializable(Object obj) {
		try {
			oos = new ObjectOutputStream(new FileOutputStream("data")); // 创建ObjectOutputStream对象
			
			oos.writeObject(obj); // 序列化对象
			
			oos.flush(); // 使用完输出流之后要刷新,清空管道
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if(oos != null) {
				try {
					oos.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}				
			}
		}
	}
	
	/**
	 * 这个方法用来进行反序列化
	 * @return
	 */
	public static Object toDeSerializable() {
		try {
			ois = new ObjectInputStream(new FileInputStream("data"));
			Object obj = ois.readObject(); // 进行反序列化
			return obj;
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if(ois != null) {
				try {
					ois.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		return null;
	}
	
	/**
	 * 这个方法用来打印二维数组
	 * @param chessboard
	 */
	public static void show(int[][] array) {
		for (int[] row : array) {
			for (int data : row) {
				System.out.printf("%d\t", data);
			}
			System.out.println();
		}
	}

}

棋盘类的编写(二维数组相关的类)

该类作为二维数组,应该具有初始化数组、更改数组值(下棋)、将反序列化的稀疏数组转换成二维数组等的方法,创建Chessboard类如下所示:

package edu.hebeu;

/**
 * 这个类作为棋盘,即原始的二维数组
 * @author 13651
 *
 */
public class Chessboard {
	
	/**
	 * 这个方法用来初始化 11 * 11的棋盘,即初始化二维数组
	 */
	public int[][] initChessboard() {
		return new int[11][11];
	}
	
	/**
	 * 这个方法表示下旗
	 * @param chessboard 棋盘
	 * @param rowPosition 下的行位置
	 * @param colPosition 下的列位置
	 * @param isRed 是否为红旗,true--红,false--黑
	 */
	public void playChess(int[][] chessboard, int rowPosition, int colPosition, boolean isRed) {
		if (isRed) {
			chessboard[rowPosition][colPosition] = 1; // 下1,表示红旗
		} else {
			chessboard[rowPosition][colPosition] = 2; // 下2,表示黑棋
		}
	}
	
	/**
	 * 这个方法用来加载稀疏数组序列化后存储的文件,并将该稀疏数组变成二维数组
	  	1. 读取持久化的二维数组;
		2. 先读取稀疏数组的第一行,根据第一行的数据(行数、列数、有效值个数)创建原始二维数组;
		3. 再从第二行开始读取(有效值的行号、有效值的列号、有效值),将每一个有效值赋给指定的 原始二维数组 的位置;
	 * @return
	 */
	public int[][] loadChess() {
		/*1.加载文件获取存储的稀疏数组*/
		int[][] sparseArray = (int[][]) Util.toDeSerializable();
		/*2. 根据稀疏数组的第一行初始化二维数组*/
		int[][] chessboard = new int[sparseArray[0][0]][sparseArray[0][1]]; // 动态初始化稀疏数组
		/*3. 再从第二行开始读取(有效值的行号、有效值的列号、有效值),将每一个有效值赋给指定的 原始二维数组 的位置;*/
		for (int i = 1; i < sparseArray.length; i++) { // 从第二行开始
			chessboard[sparseArray[i][0]][sparseArray[i][1]] = sparseArray[i][2];
		}
		
		return chessboard;
	}

}

保存棋盘状态类的编写(稀疏数组相关的类)

该类应该具有能够将普通的二维数组转换成稀疏数组、能够将稀疏数组序列化到文件的能力,如创建SparseArray类下所示:

package edu.hebeu;

/**
 * 该类使用稀疏数组来保存棋盘(二维数组)的状态
 * @author 13651
 *
 */
public class SparseArray {
	
	/**
	 * 该方法用来通过二维数组初始化一个稀疏数组
	 * 二维数组的转换成稀疏数组的思路:
		1. 遍历 原始的二维数组,得到有效数据的个数,记为sum;
		2. 根据sum和 原始二维数组的行、列,就可以创建稀疏数组,记为sparseArray int[sum + 1][3];
		3. 将原始二维数组的有效数据按照存储稀疏数组的规则存入到稀疏数组中;
		4. 将稀疏数组持久化;
	 * @param effectiveValueNum 有效值的个数
	 * @param chessboard 棋盘(原始的二维数组)
	 */
	public int[][] initSparseArray(int[][] chessboard) {
		
		/*1、计算原始二维数组的有效值个数*/
		int effectiveValueNum = 0; // 用来接收保存有效值的个数的变量
		for (int[] row : chessboard) {
			for(int data : row) {
				if(data != 0) { // 如果是有效值
					effectiveValueNum++; // 将原始二维数组的有效值个数+1
				}
			}
		}
		/*2、根据有效值的个数初始化一个稀疏数组*/
		int[][] sparesArray = new int[effectiveValueNum + 1][3];
		sparesArray[0][0] = chessboard.length; // 将第一行,第一列的元素设置为原始二维数组的行数
		sparesArray[0][1] = chessboard[0].length; // 将第一行,第二列的元素设置为原始数组的列数
		sparesArray[0][2] = effectiveValueNum; // 将第一行,第三列的元素设置为原始数组的有效值的个数
		/*3、通过原始的二维数组的有效值存入稀疏数组*/
		int sparseRow = 1; // 记录稀疏数组的行号,从1开始(即从第二行开始记录)
		for (int k = 0; k < chessboard.length; k++) {
			for (int m = 0; m < chessboard[k].length; m++) {
				if(chessboard[k][m] != 0) { // 如果该位置的元素的值不为0(即是有效值)
//					System.out.println("---, sparseRow = " + sparseRow + ", chessboard[k][m] = " + chessboard[k][m]);
					sparesArray[sparseRow][0] = k; // 将第一列的元素设置为原始数组有效值的行号
					sparesArray[sparseRow][1] = m; // 将第二列的元素设置为原始数组有效值的列号
					sparesArray[sparseRow][2] = chessboard[k][m]; // 将第三列的元素设置为原始数组的有效值
					sparseRow++; // 将稀疏数组的行号+1
				}
			}
		}
		/*4、将稀疏数组持久化*/
		Util.toSerializable(sparesArray);
		
		return sparesArray;
	}
	
}

测试类的编写

测试类主要的作用是能够下棋(改变普通二维数组的值),并且在退出后将当前的二维数组转换成稀疏数组,再序列化到指定的文件中;或者在下一次进入该程序时能够读取文件将其反序列化成稀疏数组,再将该稀疏数组转换成普通的二维数组,以实现上次状态的恢复,创建Test类如下所示:

package edu.hebeu;

import java.util.Scanner;

/**
 * 
 * @author 13651
 *
 */
public class Test {
	
	private static boolean IS_PLAY = true;
	
	private static Scanner SCANNER = new Scanner(System.in);
	
	public static void main(String[] args) {
		// 创建和设置二维数组
		Chessboard c = new Chessboard();
		int[][] initChessBoard = c.initChessboard(); // 初始化一个二维数组
		
		System.out.print("加载存档文件(读取序列化的稀疏数组,并将该数组转换成二维数组)1-确认,其他-开始新游戏:");
		String load = SCANNER.nextLine();
		if(load.equals("1")) {
			System.out.println("加载中...");
			initChessBoard = c.loadChess();
			System.out.println("加载中成功!");
		}
		
		while(IS_PLAY) {
			System.out.print("红棋/白棋(红--true;黑--false):");boolean isRed = SCANNER.nextBoolean();
			System.out.print("请输入所在的行(0-10以内):");int positionRow = SCANNER.nextInt();
			System.out.print("请输入所在的列(0-10以内):");int positionCol = SCANNER.nextInt();
			c.playChess(initChessBoard, positionRow, positionCol, isRed);
			
			System.out.print("是否退出?(1-退出,其他-继续):");String exit = SCANNER.next();
			if(exit.equals("1")) {
				if (SCANNER != null) {
					SCANNER.close();
				}
				IS_PLAY = false;
			}
		}
		
		System.out.println("棋盘(原始二维数组):");
		Util.show(initChessBoard);
		
		// 根据原始二维数组和该数组有效值的个数创建稀疏数组
		SparseArray sa = new SparseArray();
		int[][] sparseArray = sa.initSparseArray(initChessBoard);
		System.out.println("稀疏数组:");
		Util.show(sparseArray);
	}

}

测试

首先我们先进入程序开始下棋(改变二维数组),改变若干值之后在退出,如下所示:
在这里插入图片描述
退出程序后,我们会发现该项目的根路径下多出了一个名为data的文件,这就是上面的稀疏数组持久化后的文件(里面是乱码的),如下图所示:
在这里插入图片描述
我们此时再此进入程序,并且选择加载存档文件,会发现得到了上次的棋盘(普通的二维数组)和稀疏数组,如下图:
在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值