尚硅谷数据结构-学习笔记
-------------队列的基本概念
队列是一个有序列表(Ordered list),可以用数组(Array)和链表(Linked List)来实现
队列遵循先入先出的原则,即:先存入队列的数据,要先取出。后存入的要后取出
【图例1】
front是队列的头部指针,
rear就是队列的尾部指针
将两个指针的初始值都设为-1
换句话说:
arr[front]指向的是数组第一个数的前一个位置
arr[rear]指向的是数组的最后一个数
当往队列添加数据的时候,尾部指针开始逐1增加,但是头部指针不变;
当从队列中取出数据时,头部指针开始逐1增加,但是尾部指针不变;
-------------数组模拟队列的思路分析
数组的方式模拟队列:
队列本身是有序列表,若用数组的结构来存储队列的数据
则队列数组的声明如图例1,其中maxSize就是该队列的最大容量
因为队列的输出,输入是分别从头部和尾部来处理,因此需要两个变量
也就是front和rear分别记录队列前后端的下标
front会随着数据输出而改变,rear随着数据输入而改变
思路分析:
首先是将数据存入队列的“addQueue”, addQueue的处理步骤:
如果队列完全是空的,那么尾部指针就会指向头部指针,front == rear
如果队列是满的,即rear == maxSize - 1,那么无法存入数据
如果队列不是满的,即rear < maxSize - 1
那么就将数据存入rear所指的数组元素中。
-------------数组模拟队列的代码实现
Example:
import java.util.Scanner;
public class Queue {
public static void main(String[] args) {
//测试
//创建一个队列
ArrayQueue queue = new ArrayQueue(3);
char key = ' '; //接受用户输入
Scanner s = new Scanner(System.in);
boolean loop = true;
while(loop) {
System.out.println("s(shwo): 显示队列");
System.out.println("e(exit): 退出程序");
System.out.println("a(add): 添加数据到队列");
System.out.println("g(get): 从队列中取出数据");
System.out.println("h(head): 查看第一个数据");
key = s.next().charAt(0);
switch(key) {
case 's':
queue.showQueue();
case 'e':
s.close();
System.exit(0);
case 'a':
System.out.println("请输入一个数字");
int value = s.nextInt();
queue.addQueue(value);
case 'g': //取出数据的时候,可能会抛异常,用try catch
try {
queue.getQueue();
}catch(Exception e) {
System.out.println(e.getMessage());
}
case 'h': //查看队列第一个数据,可能会抛异常,用try catch
try {
queue.headQueue();
}catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
}
}
class ArrayQueue{
private int maxSize; //表示数组最大容量
private int front; //队列头
private int rear; //队列尾
private int[] arr; //该数据用于存放数据
//constructor
public ArrayQueue(int arrMaxSize) {
maxSize = arrMaxSize;
arr = new int[maxSize];
front = -1; //指向队列头部前一个位置
rear = -1; //指向队列尾部(就是队列最后一个数据)
}
//判断队列是否满
public boolean isFull(){
return rear == maxSize -1;
}
//判断是否为空
public 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.printf("arr[%d]=%d\n",i,arr[i]);
}
}
//查看队列的第一个数据(非取出)
public int headQueue() {
if(isEmpty()) {
throw new RuntimeException("队列空,没有数据");
}
return arr[front +1];
}
}
这种实现方式的问题:
使用过后的空间不能再复用,浪费数组的空间
解决方法:
利用数组构建一个环形队列,通过取模的方式来复用数组空间
-------------数组模拟环形队列的思路分析
思路分析:(利用% 改进成一个环形队列)
正常情况下,如果把数组看成一个环,那么当rear指向数组的最后一个元素时,当要添加一个新的元素,就必须判断数组前面位置里的元素是否已经被取出了。
front变量的含义需要做一个调整:arr[front]指向队列的第一个元素,所以front初始值设为0
rear变量的含义也做一个调整:arr[rear]指向队列的最后一个元素的后一个位置,rear的初始值也设为0 (这个位置在arr中实际上不存在,是一个预留空间) 数组本身的长度就是maxSize,当rear的值为maxSize - 1的时候,再次输入一个数据,就会让rear指针到达实际不存在的区域,通过取模返回到数组的前面的位置。(rear+1) % maxSize 就会指向0,也就是数组的第一个区域。
当队列满时,条件:(rear + 1)% maxSize == front; 换句话说就是取模后发现rear+1指向的是和数组的第一个数是一个位置,也就说是队列没有空余位置存放取模后的数据
注意,这里的rear+1是变化的,不一定会指向数组的最后
当队列为空,条件:rear == front;
队列中有效的数据的个数:(rear + maxSize - front) % maxSize
简单说明一下:(rear + maxSize - front) % maxSize的算法思路
这里分为两种情况
-----------------数组模拟环形队列的代码实现
Example:
import java.util.Scanner;
public class CircleQuene {
public static void main(String[] args) {
System.out.println("测试数组模拟环形队列");
//创建一个队列
//注意,这里设置的4,其队列有效数据最大为3
//因为有一个空间是作为预留用来取模的
CircleQueue queue = new CircleQueue(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'://队列可能已经满了,所以用try catch
System.out.println("请输入要加入队列的数");
try {
queue.addQueue(scanner.nextInt());
}catch(Exception e) {
System.out.println(e.getMessage());
}
break;
case 'g' ://可能里面没有数据,所以用try catch
try {
int res = queue.getQueue();
System.out.println("取出数据:" + res);
}catch(Exception e) {
System.out.println(e.getMessage());
}
break;
case 'h' ://可能里面没有数据,所以用try catch
try {
System.out.println("队列第一个数据为" + queue.HeadQueue());
}catch(Exception e) {
System.out.println(e.getMessage());
}
break;
case 'e' :
scanner.close();
loop = false;
System.out.println("程序退出");
System.exit(0);
break;
default:
break;
}
}
}
}
class CircleQueue{
private int maxSize;
private int front;
private int rear;
private int[] arr;
/*
* 构造方法
*/
public CircleQueue(int arrMaxSize) {
this.maxSize = arrMaxSize;
arr = new int[arrMaxSize];
front = 0;
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没有到最后,后移一位,取模不会对rear造成影响
//如果rear已经到了最后,后移一位,取模把rear指向0
rear = (rear+1)%maxSize;
}
/*
* 从队列取出数据
*/
public int getQueue() {
if(isEmpty()) {
throw new RuntimeException("队列空,不能取出");
}
//front指向队列的第一个元素,也就是我们要取出的数
//先把front的值保存到临时变量value,在考虑把front后移
int value = arr[front];
//后移front,不过不能直接front++了
//front也有可能从圆环的另一头过来,要考虑取模
front = (front +1) % maxSize;
//由于front指向的是队列第一个数据,也就是我们储存的value,所以返回value。
return value;
}
/*
* 显示队列的所有数据
*/
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]);
// }
//思路:从front开始遍历,遍历多少个元素(有效元素的个数)
for(int i = front; i < front + ((rear -front + maxSize)% 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];
}
}