2、队列(输入元素头指针front不变,尾指针rear+1;输出元素尾指针rear不变,头指针front+1)
一、线性结构和非线性结构
数据结构包括:线性结构和非线性结构。
1、线性结构
- 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系;
- 线性结构有两种不通的存储结构,即顺序存储结构(数组)和链式存储结构(链表)。顺序存储的线性表称为顺序表,顺序表总的存储元素是连续的;
- 链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息;
- 线性结构常见的有:数组、队列、链表和栈。
2、非线性结构
非线性结构包括:二维数组、多维数组、广义表、树结构、图结构。
二、稀疏数组和队列
1、稀疏 sparsearray 数组
(1)、先看一个实际的需求
- 编写的五子棋程序中,有存盘退出和续上盘的功能
- 分析问题:因为该二维数组的很多值是默认值 0, 因此记录了很多没有意义的数据.->稀疏数组。
(2)、基本介绍
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
稀疏数组的处理方法是:
- 记录数组一共有几行几列,有多少个有效值;
- 把有效值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
稀疏数组举例说明:
(3)、应用实例
- 使用稀疏数组,来保留类似前面的二维数组(棋盘、地图等等)
- 把稀疏数组存盘,并且可以从新恢复原来的二维数组数
- 整体思路分析:
- 代码实现
package com.narwal.sparsearray;
import java.io.*;
public class SparseArray {
public static void main(String[] args) throws IOException {
int[][] chessArr1 = new int[11][11];
chessArr1[1][2] = 1;
chessArr1[2][3] = 2;
System.out.println("原始数组");
for (int[] row : chessArr1) {
for (int data : row) {
System.out.printf("%d\t", data);
}
System.out.println();
}
/*
* 原始数组转稀疏数组
*/
// 1、遍历原始二维数组,得到有效数据的个数sum;
int sum = 0;
for (int i = 0; i < chessArr1.length; i++) {
for (int j = 0; j < chessArr1[0].length; j++) {
if (chessArr1[i][j] != 0) {
sum++;
}
}
}
// 2、创建稀疏数组
int[][] sparseArr = new int[sum + 1][3];
sparseArr[0][0] = chessArr1.length;
sparseArr[0][1] = chessArr1[0].length;
sparseArr[0][2] = sum;
int count = 0;
// 3、将有效数据填入稀疏数组
for (int i = 0; i < chessArr1.length; i++) {
for (int j = 0; j < chessArr1[0].length; j++) {
if (chessArr1[i][j] != 0) {
count++;
sparseArr[count][0] = i;
sparseArr[count][1] = j;
sparseArr[count][2] = chessArr1[i][j];
}
}
}
System.out.println("稀疏数组");
for (int[] row : sparseArr) {
for (int data : row) {
System.out.printf("%d\t", data);
}
System.out.println();
}
File file = new File("/home/wise/Music/map.data"); //存放数组数据的文件
// 将稀疏数组存入到文件中
writeArr(sparseArr, file);
// 从文件中读取二维数组
readArr(file);
/*
* 稀疏数组转原始数组
*/
// 1、读取第一行获取原始数组的行和列
int[][] chessArr2 = new int[sparseArr[0][0]][sparseArr[0][1]];
// 2、将有效数据填入原始数组
for (int i = 1; i <= sum; i++) {
chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
}
System.out.println("转回后的二维数组");
for (int[] row : chessArr2) {
for (int data : row) {
System.out.printf("%d\t", data);
}
System.out.println();
}
}
private static void readArr(File file) throws IOException {
int[][] sparseArr = new int[3][3];
BufferedReader in = new BufferedReader(new FileReader(file));
String line;
int row = 0;
while((line=in.readLine()) !=null){
String[] temp = line.split("\t");
for(int j=0;j<temp.length;j++){
sparseArr[row][j] = Integer.parseInt(temp[j]);
}
row ++;
}
in.close();
System.out.println("从文件中读取出来的稀疏数组:");
for(int[] row1:sparseArr){
for(int data:row1){
System.out.printf("%d\t", data);
}
System.out.println();
}
}
private static void writeArr(int[][] sparseArr, File file) throws IOException {
FileWriter out = new FileWriter(file);
for (int[] row : sparseArr) {
for (int data : row) {
out.write(data + "\t");
}
out.write("\n");
}
out.close();
}
}
2、队列(输入元素头指针front不变,尾指针rear+1;输出元素尾指针rear不变,头指针front+1)
(1)、队列介绍
- 队列是一个有序列表,可以用数组或是链表来实现;
- 遵循先入先出的原则;
- 示意图:(使用数组模拟队列示意图)
(2)、数组模拟队列思路
- 队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图, 其中 maxSize 是该队
列的最大容量。 - 因为队列的输出、输入是分别从前后端来处理,因此需要两个变量 front 及 rear 分别记录队列前后端的下标,
front 会随着数据输出而改变,而 rear 则是随着数据输入而改变,如图所示:
-
当我们将数据存入队列是称为“addQueue”,addQueue的处理需要两个步骤:思路分析
1)将尾指针往后移:rear+1,当front == rear, 则表示队列为空;
2)若尾指针rear小于队列的最大小标maxSize-1,则表示队列未满,可以添加数据,否则无法添加数据。
rear == maxSize-1 [队列满]
- 代码实现
package com.narwal.queue;
// 添加数据是front不变,获取数据是rear不变
import java.util.Scanner;
public class ArrayQueueDemo {
public static void main(String[] args) {
ArrayQueue queue = new ArrayQueue(4);
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 rear;
private int front;
private int[] arr;
public ArrayQueue(int arrMaxSize) {
maxSize = arrMaxSize;
arr = new int[maxSize];
front = -1; // 指向队列头的前一个位置
rear = -1; // 指向队列尾,即arr[rear]就是队列尾的数据
}
// 判断队列已满
public boolean isFull() {
return rear == maxSize - 1;
}
// 判断队列是否为空
public boolean isEmpty() {
return rear == front;
}
// 向队列中添加数据
public void addQueue(int n) {
if (isFull()) {
throw new RuntimeException("队列已满");
}
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 < rear-front; i++) {
System.out.printf("arr[%d]=%d\n", i, arr[i]);
}
}
// 显示队列头数据
public int headQueue() {
if (isEmpty()) {
throw new RuntimeException("队列已空");
}
return arr[front + 1];
}
}
(3)、数组模拟环形队列(取模的方式来实现)
- 分析说明:
1)尾索引的下一个为头索引时表示队列满,即将队列容量空出一个作为约定,这个在做判断队列满的
时候需要注意 (rear + 1) % maxSize == front 满]
2) rear == front [空]
3) 分析示意图:
4)代码实现:
package com.narwal.queue;
import java.util.Scanner;
public class CircleArrayQueueDemo {
public static void main(String[] args) {
CircleArrayQueue queue = new CircleArrayQueue(4);
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 CircleArrayQueue {
private int maxSize;
private int rear;
private int front;
private int[] arr;
public CircleArrayQueue(int arrMaxSize) {
maxSize = arrMaxSize;
arr = new int[maxSize];
front = 0; // front 就指向队列的第一个元素
rear = 0; // 指向队列的最后一个元素的后一个位置. 因为希望空出一个空间做为约定.
}
// 判断队列已满
public boolean isFull() {
return (rear + 1) % maxSize == front;
}
// 判断队列是否为空
public boolean isEmpty() {
return rear == front;
}
// 向队列中添加数据
public void addQueue(int n) {
if (isFull()) {
throw new RuntimeException("队列已满");
}
arr[rear] = n;
rear = (rear + 1) % maxSize;
}
// 获取数据
public int getQueue() {
if (isEmpty()) {
throw new RuntimeException("队列已空");
}
int value = arr[front];
front = (front + 1) % maxSize;
return value;
}
// 显示队列的所有数据
public void showQueue() {
// 遍历
if (isEmpty()) {
System.out.println("队列已空");
return;
}
for (int i = front; i < (rear + maxSize - front) % maxSize; i++) {
System.out.printf("arr[%d]=%d\n", i % maxSize, arr[i % maxSize]);
}
}
// 显示队列头数据
public int headQueue() {
if (isEmpty()) {
throw new RuntimeException("队列已空");
}
return arr[front];
}
}
(4)、总结
- 对于队列要先定义好头指针和尾指针的含义,如:
1)单向队列:
front = -1; // 指向队列头的前一个位置
rear = -1; // 指向队列尾,即arr[rear]就是队列尾的数据
2)循环队列
front = 0; // 就指向队列的第一个元素
rear = -1; // 指向队列的最后一个元素的后一个位置. 因为希望空出一个空间做为约定.