1 栈(Stack) 是一种 特殊的线性表, 其只允许在固定的一端进行插入和删除元素操作.
(1) 进行数据插入和删除操作的一端称为 栈顶, 另一端称为 栈底. 栈中的元素遵循先进后出的原则.
(2) 栈一般用 数组 实现. (定义一个数组和栈中 有效元素的个数)
(3) 标准库中的栈Stack的 入栈push(), 出栈pop(), 取栈顶元素peek() 操作都是在栈顶进行的操作.
public class MyStack {
//管理一些int元素即可,也不考虑扩容问题
private int[] array=new int[100];
private int size=0; //栈中存在多少个有效元素
//入栈
public void push(int i){
array[size]=i;
size++;
}
//取栈顶元素(最后进来的那个元素)
public int peek(){
return array[size-1];
}
//出栈
public int pop(){
int ret=array[size-1];
size--;
return ret;
}
}
2 队列(Queue) 只允许在一端进行插入数据操作, 在另一端进行删除数据操作的特殊线性表,队列具有先进先出特点.
(1) 入队列: 进行插入操作的一端称为队尾. 出队列:进行删除操作的一端称为队头.
(2) 队列一般由 数组 或 链表 实现.
(3) 标准库中入队列offer(), 出队列poll(),取队首元素peek().
(4) 由 数组 实现队列.(定义一个 数组, head, hail, 及有元素的个数 size.
[head, tail) 有效元素的范围. tail++ 之后如果超出了数组的有效范围, 就从头开始)
public class MyQueueByArray {
private int[] array=new int[100];
// [head, tail) 有效元素的范围. 注意, tail 可能在 head 之前
private int head=0;
private int tail=0;
private int size=0;//元素个数
public void offer(int val){
if(size==array.length){
//队列满了,无法继续插入
return;
}
// 得保证这个操作下标不能越界
array[tail]=val;
tail++;
// tail++ 之后如果超出了数组的有效范围, 就从头开始
if(tail>=array.length){
tail=0;
}
size++;
}
public Integer poll(){
if(size==0){
return null;
}
Integer ret=array[head];
head++;
if(head>=array.length){
head=0;
}
size--;
return ret;
}
public Integer peek(){
if(size==0){
return null;
}
return array[head];
}
}
(5) 由 链表 实现队列.
public class MyQueueByLinkedList {
static class Node{
public int val;
Node next=null;
public Node(int val){
this.val=val;
}
}
private Node head=null;
private Node tail=null;
此处我按照尾部入队列, 头部出队列的方式实现
public void offer(int val){
Node newNode=new Node(val);
if(head==null){
head=newNode;
tail=newNode;
return;
}
//当前如果不是空链表
tail.next=newNode;
tail=tail.next;
}
public Integer poll(){
//如果当前队列就是空队列, 再去 poll 显然不科学
if(head==null){
//如果出队列失败,返回一个错误值
return null;
}
int ret=head.val;
head=head.next;
if(head==null){
tail=null;
}
return ret;
}
public Integer peek(){
if(head==null){
return null;
}
return head.val;
}
}
3 循环队列: 能更有效的利用资源空间,通常由 数组 实现.
(1) 队列中 有效元素 的长度有两种情况:
head在前tail在后时, 有效元素为2 3 4.
head在后,tail在前时, 有效元素为4 5 6 7 8 1.
(2) 前者当head和tail重合时, 就是 空队列.
后者重合时, 就是一个 满队列.
4 双端队列(Deque): 两端 都可以进行入队列和出队列操作的队列.
5 栈和队列的相互实现.
(1) 两个队列实现一个栈.
入栈, A 队列负责入栈, 按照正常入队列存放元素, B是辅助队列.
出栈, 把A中的元素按照出队列放到B中, 直到剩一个元素. 最后把这一个元素出队列. 然后 交换AB队列.
取栈顶元素, 和出栈一样, 只是把最后那一个元素也要加入到B, 然后 交换AB.
判断栈是空, AB都为空, 栈才是空.
public class MyStackBy2Queue {
private Queue<Integer> A=new LinkedList<>();
private Queue<Integer> B=new LinkedList<>();
public void push(int x){
A.offer(x);
}
public Integer pop(){
if(empty()){
return null;
}
while(A.size()>1){
Integer front=A.poll();
B.offer(front);
}
int ret=A.poll();
swapAB();
return ret;
}
private void swapAB(){
Queue<Integer> tmp=A;
A=B;
B=tmp;
}
public Integer top(){
if(empty()){
return null;
}
while (A.size() > 1) {
Integer front = A.poll();
B.offer(front);
}
int ret = A.poll();
B.offer(ret); // top 和 pop 唯一的区别就是这句话
swapAB();
return ret;
}
public boolean empty(){
return A.isEmpty()&&B.isEmpty();
}
}
(2) 两个栈实现一个队列.
入队列, 先把一个栈中的元素倒到另一个栈中, 然后把目标元素进行入栈.
出队列, 先把一个栈中的元素倒到另一个栈中, 然后出栈.
取队首元素, 先把一个栈中的元素倒到另一个栈中, 然后取栈顶元素.
判断队列为空, 两个栈都为空时队列才为空.
public class MyQueueBy2Stack {
private Stack<Integer> A=new Stack<>();
private Stack<Integer> B=new Stack<>();
public void push(int x){
while (!B.isEmpty()){
int tmp=B.pop();
A.push(tmp);
}
A.push(x);
}
public Integer pop(){
if(empty()){
return null;
}
while (!A.isEmpty()){
int tmp=A.pop();
B.push(tmp);
}
return B.pop();
}
public Integer peek(){
// 1. 如果为空, 就直接返回
if (empty()) {
return null;
}
while (!A.isEmpty()) {
int tmp = A.pop();
B.push(tmp);
}
return B.peek();
}
public boolean empty() {
return A.isEmpty() && B.isEmpty();
}
}
6 判断题中的有关 栈的顺序问题. 如果是从小到大按序入栈的话, 可以把字母变成数字, 出现 大小中 的顺序的选项即为不可能出现的情况.