栈和队列
一、栈
- 栈:栈是一种后进先出的数据结构(LIFO)
- 栈的实现:为了让逻辑更为清晰,采用接口实现。在接口中实现这5种方法
- 入栈
- 出栈
- 查看栈顶
- 查看栈的元素
- 查看栈是否为空
- 定义栈的接口,并实现上述五种抽象方法
public interface Stack<E> {
int getSize();
boolean isEmpty();
void push(E e);
E pop();
E peek();
}
- 利用自定义数组来实现自定义的堆栈
package cn.itcast.day2;
public class ArrayStack<E> implements Stack<E>{
myArray<E> array; //本质上栈是数组的一个子集,因此五种方法可以通过数组来进行实现
public ArrayStack(int capacity) {
array = new myArray<E>(capacity);
}
public ArrayStack() {
array = new myArray<E>();
}
@Override
public int getSize() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
public int getCapacity() {
return array.getCapacity();
}
@Override
public void push(E e) {
try {
array.addLast(e);
} catch (Exception e1) {
e1.printStackTrace();
}
}
@Override
public E pop() {
return array.removeLast();
}
@Override
public E peek() {
return array.getLast();
}
public E getLast() {
return array.get(array.getSize()-1);
}
public E getFirst() {
return array.get(0);
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("Stack: ");
res.append('[');
for(int i=0;i<array.getSize();i++) {
res.append(array.get(i));
if(i!=array.getSize()-1) {
res.append(", ");
}
}
res.append("] top");
return res.toString();
}
}
二、栈的应用
leetCode试题:有效的括号
思路:对于给定的字符串,首先声明一个栈,然后逐一遍历这个字符串的每一个字符。如果这个字符是一个左括号,不管是大括号、中括号还是小括号,只要是左括号我们就将他压入栈。当匹配到右括号的时候,我们看此时的栈顶元素是否能与该右括号匹配。如果能够匹配,则匹配成功,此时将栈顶的元素出栈。以此类推,如果整个字符串都已经遍历完,与此同时栈还为空的(栈里没有剩下其他的左括号),那么说明当前的字符串是一个合法的字符串匹配的字符串。一旦栈顶元素与当前遍历到的字符串元素不同,那么说明不匹配,则终止算法。
- 代码
// 利用java自带的Stack求解
import java.util.Stack;
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<Character>();
for(int i=0;i<s.length();i++) {
char c =s.charAt(i);
if(c=='(' ||c == '[' || c=='{') {
stack.push(c);
}else {
if(stack.isEmpty()) {
return false;
}
char topChar = stack.pop();
if(c==')' && topChar!='(') {
return false;
}
if(c==']' && topChar!='[') {
return false;
}
if(c=='}' && topChar!='{') {
return false;
}
}
}
return stack.isEmpty(); //注意:这里不能直接return true,必须先判定栈是否为空
}
}
##python,摘自leetcode
class Solution:
def isValid(self, s):
while '{}' in s or '()' in s or '[]' in s:
s = s.replace('{}', '')
s = s.replace('[]', '')
s = s.replace('()', '')
return s == ''
三、队列Queue
- 队列:队列是一种先进先出的数据结构(FIFO)
- 队列的实现:为了让逻辑更为清晰,采用接口实现。在接口中实现这5种方法
- 入列
- 出列
- 队首元素
- 队列元素
- 队列是否为空
- 接口代码实现
package cn.itcast.day2;
public interface Queue<E>{
int getSize();
boolean isEmpty();
void enqueue(E e);
E dequeue();
E getFront();
}
- 队列的实现
package cn.itcast.day2;
public class ArrayQueue<E> implements Queue<E> {
private myArray<E> array;
public ArrayQueue(int capacity) {
array = new myArray<E>(capacity);
}
public ArrayQueue() {
array = new myArray<E>();
}
@Override
public int getSize() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
public int getCapacity() {
return array.getCapacity();
}
@Override
public void enqueue(E e) {
try {
array.addLast(e);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
@Override
public E dequeue() {
return array.removeFirst();
}
@Override
public E getFront() {
return array.getFirst();
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("Queue: ");
res.append("front [");
for(int i=0;i<array.getSize();i++) {
res.append(array.get(i));
if(i!=array.getSize()-1) {
res.append(", ");
}
}
res.append("] tail");
return res.toString();
}
public static void main(String[] args) {
ArrayQueue<Integer> queue = new ArrayQueue<Integer>();
for(int i=0;i<10;i++) {
queue.enqueue(i);
System.out.println(queue);
if(i % 3==2) {
queue.dequeue();
System.out.println(queue);
}
}
}
}
四、队列的改进:循环队列
由于原始队列的出对操作时均摊复杂度为O(n),因此为了降低复杂度,可以采用记录队首和队尾的方式来进行操作。
初始情况:front和tail都指向第一个元素(front==tail 队列为空)。入队操作如下:
当tail达到队尾时:
注意:此时索引为1的地方还为空,那么能不能继续再添加一个元素呢?答案是否定的,因为如果此时再添加一个元素,那么有front == tail。则此时 front == tail既可以表示队列为空,又可以表示队列为满,这种情况是我们不希望看到的。因此我们定义:(tail + 1) % c == front 时,队列已满(c为循环队列的长度)
- 代码实现
package cn.itcast.day2;
public class LoopQueue<E> implements Queue<E>{
private E[] data;
private int front,tail;
private int size;
public LoopQueue(int capacity) {
data = (E[])new Object[capacity + 1]; //循环数组要浪费一个单位
front = 0;
tail = 0;
size = 0;
}
public LoopQueue() {
this(10);
}
public int getCapacity() {
return data.length-1; //循环数组要浪费一个单位
}
@Override
public boolean isEmpty() {
return front==tail;
}
@Override
public int getSize() {
return size;
}
@Override
public void enqueue(E e) {
if((tail+1)%data.length == front) {
resize(getCapacity() * 2);
}
data[tail] = e;
tail = (tail+1)%data.length; //同样是为了防止越界
size ++;
}
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity+1];//循环数组要浪费一个单位
for(int i=0;i<size;i++) {
/*在原来的数组data中,有可能front不是0,我们把队首元素放在新数组中索引为0的位置。这样比较方便
但是这样存在一个问题:newData[i]对应的不是data[i],而是data[(i+front)]。(有一个大小为front的偏移)
但是由于队列是循环队列,因此(i+front)有可能大于data.length,会产生越界,因此同样要取余
*/
newData[i] = data[(i+front)%data.length]; //
}
data = newData;
front=0;
tail=size; //维护队首和队尾
}
@Override
public E dequeue() {
if(isEmpty()) {
throw new IllegalArgumentException("错误");
}
E ret = data[front];
data[front] = null; //释放内存
front = (front+1)%data.length;
size --;
if(size == getCapacity() /4 && (getCapacity() / 2) !=0) {
resize(getCapacity()/2);
}
return ret;
}
@Override
public E getFront() {
if(isEmpty()) {
throw new IllegalArgumentException("错误");
}
return data[front];
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("Queue:size=%d,capacity = %d\n",size,getCapacity()));
res.append("front [");
for(int i=front;i!= tail;i = (i+1) % data.length) {
res.append(data[i]);
if((i+1)%data.length != tail) {
res.append(", ");
}else {
res.append("] tail");
}
}
return res.toString();
}
public static void main(String[] args) {
LoopQueue<Integer> queue = new LoopQueue<Integer>();
for(int i=0;i<10;i++) {
queue.enqueue(i);
System.out.println(queue);
if(i % 3==2) {
queue.dequeue();
System.out.println(queue);
}
}
}
}