文章目录
1 稀疏数组
1.1 引入
上面的这个棋盘,如何进行存储呢?
最简单的就是一个二维数组,1表示黑子,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,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)
因为该二维数组的很多值是默认值 0 ,因此记录了很多没有意义的数据 =====> 就可以采用稀疏数组
当数组的大部分元素是0或者是同一个值得数组时候,可以使用稀疏数组来保存该数组
1.2 何谓稀疏数组
当数组的大部分元素是0或者是同一个值得数组时候,可以使用稀疏数组来保存该数组
1.2.1 稀疏数组的处理方法:
- 记录数组一共有几行几列,有多少个不同的值
- 把具有不同值的元素的行列以及记录在一个小的规模的数组里,从而缩小程序的规模
1.2.2 稀疏数组的转换思路
二维数组转稀疏数组
- 遍历二维数组,得到有效的数据个数sum
- 根据sum就可以创建一个稀疏数组sparseArr int[sum+1][3]
- 把二维数组的有效数据存入稀疏数组
比如刚刚的那个棋盘:是一个11*11的二维数组,就可以变成下面的稀疏数组:
row | col | val | |
---|---|---|---|
0 | 11 | 11 | 2 |
1 | 1 | 2 | 1 |
2 | 2 | 3 | 2 |
稀疏数组转二维数组
- 先读取稀疏数组的第一行数据,根据第一行数据,创建原始的二维数组,比如上面棋盘:chessArr = int[11][11]
- 在读取稀疏数组的后几行的数据,赋值给原始的数组
1.3 代码实现
以刚刚的棋盘为例,实现一下上面的思路
package study.wyy.struct.sparsearr;
/**
* @author by wyaoyao
* @Description
* @Date 2021/1/4 9:47 下午
* 演示
*/
public class SparesArrayDemo {
public static void main(String[] args) {
// 创建一个原始的二维数组
// 0: 表示没有棋子, 1 表示 黑子 2 表蓝子
int sourceArr[][] = new int[11][11];
sourceArr[1][2] = 1;
sourceArr[2][3] = 2;
// 输出
System.out.println("~~~~~输出原始二维数组~~~~");
for (int[] row : sourceArr) {
for (int data : row) {
System.out.printf("%d\t", data);
}
// 换个行
System.out.println();
}
System.out.println("~~~~~输出原始二维数组end~~~~");
/***********************二维数组转稀疏数组************/
System.out.println("/***********************二维数组转稀疏数组************/");
// 1 遍历二维数组统计出二维数组中有效数字的个数,本场景下就是非零数字的个数
int sum = 0;
for (int[] row : sourceArr) {
for (int data : row) {
if (data != 0) {
sum++;
}
}
}
System.out.println("二维数组中有效数字个数是:" + sum);
// 2 遍历二维数组,进行转换
// 定义稀疏数组:
int sparesArray[][] = new int[sum + 1][3];
// 第一行记录的是二维数组的形状
// 第一行的第一个元素记录的是原数组的行数
sparesArray[0][0] = sourceArr.length;
// 第一行的第2个元素记录的是原数组的列数
sparesArray[0][1] = sourceArr[0].length;
// 第一行的第3个元素记录的是原数组的有效数字个数
sparesArray[0][2] = sum;
// 遍历二维数组,将有效的数字存放到稀疏数组中
// count 用于记录第几个有效数字
int count = 0;
for (int i = 0; i < sourceArr.length; i++) {
for (int j = 0; j < sourceArr[i].length; j++) {
if (sourceArr[i][j] != 0) {
count++;
// 每一行的第一个存放的是有效数字在原始二维数组中的所在行数
sparesArray[count][0] = i;
// 每一行的第2个存放的是有效数字在原始二维数组中的所在列数
sparesArray[count][1] = j;
// 每一行的第一个存放的是有效数字的值
sparesArray[count][2] = sourceArr[i][j];
}
}
}
// 输出稀疏数组
System.out.println("~~~~~输出稀疏数组~~~~");
for (int[] row : sparesArray) {
for (int data : row) {
System.out.printf("%d\t", data);
}
// 换个行
System.out.println();
}
System.out.println("~~~~~输出稀疏数组 end~~~~");
/***********************稀疏数组转二维数组***********/
System.out.println("/***********************稀疏数组转二维数组************/");
// 1 定义一个二维数组
// 稀疏数组的第一行记录了二维数组的形状
int rows = sparesArray[0][0];
int columns = sparesArray[0][1];
int targetArray[][] = new int[rows][columns];
// 遍历稀疏数组,进行数据恢复(从第一行开始遍历)
for (int i = 1; i < sparesArray.length; i++) {
int row = sparesArray[i][0];
int column = sparesArray[i][1];
int data = sparesArray[i][2];
targetArray[row][column] = data;
}
System.out.println("~~~~~输出恢复二维数组~~~~");
for (int[] row : targetArray) {
for (int data : row) {
System.out.printf("%d\t", data);
}
// 换个行
System.out.println();
}
System.out.println("~~~~~输出恢复二维数组end~~~~");
}
}
2 队列
2.1 队列介绍
- 队列是一个有序列表, 可以用数组或是链表来实现。
- 遵循先入先出的原则, 即: 先存入队列的数据, 要先取出,后存入的要后取出
示意图: (使用数组模拟队列示意图)
2.2 数组模拟队列
2.2.1 思路分析
队列:先进先出,也就是从队尾添加数据,队首取出数据
- 添加数据的时候,从队尾开始添加
- 取数据肯定是从队首取,保证最先进去的数据,才可以先被取出来
思路一:
添加数据图解:
总结:需要定义一个标记,指向队尾,队尾所在的位置一定是最后一个数据所在的位置,在添加数据的时候,先将队尾标记后移,再将数据放到队尾(因为初始化的时候,将指向队尾的标记是放在-1处,添加的时候需要先进行后移)
取出数据图解:
总结:
- 定义一个标记用于标记队首的位置,指向的是队首的前一个位置,取数据的时候,先后移标记,然后取出数据
- 队列是否已经满了,当队尾指针等于max(数组容量)-1的时候,也就是说队尾指针已经移动到数组索引的最后位置
- 队列是否为空了,很简单当队首和队尾指向同一处时
思路二
- 定义一个标记用于标记队首的位置,指向的是队首的前一个位置,取数据的时候,先取出数据,后移标记
- 队列是否已经满了,当队尾指针等于max(数组容量)的时候
- 队列是否为空了,很简单当队首和队尾指向同一处时
2.2.2 代码实现
定义一个接口
package study.wyy.struct.queue;
/**
* @author by wyaoyao
* @Description
* @Date 2021/1/5 9:33 下午
*/
public interface Queue<T> {
/****
* 添加数据
* @param t 数据
*/
void add(T t);
/****
* 获取数据
* @return
*/
T get();
/****
* 是否是空队列
* @return
*/
boolean isEmpty();
/****
* 队列是否已满
* @return
*/
boolean isFull();
/****
* 查看队首数据,并不会取出数据,移动队首指针
* @return
*/
T peek();
}
思路一
package study.wyy.struct.queue.array;
import study.wyy.struct.queue.Queue;
/**
* @author by wyaoyao
* @Description
* @Date 2021/1/5 9:37 下午
*/
public class ArrayQueue<T> implements Queue<T> {
// 队列的大小
private final int max;
private int size;
// 指向队首的标记
private int front;
// 指向队尾的标记
private int real;
private Object[] data;
public ArrayQueue(int max) {
if (max <= 0) {
// 简单校验入参
throw new RuntimeException("queue max size can not < 0");
}
this.max = max;
this.front = -1;
this.real = -1;
data = new Object[max];
}
@Override
public void add(T t) {
// 1 如果队列满了,不能再添加数据了
if(this.isFull()){
throw new RuntimeException("queue has full");
}
// 移动队尾
this.real++;
// 添加数据
this.data[real] = t;
}
@Override
public T get() {
if(isEmpty()){
throw new RuntimeException("queue is empty");
}
// 移动队首
this.front++;
T t = (T) this.data[front];
return t ;
}
@Override
public boolean isEmpty() {
// 队列是否为空了,很简单当队首和队尾指向同一处时
return this.front==this.real;
}
@Override
public boolean isFull() {
// 队列是否已经满了,当队尾指针等于max-1的时候,也就是说队尾指针已经移动到数组索引的最后位置
return this.real == this.max-1;
}
@Override
public T peek() {
if(isEmpty()){
throw new RuntimeException("queue is empty");
}
// 这里就不能移动队首,只是看数据
T t = (T) data[front+1];
return t ;
}
}
@org.junit.Test
public void test1(){
Queue<Integer> queue = new ArrayQueue<Integer>(3);
queue.add(1);
queue.add(2);
queue.add(3);
try {
// 这里会抛出异常
queue.add(4);
}catch (Exception e){
System.out.println(e.getMessage());
}
Integer data = queue.get();
Assert.that(data.equals(1),"数据不对");
data = queue.get();
Assert.that(data.equals(2),"数据不对");
data = queue.get();
Assert.that(data.equals(3),"数据不对");
try {
// 这里会抛出异常
queue.get();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
思路二实现
package study.wyy.struct.queue.array;
import study.wyy.struct.queue.Queue;
/**
* @author by wyaoyao
* @Description
* @Date 2021/1/5 9:37 下午
*/
public class ArrayQueue2<T> implements Queue<T> {
// 队列的大小
private final int max;
private int size;
// 指向队首的标记
private int front;
// 指向队尾的标记
private int real;
private Object[] data;
public ArrayQueue2(int max) {
if (max <= 0) {
// 简单校验入参
throw new RuntimeException("queue max size can not < 0");
}
this.max = max;
this.front = 0;
this.real = 0;
data = new Object[max];
}
@Override
public void add(T t) {
// 1 如果队列满了,不能再添加数据了
if (this.isFull()) {
throw new RuntimeException("queue has full");
}
// 添加数据
this.data[real] = t;
// 移动队尾
this.real++;
}
@Override
public T get() {
if (isEmpty()) {
throw new RuntimeException("queue is empty");
}
T t = (T) this.data[front];
// 移动队首
this.front++;
return t;
}
@Override
public boolean isEmpty() {
// 队列是否为空了,很简单当队首和队尾指向同一处时
return this.real == this.front;
}
@Override
public boolean isFull() {
// 队列是否已经满了,当队尾指针等于max(数组容量)的时候
return this.real==this.max;
}
@Override
public T peek() {
if (isEmpty()) {
throw new RuntimeException("queue is empty");
}
// 这里就不能移动队首,只是看数据
T t = (T) data[front + 1];
return t;
}
}
@org.junit.Test
public void test2(){
Queue<Integer> queue = new ArrayQueue2<Integer>(3);
queue.add(1);
queue.add(2);
queue.add(3);
try {
// 这里会抛出异常
queue.add(4);
}catch (Exception e){
System.out.println(e.getMessage());
}
Integer data = queue.get();
Assert.that(data.equals(1),"数据不对");
data = queue.get();
Assert.that(data.equals(2),"数据不对");
data = queue.get();
Assert.that(data.equals(3),"数据不对");
try {
// 这里会抛出异常
queue.get();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
问题
- 目前这个队列只能一次,不能重复使用,可运行下面的测试代码观察该问题
@org.junit.Test
public void test2(){
Queue<Integer> queue = new ArrayQueue2<Integer>(3);
queue.add(1);
queue.add(2);
queue.add(3);
try {
// 这里会抛出异常
queue.add(4);
}catch (Exception e){
System.out.println(e.getMessage());
}
Integer data = queue.get();
Assert.that(data.equals(1),"数据不对");
data = queue.get();
Assert.that(data.equals(2),"数据不对");
data = queue.get();
Assert.that(data.equals(3),"数据不对");
try {
// 这里会抛出异常
queue.get();
}catch (Exception e){
System.out.println(e.getMessage());
}
// 这里数据已经取完了,但是我存数据还是会告知队列已满已满
try {
// 这里会抛出异常
queue.add(5);
}catch (Exception e){
System.out.println(e.getMessage());
}
}
2.3 数组模拟环形队列
我更习惯思路二的思维方式,就在思路二的基础上进行扩展:
2.3.1 思路图解
2.
3.
4.
总结:
- 队列判空:front == rear
- 队列判满:(real+1)%n = front (n为数组的大小)
- 数组还剩一个位置,也就是说当real后移一下(real+1)的时候队首和队尾就重合
- 队列最多max=n-1个元素(n = max +1)
- 队列中有效元素个数:
- 当 rear 比 front 大时,即 (rear -front) > 0 ,(rear -front) 即是队列元素个数
- 当 rear 比 front 小时,即 (rear -front) < 0 ,(rear -front) 表示数组还差多少个元素存满(负数),(rear + n - front) 即是队列元素个数
- 综上:(rear + n - front) % n
**甚至都不需要去计算,可以定义一个变量(size)来记录队列中实际的元素个数,通过这个来判断是否已满,是否为空 **
- 队列满判断就很简单了:size=max
- 队列空判断就很简单了:size=0
基于此,接口中,完全可以声明一个方法,返回队列中元素的个数
package study.wyy.struct.queue;
/**
* @author by wyaoyao
* @Description
* @Date 2021/1/5 9:33 下午
*/
public interface Queue<T> {
/****
* 添加数据
* @param t 数据
*/
void add(T t);
/****
* 获取数据
* @return
*/
T get();
/****
* 是否是空队列
* @return
*/
boolean isEmpty();
/****
* 队列是否已满
* @return
*/
boolean isFull();
/****
* 查看队首数据,并不会取出数据,移动队首指针
* @return
*/
T peek();
/**
* 返回队列中元素的个数
*/
int size();
}
还有个问题:
real能一直++后移吗,超了数组下界的时候,放元素就会抛出数组越界异常啊
- real=n-1(索引从0开始)的时候,在进行后移的时候必须对数组大小n取余 real = (real+1)%n
- 比如,上面real=4的时候,real后移就要移动到0的位置:(4+1)% 5 = 0
- real =2 (<n-1)后移的时候的同样也满足取余,这就是取余的作用:(3 +1)% 5 = 4
队首也是如此,后移的时候不能超出数组下界
这就是取余的作用:
- m对n取余的结果一定在 [0,n-1]
2.3.2 代码实现
package study.wyy.struct.queue.array;
import study.wyy.struct.queue.Queue;
/**
* @author by wyaoyao
* @Description
* @Date 2021/1/5 9:37 下午
* 环形队列
*/
public class ArrayQueue3<T> implements Queue<T> {
// 队列中最多可以放多少个元素,数组的大小-1
private final int max;
// 队首指针
private int front;
// 队尾指针
private int real;
private Object[] data;
// 队列中元素的个数
private int size;
public ArrayQueue3(int max) {
this.max = max;
// 数组的大小 = max+1
data = new Object[max + 1];
// size初始化0
this.size = 0;
front = 0;
real = 0;
}
@Override
public void add(T t) {
if(isFull()){
throw new RuntimeException("队列已满");
}
// 放元素,放到队尾所在的索引处
data[real] = t;
// 后移, 需要考虑的是real不能一直++,不能超了数组下界
// 是个循环,要取余
real = (real+1) % (max + 1);
size++;
}
@Override
public T get() {
if(isEmpty()){
throw new RuntimeException("队列为空");
}
// 取出队首所在索引位置
T t = (T) data[front];
// 后移,和real一样
front = (front + 1) % (max + 1);
size--;
return t;
}
@Override
public boolean isFull() {
// 队列满判断就很简单了:size=max
return this.size == this.max;
}
@Override
public boolean isEmpty() {
// 队列空判断就很简单了:size=0
return size == 0;
}
@Override
public T peek() {
// 判断
if (isEmpty()) {
throw new RuntimeException("队列空的,没有数据~~");
}
return (T) data[front];
}
@Override
public int size() {
return this.size;
}
}
@org.junit.Test
public void test3(){
Queue<Integer> queue = new ArrayQueue3<Integer>(3);
queue.add(1);
queue.add(2);
queue.add(3);
try {
// 这里会抛出异常 队列已满
queue.add(4);
}catch (Exception e){
System.out.println(e.getMessage());
}
Assert.that(queue.size()==3,"数据不对");
Integer data = queue.get();
Assert.that(data.equals(1),"数据不对");
Assert.that(queue.size()==2,"数据不对");
data = queue.get();
Assert.that(data.equals(2),"数据不对");
Assert.that(queue.size()==1,"数据不对");
data = queue.get();
Assert.that(data.equals(3),"数据不对");
Assert.that(queue.size()==0,"数据不对");
try {
// 这里会抛出异常 队列为空
queue.get();
}catch (Exception e){
System.out.println(e.getMessage());
}
// 这里数据已经取完了,在放数据
try {
// 这里不会抛出异常
queue.add(5);
}catch (Exception e){
System.out.println(e.getMessage());
}
Assert.that(queue.size()==1,"数据不对");
data = queue.get();
Assert.that(data.equals(5),"数据不对");
Assert.that(queue.size()==0,"数据不对");
}