栈
(1)栈也是一种线性结构
(2)栈对应的操作是数组的子集
(3)栈本质就是一个数组,元素后进先出
(4)限定数组形成栈这种数据结构
栈的应用
(1)通过栈顶元素确认最近的操作
(2)程序调用的系统栈
在程序调用过程中经常出现在一个逻辑的中间终止然后跳到另外一个逻辑,例如子函数的调用,使用系统栈记录程序的调用过程
A中间调用子函数B,跳转到B,暂时中断后,中断调用信息入栈,C执行完后跳到B2,B执行完后跳到A2,A执行完系统栈为空,整个过程结束
push 和pop可能触发resize,均摊复杂度为O(1)
(1)栈和队列都有多种实现方式,ArrayStack基于动态数组实现的栈
(2)getCapacity只有在用动态数组来实现栈的时候才存在容量这个概念
public interface Stack<E> {
void push(E n);
E pop();
E top();
int getSize();
boolean isEmpty();
}
public class ArrayStack <E> implements Stack<E>{
ArrayDemo<E> array;
public ArrayStack(int capacity){
array=new ArrayDemo<>(capacity);
}
public ArrayStack(){
array=new ArrayDemo<>();
}
public int getCapacity(){
return array.getCapacity();
}
@Override
public void push(E n) {
//调用array的resize方法,用户不需要考虑空间是否足够
array.addLast(n);
}
@Override
public E pop() {
return array.removeLast();
}
@Override
public E top() {
return array.getLast();
}
@Override
public int getSize() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
@Override
public String toString() {
StringBuilder res=new StringBuilder();
res.append("Stack: ");
res.append('[');
for(int i=0;i<getSize();i++){
res.append(array.get(i));
}
res.append("] top");
return res.toString();
}
}
```java
//修改动态数组
//取出数组中的某一个元素,必须判断index是否合法,通过get方法,用户永远无法查询没有使用的空间
//通过封装的方式,保证了数据的安全
E get(int index){
if(index<0||index>size-1)
throw new IllegalArgumentException("get failed,Array is full");
return data[index];
}
public E getLast(){
//方法中调用方法,直接调用
return get(size-1);
//return data[size-1],不能保证索引一定合法,用get方法能用上条件判断
}
public E getFirst(){
return get(0);
}
栈的应用:括号的匹配
(1)逐一遍历
(2) 只要遇到左括号入栈
(3)遇到右括号:如果栈为空,不需要再进行匹配,直接返回false(右括号多余)
(4)遇到右括号:如果栈不为空,判断右括号是否与栈顶左括号匹配。不匹配返回false
(5)遍历结束后,若栈不为空,仍然返回false(左括号多余)
(6)栈顶元素反映了在嵌套的层次关系中,最近的需要匹配的元素
队列:队列是一个线性结构,数据排成一排,相比数组,队列对应的操作是数组子集,先进先出
依托不同的底层数据结构实现接口Queue
数组队列
public class ArrayQueue<E> implements Queue<E>{
ArrayDemo<E> arrayDemo;
public ArrayQueue(int capacity){
arrayDemo=new ArrayDemo<>(capacity);
}
public ArrayQueue(){
arrayDemo=new ArrayDemo<>();
}
@Override
public void enqueue(E n) {
arrayDemo.addLast(n);
}
@Override
public E dequeue() {
return arrayDemo.removeFirst();
}
@Override
public E getFront() {
return arrayDemo.getFirst();
}
@Override
public int getSize() {
return arrayDemo.getSize();
}
@Override
public boolean isEmpty() {
return arrayDemo.isEmpty();
}
}
出队时移除数组中第一个元素,剩余元素都要向前移动一位,时间复杂度O(n),需要寻求更好的方法,保证入队为O(1),出队为O(1)于是有循环队列
循环队列
(1)改进:队首出队后,只需维护一下front的指向,不移动剩余元素
(2)初始front tail指向0,front=tail队列为空
(3)元素入队,维护tail;元素出队,维护front
(4)数组前端为空,末尾为满,元素入队可利用前端空间,形成循环队列
(5)队列为满
1、此时tail指向的位置就是下一次有元素入队应该放入的位置,新元素放入后,tail+1与front重叠,说明整个队列只剩下最后一个空间,可以认为数组已经满了,相应地可进行扩容,有意识地浪费一个空间避免front=tail同时可以表示空或满,因此定义(tail+1)%c=front队列满
2、(tail+1)%c=front:循环队列由于到了数组末端还可以返回回来到前面,即一个求余操作
public class CircleQueue<E> implements Queue<E>{
private E[] array;
private int front;
private int tail;
private int size
public CircleQueue(int capacity){
//capacity是用户希望队列最多能承载的容量,而在底层实现中,为了避免队列空、队列满表示时的冲突,capacity+1
//有意识地浪费一个单位
//不能直接new一个泛型数组
array=(E[])new Object[capacity+1];
this.front=0;
this.tail=0;
this.size=0;
}
public CircleQueue(){
this(10);
}
public int getCapacity(){
return array.length-1;
}
@Override
public void enqueue(E n) {
//队列满,不能入队,注意这里的队列实际大小为array.length,用户可见的capacity为array.length-1
if((tail+1)%array.length==front){
//只要保证capacity翻倍,数组实际大小为capacity+1,resize过程与构造初始数组一致,只不过capacity翻倍了
resize(getCapacity()*2);
}
//tail指向第一个没有元素的空间,先存放,再维护tail
array[tail]=n;
tail=(tail+1)%array.length;
size++;
}
private void resize(int newCapacity) {
E[] newArray=(E[])(new Object[newCapacity+1]);
//将array中元素全部放到newArray中
//不同的遍历方式
for(int j=0;j<size;j++){
//原队列的队首元素不在0的位置时,把原队列的front放到新数组索引为0的位置,剩余元素依次排列
//newArray[0]--array[front]
newArray[j]=array[(j+front)%array.length];
}
array=newArray;
front=0;
//扩容操作没有影响队列中的元素个数,size不需要维护
tail=size;
}
@Override
public E dequeue() {
//队列空,不能出队
if(isEmpty()){
throw new IllegalArgumentException("dequeue failed");
}
E res=array[front];
array[front]=null;
front=(front+1)%array.length;
size--;
//出队后剩下的元素等于循环队列最多可以承载的容量的1/4,缩容为一半
if(size==getCapacity()/4&&getCapacity()/2!=0){
resize(getCapacity()/2);
}
return res;
}
@Override
public E getFront() {
if(isEmpty()){
throw new IllegalArgumentException("get failed");
}
return array[front];
}
@Override
public int getSize() {
// return (tail-front+getCapacity())%getCapacity();
return size;
}
@Override
public boolean isEmpty() {
return front==tail;
}
@Override
public String toString() {
//拼接一个字符串
StringBuilder res=new StringBuilder();
//在res后面添加一个字符串,这个字符串用format方法初始化
res.append(String.format("Queue:size=%d,capacity=%d\n",size,getCapacity()));
res.append("front [");
//不同的遍历方式
for(int i=front;i!=tail;i=(i+1)%array.length) {//容量为capacity,但实际没有那么多元素,所以只能遍历到size
res.append(array[i]);
if((i+1)%array.length!=tail)
res.append(", ");
}
res.append("] tail");
return res.toString();
}
}
(1)两种不同的遍历方式,在for中的两种索引范围
for(int i=front;i!=tail;i=(i+1)%array.length) {//容量为capacity,但实际没有那么多元素,所以只能遍历到size
//tail可能比front小,所以循环的条件为!=tail;i可能超出array.length所以i=(i+1)%array.length
res.append(array[i]);
if((i+1)%array.length!=tail)
res.append(", ");
}
for(int i=0;i<size;i++){
//原队列的队首元素不在0的位置时,把原队列的front放到新数组索引为0的位置,剩余元素依次排列
//newArray[0]--array[front]
newArray[i]=array[(i+front)%array.length];
}
(2)动态扩容、缩容,触发条件是==,而不是<>,这是因为队列逐个入队出队,触发条件就扩容缩容,扩容缩容没有影响size,不需要维护
动态扩容条件 if((tail+1)%array.lengthfront)
动态缩容条件 if(sizegetCapacity()/4&&getCapacity()/2!=0){
resize(getCapacity()/2);
}
(3)队列的实际大小和用户能使用的容量 :capacity是用户希望队列最多能承载的容量,而在底层实现中,为了避免队列空、队列满表示时的冲突,队列实际大小length=capacity+1有意识地浪费一个单位,用户能访问的容量大小capacity=length-1
数组队列和循环队列的运行比较
public class Main {
//类型不是局限在CircleQueue或ArrayQueue。而是Queue,Array和Circle都可以传进这个参数中
private static double testQueue(queuedemo.Queue<Integer> q,int opCount){
long startTime=System.nanoTime();
for(int i=0;i<opCount;i++){
q.enqueue(1);
}
for(int i=0;i<opCount;i++){
q.dequeue();
}
long endTime=System.nanoTime();
return endTime-startTime;
}
public static void main(String[] args) {
int opCount=100000;
CircleQueue<Integer> circleQueue=new CircleQueue<>();
System.out.println(Main.testQueue(circleQueue,opCount));
ArrayQueue<Integer> arrayQueue=new ArrayQueue<>();
System.out.println(Main.testQueue(arrayQueue,opCount));
}
}