稀疏 sparsearray 数组
基本介绍
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。 稀疏数组的处理方法是:
记录数组一共有几行几列,有多少个不同的值把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
为什么要使用稀疏数组?
稀疏数组可以简单的看作为是压缩,在开发中也会使用到。比如将数据序列化到磁盘上,减少数据量,在IO过程中提高效率等等。由于稀疏矩阵中存在大量的“空”值,占据了大量的存储空间,而真正有用的数据却少之又少,且在计算时浪费资源,所以要进行压缩存储以节省存储空间和计算方便。
举例说明
右边稀疏数组的翻译: [0]第1行:二维数组一共有6行7列,8个非0数字 [1]第2行:第一个数在二维数组的第1行第4列 (0,3),值为22 [2]第3行:第2个数字在二维数组的第1行第7列(0,6)值为15
应用实例
使用稀疏数组,来保留类似前面的二维数组(棋盘、地图等等)把稀疏数组存盘,并且可以从新恢复原来的二维数组数整体思路分析
二维数组转稀疏数组的思路
遍历原始的二维数组,得到有效数据的个数sum根据sum就可以创建稀疏数组spraseArr int[sum+1][3]将二维数组的有效数据导入到稀疏数组
稀疏数组转原始的二维数组的思路
先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组再读取稀疏数组后几行的数据,并赋给原始的二维数组即可。
注意
数组下标加一稀疏数组固定3列
代码实现
import java
.io
.*
;
import java
.io
.OutputStreamWriter
;
import java
.io
.InputStreamReader
;
public class SparseArray {
public static void main(String
[] args
) {
int chessArr1
[][] = new int[11][11];
chessArr1
[1][2] = 1;
chessArr1
[2][3] = 2;
chessArr1
[4][5] = 2;
System
.out
.println("原始的二维数组~~");
for (int[] row
: chessArr1
) {
for (int data
: row
) {
System
.out
.printf("%d\t", data
);
}
System
.out
.println();
}
System
.out
.println("-----------------------------------------------");
int sum
= 0;
for (int i
= 0; i
< chessArr1
.length
; i
++) {
for (int j
= 0; j
< chessArr1
[i
].length
; j
++) {
if (chessArr1
[i
][j
] != 0) {
sum
++;
}
}
}
int[][] sparseArr
= new int[sum
+ 1][3];
sparseArr
[0][0] = chessArr1
.length
;
sparseArr
[0][1] = chessArr1
[0].length
;
sparseArr
[0][2] = sum
;
int count
= 0;
for (int i
= 0; i
< chessArr1
.length
; i
++) {
for (int j
= 0; j
< chessArr1
[i
].length
; j
++) {
if (chessArr1
[i
][j
] != 0) {
count
++;
sparseArr
[count
][0] = i
;
sparseArr
[count
][1] = j
;
sparseArr
[count
][2] = chessArr1
[i
][j
];
}
}
}
System
.out
.println("稀疏数组为:");
for (int i
= 0; i
< sparseArr
.length
; i
++) {
System
.out
.printf("%d\t%d\t%d\t\n", sparseArr
[i
][0], sparseArr
[i
][1], sparseArr
[i
][2]);
}
int[][] chessArr2
= new int[sparseArr
[0][0]][sparseArr
[0][1]];
for (int i
= 1; i
< sparseArr
.length
; i
++) {
chessArr2
[sparseArr
[i
][0]][sparseArr
[i
][1]] = sparseArr
[i
][2];
}
System
.out
.println("-----------------------------------------------");
System
.out
.println("恢复的原始二维数组为:");
for (int[] row
: chessArr2
) {
for (int data
: row
) {
System
.out
.printf("%d\t", data
);
}
System
.out
.println();
}
System
.out
.println("-----------------------------------------------");
System
.out
.println("-----------------------------------------------");
try {
System
.out
.println("将稀疏数组保存到磁盘并命名为map.data");
File f
= new File("D:\\map.data");
FileOutputStream fos
= new FileOutputStream(f
);
OutputStreamWriter osw
= new OutputStreamWriter(fos
, "UTF-8");
System
.out
.println("写入中----------");
for (int i
= 0; i
< sparseArr
.length
; i
++) {
osw
.write(sparseArr
[i
][0] + "," + sparseArr
[i
][1] + "," + sparseArr
[i
][2] + ",");
}
osw
.close();
fos
.close();
System
.out
.println("写入磁盘成功");
System
.out
.println("读取中----------");
FileInputStream fis
= new FileInputStream(f
);
InputStreamReader isr
= new InputStreamReader(fis
, "UTF-8");
StringBuffer sb
= new StringBuffer();
while (isr
.ready()) {
sb
.append((char) isr
.read());
}
isr
.close();
fis
.close();
System
.out
.println("读取成功");
String ss
= sb
.toString();
String
[] sb1
= sb
.toString().split(",");
System
.out
.printf("从磁盘读取的字符串为:\n%s\n", ss
);
int sum1
= 0;
int[][] sparseArr1
= new int[sb1
.length
/ 3][3];
sparseArr1
[0][0] = Integer
.parseInt(sb1
[0]);
sparseArr1
[0][1] = Integer
.parseInt(sb1
[1]);
sparseArr1
[0][2] = Integer
.parseInt(sb1
[2]);
for (int i
= 3; i
< sb1
.length
; i
+= 3) {
sum1
++;
sparseArr1
[sum1
][0] = Integer
.parseInt(sb1
[i
]);
sparseArr1
[sum1
][1] = Integer
.parseInt(sb1
[i
+ 1]);
sparseArr1
[sum1
][2] = Integer
.parseInt(sb1
[i
+ 2]);
}
System
.out
.println("还原后的稀疏数组为:");
for (int i
= 0; i
< sparseArr1
.length
; i
++) {
System
.out
.printf("%d\t%d\t%d\n", sparseArr1
[i
][0], sparseArr1
[i
][1], sparseArr1
[i
][2]);
}
int[][] chessArr3
= new int[sparseArr1
[0][0]][sparseArr1
[0][1]];
for (int i
= 1; i
< sparseArr1
.length
; i
++) {
chessArr3
[sparseArr1
[i
][0]][sparseArr1
[i
][1]] = sparseArr1
[i
][2];
}
System
.out
.println("还原后的二维数组为:");
for (int[] a
: chessArr3
) {
for (int b
: a
) {
System
.out
.printf("%d\t", b
);
}
System
.out
.println();
}
} catch (IOException e
) {
e
.printStackTrace();
}
}
}
运行结果
原始的二维数组
~~
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 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
-----------------------------------------------
稀疏数组为:
11 11 3
1 2 1
2 3 2
4 5 2
-----------------------------------------------
恢复的原始二维数组为:
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 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
-----------------------------------------------
-----------------------------------------------
将稀疏数组保存到磁盘并命名为map
.data
写入中
----------
写入磁盘成功
读取中
----------
读取成功
从磁盘读取的字符串为:
11,11,3,1,2,1,2,3,2,4,5,2,
还原后的稀疏数组为:
11 11 3
1 2 1
2 3 2
4 5 2
还原后的二维数组为
:
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 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
队列
队列介绍
队列是一个有序列表,可以用数组或是链表来实现。循先入先出的原则:先存入队列的数据,要先取出。后存入的要后取出rear 是队列最后[含]front 是队列最前元素[不含]示意图:(使用数组模拟队列示意图)
数组模拟顺序队列思路
队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图, 其中 maxSize 是该队列的最大容量。队列的输出、输入是分别从前后端来处理,因此需要两个变量 front 及 rear 分别记录队列前后端的下标, front(头部输出)随着数据输出而改变,而 rear (尾部放入)着数据输入而改变,如图所示:.
入队
当我们将数据存入队列时称为”addQueue”,addQueue 的处理需要有两个步骤:思路分析 1 将尾指针往后移:rear+1 , 当front == rear 【空】
2 若尾指针 rear 小于队列的最大下标 maxSize-1,则将数据存入 rear 所指的数组元素中,否则无法存入数据。 rear == maxSize - 1[队列满]
代码实现
public class ArrayQueueDemo {
public static void main(String
[] args
) {
ArrayQueue queue
= new ArrayQueue(3);
char key
= ' ';
Scanner scanner
= new Scanner(System
.in
);
boolean loop
= true;
while(loop
) {
System
.out
.println("s(show): 显示队列");
System
.out
.println("e(exit): 退出程序");
System
.out
.println("a(add): 添加数据到队列");
System
.out
.println("g(get): 从队列取出数据");
System
.out
.println("h(head): 查看队列头的数据");
key
= scanner
.next().charAt(0);
switch (key
) {
case 's':
queue
.showQueue();
break;
case 'a':
System
.out
.println("输出一个数");
int value
= scanner
.nextInt();
queue
.addQueue(value
);
break;
case 'g':
try {
int res
= queue
.getQueue();
System
.out
.printf("取出的数据是%d\n", res
);
} catch (Exception e
) {
System
.out
.println(e
.getMessage());
}
break;
case 'h':
try {
int res
= queue
.headQueue();
System
.out
.printf("队列头的数据是%d\n", res
);
} catch (Exception e
) {
System
.out
.println(e
.getMessage());
}
break;
case 'e':
scanner
.close();
loop
= false;
break;
default:
break;
}
}
System
.out
.println("程序退出~~");
}
}
class ArrayQueue {
private int maxSize
;
private int front
;
private int rear
;
private int[] arr
;
public ArrayQueue(int arrMaxSize
) {
maxSize
= arrMaxSize
;
arr
= new int[maxSize
];
front
= -1;
rear
= -1;
}
public boolean isFull() {
return rear
== maxSize
- 1;
}
public boolean isEmpty() {
return rear
== front
;
}
public void addQueue(int n
) {
if (isFull()) {
System
.out
.println("队列满,不能加入数据~");
return;
}
rear
++;
arr
[rear
] = n
;
}
public int getQueue() {
if (isEmpty()) {
throw new RuntimeException("队列空,不能取数据");
}
front
++;
return arr
[front
];
}
public void showQueue() {
if (isEmpty()) {
System
.out
.println("队列空的,没有数据~~");
return;
}
for (int i
= 0; i
< arr
.length
; i
++) {
System
.out
.printf("arr[%d]=%d\n", i
, arr
[i
]);
}
}
public int headQueue() {
if (isEmpty()) {
throw new RuntimeException("队列空的,没有数据~~");
}
return arr
[front
+ 1];
}
}
环形(循环)队列
数组模拟环形队列
对前面的数组模拟队列的优化,充分利用数组. 因此将数组看做是一个环形的。(通过取模的方式来实现即可) 分析说明,思路如下:
front 变量的含义做一个调整: front 就指向队列的第一个元素, 也就是说 arr[front] 就是队列的第一个元素 front 的初始值 = 0rear 变量的含义做一个调整:rear 指向队列的最后一个元素的后一个位置. 因为希望空出一个空间做为约定.rear 的初始值 = 0当队列满时,条件是 (rear + 1) % maxSize == front 【满】对队列为空的条件,rear == front 空当我们这样分析, 队列中有效的数据的个数rear + maxSize - front) % maxSize // 例如rear = 1 front = 0 (类似于钟表,5点和16点在表盘上差了几点:(5+12-16) = 1)我们就可以在原来的队列上修改得到,一个环形队列
常见问题:
a.第一个问题什么要使用环形队列? 普通队列缺点:用一次就不能用了 顺序队列由于有潜在的假溢出问题所以其实不用太抠细节,把循环队列搞懂就行。
b.第二个问题:循环队列为什么用空一个元素的位置?
循环队列中,由于入队时尾指针向前追赶头指针;出队时头指针向前追赶尾指针,造成队空和队满时头尾指针均相等。因此,无法通过条件front==rear来判别队列是"空"还是"满"。 解决这个问题的方法至少有三种: ① 另设一布尔变量以区别队列的空和满; ② 少用一个元素的空间。约定入队前,测试尾指针在循环意义下加1后是否等于头指针,若相等则认为队满(注意:rear所指的单元始终为空);(一般用这种) ③使用一个计数器记录队列中元素的总数(即队列长度)。
c.第三个问题:为什么取模?
因为循环bai队列是一个环,而这个环在达到末尾的du时zhi候再挪到下一个的时候就应该指到初始位置了dao。而取余这个操作可以满足一个数一直加但是最终结果一直在0和被除数之间循环的要求。
举个例子,比如说一个长度为6的循环队列: 0-1-2-3-4-5- 比如说我要加进去8个数字0-7,每个位置一个数字。那么数字0(第1个数字)就在队列0的位置、数字1在队列中1的位值 … 数字5在队列5的位置上。那么数字6呢?按照循环队列的要求,5的下一个位置是0,但是怎么才能根据已知条件(第n个数字和队列长度6)来求出这个0呢 那就要用到取余了。 6(第7个数字)%6(队列长度) = 0 那么这个6 就放在0 的位置上 继续, 7(第8个数字)%6(队列长度) = 1 那么这个7 就放在1 的位置上 现在比如说你有许多个数字要放进一个长度为 L 的循环队列中, 那么第n个数字要放在队列中的位置x就是 x = ( n - 1 ) % L 总结一下取余的目的就是为了让一个公差为1的递增序列变为在一个从0到L范围内循环的数列。
代码实现
import java
.util
.Scanner
;
public class CircleArrayQueueDemo {
public static void main(String
[] args
) {
System
.out
.println("测试数组模拟环形队列的案例~~~");
CircleArray queue
= new CircleArray(4);
char key
= ' ';
Scanner scanner
= new Scanner(System
.in
);
boolean loop
= true;
while (loop
) {
System
.out
.println("s(show): 显示队列");
System
.out
.println("e(exit): 退出程序");
System
.out
.println("a(add): 添加数据到队列");
System
.out
.println("g(get): 从队列取出数据");
System
.out
.println("h(head): 查看队列头的数据");
key
= scanner
.next().charAt(0);
switch (key
) {
case 's':
queue
.showQueue();
break;
case 'a':
System
.out
.println("输出一个数");
int value
= scanner
.nextInt();
queue
.addQueue(value
);
break;
case 'g':
try {
int res
= queue
.getQueue();
System
.out
.printf("取出的数据是%d\n", res
);
} catch (Exception e
) {
System
.out
.println(e
.getMessage());
}
break;
case 'h':
try {
int res
= queue
.headQueue();
System
.out
.printf("队列头的数据是%d\n", res
);
} catch (Exception e
) {
System
.out
.println(e
.getMessage());
}
break;
case 'e':
scanner
.close();
loop
= false;
break;
default:
break;
}
}
System
.out
.println("程序退出~~");
}
}
class CircleArray {
private int maxSize
;
private int front
;
private int rear
;
private int[] arr
;
public CircleArray(int arrMaxSize
) {
maxSize
= arrMaxSize
;
arr
= new int[maxSize
];
}
public boolean isFull() {
return (rear
+ 1) % maxSize
== front
;
}
public boolean isEmpty() {
return rear
== front
;
}
public void addQueue(int n
) {
if (isFull()) {
System
.out
.println("队列满,不能加入数据~");
return;
}
arr
[rear
] = n
;
rear
= (rear
+ 1) % maxSize
;
}
public int getQueue() {
if (isEmpty()) {
throw new RuntimeException("队列空,不能取数据");
}
int value
= arr
[front
];
front
= (front
+ 1) % maxSize
;
return value
;
}
public void showQueue() {
if (isEmpty()) {
System
.out
.println("队列空的,没有数据~~");
return;
}
for (int i
= front
; i
< front
+ size() ; i
++) {
System
.out
.printf("arr[%d]=%d\n", i
% maxSize
, arr
[i
% maxSize
]);
}
}
public int size() {
return (rear
+ maxSize
- front
) % maxSize
;
}
public int headQueue() {
if (isEmpty()) {
throw new RuntimeException("队列空的,没有数据~~");
}
return arr
[front
];
}
}