栈
百科:栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。
栈是允许在同一端进行插入和删除操作的特殊线性表。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。栈也称为先进后出表。
栈可以用来在函数调用的时候存储断点,做递归时要用到栈!
示意图
先进后出。
实现思路
数组实现
数据无限制输入输出的话,需要用动态数据。我们就举个固定数量的数组来实现栈先进后出。定义一个固定长度数组存储值或对象,定义一个putIndex下标 = 0记录下一个数据存放的位置,弹出的位置为putIndex-1,也就是栈顶。push一个数据,那么putIndex++,那么下次的数据存储数据下标的位置就有了。如果想要弹出,那么putIndex-1就是栈顶的数据。这里因为固定数量的数组,所以需要判定是否满了,或者全部以弹出。
public static class MyStack {
int[] arr;
int putIndex = 0;
int limit;
MyStack (Integer limit) {
this.arr = new int[limit];
this.limit = limit;
}
public int push(int value) throws Exception {
if (putIndex == limit) {
throw new Exception("满了");
}
arr[putIndex] = value;
putIndex ++;
return putIndex - 1;
}
public int pop() throws Exception {
if (putIndex == 0) {
throw new Exception("没了");
}
int value = arr[putIndex - 1];
putIndex --;
return value;
}
}
双向链表实现
使用存储头尾的双向链表实现就很简单了,从头部插入,从头部弹出,不就实现了先进后出的栈结构了吗?而且时间复杂度都是O(1),因为始终都是头部的删除插入的常数操作。
public static class MyStackByDoubleNode<T> {
DoubleNodeQueueAndStack<T> doubleNode = new DoubleNodeQueueAndStack();
public void addFromHead(T value) {
doubleNode.addFromHead(value);
}
public void popFromHead() {
doubleNode.popFromHead();
}
}
public static class DoubleNodeQueueAndStack<T> {
DoubleNode<T> head;
DoubleNode<T> tail;
/**
* 换新的头部节点
* @param value
*/
public void addFromHead(T value) {
DoubleNode<T> cur = new DoubleNode<>(value, null, null);
// 新增头部
if (head == null) {
tail = cur;
} else {
// 当前节点的下一个节点为原来的头
cur.next = head;
// 原来的头节点的上一个节点为当前节点
head.prev = cur;
}
// 头部节点重置为当前节点
head = cur;
}
public void addFromBottom(T value) {
DoubleNode<T> cur = new DoubleNode<>(value, null, null);
if (head == null) {
head = cur;
} else {
// 新的节点的上一个节点引用原来的尾节点
cur.prev = tail;
// 原来的尾节点的下一个节点引用新节点
tail.next = cur;
}
// 尾部节点重置为当前节点
tail = cur;
}
public T popFromBottom() {
if (head == null) {
return null;
}
DoubleNode<T> cur = tail;
if (head == tail) {
head = null;
tail = null;
} else {
// 尾节点重置为当前尾节点的上一个节点
tail = tail.prev;
// 新的尾节点的下一个节点指向null
tail.next = null;
// 旧的尾节点的上一个节点指向null
cur.prev = null;
}
return cur.data;
}
public T popFromHead() {
if (head == null) {
return null;
}
DoubleNode<T> cur = head;
if (head == tail) {
head = null;
tail = null;
} else {
// 头部节点重置为下一个节点
head = head.next;
// 旧的头节点的下一个节点指向null
cur.next = null;
// 新的头节点的上一个节点指向null
head.prev = null;
}
return cur.data;
}
@Override
public String toString() {
return "DoubleNodeQueue{" +
"head=" + head +
", tail=" + tail +
'}';
}
}
@AllArgsConstructor
public static class DoubleNode<T> {
// 节点数据
T data;
// 上一个节点
DoubleNode<T> prev;
// 下一个节点
DoubleNode<T> next;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public DoubleNode<T> getPrev() {
return prev;
}
public void setPrev(DoubleNode<T> prev) {
this.prev = prev;
}
public DoubleNode<T> getNext() {
return next;
}
public void setNext(DoubleNode<T> next) {
this.next = next;
}
@Override
public String toString() {
return "DoubleNode{" +
"data=" + data +
// ", prev=" + prev +
", next=" + next +
'}';
}
}
队列
百科:队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
示意图
先进先出。
实现思路
环形数组实现
参考之前实现的栈,那么我们只需要保证在一定数量内的数组,只要数组还有空间,我们就把数据放入下一个空余的位置index。弹出也是同理,只要还有可以弹出的值,就一次递增index弹出相应的先插入的数据。
public static class MyQueue {
int[] arr;
int putIndex = 0;
int popIndex = 0;
int limit;
int size = 0;
MyQueue (Integer limit) {
this.arr = new int[limit];
this.limit = limit;
}
public int push(int value) throws Exception {
if (size == limit) {
throw new Exception("队列满了");
}
size ++;
arr[putIndex] = value;
putIndex = getNextIndex(putIndex);
return putIndex - 1;
}
public int pop() throws Exception {
if (size == 0) {
throw new Exception("没了");
}
size --;
int value = arr[popIndex];
popIndex = getNextIndex(popIndex);
return value;
}
/**
* 获取下一个数据存储位置的下标
* @param index 数据下标
* @return
*/
public int getNextIndex(int index) {
return index < limit -1 ? index + 1 : 0;
}
}
双向链表实现
队列讲究先进先出嘛,就是头部进,尾部出。什么意思呢?每次插入新节点,作为头部节点,那么原来的头节点就变成了尾部节点,那么最先插入的节点不就是尾部节点了吗?我们把双向链表的头尾存储起来,每次头部插入新节点,从尾部删除节点就ok了。
public static class MyQueueByDoubleNode<T> {
DoubleNodeQueueAndStack<T> doubleNode = new DoubleNodeQueueAndStack();
public void addFromHead(T value) {
doubleNode.addFromHead(value);
}
public void popFromBottom() {
doubleNode.popFromBottom();
}
}
拓展
使用队列实现栈
使用栈实现队列
包含最小值的栈
public static class MinStack {
Stack<Integer> data = new Stack<>();
Stack<Integer> min = new Stack<>();
public void push(Integer value) throws Exception {
if (min.empty()) {
min.push(value);
} else if (value < getMin()) {
min.push(value);
} else {
min.push(getMin());
}
data.push(value);
}
public Integer pop() throws Exception {
if (data.empty()) {
throw new Exception("没了");
}
min.pop();
return data.pop();
}
public Integer getMin() throws Exception {
if (min.empty()) {
throw new Exception("没了");
}
return min.peek();
}
}
第二种方案
这种是时间复杂度高一些,空间复杂度低一些。