数据结构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

posted @ 2018-07-25 08:31  寒山道杳  阅读(152)  评论(0编辑  收藏  举报