目录
一、基本介绍
数据结构包括:线性结构和非线性结构。
1.1 线性结构
- 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系
- 线性结构有两种不同的存储结构,即顺序存储结构(数组)和链式存储结构(链表)。顺序存储的线性表称为顺序表,顺序表中的存储元素的地址是连续的;链式存储结构中元素的地址不一定连续
- 链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息(链表可以充分的利用内存)
- 线性结构常见的有:数组、队列、链表和栈
1.2 非线性顺序结构
非线性结构包括:二维数组、多维数组、广义表、树结构、图结构(这两种引伸出来了很多算法)
二、稀疏数组
2.1 基本介绍
当一个数组中大部分元素是0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组
处理方法:
- 记录数组一共几行几列,有多少个不同的值
- 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
我们看看下面的图,看看稀疏数组到底是怎么回事
如果我们使用左侧的的二维数组直接记录,我们会有6*7=42个数据。
我们看一下右侧的稀疏数组,一打眼就没有42个数据。变成了九行三列 9*3=27个数据
稀疏数组是怎么表示的呢?
- 表头是行、列、值(利用行、列就可以确定具体的位置,初中的平面坐标系)
- 第一行记录多少总行数,总列数,有多少个不同的值
- 下面便开始记录数据,比如第二行,表示第零行(数组是从0开始的,实际是第一行),第三列(左图第四列),此值是22
- 依次表示出来,只记录非0值的行列值
2.1.1 应用场景
使用稀疏数组,来保留类似前面的二维数组(棋盘、地图等)
把稀疏数组存盘,并且可以从新恢复到原来的二维数组
整体思路分析
2.1.2 实现思路
二维数组 转 稀疏数组的思路
1. 遍历 原始的二维数组,得到有效数据的个数 sum
2. 根据sum 就可以创建 稀疏数组 sparseArr int[sum + 1] [3]
3. 将二维数组的有效数据数据存入到 稀疏数组
稀疏数组转原始的二维数组的思路
1. 先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组,比如上面的 chessArr2 = int [11][11]
2. 在读取稀疏数组后几行的数据,并赋给 原始的二维数组 即可.
2.2 代码实现
2.2.1 原始数组
创建二维数组实现上面的那个图,1代表黑子,2表示蓝子
// 0表示无子,1表示黑子,2表示蓝子
int chessArr1[][] = new int[11][11];
chessArr1[1][2] = 1;
chessArr1[2][3]=2;
// 输出原始二维数组
for(int[] row:chessArr1){
for(int data:row){
// %d +制表符
System.out.printf("%d\t",data);
}
System.out.println();
}
2.2.2 原始数组转化为稀疏数组
// 转化成稀疏数组
// 遍历二维数组,找到非零数据的个数 chessArr1[0].length列数,chessArr1.length行数
int sum = 0;
for (int i=0;i<11;i++){ //行
for (int j=0;j<11;j++){ //列
if(chessArr1[i][j]!=0){
sum++;
}
}
}
// 创建稀疏数组 sum+1表示列数
int sparseArr[][] = new int[sum+1][3];
sparseArr[0][0] =11; //行
sparseArr[0][1]=11; //列
sparseArr[0][2]=sum; //不同值的个数
// 遍历二维数组,将二维数组中的有效值存放到稀松数组中
int count =0;
for (int i=0;i<11;i++){
for (int j=0;j<11;j++){
if(chessArr1[i][j]!=0){
count++;
sparseArr[count][0]=i;
sparseArr[count][1]=j;
sparseArr[count][2]=chessArr1[i][j];
}
}
}
// 输出稀疏数组的形式
for (int i=0;i<sum+1;i++){
for (int j=0;j<3;j++){
System.out.print(sparseArr[i][j]+"\t");
}
System.out.println();
}
2.2.3 稀疏数组转化为原始数组
int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];
// 遍历稀疏数组,取数据
for (int i=1 ; i<sparseArr.length;i++){
chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
}
for(int[] row:chessArr2){
for(int data:row){
// %d +制表符
System.out.printf("%d\t",data);
}
System.out.println();
}
三、队列的应用场景和介绍
有序列表,可以用数组或者链表来实现
遵循先入先出的原则。即:先存入队列的数据先要取出,后存入的要后取出(队首取数据、队尾存数据)
我们可以仔细看一下,下面的这个图,front代表队列首(头)的前一个位置(初始时指向第一个元素之前,所以是-1),rear代表队列尾,初始时都是-1
当数据进来的时候,我们发现front还是-1,rear跟着新加入的元素移动,指向新元素
当数据出去的时候,此时我们的front开始移动了,并且执行现如今最早加入的元素
简言之:front随着数据输出而改变,rear随着数据的增加而改变
3.1 数组模拟队列
3.1.1数组模拟队列的分析
我们将数据存入队列时称为“addQueue”,其中处理需要两个步骤
- 将尾指针往后移动:rear+1,当front == rear ,此时队列为空
- 若尾指针rear小于队列的最大下标maxSize-1,则将数据存入rear所指的数组元素中,否则无法存入数据。 rear==maxSize-1 时,队列存满
3.1.2 代码实现数组模拟队列
虽然我们这个地方模拟成功了,但是这个数组只能用一次,我们后面会讲环型数组,就真正的实现了队列
class ArrayQueue{
private int maxSize; //数组最大容量
private int front; //队列头
private int rear; //队列尾
private int[] arr; //该数据用于存放数据,模拟队列
public ArrayQueue(int maxSize) {
this.maxSize = maxSize;
this.front=-1; //初始时指向队列头部的前一个位置
this.rear=-1; //执行队列的尾部,即就是队列的最后一个数据
arr = new int[maxSize];
}
// 判断队列是否满
public boolean isFull(){
// 相等true就是满了
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++; //后移动
return arr[front];
}
// 展示队列的所有数据
public void showQueue(){
// 遍历
if(isEmpty()){
System.out.println("队列空的,无法遍历");
return;
}
for(int i =0;i<arr.length;i++){
System.out.println(arr[i]);
}
}
// 显示队列的头数据,不是取出数据
public int headQueue(){
if (isEmpty()){
System.out.println("队列空,没有数据");
throw new RuntimeException("队列空,没有数据");
}
return arr[front+1];
}
}
测试程序
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{
int res = arrayQueue.getQueue();
System.out.println("取出的数据是:"+res);
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
case 'h':
try{
int res = arrayQueue.headQueue();
System.out.println("队列头的数据是"+res);
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
case 'e':
scanner.close();
loop=false;
break;
default:
break;
}
}
System.out.println("程序退出");
}
}
3.2 环型队列思路分析图
刚刚我们用数组实现的队列只能使用一次,没有达到复用的效果,下面我们将这个数组使用算法,改进成一个环型的队列
-
front 变量的含义做一个调整: front 就指向队列的第一个元素, 也就是说 arr[front] 就是队列的第一个元素 front 的初始值 = 0
-
rear 变量的含义做一个调整:rear 指向队列的最后一个元素的后一个位置. 因为希望空出一个空间做为约定(这个地方一定要读明白,有些算法可以不预留,看个人习惯)。rear 的初始值 = 0
-
当队列满时,条件是 (rear + 1) % maxSize == front 【满】
-
对队列为空的条件, rear == front 空
-
当我们这样分析, 队列中有效的数据的个数 (rear + maxSize - front) % maxSize // rear = 1 front = 0
先解释一下上面队列满的时候的条件(rear + 1) % maxSize == front 【满】
我们直接把环型队列想想成一个环状,看下面的分析
按照上面进行修改,就可以得到一个环型队列
3.2.1 环型队列实现
通过取模的方式实现
class CircleArray{
private int maxSize; //数组最大容量
private int front ; //队列头 执行第一个元素,初始值0
private int rear=0; //队列尾 执行最后一个元素的下一个位置,初始值0
private int[] arr; //该数据用于存放数据,模拟队列
public CircleArray(int maxSize) {
this.maxSize = maxSize;
this.front=0; //初始时指向队列头部的前一个位置
this.rear=0; //执行队列的尾部,即就是队列的最后一个数据
arr = new int[maxSize];
}
// 判断队列是否满
public boolean isFull(){
// 相等true就是满了
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 = (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(arr[i%maxSize]);
}
}
// 求出当前队列的有效数据个数
public int size(){
return (rear+maxSize-front)%maxSize;
}
// 显示队列的头数据,不是取出数据
public int headQueue(){
if (isEmpty()){
System.out.println("队列空,没有数据");
throw new RuntimeException("队列空,没有数据");
}
return arr[front];
}
}