1. 栈
栈是一种特殊的线性表,只能够在一端进行操作。特点是后进先出LIFO。 每加入一个元素会放在栈顶,将之前的元素往栈顶的位置移动。 出栈的元素也只能是栈顶元素。
1.1 接口设计
int size() :返回元素数量;boolean isEmpty() :栈是否为空;void push(E e) :元素入栈;E pop() :元素出栈;E top() :返回栈顶元素。
1.2 方法实现
栈中的方法实际上可以直接运用ArrayList 或者是LinkedList 中的方法,这里没有使用。
1.2.1 成员变量
这里的成员变量跟ArrayList 一样。
private E[] elements;
private int size;
private static final int DEFAULT_CAPACITY = 10;
1.2.2 方法实现
public void push(E e){
if (size >= elements.length){
ensureCapacity(size + 1);
}
elements[size++] = e;
}
public E pop() throws IndexOutOfBoundsException {
if (size < 1){
throw new IndexOutOfBoundsException("Size:" + size);
}
return elements[--size];
}
public E top(){
if (size < 1){
throw new IndexOutOfBoundsException("Size:" + size);
}
return elements[size - 1];
}
private void ensureCapacity(int capacity) {
int oldCapacity = elements.length;
if (oldCapacity >= capacity) {
return;
}
int newCapacity = oldCapacity + (oldCapacity >> 1);
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements;
}
1.3 力扣题目
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。 有效字符串需满足: 1. 左括号必须用相同类型的右括号闭合。 2. 左括号必须以正确的顺序闭合。 https://leetcode-cn.com/problems/valid-parentheses/
整体思路就是:将左括号的值加入到栈中,如果是右括号就弹出栈顶判断是否与左括号符合。
public static boolean isValid(String s) {
if (s == null || s.length() % 2 != 0 || s.length() == 0) {
return false;
}
int len = s.length();
Stack<Character> stack = new Stack<>();
for (int i = 0; i < len; i++) {
char c = s.charAt(i);
if (c == '(' || c== '[' || c == '{' ){
stack.push(c);
}else {
if (stack.isEmpty()) {
return false;
}
char left = stack.pop();
if (left == '{' && c!= '}'){
return false;
}
if (left == '[' && c!= ']'){
return false;
}
if (left == '(' && c!= ')'){
return false;
}
}
}
return stack.isEmpty();
}
2. 队列
队列是一种特殊的线性表,只能在头尾两端进行操作。 只能从队尾添加元素,在对头移除元素。 先进先出原则,FIFO
2.2 LinkedList 实现队列
因为栈中队列中只能对头元素和尾元素进行操作,所以使用双向链表来实现是最合适的,这里直接选用Java官方的LinkedList 来实现队列。Java官方的Queue 接口也是由LinedList 来实现的。
接口实现:
int size() :队中元素数量;boolean isEmpty() :队列是否为空;void enQueue(E e) :入队;E deQueue() :出队;E front() :返回队头元素;void clear() :清空。
public class Queue<E> {
LinkedList<E> list = new LinkedList<>();
public int size(){
return list.size();
}
public boolean isEmpty(){
return list.isEmpty();
}
public void enQueue(E e){
list.add(e);
}
public E deQueue(){
return list.remove(0);
}
public E front(){
return list.get(0);
}
public void clear(){
list.clear();
}
}
2.3 Stack 实现队列
因为栈是在一端进行操作,而队列是在两端进行操作的,因此可以使用两个栈来实现队列。 相同与力扣题目:用栈实现队列。
实现思路:
- 准备两个栈:
inStack 和outStack ; - 入队时将元素添加到
inStack 中; - 出队时如果
outStack 为空,就将inStack 中的元素加入到outStack 中,然后outStack 弹出;如果outStack 不为空,直接弹出。
private Stack<Integer> inStack;
private Stack<Integer> outStack;
public StackQueue() {
inStack = new Stack<>();
outStack = new Stack<>();
}
public void push(int x) {
inStack.push(x);
}
public int pop() {
checkStack();
return outStack.pop();
}
public int peek() {
checkStack();
return outStack.peek();
}
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
private void checkStack(){
if (outStack.isEmpty()){
while (!inStack.isEmpty()){
outStack.push(inStack.pop());
}
}
}
2.3 双端队列
双端队列是能在头尾两端进行添加、删除的队列。Java官方中也是使用LinkedList 来实现的Deque 接口。
接口实现:
int size() :队中元素数量;boolean isEmpty() :队列是否为空;void enQueueFront(E e) :从队头入队;void enQueueRear(E e) :从队尾入队;E deQueueRear() :从队尾出队;E deQueueFront() :从队头出队;E front() :返回队头元素;E rear() :返回队尾元素;void clear() :清空。
public class Deque<E> {
LinkedList<E> list = new LinkedList<>();
public int size(){
return list.size();
}
public boolean isEmpty(){
return list.isEmpty();
}
public void enQueueRear(E e){
list.add(e);
}
public void enQueueFront(E e){
list.add(0,e);
}
public E deQueueRear(){
return list.remove(list.size() - 1);
}
public E deQueueFront(){
return list.remove(0);
}
public E front(){
return list.get(0);
}
public E rear(){
return list.get(list.size() - 1);
}
public void clear(){
list.clear();
}
}
2.4 循环队列
其实就是将一个模拟成环形,加入一个就在尾端新增一个元素,出队一个就修改front的指向。实现环形
要加入节点:将新节点放到尾部 删除节点:front指向下一个元素
int size() :队中元素数量;boolean isEmpty() :队列是否为空;void enQueue(E e) :入队;E deQueue() :出队;E front() :返回队头元素;void clear() :清空。
private int size;
private E[] elements;
private int front;
private static final int DEFAULT_CAPACITY = 10;
public CircleQueue(){
elements = (E[]) new Object[DEFAULT_CAPACITY];
}
void enQueue(E e) :因为如果时的元素放到队的尾部,直接使用front + size在对数组长度进行取模可以实现。
public void enQueue(E e){
ensureCapacity(size + 1);
elements[(front + size) % elements.length] = e;
size++;
}
E deQueue() :出队时只需要将front下标的数组元素返回,并且赋值为空,再将front指向下一元素,为防止下标溢出所以需要对长度取模。
public E deQueue(){
E e = elements[front];
elements[front] = null;
front = (front + 1) % elements.length;
size--;
return e;
}
- 扩容方法:直接将front指向数组中第一个元素,剩余元素按顺序放到其后面。
private void ensureCapacity(int capacity) {
int oldCapacity = elements.length;
if (oldCapacity >= capacity){
return;
}
int newCapacity = oldCapacity + (oldCapacity >> 1);
E[] newElements = (E[]) new Object[capacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[(i + front) % elements.length];
}
elements = newElements;
front = 0;
}
2.5 双端循环队列
可以从两端进行添加和删除的循环队列。
接口实现:
int size() :队中元素数量;boolean isEmpty() :队列是否为空;void enQueueFront(E e) :从队头入队;void enQueueRear(E e) :从队尾入队;E deQueueRear() :从队尾出队;E deQueueFront() :从队头出队;E front() :返回队头元素;E rear() :返回队尾元素;void clear() :清空。
因为从队尾入队和从队头出队等没有变化,这里直接就不写了。
public void enQueueFront(E e){
ensureCapacity(size + 1);
front = getIndex(-1);
elements[front] = e;
size++;
}
public E deQueueRear(){
int rear = getIndex(size - 1);
E e = elements[rear];
elements[rear] = null;
size--;
return e;
}
public E rear(){
return elements[getIndex(size - 1)];
}
getIndex() :根据传入的索引,计算出新的索引值:因为使用取模运算%时CPU消耗内存过大,因此这里使用三元运算符来代替。循环队列也可以修改。
private int getIndex(int index){
index += front;
if (index < 0){
return index + elements.length;
}
return index - (index >= elements.length ? elements.length : 0);
}
- 清空队列:这里的清空需要使用
getIndex() 方法转换索引才能实现真正的清空。
public void clear(){
for (int i = 0; i < elements.length; i++) {
elements[getIndex(i)] = null;
}
size = 0;
front = 0;
elements = null;
}
|