栈(先进后出)
撤销操作(Ctrl + z)
我们每天无处不用的撤销操作其实就是吧入栈里面的元素取出来然后再进行去除,最后成功达到我们删除加改正的操作。
我们实现栈需要创建一个接口,接口里面是实现栈的一些方法,我们有了接口自然要有实现类,栈也是一种储存的容器因此我们需要用到我们前面所说的数组,栈有俩个动作那就是入栈(push),出栈(pop)等,我们还要判断栈是不是为空 因此我们需要定义一个新的boolean方法 isEmpty,同时还需要判断栈顶元素方法peek 等等;实现类里面当然是重写接口中的方法,具体代码如下
接口
public interface Stack<E>{
int getSize();
boolean isEmpty();
void push(E e);
E pop();
E peek();
}
实现类
public class ArrayStack<E> implements Stack<E> {
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();
}
public int getCapacity() {
return array.getCapacity();
}
@Override
public void push(E e) {
array.addLast(e);
}
@Override
public E pop() {
return array.removeLast();
}
@Override
public E peek() {
return array.getLast();
}
@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();
}
}
队列(先进先出)
我们实现队列也同样需要创建接口,实现接口,方法和栈类似只是名字换了而已。入队(enqueue)出队(dequeue)队首元素(getFront)判断是否为空依然是boolean isEmpty;实现代码类似:
接口
public interface Queue<E> {
int getSize();
boolean isEmpty();
void enqueue(E e);
E dequeue();
E getFront();
}
实现类
public class ArrayQueue<E> implements Queue<E> {
private Array<E> array;
public ArrayQueue(int capacity) {
array = new Array<>();
}
public ArrayQueue() {
array = new Array<>();
}
@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) {
array.addLast(e);
}
@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();
}
}
循环队列(capacity = capacity -1)
front = tail 队列为空(首的下标等于尾的下标)
(tail + 1) % c = front 队列满了(可以使队列进行循环)
循环队列也有出队,入队等操作,就和前面数组一样 会有位置的移动问题,所以我们也要考虑位置的移动以及变化之后的新的循环队列和数组类似 也有队首元素 判断是不是为空,容量是不是已经满了等等。
实现代码
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;
}
我们首先来说它的入队
他的入队和数组差不多他们都是添加一个元素然后要进行size加一操作,但是他是循环的 所以就有了(tail +1 )% data.length = tail这样的操作。实现代码如下
public void enqueue(E e) {
if((tail + 1) % data.length == front) {
resize(getCapacity() * 2 );
}
data[tail] = e;//加入的元素
tail = (tail + 1) % data.length;//进行循环
size++;//容量加一
}
其次便是它的出队
它的出队需要考虑队列不是空的,如果是空的那么就没有元素,也就不能进行出队操作了,因此我们需要判断队列是不是空的。如果是空的,我们会抛出一个异常throw new IllegalArgumentException(“不能从一个空队中进行出队”);
因为队列的特点是先进先出,那么出对的一定是front所指的元素因此出队列了,那么对应的就要调整front的位置,同时要进行size减一的操作,具体的实现代码如下:
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;
}
队列的扩容(缩容)
其实和数组类似,也是元素满了,进行自动扩容,原来的队列发生了改变,用一个新的队列来替换原来的队列。但是由于新队列是一个循环队列所以新队列的首元素便不是原来队列的首元素了因此也就有了(缩容吧容量变为原来的1/2)
newData[i] = data[(i + front) % data.length];这样的操作,具体实现代码如下:
private void resize(int newCapacity) {
E[] newData = (E[])new Object[newCapacity + 1];
for(int i = 0; i < size; i++ ) {
newData[i] = data[(i + front) % data.length];
}
data = newData;
front = 0;
tail = size;
}
重新的toString方法
因为循环队列 首元素下标不一定为0 所以他的首元素下标为front;同时他的front = tail 那个队列为空 所以他不能相等,同时他是循环的所以i = (i+1) % data.length然后其他的都简单就不一一讲解了,具体的实现代码如下:
@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(",");
}
}
res.append("] tail");
return res.toString();
}
循环队列性能优于数组队列;实现代码如下
public class Main {
//测试使用q运行opCount个enqueue和dequeue操作所需要的时间,单位:秒
private static double testQueue(Queue<Integer> q,int opCount) {
long startTime = System.nanoTime();
Random random = new Random();
for(int i = 0; i < opCount; i++) {
q.enqueue(random.nextInt(Integer.MAX_VALUE));
}
for(int i = 0; i < opCount; i++) {
q.dequeue();
}
long endTime = System.nanoTime();
return (endTime - startTime) / 1000000000.0;
}
public static void main(String[] args) {
int opCount = 100000;
ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
double time1 = testQueue(arrayQueue,opCount);
System.out.println("ArrayQueue,time: " + time1 + "s");
LoopQueue<Integer> loopQueue = new LoopQueue<>();
double time2 = testQueue(loopQueue,opCount);
System.out.println("LoopQueue,time: " + time2 + "s");
}
这是对栈和队列的理解,不喜勿喷,谢谢。