3.1 稀疏数组简介:
当一个数组中大部分元素都为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组,会使得原本的数组大小得到压缩减少内存存储空间。
目标导向学习,学完即用于解决问题: 一个五子棋盘用二维数组11行11列表示,有存盘退出和续上盘的功能。由于棋盘中记录了很多没有作用的默认值0,如何使用稀疏数组进行压缩?
3.1.1 稀疏数组的处理方法:
1)第一行记录数组共有多少行,多少列,以及有多少个不同的值。
2)然后就记录在具体那一行,那一列中,具体的值是多少。
- 举例图:
3.1.2、应用实例:
1)使用稀疏数组,来保留类似上面距离图的二维数组
2)把稀疏数组存盘,并且可以从新恢复原来的二维数组
思路分析:
3.1.3、测试代码
代码思路复习:
1、将二维数组转化为稀疏数组
- 1.1、定义二维数组 11*11
- 1.2、给二维数组对应的位置赋值 arr[1][2] = 1 arr[2][3] = 2;
- 1.3、将二维数组进行压缩
- 1.3.1、计算二维数组中的非0值的个数num
- 1.3.2、定义稀疏数组的大小int sparsearr [][] = new int sparsearr[num + 1][3] (行数+1是由默认的第一行记录原二位数组的行列及非0值的个数。
- 1.3.3、在稀疏数组的逻辑存储位置上将原二维数组非0值的数据记录(用计数器)
2、将稀疏数组复原为二维数组
- 2.1、根据稀疏数组的特点得到原二维数组的行列值从而复原原二维数组的行数和列数
int newarr[] []=new int [ sparsearr[0][0 ]] [ sparsearr[0][1] ]; - 2.2、根据稀疏数组的特点得到对应的值所在的行和列复原原二维数组的非0值 for(int i = 1;i<sparsearr.length;i++){ newarr[sparsearr[ i][0] ][ sparsearr[i][1] ] = sparse[i][2] }
- 2.3、循环输出复原的二维数组
public static void main(String[] args) {
//1、将二维数组转为稀疏数组
//1.1、创建一个11*11的二维数组
int arr [][] = new int [11][11];
//1.2、在二维数组中赋值,0表示没有子,1蓝,2黑
arr[1][2] = 1;
arr[2][3] = 2;
//遍历将二维数组输出,使用for循环。(增强for循环已经忘记,需要复习。)
System.out.println("原始的二维数组为:");
for(int i = 0;i < arr.length;i++) {
for(int j = 0;j < arr.length;j++) {
System.out.printf("%d\t",arr[i][j]);
}
System.out.println();
}
//1.3.1先遍历二维数组,得到值不为0的数据个数
int num = 0;
for(int i = 0; i < arr.length; i++) {
for(int j = 0; j < arr.length; j++) {
if(arr[i][j] != 0) {
num++;
}
}
}
//1.3.2、创建对应的稀疏数组;并且将非0的值放到稀疏数组中
int sparsearr[][] = new int [num+1][3];
//给稀疏数组赋值(定义稀疏数组的首行的数据:行数、列数、值非0的个数
sparsearr[0][0] = 11;
sparsearr[0][1] = 11;
sparsearr[0][2] = num;
//遍历二维数组,将非0的值存放到sparsearr中
int count = 0;//记录是第几个非0的数值
for(int i = 0; i < arr.length; i++) {
for(int j = 0 ;j < arr.length;j++) {
if(arr[i][j] != 0) {
count++;
sparsearr[count][0] = i;
sparsearr[count][1] = j;
sparsearr[count][2] = arr[i][j];
}
}
}
//1.3.3、输出稀疏数组的形式
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]);
}
System.out.println();
//2、稀疏数组转化为二维数组
//2.1、复原二维数组的行列
int newarr[][] = new int [sparsearr[0][0]][sparsearr[0][1]];
//2.2、在逻辑和存储位置上将恢复二维数组原本的数据
for(int i = 1; i < sparsearr.length;i++){
newarr[ sparsearr[i][0] ][ sparsearr[i][1] ] = sparsearr[i][2];
}
//2.3、将恢复的数据遍历输出
System.out.println("复原后的二维数组为:");
for(int i = 0;i< newarr.length;i++) {
for(int j = 0;j < newarr.length;j++) {
System.out.printf("%d\t",newarr[i][j]);
}
System.out.println();
}
3.2 队列
3.2.1、队列的使用场景
银行排队场景:
3.2.2、队列简介
1)队列存储数据是有序的,可以使用数组或链表实现。
2)队列的原则:遵循先进先出,后进后出的原则。
3.2.3、数组模拟队列思路
- 因为队列的输入和输出在首端和尾端进行,所以需要两个指针变量来表示:队列的首端front,队列的尾端rear;maxsize则表示队列的长度。
- front随着队列元素的输出而改变,rear随着队列元素的输入而改变。
- 数组模拟队列注意点:
1)每添加一个元素进入队列时,rear+1 队尾往后移。
2)当rear > maxsize(队列的长度)时,表示队列已经添加满元素。
代码实现 数组模拟队列
public class ArrayQueue {
public static void main(String[] args) {
//实例化队列Queue
Queue queue = new Queue(3);
//定义一个简单的交互界面
char key = ' ';//接收用户输入
//定义Scanner输入类实例化
Scanner scanner = new Scanner(System.in);
boolean loop = true;//便于循环条件
//输出一个菜单
while(loop) {
System.out.println("s(show),显示队列");
System.out.println("e(exit),退出队列");
System.out.println("g(get),从队列获取数据");
System.out.println("h(head),查看队列的头部数据");
System.out.println("a(add),从队列添加元素");
key = scanner.next().charAt(0);//接收一个字符
switch (key) {
//显示队列数据
case 's':
queue.shwoQueue();
break;
//退出Scanner
case 'e':
scanner.close();
loop = false;
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) {
// TODO: handle exception
System.out.println(e.getMessage());
}
break;
//添加元素进队列
case 'a':
System.out.println("请输入一个数");
int value = scanner.nextInt();
queue.addQueue(value);
break;
default:
break;
}
}
System.out.println("程序退出");
}
}
//队列是一个类
class Queue{
//定义队列所需的属性
int front;//定义队列的首端
int rear;//定义队列的尾端
int maxsize;//定义队列的长度
int [] arr;//用数组模拟队列
//定义队列的构造方法(空参、有参)
public Queue(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++;//front在队列输出时进行变化
return arr[front];
}
//显示队列的所有数据
public void shwoQueue() {
//边界问题预防
if(isEmpty()) {
System.out.println("队列为空,无数据");
}
//遍历输出队列的数据
for(int i = 0; i < arr.length;i++) {
System.out.printf("arr[%d]=%d\n",i,arr[i]);//%d表示下标
//输出队列中的下标、下标对应的值
}
}
//显示队列的头部数据
public int headQueue() {
//边界条件预防
if(isEmpty()) {
System.out.println("队列为空,无数据");
}
//否则返回队列的头部数据
return arr[front+1];
}
}
3.2.4 数组模拟环形队列
- 由于数组模拟队列只能使用一次,原因就是在于‘数组的位置没有变动’。在数组模拟队列的基础上进行优化 => 数组模拟环形队列
- 实现原理:在判断队列已满的条件时需要优化,在队列已满时为队列空出一个位置
3.2.5 数组模拟环形队列思路分析:
- 原数组队列的条件:
- front = -1在队列头部的前一个位置,随着输出元素而改变
- rear = -1也在队列头部的前一个位置,随着输入元素而改变
- 最大的长度还是maxsize。
环形队列思路分析:
可以将原本的数组组成的队列看做题目的原条件,而将数组组成的队列优化为‘环形队列’则是需要解决的问题
- 原数组队列优化为环形数组队列的条件优化(解题思路):
- 1、原 front = -1 变量调整为 front = 0,即front指针变量不再是指向队列头部的前一个位置而是指向队列的第一个位置。
- 2、原 rear = -1 变量调整为 rear = 0,即rear指针变量不再是指向队列尾部的后一个位置,而是做一个约定:将队列最后一个位置空出来。
- 3、判断队列满的条件是 (rear + 1)% maxsize = front。
- 3.1、假设rear = 3即队列中现有4个元素 + 约定中rear空出的一个位置
- 3.2 、maxsize即队列的长度为4
- 3.3、front没有输出元素为0
- 按照判断队列是否已满的条件为 (3+1)% 4 = 0 ,由于队列没有输出元素,所以此时队列已满。
- 4、分析队列的有效数据的个数公式
- (rear + maxsize - front)% maxsize
- 5、队列为空的条件:rear = front
以上5小点就是实现将**‘’数组有序队列‘’优化为‘’环形队列‘’**的思路。
测试代码:
public class CircleArrayQueue {
public static void main(String[] args) {
//实例化环形队列
CircleArray queue = new CircleArray(3);//由于约定需要空出一个,则实际有效的最大数据为2
//定义Scanner类
Scanner scanner = new Scanner(System.in);
//定义条件语句的钥匙
char key = ' ';
//定义循环的条件
boolean loop = true;
//定义循环来执行Scanner界面
while(loop) {
//定义菜单
System.out.println("a(add),往队列中添加数据");
System.out.println("g(get),从队列中取出数据");
System.out.println("s(show),显示队列中的所有数据");
System.out.println("h(headQueue),获取队列的头部数据");
System.out.println("e(exit),退出程序");
//使用key转接用户输入的数据
key = scanner.next().charAt(0);
switch (key) {
//往队列中添加数据
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) {
e.getMessage();
}
break;
//显示队列的所有数据
case 's':
queue.showQueue();
break;
//获取队列的头部数据
case 'h':
try {
int res = queue.headQueue();
System.out.printf("队列的头部数据为%d\n",res);
}catch(Exception e){
e.getMessage();
}
break;
//退出程序
case 'e':
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序结束");
}
}
class CircleArray{
//定义CircleArrayQueue变量
private int front;//定义队列的首端元素front,根据思路定义为 0
private int rear;//定义队列的尾端元素rear,根据约定:要空一个位置给队列。值为0.
private int maxsize;//定义队列的长度。
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 front == rear;
}
//往队列添加元素,入队列
public void addQueue(int n) {
if(isFull()) {
System.out.println("队列已满,无法添加数据");
return;
}
arr[rear] = n;//由于约定,rear的位置是空出来了一位,所以可以直接添加
rear = (rear +1) % maxsize;//避免rear指针越界
}
//获取队列的数据,出队列
public int getQueue() {
if(isEmpty()) {
throw new RuntimeException("队列为空,无法取数据");
}
/*
* 考虑队列优化之后front输出数据
* 直接返回front,因为其在队列的第一位。
* 返回后需要后移将下一个队列的元素作为队列的头部元素
*
* 不能直接返回front对应队列中的元素,这样就不能后移,需要借助第三方变量
*
* 1、使用一个value保存arr[front]元素
* 2、将front后移
* 3、返回第三方变量
*/
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(isFull()) {
System.out.println("队列为空,无法取数据");
}
//队列不为空,则将队列的头部数据输出
return arr[front];
}
}