1、线性结构与非线性结构
数据结构包括:线性结构与非线性结构
线性结构
- 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系
- 线性结构有两种不同的存储结构,即顺序存储结构(数组)和链式存储结构(链表)。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的
- 链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息
- 线性结构常见的有:数组、队列、链表和栈。
非线性结构
非线性结构包括:二维数组、多维数组,广义表,树结构,图结构。
2、稀疏数组
基本介绍 ·
当一个数组中大部分元素为0,或者为同一个值的数组时,可以用稀疏数组来保存该数组。
稀疏数组的处理方法:
- 记录数组一共有几行几列,有多少个不同的值
- 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
应用实例
- 使用稀疏数组,来保留类似前面的二维数组(棋盘,地图)
- 把稀疏数组存盘,并且可以重新恢复原来的二维数组
- 整体思路分析
/*
二维数组转稀疏数组的思路
1、遍历原始的二维数组,得到有效数据的个数sum
2、根据sum就可以创建稀疏数组sparseArr int[sum+1][3]
3、将二维数组的有效数据存入到稀疏数组
稀疏数组转原始的二维数组的思路
1、先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组,比如上面的chessArr2=iny[11][11]
2、再读取稀疏数组后面几行的数据,并复制给原始的二维数组即可
*/
稀疏数组的代码实现
代码实现
package A稀疏数组;
import java.util.Arrays;
/**
* @Author Zhou jian
* @Date 2019 ${month} 2019/12/24 0024 15:23
*/
public class SparseArray
{
public static void main(String[] args) {
/*
创建一个原始的二维数组11*11
0:表示没有旗子
1:表示黑子
2:表示蓝字
*/
//构建数组,并在非0数据处存放元素
int[][] chessArr = new int[11][11];
chessArr[1][2]=1;
chessArr[2][3]=2;
//输出原始的二维数组
for(int[] row:chessArr){
for(int data:row){
System.out.printf("%d\t",data);
}
System.out.println();
}
/**
* 将二维数组转换为稀疏数组:
* 1、先遍历二维数组得到非0数据的个数
* 2、根据sum就可以创建稀疏数组sparseArr int[sum+1][3]
* 3、将二维数组的有效数据存入到稀疏数组
*/
int sum = 0;
for(int[] row:chessArr){
for(int data:row){
if(data!=0){
sum++;
}
}
}
//创建对应的稀疏数组
int[][] sparseArr = new int[sum+1][3];
sparseArr[0][0]= chessArr.length;
sparseArr[0][1]= chessArr[0].length;
sparseArr[0][2]= sum;
//遍历二维数组,将非0的值存入稀疏数组中
int count =1; //count用于记录第几个非0数据
for(int row =0;row<chessArr.length;row++){
for(int col=0;col<chessArr[0].length;col++){
if(chessArr[row][col]!=0){
sparseArr[count][0]=row;
sparseArr[count][1]=col;
sparseArr[count][2]=chessArr[row][col];
count++;
}
}
}
//输出稀疏数组
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]);
}
/**
* 将稀疏数组恢复成原始的二维数组
* 1、先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组,比如上面的chessArr2=iny[11][11]
2、再读取稀疏数组后面几行的数据,并复制给原始的二维数组即可
*/
int[][] chessArry2 = new int[sparseArr[0][0]][sparseArr[0][1]];
for(int i=1;i<sparseArr.length;i++){
chessArry2[sparseArr[i][0]][sparseArr[i][1]]=sparseArr[i][2];
}
System.out.println("恢复的原始数组为");
for(int[] row:chessArry2){
for(int data:row){
System.out.printf("%d\t",data);
}
System.out.println();
}
}
}
3、队列
基本介绍
- 队列是一个有序列表,可以用数组或是链表来实现。
- 遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出(出入口在两个方向)
数组模拟简单队列
-
队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图,其中maxSize是该队列的最大容量
-
因为队列的输出、输入分别是从前后两端来处理,因此需要两个变量front及rear分别记录队列前后两端的下标,front会随着数据输出而改变,rear会随着数据输入而改变
package B队列; import java.util.Scanner; /** * @Author Zhou jian * @Date 2019 ${month} 2019/12/24 0024 16:16 */ public class ArrayQueueDemo { public static void main(String[] args) { //创建一个队列 ArrayQueue arrayQueue = 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': arrayQueue.showQueue(); break; case 'a': System.out.println("输入一个数"); int value = scanner.nextInt(); arrayQueue.addQueue(value); break; case 'g': try{ System.out.println("取出的数据是"+arrayQueue.getQueue()); }catch (Exception e){ System.out.println(e); } break; case 'h': try { System.out.println("取出的头部数据为"+arrayQueue.headQueue()); }catch (Exception e){ System.out.println(e.getMessage()); } break; case 'e': scanner.close(); loop = false; } } } } //使用数组模拟队列--编写一个ArrayQueue 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; //指向队列头部,分析出front是指向队列头的前一个位置(不包含) rear =-1; //指向队列尾部,指向队列尾部的具体数据(即包含,就是队列的最后一个数据) } //判断队列是否满 public boolean isFull(){ return rear==maxSize-1; } //判断队列是否为空 public boolean isEmpty(){ return rear==front; } //添加数据到队列 public void addQueue(int data){ //判断队列是否满 if(isFull()) { System.out.println("队列满。不能加入数据~"); return; }else{ rear++; arr[rear]=data; } } //获取队列数据出队列 public int getQueue(){ if(isEmpty()){ System.out.println("队列为空"); throw new RuntimeException("队列空,不能取数据"); }else{ front++; //front后羿 return arr[front]; } } //显示队列的所有数据:不是 public void showQueue(){ //遍历 if(isEmpty()){ System.out.println("队列空的,没有数据"); return; }else{ 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("队列为空"); }else{ return arr[front+1]; } } }
问题分析并优化
目前数组使用一次就不能复用
将这个数组使用算法,改进成一个环形的数组:取模实现
数组模拟环形队列
对前面的数组模拟队列的优化,充分利用数组,因此将数组看做是一个环形的(通过取模的方式来实现即可)
思路如下
-
front变量的含义做一个调整:front指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素;front的初始值为0
-
rear的变量的含义做一个调整:rear指向队列的最后一个元素的后一个位置,因为希望空出一个空间作为一个约定,rear的初始值0
-
当队列满时,条件是:(rear+1)%maxSize=front(思考为甚么要取模)
-
当队列空时,条件是:rear等于front
-
当我们这样分析时,队列中的有效数据个数==(rear+maxSize-front)%maxSize==
环形队列中需要预留一个空间用来判断队列是否满(尾索引的下一个为头索引时表示队列满,即将队列容量空出一个做为约定);也可以用其他的算法实现,但是本实现中采用此种思路
思考:若没有预留空间会出现什么情况??则无法判断队列空与满的情况
package B队列;
import java.util.Scanner;
/**
* @Author Zhou jian
* @Date 2019 ${month} 2019/12/24 0024 17:25
环形的数组队列实现
*/
public class CircleArrayDemo {
public static void main(String[] args) {
System.out.println("测试数组模拟环形队列");
//创建一个队列,最多存储三个数据
CircleArray circleArray = new CircleArray(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':
circleArray.showQueue();
break;
case 'a':
System.out.println("输入一个数");
int value = scanner.nextInt();
circleArray.addQueue(value);
break;
case 'g':
try{
System.out.println("取出的数据是"+circleArray.getQueue());
}catch (Exception e){
System.out.println(e);
}
break;
case 'h':
try {
System.out.println("取出的头部数据为"+circleArray.headQueue());
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
case 'e':
scanner.close();
loop = false;
}
}
}
}
class CircleArray{
//表示数组最大容量
private int maxSize;
//front变量的含义做一个调整:front指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素;
// ==front的初始值为0==
private int front;
//rear的变量的含义做余个调整:rear指向队列的最后一个元素的后一个位置,因为希望空出一个空间作为一个约定,
// ==rear的初始值=0==
private int rear;
private int[] arr;//该数组用于存放数据
public CircleArray(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 data){
//判断队列是否满
if(isFull()) {
System.out.println("队列满。不能加入数据~");
return;
}else{
//因为rear指向最后一个元素的下一个位置
//直接将数据加入
arr[rear]=data;
//将rear后羿,必须考虑取模
rear = (rear+1)%maxSize;
}
}
//获取队列数据出队列
public int getQueue(){
if(isEmpty()){
System.out.println("队列为空");
throw new RuntimeException("队列空,不能取数据");
}else{
//这里需要分析出,front是指向队列的第一个元素
//先把front对应的值保存到一个临时变量
int value = arr[front];
//将front后羿
front = (front+1)%maxSize;
//将临时保存的变量返回
return value;
}
}
//显示队列的所有数据:不是
public void showQueue(){
//遍历
if(isEmpty()){
System.out.println("队列空的,没有数据");
return;
}else{
//从front开始遍历,遍历多少个元素
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-front+maxSize)%maxSize;
}
//显示队列的头数据,不是取数据
public int headQueue(){
if(isEmpty()){
throw new RuntimeException("队列为空");
}else{
return arr[front];
}
}
}
------------------------------------2019/12/25