数据结构(二):栈和队列

本系列为数据结构学习笔记,如有错误请指正~

数据结构(一):数组和链表

一、理论知识

栈和队列都是线性数据结构,属于逻辑结构,是从物理结构(数组和链表)衍生出来的,所以栈和队列既可以数组实现,也可以链表实现,两种实现方式各种优劣。

1. 栈

基本概念

  • 栈顶:最后进入的元素存放的位置
  • 栈底:最早进入的元素存放的位置
  • 先入后出(First In Last Out, FILO):比如12345五个数字依次入栈,出栈顺序为54321
  • 入栈(push):将新元素放入栈中
  • 出栈(pop):将元素从栈中取出,只有栈顶的元素才允许出栈,出栈元素的前一个元素成为新的栈顶

编码实现思路

以数组实现栈,整个栈的操作其实就是对栈顶这个位置的元素进行操作:入栈时,栈顶元素变更为数组新元素的下标;出栈时,栈顶元素变更为出栈元素的前一个元素的数组下标。

因为数组的大小是一定的,所以必须注意越界的问题。读取时判断是否为空,插入时判断是否已满。

应用场景

因为栈的输出顺序和输入顺序相反,所以栈通常用于对“历史”的回溯,比较典型的一个应用就是面包屑导航,可以很方便的回溯到前一步或前几步的操作

2. 队列

基本概念

  • 队头(front):队列的出口
  • 队尾(rear):队列的入口
  • 入队(enqueue):只允许队尾的位置放入元素
  • 出队(dequeue):只允许在队头一侧移出元素
  • 先入先出(First In First Out,FIFO):比如12345依次入队,出队顺序为12345

编码实现思路

以数组实现队列,和栈一样,其实都会转换为对数组的操作。和栈不同的是,栈只需要关心栈顶就行了,队列需要关心“两头”,也就是队头和队尾,其余的实现原理相似。

和栈一样,数组的大小是一定的,所以必须注意越界的问题。读取时判断是否为空,插入时判断是否已满。

应用场景

爬虫算是一个比较典型的应用了,爬取网站时,不可能单线程爬取,多线程爬取时需要解决重复爬取的问题。所以可以将要爬取的url放入队列,按照队列的顺序依次爬取。

同样的道理,在一些多线程应用中,都可以考虑下队列。

源码

/**
 * 栈实现demo
 * @author yangjunqiang
 *
 */
public class MyStackDemo {

    // 数组实现栈
    private int[] stackArray;
    // 栈顶元素的数组下标
    private int top;
    // 栈的大小
    private int size;
    
    /**
     * 初始化,指定大小
     * @param size
     */
    public MyStackDemo(int size) {
        this.size = size;
        this.stackArray = new int[size];
        // 栈初始化, -1代表栈中没有任何元素
        this.top = -1;
    }
    
    /**
     * 入栈
     * @param element
     * @return 入栈的
     */
    public int push(int element) {
        // 因为设置了栈的大小,所以入栈时要判断是否已满
        if(isFull()) {
            throw new IndexOutOfBoundsException("栈已满");
        }
        // 入栈操作,新元素对应新的数组元素下标
        top += 1;
        stackArray[top] = element;
        return element;
    }
    
    /**
     * 出栈
     * @return
     */
    public int pop() {
        // 每次出栈都需要确保栈不为空
        if(isEmpty()) {
            throw new NoSuchElementException("空栈,没有可供读取的元素");
        }
        int element = stackArray[top];
        // 出栈,修改栈顶元素位置为前一个元素的下标
        top -= 1;
        return element;
    }
    
    /**
     * 清空栈
     * 实际上数组并未被清空,只是栈顶元素的位置被重置了
     */
    public void clear() {
        top = -1;
    }
    
    /**
     * 栈的大小,实际上就是初始化时定义的数组大小
     * @return
     */
    public int size() {
        return size;
    }
    
    /**
     * 判断栈是否为空
     * @return
     */
    public boolean isEmpty() {
        return (top == -1);
    }
    
    /**
     * 判断栈是否已满
     * @return
     */
    public boolean isFull() {
        return (top == size - 1);
    }
}

队列

/**
 * 循环队列
 * @author yangjunqiang
 *
 */
public class MyQueueDemo {

    // 数组实现队列
    private int[] array;
    // 队首指针
    private int front;
    // 队尾指针,队尾指针指向的永远是空,也就是队尾指针对应的位置没有元素
    private int rear;
    
    /**
     * 初始化队列,指定队列大小
     * @param size
     */
    public MyQueueDemo(int size) {
        this.array = new int[size];
    }
            
    public void enQueue(int element) throws Exception {
        if(isFull()) {
            throw new Exception("队列已满");
        }
        
        array[rear] = element;
        // 队尾指针后移,因为循环,所以如果队尾指针的值大小如果大于数组长度,代表队头有元素出队了,可以将队尾指针重新指回数组的首位
        // 就像贪吃蛇一样,只要首尾不相等就可永远循环下去,如果首尾相接,则说明队列满了
        rear = (rear + 1) % array.length;
    }
    
    public int deQueue() throws Exception {
        if(isEmpty()) {
            throw new Exception("队列已空");
        }
        // 出队列,队首
        int deQueueElement = array[front];
        // 队首的指针后移
        front = (front + 1) % array.length;
        return deQueueElement;
    }
    
    public void printf() {
        for(int i = front; i != rear; i = (i + 1) % array.length) {
            System.out.println(array[i]);
        }
    }
    
    /**
     * 判断队列是否已满,首尾相接即为满队列。
     * 因为队尾永远为空,所以公式为 :(队尾下标 + 1) % 数组长度 = 队头下标
     * @return
     */
    public boolean isFull() {
        return ((rear + 1) % array.length == front);
    }
    
    /**
     * 判断队列是否为空
     * 队头=队尾
     */
    public boolean isEmpty() {
        return (rear == front);
    }
    
}

你可能感兴趣的:(数据结构(二):栈和队列)