数据结构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://blog.csdn.net/zfy163520

猜你喜欢

转载自blog.csdn.net/zfy163520/article/details/81193735