数据结构02——栈和队列
“栈”是我们常常听到的一个术语,那么什么是栈呢,很简单,栈(Stack)是一个后进先出(Last in first out,简称:LIFO)的线性表,它只能从一端添加元素,也只能从一端去除元素,这一端就称为:栈顶。在计算机的世界中,栈是有着一些不可思议的作用的,例如你在编辑器中输入你想要输入的文字是,当你发现输入有误时,你会进行撤回的操作。而这就是无处不在的Undo操作。还有你安装软件时的next和back操作等等。
接下来我们就将继续我们的探索之路,来自己手写实现一个栈功能。
一.栈
1.首先我们定义一个栈的接口
public interface Stack<E> { int getSize();//获取栈的大小 boolean isEmpty();//判断栈是够为空 void push(E e);//进行压栈操作 E pop();//进行出站操作 E peek();//查看栈顶内容 }
2.实现栈的主要数据机制
栈的机制可以用数组来实现,也可以用链表来实现,我们将用上篇文章“小小数组”,不可小觑中所创建的数组来实现栈的基本操作:
package com.zfy.stackorqueues; public class Array<E> { private E[] data; private int size; //构造函数,传入数组的容量capacity构造Array数组 public Array(int capacity) { data = (E[]) new Object[capacity]; size = 0; } //无参数构造函数,默认数组的容量capacity=10 public Array() { this(10); } //获取数组中的元素个数 public int getSize() { return size; } //获取数组的容量 public int getCapacity() { return data.length; } //返回数组是否为空 public boolean isEmpty() { return size == 0; } //向所有元素后添加一个新元素 public void addLast(E e) { add(size, e); } // 在所有元素前添加一个新元素 public void addFirst(E e) { add(0, e); } // 在index索引的位置插入一个新元素e public void add(int index, E e) { if(index < 0 || index > size) throw new IllegalArgumentException("AddLast failed. Require index >= 0 and index <= size."); if(size == data.length) resize(2*data.length); for (int i = size-1; i >= index; i--) data[i+1] = data[i]; data[index] = e; size ++; } //获取index索引位置的元素 public E get(int index) { if(index < 0 || index >= size) throw new IllegalArgumentException("Get failed. Index is illegal"); return data[index]; } public E getLast() { return get(size - 1); } public E getFirst() { return get(0); } //修改index索引位置的元素为e public void set(int index, E e) { if(index < 0 || index >= size) throw new IllegalArgumentException("Set failed. Index is illegal."); data[index] = e; } //查找数组中是否有元素e public boolean cotains(E e) { for (int i = 0; i < size; i++) if (data[i].equals(e)) return true; return false; } //查找数组中元素e所在的索引,如果不存在元素e,则返回-1 public int find(E e) { for (int i = 0; i < size; i++) if (data[i].equals(e)) return i; return -1; } //从数组中删除index位置的元素, 返回删除的元素 public E remove(int index) { if(index < 0 || index >= size) throw new IllegalArgumentException("Remove failed. Index is illegal."); E ret = data[index]; for (int i = index + 1; i < size; i++) data[i -1] = data[i]; size --; data[size] = null; // loitering objects != memory leak if(size == data.length / 4) resize(data.length / 2); return ret; } //从数组中删除第一个元素, 返回删除的元素 public E removeFirst() { return remove(0); } // 从数组中删除最后一个元素, 返回删除的元素 public E removeLast(){ return remove(size - 1); } //从数组中删除元素e public void removeElement(E e) { int index = find(e); if (index != -1) remove(index); } @Override public String toString() { StringBuilder res = new StringBuilder(); res.append(String.format("Array: size = %d, capacity = %d \n ", size,data.length)); res.append("["); for (int i = 0; i < size; i++) { res.append(data[i]); if (i != size - 1) { res.append(","); } } res.append("]"); return res.toString(); } // 将数组空间的容量变成newCapacity大小 private void resize(int newCapacity) { E[] newData = (E[]) new Object[newCapacity]; for (int i = 0; i < size; i++) newData[i] = data[i]; data = newData; } }
3.最后实现栈的功能
package com.zfy.stackorqueues; 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(); } }
4.测试类以及用栈实现符号匹配功能
1.测试类:
public static void main(String[] args) { ArrayStack<Integer> stack = new ArrayStack<>(); for(int i = 0 ; i < 5 ; i ++){ stack.push(i); System.out.println(stack); } stack.pop(); System.out.println(stack); }
2.实现符号匹配功能
public boolean isValid(String s) { ArrayStack<Character> stack = new ArrayStack<>(); 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(); } public static void main(String[] args) { System.out.println((new Solution()).isValid("()[]{}")); System.out.println((new Solution()).isValid("([)]")); }
数据的入栈和出栈的时间复杂度均为O(1),因此这个栈在时间性能上是很好的。
二.队列
队列也是线性结构,与数组相比较,队列对应的操作是数组的子集。而且队列它只能从一端(队尾)添加元素,且只能从另一端(队首)取出元素。这个和我们日常生活中的排队是类似的,因此队列是一种先进先出(First In First Out)的数据结构。接下来我们将用数组机制来实现一个数组队列。
1.定义一个队列的接口
public interface Queue<E> { int getSize();//获取队列size boolean isEmpty();//判断队列是否为空 void enqueue(E e);//入队操作 E dequeue();//出队操作 E getFront();//获取 front }
2.实现队列的接口
因为我们这个队列和栈一样都是用我们前面的数组进行实现的,因此这里就不再贴代码了。
package com.zfy.stackorqueues; 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(); } 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(); } public static void main(String[] args) { ArrayQueue<Integer> queue = new ArrayQueue<>(); for(int i = 0 ; i < 10 ; i ++){ queue.enqueue(i); System.out.println(queue); if(i % 3 == 2){ queue.dequeue(); System.out.println(queue); } } } }
3.最后我们来实现一个循环队列
因为数组队列是具有局限性的,因为它的出队操作的复杂度为O(n)级别的,因此我们再来实现一个循环数组。
package com.zfy.stackorqueues; 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(10);//定义无参构造函数,设置其初始长度为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) {//判断tail+1余当前数组的length是否等于front,如果等于则队列是满的 resize(getCapacity() * 2);//这里使用getCapaCity(),是因为我们前面设置的数组length可以减了一个1 } data[tail] = e; tail = (tail + 1) % data.length;//本来是tail++;但是由于是循环数组,所以需要这样写 size ++; } @Override public E dequeue() { if(isEmpty()) throw new IllegalArgumentException("Cannot dequeue from an empty queue."); E ret = data[front]; data[front] = null; front = (front + 1) % data.length;//本来是tail++;但是由于是循环数组,所以需要这样写 size --; if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {//为了减少空间浪费,当size为getCapaCity() / 4时,进行缩容操作,且getCapaCity() / 2不能为零 resize(getCapacity() / 2); } return ret; } @Override public E getFront() { if(isEmpty()) throw new IllegalArgumentException("Queue is empty."); return data[front]; } private void resize(int newCapacity){ E[] newData = (E[]) new Object[newCapacity+1];//因为前面数组的length-1,所以这里要想容纳就必须要+1,这个和构造函数里的+1是一样的 for (int i = 0; i < size; i++) { newData[i] = data[(i + front) % data.length];//因为是循环数组,所以,新的data的队首对应的就是(i+front)的这样一个偏移位置,但是由于这是循环数组,(i+front)可能会越界,所以我们需要%data.length一下 } data = newData; front = 0; tail = size; } @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 static void main(String[] args){ LoopQueue<Integer> queue = new LoopQueue<>(); 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(1)。
最后语:不积跬步,无以至千里;不积小流,无以成江海。数据结构其实是一个非常美妙的东西,只要你能领会其中的含义,你就可能会爱不释手,个人觉得代码还是要自己动手去写,这样才能领会其中真意!
参考:bobobo老师的玩转数据结构
版权声明:尊重博主原创文章,转载请注明出处 https://www.cnblogs.com/hsdy