本系列为数据结构学习笔记,如有错误请指正~
一、理论知识
栈和队列都是线性数据结构,属于逻辑结构,是从物理结构(数组和链表)衍生出来的,所以栈和队列既可以数组实现,也可以链表实现,两种实现方式各种优劣。
1. 栈
基本概念
- 栈顶:最后进入的元素存放的位置
- 栈底:最早进入的元素存放的位置
- 先入后出(First In Last Out, FILO):比如12345五个数字依次入栈,出栈顺序为54321
- 入栈(push):将新元素放入栈中
- 出栈(pop):将元素从栈中取出,只有栈顶的元素才允许出栈,出栈元素的前一个元素成为新的栈顶
编码实现思路
以数组实现栈,整个栈的操作其实就是对栈顶这个位置的元素进行操作:入栈时,栈顶元素变更为数组新元素的下标;出栈时,栈顶元素变更为出栈元素的前一个元素的数组下标。
因为数组的大小是一定的,所以必须注意越界的问题。读取时判断是否为空,插入时判断是否已满。
应用场景
因为栈的输出顺序和输入顺序相反,所以栈通常用于对“历史”的回溯,比较典型的一个应用就是面包屑导航,可以很方便的回溯到前一步或前几步的操作
2. 队列
基本概念
- 队头(front):队列的出口
- 队尾(rear):队列的入口
- 入队(enqueue):只允许队尾的位置放入元素
- 出队(dequeue):只允许在队头一侧移出元素
- 先入先出(First In First Out,FIFO):比如12345依次入队,出队顺序为12345
编码实现思路
以数组实现队列,和栈一样,其实都会转换为对数组的操作。和栈不同的是,栈只需要关心栈顶就行了,队列需要关心“两头”,也就是队头和队尾,其余的实现原理相似。
和栈一样,数组的大小是一定的,所以必须注意越界的问题。读取时判断是否为空,插入时判断是否已满。
应用场景
爬虫算是一个比较典型的应用了,爬取网站时,不可能单线程爬取,多线程爬取时需要解决重复爬取的问题。所以可以将要爬取的url放入队列,按照队列的顺序依次爬取。
同样的道理,在一些多线程应用中,都可以考虑下队列。
源码
栈
/**
* 栈实现demo
* @author yangjunqiang
*
*/
public class MyStackDemo {
// 数组实现栈
private int[] stackArray;
// 栈顶元素的数组下标
private int top;
// 栈的大小
private int size;
/**
* 初始化,指定大小
* @param size
*/
public MyStackDemo(int size) {
this.size = size;
this.stackArray = new int[size];
// 栈初始化, -1代表栈中没有任何元素
this.top = -1;
}
/**
* 入栈
* @param element
* @return 入栈的
*/
public int push(int element) {
// 因为设置了栈的大小,所以入栈时要判断是否已满
if(isFull()) {
throw new IndexOutOfBoundsException("栈已满");
}
// 入栈操作,新元素对应新的数组元素下标
top += 1;
stackArray[top] = element;
return element;
}
/**
* 出栈
* @return
*/
public int pop() {
// 每次出栈都需要确保栈不为空
if(isEmpty()) {
throw new NoSuchElementException("空栈,没有可供读取的元素");
}
int element = stackArray[top];
// 出栈,修改栈顶元素位置为前一个元素的下标
top -= 1;
return element;
}
/**
* 清空栈
* 实际上数组并未被清空,只是栈顶元素的位置被重置了
*/
public void clear() {
top = -1;
}
/**
* 栈的大小,实际上就是初始化时定义的数组大小
* @return
*/
public int size() {
return size;
}
/**
* 判断栈是否为空
* @return
*/
public boolean isEmpty() {
return (top == -1);
}
/**
* 判断栈是否已满
* @return
*/
public boolean isFull() {
return (top == size - 1);
}
}
队列
/**
* 循环队列
* @author yangjunqiang
*
*/
public class MyQueueDemo {
// 数组实现队列
private int[] array;
// 队首指针
private int front;
// 队尾指针,队尾指针指向的永远是空,也就是队尾指针对应的位置没有元素
private int rear;
/**
* 初始化队列,指定队列大小
* @param size
*/
public MyQueueDemo(int size) {
this.array = new int[size];
}
public void enQueue(int element) throws Exception {
if(isFull()) {
throw new Exception("队列已满");
}
array[rear] = element;
// 队尾指针后移,因为循环,所以如果队尾指针的值大小如果大于数组长度,代表队头有元素出队了,可以将队尾指针重新指回数组的首位
// 就像贪吃蛇一样,只要首尾不相等就可永远循环下去,如果首尾相接,则说明队列满了
rear = (rear + 1) % array.length;
}
public int deQueue() throws Exception {
if(isEmpty()) {
throw new Exception("队列已空");
}
// 出队列,队首
int deQueueElement = array[front];
// 队首的指针后移
front = (front + 1) % array.length;
return deQueueElement;
}
public void printf() {
for(int i = front; i != rear; i = (i + 1) % array.length) {
System.out.println(array[i]);
}
}
/**
* 判断队列是否已满,首尾相接即为满队列。
* 因为队尾永远为空,所以公式为 :(队尾下标 + 1) % 数组长度 = 队头下标
* @return
*/
public boolean isFull() {
return ((rear + 1) % array.length == front);
}
/**
* 判断队列是否为空
* 队头=队尾
*/
public boolean isEmpty() {
return (rear == front);
}
}
网友评论