1.数据结构分类
数据结构包括:线性结构和非线性结构
-
线性结构:
1)线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系
2)线性结构有两种不同的存储结构,即顺序存储结构和链式存储结构。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的
3)链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息
4)线性结构常见的有:数组、队列、链表和栈
-
非线性结构:数据元素之间不是一对一的线性关系
1)常见结构:二维数组,多维数组,广义表,树结构,图结构
2.稀疏数组
(1)提出需求:在编写的五子棋程序中,实现存盘退出和续上盘的功能
(2)存在问题:用二维数组记录时很多默认值为0,这些数据是没有意义的。---->稀疏数组
(3)稀疏数组的处理方法:
- 记录数组一共有几行几列,有多少个不同的值
- 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
[0]行记录数据的规模及非有效数值的个数,后面分别进行对每个数值进行描述。
(4)应用实例
- 使用稀疏数组,来保留类似前面的二维数组(棋盘、地图等)
- 把稀疏数组存盘,并且可以重新恢复原来的二维数组
- 思路分析:
二维数组转稀疏数组的思路:
- 遍历原始的二维数组,得到有效数据的个数sum
- 根据sum创建稀疏数组sparseArr int[sum + 1][3]
- 将二维数组的有效数据存入到稀疏数组中
稀疏数组转二维数组的思路:
- 先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组
- 再读取稀疏矩阵后几行数据,并赋给原始的二维数组
(5)代码实现
package com.atlige;
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
import org.junit.Test;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
/**
* @author Jeremy Li
* @data 2020/12/26 - 18:10
*/
public class SparseArray {
@Test
public void test1() throws Exception {
// 先创建一个原始二维数组 11*11
// 2:蓝子,1:黑子,0:没有棋子
int chessArr1[][] = new int[11][11];
chessArr1[1][2] = 1;
chessArr1[2][3] = 2;
// 输出
System.out.println("原始的二维数组:");
printArr(chessArr1);
// 将二维数组转稀疏数组的思路
int[][] sparseArr = createSparseArr(chessArr1);
// 输出稀疏数组的形式
System.out.println("稀疏数组如下:");
printArr(sparseArr);
System.out.println();
// 稀疏数组转原始二维数组的思路
int[][] chessArr2 = convertTargetArr(sparseArr);
// 恢复后的二维数组
System.out.println("恢复后的二维数组如下:");
printArr(chessArr2);
// 我的写入方式!!!!!!!
String fileName = "data.txt";
saveArr(fileName, sparseArr);
// 写出并遍历
int[][] newSparseArray = readArr(fileName);
printArr(newSparseArray);
}
/**
* 遍历二维数组
* @param arr
*/
public static void printArr(int[][] arr){
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[0].length; j++) {
System.out.print(arr[i][j] + "\t");
}
System.out.println();
}
}
/**
* 将稀疏矩阵写入文件
* @param fileName 文件名
* @param sparseArr 稀疏矩阵
* @throws Exception
*/
public static void saveArr(String fileName, int[][] sparseArr) throws IOException {
FileWriter fw = new FileWriter(fileName);
for (int[] ints : sparseArr){
for (int i : ints){
fw.write(i + "\t");
}
fw.write("\n");
}
fw.close();
}
/**
* 将文件中的稀疏矩阵读到内存中
* @param fileName 文件名
* @return newSparseArray 获得的稀疏矩阵
* @throws IOException
*/
public static int[][] readArr(String fileName) throws IOException {
List<String> list = Files.readAllLines(Paths.get(fileName));
// 获取稀疏矩阵的行数
int size = list.size();
int[][] newSparseArray = new int[size][3];
String[] split = null;
for (int i = 0; i < size; i++) {
split = list.get(i).split("\t");
for (int j = 0; j < split.length; j++) {
newSparseArray[i][j] = Integer.parseInt(split[j]);
}
}
return newSparseArray;
}
/**
* 获得目标数组中的有效数字个数(非零)
* @param targetArr 目标数据
* @return
*/
public static int realValNum(int[][] targetArr){
int sum = 0;
for (int i = 0; i < targetArr.length; i++) {
for (int j = 0; j < targetArr[0].length; j++) {
if (targetArr[i][j] != 0) {
sum++;
}
}
}
return sum;
}
/**
* 根据目标矩阵创建一个相对应的稀疏矩阵
* @param targetArr 目标矩阵
* @return
*/
public static int[][] createSparseArr(int[][] targetArr){
// 1.先遍历二维数组得到非0数据的个数
int num = realValNum(targetArr);
// 2.创建对应的稀疏数组
int[][] sparseArr = new int[num + 1][3];
// 3.给稀疏数组赋值
sparseArr[0][0] = targetArr.length;
sparseArr[0][1] = targetArr[0].length;
sparseArr[0][2] = num;
// 4.遍历二维数组,将非0值存放到sparseArr中
// count用于记录是第几个非0数据
int count = 0;
for (int i = 0; i < targetArr.length; i++) {
for (int j = 0; j < targetArr[0].length; j++) {
if (targetArr[i][j] != 0) {
count++;
sparseArr[count][0] = i;
sparseArr[count][1] = j;
sparseArr[count][2] = targetArr[i][j];
}
}
}
return sparseArr;
}
/**
* 将稀疏矩阵转换为原始矩阵
* @param sparseArr 稀疏矩阵
* @return
*/
public static int[][] convertTargetArr(int[][] sparseArr){
// 1. 先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组
int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];
// 2. 读取稀疏数组后几行的数据(第二行开始),并赋给原始的二维数组
for (int i = 1; i < sparseArr.length; i++) {
chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
}
return chessArr2;
}
}
3.队列
- 队列是一个有序列表,可以用数组或是链表来实现。
- 遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出
- 示意图:(使用数组模拟队列示意图)
(1)数组模拟队列
maxSize 是该队列的最大容量,因为队列的输出、输入是分别从前后端来处理,需要两个变量front及 rear分别记录队列前后端的下标。
需注意:
-
将尾指针往后移:rear+1 , 当front == rear 【空】
-
若尾指针 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 maxSize){
this.maxSize = maxSize;
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++; // 让rear后移
arr[rear] = n;
}
// 获取队列的数据,出队列
public int getQueue(){
// 判断队列是否空
if (isEmpty()){
// 通过抛出异常来处理
throw new RuntimeException("队列为空,没有数据!");
}
front++; // 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];
}
}
存在问题:
- 目前数组使用一次就不能再次使用,没有达到复用的效果
- 将这个数组使用算法,改进成一个环形的数组 取模:%
(2)数组模拟环形队列
思路分析:
- front变量:指向队列的第一个元素,初始值为0
- rear变量:指向队列的的最后一个元素的后一个位置,空出一个位置作为约定
- 队满的判断条件:(rear + 1)% maxSize == front
- 一定要注意是环形结构,所以要考虑取模
- 由于空出一个位置,rear本来就是最后一个元素后一个位置,所以当队满的时候,rear+1时必然与front相等,取模是因为在front==0时,rear+1 == maxSize,但是所指的位置是相同的。
- 队空的判断条件:rear == front
- 考虑只有一个元素时,取出之后,front = front+1, 故front == rear
代码实现
package com.atlige;
import java.util.Scanner;
/**
* @author Jeremy Li
* @data 2020/12/26 - 21:39
* 使用数组模拟队列
*/
public class CircleArrayQueueDemo {
public static void main(String[] args) {
// 创建环形队列
ArrayQueue1 queue = new ArrayQueue1(4);
char key = ' '; // 接收用户输入
Scanner scanner = new Scanner(System.in);
boolean loop = true;
// 菜单
while (loop){
System.out.println("s(show): 显示队列");
System.out.println("a(add): 添加数据到队列");
System.out.println("g(get): 从队列取出数据");
System.out.println("h(head): 查看队列头的数据");
System.out.println("e(exit): 退出程序");
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 ArrayQueue1{
private int maxSize; // 最大容量
private int front; // 队列头, 初始值为0
private int rear; // 队列尾, 初始值为0
private int[] arr; // 该数组用于存放数据,模拟队列
private int temp;
public ArrayQueue1(int maxSize){
this.maxSize = maxSize;
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("队列为空,没有数据!");
}
temp = arr[front];
front = (front + 1) % maxSize;
return temp;
}
// 显示队列所有数据
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];
}
}