稀疏数组
- 前言
- 稀疏数组
- 稀疏数组案例
- 队列
- 数组模拟队列
- 数组模拟环形队列
前言
数据结构和算法的在程序开发中是十分重要的,算法是程序的灵魂,优秀的程序可以在海量数据计算时,依然保持高速计算。
数据结构和算法的关系
1、数据结构是一门研究组织数据方式的学科,有了编程语言也就有了数据结构,学好数据结构可以写出更加有效率的代码。
2、程序=数据结构+算法。
3、数据结构是算法的基础,学算法之前必须要有扎实的数据结构知识。
数据结构的分类
数据结构包括线性结构和非线性结构。
1、线程结构
1.1:线性结构作为最常用的数据结构,其特点是数据元素之间存在着“一对一”的线性关系的数据结构,如(a0,a1,a2,…,an),a0为第一个元素,an为最后一个元素,此集合即为一个线性结构的集合。
1.2:线性结构有两种不同的存储结构,即顺序存储结构和链式存储结构,顺序存储的线性表为顺序表,顺序表中的存储元素是连续的。
1.3:链式存储的线性表称为链表,链表中存储的元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息。
1.4:线性结构常见的有:数组、队列、链表和栈。
非线程结构
非线性结构包括:二维数组、多维数组、广义表、树结构、图结构。
稀疏数组
基本介绍
当一个数组中大部分元素为0或者同一个值的数组时,可以使用稀疏数组来保存该数组。
稀疏数组的处理方法是:1、记录数组一共有几行几列,有多少个不同的值。2、把具有不同值的元素的行列记录在一个小规模的数组中(它就是稀疏数组),从而缩小数组的规模。
例如:五子棋的位置保存问题,可以使用二维数组去记录,1表示黑子,2表示白子,记录保存问题可以使用稀疏数组记录。
二维数组转换称稀疏数组图示
上图所示:将一个6行6列的二维数组转换成为一个3行6列的一个稀疏数组,稀疏数组的第一行记录的是原始数组有多少行,多少列,多少个元素。当然数据量变大之后,稀疏数组不再适合使用。
稀疏数组案例
假定有一个十行十列有一个五子棋棋盘,需要保存起来,然后恢复成该棋盘的样子。
上图中所示的棋盘转换为二维数组、二维数组再转换为稀疏数组、稀疏数组还原成二维码数组。
二维数组转稀疏数组的思路
1、遍历原始的二维数组,获取有效数据的个数 count。
2、根据 count就可以创建稀疏数组 sparseArr int[count + 1] [3]
3、将二维数组的有效数据存入到稀疏数组中
4、稀疏数组中的第一行存储的数据是原始数组有多少行 多少列,多少个有效数据。
稀疏数组转二维数组的思路
1、先读取稀疏数组的第一行,根据第一行的数据,创建原始二维数组。
2、再读取稀疏数组后几行的数据,并赋值给原始数组。
代码示例
package com.example.data.sparse;
import java.io.*;
/**
* 稀疏数组
*
* @author zjt
*/
public class SparseArr {
public static void main(String[] args) throws Exception {
// 先创建一个原始的二维数组 10 * 10
// 0 表示没有棋子 1 表示黑色 2表示 白色
int[][] chessArr = new int[10][10];
chessArr[1][2] = 1;
chessArr[2][3] = 2;
chessArr[3][3] = 1;
// 输出原始的二维数组
System.out.println("输出原始的二维数组~~");
for (int[] row : chessArr) {
for (int data : row) {
System.out.print(String.format("%d\t", data));
}
System.out.println();
}
// 将二维数组转成稀疏数组
// 1、遍历二维数组 得到非0数据的个数
int sum = 0;
for (int[] row : chessArr) {
for (int data : row) {
if (data > 0) {
sum++;
}
}
}
System.out.println("-------------------------------");
System.out.println("------二维数组转稀疏数组---------");
// 2、创建对应的稀疏数组
int[][] sparseArr = new int[sum + 1][3];
// 给稀疏数组赋值
sparseArr[0][0] = 10;
sparseArr[0][1] = 10;
sparseArr[0][2] = sum;
// 遍历二维数组将非0的值赋值到稀疏数组中
// count 用于记录是第几个非0数据
int count = 0;
for (int i = 0; i < chessArr.length; i++) {
int[] data = chessArr[i];
for (int j = 0; j < data.length; j++) {
if (data[j] != 0) {
count++;
sparseArr[count][0] = i;
sparseArr[count][1] = j;
sparseArr[count][2] = data[j];
}
}
}
// 得到的稀疏数组的形式
System.out.println();
System.out.println("得到的稀疏数组的形式");
for (int[] data : sparseArr) {
System.out.println(String.format("%d\t%d\t%d\t", data[0], data[1], data[2]));
}
System.out.println("-------------------------------");
System.out.println("------稀疏数组转二维数组---------");
// 稀疏数组的第一行存储的是 行数 列数 有效数据总个数
int[][] chessArr1 = new int[sparseArr[0][0]][sparseArr[0][1]];
for (int i = 1; i < sparseArr.length; i++) {
int[] data = sparseArr[i];
chessArr1[data[0]][data[1]] = data[2];
}
System.out.println("输出转换后的二维数组~~");
for (int[] row : chessArr1) {
for (int data : row) {
System.out.print(String.format("%d\t", data));
}
System.out.println();
}
// 将获取到的稀疏数组写出到磁盘
// 起到一个保存的作用
System.out.println("-------------------------------");
System.out.println("将获取到的稀疏数组写出到磁盘");
File file = new File("/Users/zjt/Desktop/arr.txt");
if (!file.exists()) {
file.createNewFile();
}
BufferedWriter bw = new BufferedWriter(new FileWriter(file));
StringBuilder sb = new StringBuilder();
for (int[] data : sparseArr) { // 数组转成字符串存储
for (int datum : data) {
sb.append(datum).append("\t");
}
sb.append("\n");
}
bw.write(String.valueOf(sb));
bw.flush();
bw.close();
System.out.println("-------------------------------");
System.out.println("从磁盘读取数组");
// 从磁盘读取数组
int[][] sArr;
BufferedReader br = new BufferedReader(new FileReader("/Users/zjt/Desktop/arr.txt"));
// 第一行数据 存储的是 原始二维数组的 行列数 有效值的个数
String line = br.readLine();
String[] arr = line.split("\t");
sArr = new int[Integer.parseInt(arr[0])][Integer.parseInt(arr[1])];
while ((line = br.readLine()) != null) {
String[] s2 = line.split("\t");
sArr[Integer.parseInt(s2[0])][Integer.parseInt(s2[1])] = Integer.parseInt(s2[2]);
}
br.close();
for (int[] row : sArr) {
for (int data : row) {
System.out.print(String.format("%d\t", data));
}
System.out.println();
}
}
}
队列
基本介绍
1、队列是一个有序列表,可以用数组或者链表来实现。
2、遵循先入先出原则,即先存入的数据要先取出,后存入的数据要后取出。
3、示意图:(使用数组模拟队列)
上图简要说明,声明了两个指针,rear和front两个变量,初始值是-1,rear是队首,front是队尾。当有数组加入时rear增加front不变,取出数据时rear不变。
数组模拟队列
1、队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如上图,其中maxSize是该队列的最大容量。
2、因为队列的输出、输入是分别从前后端来处理,因此需要两个变量front及rear分别记录队列前后端的下标,front会随着数据的输出而改变,而rear则是根据数据的输入而改变。
思路分析
当数据存入队列是使用addQueue()方法,addQueue()的处理需要两个步骤:
1、将尾指针往后移,rear+1,当 front == rear 表示空队列。
2、若尾指针rear小于队列的最大下标 MaxSize-1,则存入rear所指的数组无法存入数据,rear == MaxSize-1 表示满队列。
代码实现
package com.example.data.sparse.queue;
import java.util.Scanner;
/**
* 数组模拟队列
* 当前队列存在的问题 队列是一次性的 也就是即是空的也是满的
* 空间不能重复使用
*
* @author zjt
*/
public class ArrayQueue {
private int maxSize; // 表示数组的最大容量
private int front; // 队列的头部
private int rear; // 队列的尾部
private int[] arr; // 数组用于存放数据
public ArrayQueue(int arrMaxSize) {
if (arrMaxSize < 3) { //数组最小长度是 3
arrMaxSize = 3;
}
maxSize = arrMaxSize;
arr = new int[arrMaxSize];
front = -1;// 指向队列的头部, 指向队列头的前一个位置
rear = -1; // 指向队列尾部,指向队列尾的数据,也就是队列的最后一个数据
}
private boolean isFull() { // 判断队列是否已经满了
return rear == maxSize - 1;
}
private boolean isEmpty() { // 判读队列是否为空
return front == rear;
}
// 添加数据到队列
public void addQueue(int n) {
if (isFull()) {
System.out.println("队列已经满了");
return;
}
rear++; // 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.println(String.format("arr[%d] = %d", i, arr[i]));
}
}
// 显示队列的头数据 不是取出数据
public int getHead() {
if (isEmpty()) {
throw new RuntimeException("队列为空");
}
return arr[front + 1];
}
}
class Client {
public static void main(String[] args) {
// 创建一个队列
ArrayQueue queue = new ArrayQueue(4);
char key = ' ';//用于接收用户输入
Scanner scanner = new Scanner(System.in);
boolean flag = true;
while (flag) {
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 'e':
scanner.close();
flag = false;
break;
case 'a':
System.out.println("请输入一个数");
int i = scanner.nextInt();
queue.addQueue(i);
break;
case 'g':
try {
int result = queue.getQueue(); // 处理一下异常 避免程序直接结束
System.out.println("取出的数据为:" + result);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'h':
try {
int result = queue.getHead();
System.out.println("查看队列头的数据:" + result);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
default:
break;
}
}
System.out.println("程序退出");
}
}
目前数组仅利用一次,没有达到空间复用的效果。
数组模拟环形队列
思路分析
1、front指针的含义调整为->front指向队列的第一个元素,也就是arr[front]就是队列的第一个元素。
2、real指针的含义调整为->rear指向队列的对后一个元素的后一个位置,原因是希望空出一个空间做约定。
3、当队列满时,条件是 rear == maxSize-1 调整为 (rear +1) % maxSize == front 表示队列满了。
4、 队列为空的条件是 rear == front 是空。
5、rear 和 front 的初始值都为0。
6、队列中有效的数据的个数 为 (rear + maxSize - front)%maxSize <==> Math.abs(rear-front) % maxSize
7、可以将原来的队列修改转换成为环形队列
代码实现
class Client {
public static void main(String[] args) {
// 创建一个队列
RingArrayQueue queue = new RingArrayQueue(4);
char key = ' ';//用于接收用户输入
Scanner scanner = new Scanner(System.in);
boolean flag = true;
while (flag) {
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 'e':
scanner.close();
flag = false;
break;
case 'a':
System.out.println("请输入一个数");
int i = scanner.nextInt();
queue.addQueue(i);
break;
case 'g':
try {
int result = queue.getQueue(); // 处理一下异常 避免程序直接结束
System.out.println("取出的数据为:" + result);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'h':
try {
int result = queue.getHead();
System.out.println("查看队列头的数据:" + result);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
default:
break;
}
}
System.out.println("程序退出");
}
}
class RingArrayQueue {
private int maxSize; // 表示数组的最大容量
// front指针的含义调整为->front指向队列的第一个元素,也就是arr[front]就是队列的第一个元素。
private int front; // 队列的头部 初始值为0
// real指针的含义调整为->rear指向队列的对后一个元素的后一个位置,原因是希望空出一个空间做约定。
private int rear; // 队列的尾部 初始值为0
private int[] arr; // 数组用于存放数据 这里需要预留一个空的空间去应对环形的问题
// 实际存放的元素个数 是 arrMaxSize-1 需要约定一个空的位置 而这个空的位置是会移动的。
public RingArrayQueue(int arrMaxSize) {
maxSize = arrMaxSize;
arr = new int[arrMaxSize];
}
private boolean isFull() { // 判断队列是否已经满了
return (rear + 1) % maxSize == front; // 预留出一个空的空间 需要 +1
}
private boolean isEmpty() { // 判读队列是否为空
return front == rear;
}
// 添加数据到队列
public void addQueue(int n) {
if (isFull()) {
System.out.println("队列已经满了");
return;
}
// 直接将数据加入
arr[rear] = n;
// 将rear后移 但是需要考虑环形 也就是取模
rear = (rear + 1) % maxSize;
}
// 取出数据
public int getQueue() {
if (isEmpty()) {
throw new RuntimeException("队列是空的");
}
// 这里需要分析出 front 是指向队列的第一个元素
// 先把front 对应的值保留到一个临时变量 将front后移 然后将临时变量返回
int value = arr[front];
front = (front + 1) % maxSize;
return value;
}
// 显示队列的所有数据
public void showQueue() {
if (isEmpty()) {
System.out.println("队列为空");
return;
}
// 从 front 开始遍历 遍历多少个元素
for (int i = front; i < front + size(); i++) {
System.out.println(String.format("arr[%d] = %d", i % maxSize, arr[i % maxSize]));
}
}
// 求出当前队列有效数据的个数
public int size() {
return (rear + maxSize - front) % maxSize;
}
// 显示队列的头数据 不是取出数据
public int getHead() {
if (isEmpty()) {
throw new RuntimeException("队列为空");
}
return arr[front];
}
}