1 稀疏数组

  • 所谓稀疏数组就是当数组中大部分的内容值都未被使用(或都为零),在数组中仅有少部分的空间使用。因此<font color="red">造成内存空间的浪费</font>,为了节省内存空间,并且不影响数组中原有的内容值,我们可以使用稀疏数组去压缩数据。

1.1 稀疏数组的应用场景

算法与数据结构(Java版)01-稀疏数组和队列详解及代码实现_数组

1.2 稀疏数组的基本介绍

  • 当一个数组中大部分元素为0,或者为同一个数值时,可以使用稀疏数组来保存该数组
  • 稀疏数组的处理方法
  • 记录数组一共有几行几列,有多少个不同的值
  • 把具有不同值的元素的行列及记录在一个小规模的数组中从而缩小程序的规模

1.3 稀疏数组的应用

算法与数据结构(Java版)01-稀疏数组和队列详解及代码实现_数组_02

  • 将二维数组转换成稀疏数组
  • 遍历原有的二维数组,得知有效数据数据的总个数
  • 根据有效数据个数来创建稀疏数组 [有效数组+1]
  • 将二维数组中的有效数据存入到稀疏数组中
  • 将稀疏数组通过IO写入磁盘
  • 稀疏数组转成原始的二维数组
  • 从磁盘中读取到稀疏数组,根据第一行的数据,创建原始的二维数组 如上图 int [15] [15]
  • 在读取稀疏数组中剩余几行的数据,赋给二维数组即可

1.4 稀疏数组代码实现

1.4.1 创建二维数组并赋予初始值

/**
* 二维数组初始化
*
* <p>
* 这里模拟定义一个15*15的五子棋棋盘,并已知棋盘中已存在两个棋子
* 来创建一个[15][15]的二维数组
* </p>
* @param
* @return int[][]
* @author ZhangSan_Plus
* @description //TODO
* @date 15:22 2022/4/15
**/
public static int[][] twoDimensionalGoBang() {
int goBang[][] = new int[15][15];
goBang[3][3] = 1;
goBang[4][4] = 2;
return goBang;
}

1.4.2 将原有的二维数组转化为稀疏数组

/**
* 二维数组转成稀疏数组
*
* <p>
* 1.首先遍历原有的二维数组,得知原二维数组有效数据的总个数
* 2.根据得知的有效数据来创建稀疏数组 [有效数据+1]
* 3.将二维数据中有效数据的位置和值放到稀疏数组中
* </p>
* @param
* @return int[][]
* @author ZhangSan_Plus
* @description //TODO
* @date 15:37 2022/4/15
**/
public static int[][] twoDimensionalToSparse() {
//创建一个原始的二位数组
int[][] goBang = twoDimensionalGoBang();
//1.遍历原有的二维数组,得知有效数据数据的总个数
int valid = 0;
for (int i = 0; i < goBang.length; i++) {
for (int j = 0; j < goBang.length; j++) {
if (goBang[i][j] != 0) {
valid++;
}
}
}
//2.根据有效数据来创建稀疏数组
int[][] sparse = new int[valid + 1][valid + 1];
sparse[0][0] = goBang.length;
sparse[0][1] = goBang.length;
sparse[0][2] = valid;
int count = 0;
for (int i = 0; i < goBang.length; i++) {
for (int j = 0; j < goBang.length; j++) {
if (goBang[i][j] != 0) {
count++;
sparse[count][0] = i;
sparse[count][1] = j;
sparse[count][2] = goBang[i][j];
}
}
}
return sparse;
}

1.4.3 将稀疏数组转化为原来的二维数组

    public static void main(String[] args) {
/**
* <p>
* 1.根据稀疏数组的特性已知其第一行存放为 二维数组的 行、列、值
* 2.除第一行以外的数据其余存放的则是有效数据的 行、列、值
* </p>
*/
int[][] sparse = twoDimensionalToSparse();
int[][] two = new int[sparse[0][0]][sparse[0][1]];
for (int i = 1; i < sparse.length; i++) {
//稀疏数组的第i行第0,1列为二维数组所在的位置 稀疏数组的第2列则为二维数组的值
two[sparse[i][0]][sparse[i][1]] = sparse[i][2];
}
traverse(two);
}

1.4.4 遍历二维数组

/**
* 二维数组遍历
*
* @param array 二维数组
* @return void
* @author ZhangSan_Plus
* @description //TODO
* @date 15:22 2022/4/15
**/
public static void traverse(int[][] array) {
for (int[] row : array) {
for (int data : row) {
System.out.printf("%d\t", data);
}
System.out.println();
}
}

2 队列

  • 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

算法与数据结构(Java版)01-稀疏数组和队列详解及代码实现_数组_03

2.1 队列的应用场景

  • 如:银行存款取款等、医院的叫号等都算是队列

2.2 队列的基本介绍

  • 队列是一个有序列表,可以使用数组或者链表来实现
  • 遵循<font color="red">先入先出</font>的原则,即:先存入队列的数据要先取出,后存入的后取出

2.3 单向队列模拟实现

2.3.1 使用数组来模拟实现队列

public class ArrayImitateQueue {
/**
* 数组最大容量
*/
private int maxSize;
/**
* 队列头
*/
private int front;
/**
* 队列尾
*/
private int rear;
/**
* 存放数据
*/
private int[] arr;

public ArrayImitateQueue(int maxSize) {
this.maxSize = maxSize;
arr = new int[maxSize];
//指向队列头的前一个位置
front = -1;
//指向队列尾
rear = -1;
}

/**
* 判断队列是否已满
*
* @param
* @return boolean
* @author ZhangSan_Plus
* @description //TODO
* @date 16:43 2022/4/15
<strong>/
public boolean isFull() {
return rear == maxSize - 1;
}

/</strong>
* 判断队列是否为空
*
* @param
* @return boolean
* @author ZhangSan_Plus
* @description //TODO
* @date 16:44 2022/4/15
<strong>/
public boolean isEmpty() {
return rear == front;
}

/</strong>
* 向队列中添加数据
*
* @param num
* @return void
* @author ZhangSan_Plus
* @description //TODO
* @date 16:46 2022/4/15
<strong>/
public void addQueue(int num) {
if (isFull()) {
System.err.println("队列已满不能添加数据");
return;
}
rear++;
arr[rear] = num;
}

/</strong>
* 取出队列中的值
*
* @param
* @return int
* @author ZhangSan_Plus
* @description //TODO
* @date 16:50 2022/4/15
<strong>/
public int getQueue() {
if (isEmpty()) {
throw new RuntimeException("ERROR:index out of bounds");
}
front++;
return arr[front];
}

/</strong>
* 显示队列中全部内容
*
* @param
* @return void
* @author ZhangSan_Plus
* @description //TODO
* @date 16:50 2022/4/15
<strong>/
public void showQueue() {
if (isEmpty()) {
System.err.println("Queue is Null~");
return;
}
for (int i : arr) {
System.out.println(i);
}
}

/</strong>
* 获取队列的头数据
*
* @param
* @return int
* @author ZhangSan_Plus
* @description //TODO
* @date 16:52 2022/4/15
**/
public int getFirstQueue() {
if (isEmpty()) {
throw new RuntimeException("ERROR:index out of bounds");
}
return arr[front + 1];
}
}

2.3.2 使用链表来模拟实现队列

public class ChainImitateQueue<T> {
private Node first;
private Node last;
private int n;

/**
* 在链表中添加数据
*
* @param item
* @return void
* @author ZhangSan_Plus
* @description //TODO
* @date 17:09 2022/4/15
<strong>/
public void addQueue(T item) {
//在链表尾部插入结点
Node oldLast = last;
last = new Node();
last.item = item;
last.next = null;
if (isEmpty()) {
//队列插入第一个元素
first = last;
} else {
oldLast.next = last;
}
n++;
}

/</strong>
* 删除链表中的头数据
*
* @param
* @return T
* @author ZhangSan_Plus
* @description //TODO
* @date 17:10 2022/4/15
<strong>/
public T removeQueue() {
//在链表表头删除结点,(对单链表而言,不好删除表尾结点)
if (isEmpty()) {
//删除队列中最后一个元素
throw new RuntimeException("ERROR: There is no more data in the queue to delete");
}
T item = first.item;
first = first.next;
n--;
return item;
}

/</strong>
* 判断列表是否为空
*
* @param
* @return boolean
* @author ZhangSan_Plus
* @description //TODO
* @date 17:10 2022/4/15
<strong>/
public boolean isEmpty() {
return n == 0 ? true : false;
}

/</strong>
* 获取列表的长度
*
* @param
* @return int
* @author ZhangSan_Plus
* @description //TODO
* @date 17:11 2022/4/15
**/
public int size() {
return n;
}


class Node {
Node next;
T item;
}

public static void main(String[] args) {
ChainImitateQueue<Integer> queue = new ChainImitateQueue<>();
queue.addQueue(10);
queue.addQueue(100);
System.out.println(queue.size());
System.out.println(queue.removeQueue());
System.out.println(queue.removeQueue());
System.out.println(queue.size());
System.out.println(queue.removeQueue());
}
}

2.3.3 单向队列存在的问题

  • 因为采用rear=maxSize-1 作为队列已满的条件缺陷,当队列条件为真时,队中可能还有若干空位置 <font color="red">队列中明明还有空位置为何不能进入</font>
  • 这种溢出并不是真正的溢出,称为<font color="red">假溢出</font>
  • 从而引出环形队列

2.4 环形队列模拟实现

2.4.1 环形队列基本介绍

  • 因为单向队列存在假溢出的缺陷,从而引入环形队列。
  • 采用把数组的前端和后端连接起来,形成一个环形的顺序表,即把存储队列元素的表从逻辑上看成一个环,称为环形队列或循环队列。
  • 它是一个首尾相连的FIFO的数据结构,采用数组的线性空间,数据组织简单。能很快知道队列是否满为空。能以很快速度的来存取数据

算法与数据结构(Java版)01-稀疏数组和队列详解及代码实现_数据_04

2.4.2 环形队列设计思路

  • 实际上内存地址一定是连续的,不可能是环形的,通常采用逻辑的方式来实现环形队列,将rear++ 改为( <font color="red">rear=(rear+1)%maxSize</font>) 和 front++ 改为( <font color="red">front=(front+1)%maxSize</font>)

算法与数据结构(Java版)01-稀疏数组和队列详解及代码实现_二维数组_05

  • <font color="red">队空条件</font>:front=rear
  • <font color="red">队满条件</font>:(rear+1)%maxSize=front
  • <font color="red">进队操作</font>:rear=(rear+1)%maxSize; 将进队放在rear处
  • <font color="red">出队操作</font>:front=(front+1)%maxSize; 取出front处的元素

2.4.3 环形队列数组实现

public class RingImitateQueue {
/**
* 数组最大容量
*/
private int maxSize;
/**
* 队列头 初始值=0
*/
private int front;
/**
* 队列尾 rear=0
*/
private int rear;
/**
* 存放数据
*/
private int[] arr;

public RingImitateQueue(int maxSize) {
this.maxSize = maxSize;
arr = new int[maxSize];
front = 0;
rear = 0;
}

public boolean isFull() {
return (rear + 1) % maxSize == front;
}

public boolean isEmpty() {
return rear == front;
}

public void addQueue(int num) {
if (isFull()) {
throw new RuntimeException("队列已满");
}
arr[rear] = num;
//将rear后移
rear = (rear + 1) % maxSize;
}

public int getQueue() {
if (isEmpty()) {
throw new RuntimeException("队列为空");
}
//front指向队列的第一个元素
int value = arr[front];
front = (front + 1) % maxSize;
return value;
}

public void showQueue() {
if (isEmpty()) {
System.err.println("Queue is Null~");
return;
}
for (int i = 0; i < front + site(); i++) {
System.out.println(arr[i % maxSize]);
}
}

public int site() {
return (rear + maxSize - front) % maxSize;
}
public int getFirstQueue() {
if (isEmpty()) {
throw new RuntimeException("ERROR:index out of bounds");
}
return arr[front];
}
}