------------------------------栈-----------------------------------------------------
栈可以理解为是数组的子集,只能从一端添加元素,且从同一端删除元素,这一端称为栈顶。
last in first out : LIFO 后进先出
栈的实现主要有如下内容:
Stack<E>
push(E)
E pop()
E peek()
int getSize
boolean isEmpty()
定义接口Stack,接口中含有如上5种方法,定义实现类ArrayStack,由于是数组实现,所以可以在实现中额外增加一个获取容量的方法getCapacity()
接口Stack定义如下:
package stack;
public interface Stack<E> {
public int getSize();
public boolean isEmpty();
public void push(E e);
public E pop();
public E peek();
}
实现类ArrayStack如下:
package stack;
import array.Array;
public class ArrayStack<E> implements Stack<E> {
//基于自定义的Array类实现栈
Array<E> array;
public ArrayStack(int capacity){
array = new Array(capacity);
}
public ArrayStack(){
array = new Array<>();
}
@Override
public int getSize() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
@Override
public void push(E e) {
array.addLast(e);
}
@Override
public E pop() {
return array.removeLast();
}
@Override
public E peek() {
return array.getLast();
}
public int getCapacity(){
return array.getCapacity();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Stack:[");
int size = array.getSize();
for(int i=0;i<size;i++){
sb.append(array.get(i));
if(i != size-1){
sb.append(",");
}
}
sb.append("] top");
return sb.toString();
}
}
测试代码如下:
package stack;
public class Main {
public static void main(String[] args) {
Stack<Integer> stack = new ArrayStack<>(8);
for(int i=0;i<5;i++){
stack.push(i);
System.out.println(stack);
}
stack.pop();
System.out.println(stack);
System.out.println(stack.peek());
}
}
-------------------------------------队列-------------------------------------------------------------------------
队列也是线性结构(数据排成一排),队列的操作也是数组操作的子集,只能一端添加元素(队尾),另一端(队首)取出元素。
队列的定义如下:
Queue<E>
void enqueue(E) 入队 相当于栈的push
E dequeue() 出队 相当于栈的 pop
E getFront() 查看队首元素 相当于栈的peek
int getSize()
boolean isEmpty()
实现类ArrayQueue代码如下:
package queue;
import array.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() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
@Override
public void enqueue(E e) {
array.addLast(e);
}
@Override
public E dequeue() {
return array.removeFirst();
}
@Override
public E getFront() {
return array.getFirst();
}
public int getCapacity(){
return array.getCapacity();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Queue:head[");
int size = array.getSize();
for(int i=0;i<size;i++){
sb.append(array.get(i));
if(i != size-1){
sb.append(",");
}
}
sb.append("] tail");
return sb.toString();
}
public static void main(String[] args) {
ArrayQueue<Integer> arrayQueue = new ArrayQueue<>(8);
for(int i=0;i<5;i++){
arrayQueue.enqueue(i);
System.out.println(arrayQueue);
}
arrayQueue.dequeue();
System.out.println(arrayQueue);
System.out.println(arrayQueue.getFront());
System.out.println(arrayQueue);
}
}
复杂度分析:
入队enqueue:O(1) 均摊
出队dequeue:O(n)
查询队首元素getFront:O(1)
查询元素个数getSize:O(1)
判断队列是否为空isEmpty:O(1)
问题:所以对于出队,如果队列中的数量较大的话,会很耗时,如何解决呢?
解决办法:循环队列
front==tail 则队列为空,(tail+1)%data.length==front则队列为满,需要有意识的浪费一个空间,让最后一次tail可以指向这个位置,所以构造函数时传capacity参数应该创建capacity+1长度的数组
两种遍历方式:
方式一:for(int i=0;i<size;i++){实现体里取偏移量}
方式二:for(int i=front;i!=tail;i=(i+1)%data.length)
注意好什么位置用的data.length,什么位置用的getCapacity()
1) 初始创建的队列,front和tail、size都指向0
2) 先写resize方法
3) 再写入队方法
4) 出队
5) 查看队首元素
6) 重写toString()方法
7)进行测试
8)数组队列与循环队列的性能测试
具体代码实现如下:
package queue;
public class LoopQueue<E> implements Queue<E> {
private E[] data;
private int front;
private int tail;
private int size;
public LoopQueue(int capacity){
data = (E[])new Object[capacity+1];
front = 0;
tail = 0;
size = 0;
}
public LoopQueue(){
this(8);
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return front==tail;
}
//getCapacity返回用户定义的容量,而data.length比getCapacity大1,是有意多留一个空间给tail最后一次指向使用
public int getCapacity(){
return data.length-1;
}
//为了方便记忆,可记住取余运算的时候全都是用的data.length,其他时候用getCapacity()
public void resize(int newCapacity){
//注意此处要与构造函数保持一直,需要定义newCapacity+1个长度
E[] newData = (E[])new Object[newCapacity +1];
for(int i=0;i<size;i++){
//此处取余用data.length
newData[i] = data[(i+front)%data.length];
}
data = newData;
//注意resize之后size大小不变,但是要重新定位front和tail的位置
front = 0;
tail = size;
}
@Override
public void enqueue(E e) {
//入队时需要先判断队列是否已满,如果满了需要先扩容,此处也是用data.length做取余运算
if((tail+1)%data.length==front){
//resize传的参数要用getCapacity,而不能用data.length
resize(2*getCapacity());
}
//如果未到扩容条件,则tail指向的位置就是需要入队的位置
//而如果达到扩容条件,进行了扩容,则在扩容方法中,已经重新定位了front和tail的位置,
// front指向0,tail指向size位置,也是需要入队的最新位置
data[tail] = e;
//入队后需要更改tail的位置和size的大小
tail = (tail+1)%data.length;
size++;
}
@Override
public E dequeue() {
//判断如果队列为空,则抛出异常,也可改为返回空值
if(isEmpty()){
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
}
//取出队首元素以便最后返回
E e = data[front];
//取出队首元素后,要将队首元素置为null
data[front] = null;
//只需要更改front的位置和size的大小即可
front = (front+1)%data.length;
size--;
//判断是否需要缩容
if(size == getCapacity()/4 && getCapacity()/2 != 0){
resize(getCapacity()/2);
}
return e;
}
@Override
public E getFront() {
return data[front];
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(String.format("LoopQueue:size = %d ,capacity = %d \n",size,getCapacity()));
sb.append("front[");
//第二种方式 遍历循环队列中的数据
for(int i=front;i !=tail;i=(i+1)%data.length){
sb.append(data[i]);
//如果不是最后一个元素
if((i+1)%data.length != tail){
sb.append(",");
}
}
sb.append("]tail");
return sb.toString();
}
public static void main(String[] args) {
LoopQueue<Integer> loopQueue = new LoopQueue<>();
for(int i=0;i<10;i++){
loopQueue.enqueue(i);
System.out.println(loopQueue);
//每入队三个元素,就出队一个元素
if(i%3 == 2){
loopQueue.dequeue();
System.out.println(loopQueue);
}
}
}
}
时间复杂度:
enqueue: O(1) 均摊:涉及到扩容
dequeue: O(1) 均摊:涉及到缩容
getFront: O(1)
getSize: O(1)
isEmpty: O(1)
测试数组队列和循环队列的性能差别:
package queue;
import java.util.Random;
public class Main {
public static double testQueue(Queue<Integer> queue,int count){
Random random = new Random();
long startTime = System.nanoTime();
for(int i=0;i<count;i++){
queue.enqueue(random.nextInt());
}
for(int i=0;i<count;i++){
queue.dequeue();
}
long endTime = System.nanoTime();
double cost = (endTime - startTime)/1000000000.0;
return cost;
}
public static void main(String[] args) {
int count = 100000;
ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
double cost1 = testQueue(arrayQueue,count);
System.out.println(cost1);//12.8839
LoopQueue<Integer> loopQueue = new LoopQueue<>();
double cost2 = testQueue(loopQueue,count);
System.out.println(cost2);//0.0100
}
当队列中存放的数据量较大,且频繁出队时,循环队列的性能明显由于数组队列。
}
若有理解不到之处,望指正!