“栈”是我们常常听到的一个术语,那么什么是栈呢,很简单,栈(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://blog.csdn.net/zfy163520