一、栈
栈是一种线性结构,栈对应的操作是数组的子集;栈只能从一端添加元素,也只能从一端取出元素,这一端称为栈顶,是一种后进先出的数据结构。
栈的一些常见的应用有undo操作(撤销)、程序调用的系统栈(程序中断时进入系统栈,当子过程执行完成后,通过系统栈回到上层中断的位置继续执行)。
因为栈对应的操作是数组的子集,所以直接使用上一节数据结构----数组(java实现代码)的删除元素和添加元素的功能即可。具体实现代码如下:
创建一个栈的接口:
package com.lxr.stack0331;
public interface Stack<E> {
int getSize();//复杂度o(1)
boolean isEmpty();//复杂度o(1)
void push(E e);//复杂度o(1),均摊
E pop();//复杂度o(1),均摊
E peek();//复杂度o(1)
}
ArryayStack.java实现代码:
package com.lxr.stack0331;
import com.lxr.array0330.Array;
public class ArryayStack<E> implements Stack<E>{
Array<E> array;
public ArryayStack(int capacity) {
// TODO Auto-generated constructor stub
array=new Array<>(capacity);
}
public ArryayStack() {
// TODO Auto-generated constructor stub
array=new Array<>();
}
@Override
public int getSize() {
return array.getsize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
@Override
public void push(E e) {
array.addElement(e);
}
@Override
public E pop() {
return array.delateLast();
}
@Override
public E peek() {
return array.getLast();
}
public int getCapacity() {
return array.getCapacity();
}
@Override
public String toString() {
StringBuilder reStringBuilder =new StringBuilder();
reStringBuilder.append(String.format("Stack"));
reStringBuilder.append('[');
for(int i=0;i<array.getsize();i++) {
reStringBuilder.append(array.get(i));
if(i!=array.getsize()-1)
reStringBuilder.append(", ");
}
reStringBuilder.append("] top");
return reStringBuilder.toString();
}
}
栈的应用例举-------leetcode20(匹配括号)
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: "()"
输出: true
示例 2:
输入: "()[]{}"
输出: true
示例 3:
输入: "(]"
输出: false
示例 4:
输入: "([)]"
输出: false
示例 5:
输入: "{[]}"
输出: true
分析:即当字符为左括弧时即“(、{、【”时则入栈,当字符为右括弧时判断栈顶字符是否匹配,如果匹配则出栈,否则返回false,代码如下:
public class Solution {
public boolean isValid(String s) {
Stack<Character> stack=new Stack<>();
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!='('||c=='}'&&topChar!='{'||c==']'&&topChar!='[')
return false;
}
}
return stack.isEmpty();
}
}
二、队列
队列也是一种线性结构,相比数组,队列对应的操作是数组的子集,智能从一端(队尾)添加元素,只能从另一端(队首)取出元素,队列是一种先进先出的数据结构(FIFO)
1.数组队列:
数组队列出队的时间复杂度是o(n),因为数组删除第一个元素的时间复杂度是o(n),因为数组队列对应的操作也是数组的子集,所以直接使用上一节数据结构----数组(java实现代码)的删除元素和添加元素的功能即可。具体实现代码如下:
创建一个数组队列的接口:
package com.lxr.Queue0402;
public interface Queue <E>{
int getSize();//复杂度o(1)均摊
boolean isEmpty();//复杂度o(1)
void enqueue(E e);//复杂度o(1)
E dequeue();//复杂度o(n)
E getFrount();//复杂度o(1)
}
ArrayQueue.java代码:
package com.lxr.Queue0402;
import com.lxr.array0330.Array;
public class ArrayQueue<E> implements Queue<E>{
private Array<E> array;
public ArrayQueue(int capacity) {
array=new Array<>(capacity);
}
public ArrayQueue() {
array=new Array<>();
}
@Override
public int getSize() {
// TODO Auto-generated method stub
return array.getsize();
}
@Override
public boolean isEmpty() {
// TODO Auto-generated method stub
return array.isEmpty();
}
@Override
public void enqueue(E e) {
// TODO Auto-generated method stub
array.addElement(e);
}
@Override
public E dequeue() {
// TODO Auto-generated method stub
return array.delateFrist();
}
@Override
public E getFrount() {
// TODO Auto-generated method stub
return array.getFirst();
}
public int getCapacity() {
return array.getCapacity();
}
@Override
public String toString() {
StringBuilder reStringBuilder =new StringBuilder();
reStringBuilder.append("Queue ");
reStringBuilder.append("front[");
for(int i=0;i<array.getsize();i++) {
reStringBuilder.append(array.get(i));
if(i!=array.getsize()-1)
reStringBuilder.append(", ");
}
reStringBuilder.append("] tail");
return reStringBuilder.toString();
}
public static void main(String[] args) {
//测试
ArrayQueue<Integer> arrayQueue=new ArrayQueue<>(20);
for(int i=0;i<20;i++) {
arrayQueue.enqueue(i);
if(i%3==2) {
arrayQueue.dequeue();
}
System.out.println(arrayQueue);
}
}
}
2.循环队列
为解决数列数组出队时时间复杂度为O(n),引出了循环队列。循环队列中有front和tail,front指向第一个元素,tail指向最后一个元素的后一个元素,frount==tail是队列为空,当(tail+1)%c ==front(c为队列长度)时表示队列已满,有意识的浪费了一个空间,这块空间是由tail指向。
循环队列的动态扩容是从front指向的元素开始复制到新的数组中,改变front,tail,size的值。
创建一个循环队列的接口:
package com.lxr.Queue0402;
public interface Queue <E>{
int getSize();//复杂度o(1)均摊
boolean isEmpty();//复杂度o(1)
void enqueue(E e);//复杂度o(1)
E dequeue();//复杂度o(1),均摊
E getFrount();//复杂度o(1)
}
LoopQueue.java代码:
package com.lxr.Queue0402;
import java.awt.Font;
public class LoopQueue<E> implements Queue<E>{
private E[] data;
private int front,tail;
private int size;
public LoopQueue(int capacity) {
// TODO Auto-generated constructor stub
data=(E[])new Object[capacity+1];
front=0;
tail=0;
size=0;
}
//初始容量为10
public LoopQueue() {
this(10);
}
public int getCapacity() {
return data.length-1;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return front==tail;
}
//队列添加元素
@Override
public void enqueue(E e) {
//如果队列已满,动态添加队列长度,使用getCapacity()避免多浪费一个空间
if((tail+1)%data.length==front) {
resize(getCapacity()*2);
}
data[tail]=e;
tail=(tail+1)%data.length;
size++;
}
//动态添加空间
private void resize(int i) {
E[] newdata=(E[])new Object[i+1];
//第一种遍历队列的方式
for (int j = 0; j < size; j++) {
newdata[j]=data[(j+front)%data.length];
}
data=newdata;
front=0;
tail=size;
}
//出队操作
@Override
public E dequeue() {
if(isEmpty())
throw new IllegalArgumentException("队列为空");
E a=data[front];
data[front]=null;
front=(front+1)%data.length;
size--;
//动态缩容
if(size==getCapacity()/4&&getCapacity()/2!=0)
resize(getCapacity()/2);
return a;
}
//返回第一个元素
@Override
public E getFrount() {
if(isEmpty())
throw new IllegalArgumentException("队列为空");
return data[front];
}
@Override
public String toString() {
StringBuilder reStringBuilder =new StringBuilder();
reStringBuilder.append(String.format("Queue:size=%d,capacity=%d\n",size,getCapacity()));
reStringBuilder.append("front [");
//第二种遍历队列的方式
for(int i=front;i!=tail;i=(i+1)%data.length) {
reStringBuilder.append(data[i]);
if((i+1)%data.length!=tail)
reStringBuilder.append(", ");
}
reStringBuilder.append("] tail");
return reStringBuilder.toString();
}
public static void main(String[] args) {
//测试
LoopQueue<Integer> loopQueue=new LoopQueue<>(10);
for(int i=0;i<20;i++) {
loopQueue.enqueue(i);
if(i%3==2) {
loopQueue.dequeue();
}
System.out.println(loopQueue);
}
}
}
3.数组队列和循环队列比较
数组队列和循环队列的复杂度的区别主要是在出队时,通过相同次数的入队出队操作后所需要的时间来比较数组队列和循环队列的效率。
测试代码:
package com.lxr.Queue0402;
import java.util.Random;
public class Main {
public static double test(Queue<Integer> q,int num) {
//开始时间计时
long startTime=System.nanoTime();
//进行num次入队和出队的操作
Random random=new Random();
for(int i=0;i<num;i++)
q.enqueue(random.nextInt(Integer.MAX_VALUE));
for(int i=0;i<num;i++)
q.dequeue();
//结束时间计时
long endTime=System.nanoTime();
return (endTime-startTime)/1000000000.0;
}
public static void main(String[] args) {
int num=100000;
ArrayQueue<Integer> arrayQueue=new ArrayQueue<Integer>();
double time1=test(arrayQueue, num);
System.out.println("ArrayQueue,time: "+time1+" s");
LoopQueue<Integer> loopQueue=new LoopQueue<Integer>();
double time2=test(loopQueue, num);
System.out.println("LoopQueue,time: "+time2+" s");
}
}
测试结果:
可以看出数组队列明显比循环队列用时时间长。不同的JMV或电脑运行的时间可能会有所不同。