zoukankan      html  css  js  c++  java
  • 数据结构基础(四)堆栈

    在这一章我们来了解两个很特殊的数据结构:堆栈 (Stack) 和队列 (Queue)。这两个数据结构类似垃圾桶和队伍,栈是先进后出型,队列是先进先出型。

    堆栈(Stack)

    概念

    堆栈是一种常用的数据结构,这种数据结构的存储方式和垃圾桶一样,后面放进去的元素可以先取出来,而最早放入的元素会被压在最下面,最后才能被拿出来。我们也可以把栈的储存方式简单理解为堆盘子,后面加入的盘子会被堆到最上面,最早堆入的盘子在最下面:

    所以栈是一种后进先出(Last In First Out)的数据结构,后入的元素先出,先入的元素后出。

    堆栈主要支持以下两种操作:

    • 入栈(Push):将一个元素放入栈,用来加入数据。
    • 出栈(Pop):将一个元素弹出栈,用来删除数据。

    还有以下两种辅助操作:

    • Peek:查看最顶部的元素。
    • isEmpty:查看栈是否为空。
    Stack Representation

    数组栈实现

    链表有两种实现方式,一种是数组,另一种是链表。数据的实现方式很简单,以下是数组栈的定义:

    public class Stack {
        static final int CAPACITY = 1000;
        int top;
        int stack[];
    
        public Stack() {
            top = -1;
            stack = new int[CAPACITY];
        }
    }

    在数组栈中,我们使用数组来储存数据,其中包含一个CAPACITY来限制栈的容量,并使用指针top来记录最顶端元素的位置,top初始化为-1,代表数组栈没有任何元素。以下是push和pop方法的定义:

    public boolean push(int val) {
        if (top >= (CAPACITY - 1)) {
            System.out.println("Stack Overflow.");
            return false;
        }
        stack[++top] = val;
        return true;
    }
    
    public int pop() {
        if (top < 0) {
            System.out.println("Stack Underflow.");
            return 0;
        }
        int element = stack[top--];
        return element;
    }

    在push中,我们需要检查顶部元素是否达到容量限制,如果是,输出“溢出栈”错误。否则移动top指针,加入新的元素。在pop中,也要查看栈是否为空,如果是,那么输出“栈下溢”错误。否则将top指针减一。另外还有peek和isEmpty的实现:

    public int peek() {
        if (top < 0) {
            System.out.println("Stack Underflow");
            return 0;
        }
        int element = stack[top];
        return element;
    }
    
    public boolean isEmpty() {
        return top < 0;
    }

    peek也要查看栈是否为空,如果不是,直接返回top指向的元素。isEmpty只要查看top是否小于0即可。

    链式栈实现

    除了数组栈,我们也可以使用链表来实现栈,以下是链式栈的定义:

    public class ListStack {
    
        static class StackNode {
            int val;
            StackNode next;
            StackNode(int val) {
                this.val = val;
            }
        }
    
        StackNode top;
    
        public ListStack() {
            top = null;
        }
    }

    在链式栈中,我们先定义节点StackNode,节点中包含数值和下一个节点的指针。在链式栈中,我们只需要记录top节点,在初始化时定义为null。以下是push和pop的定义:

    public void push(int val) {
        StackNode newNode = new StackNode(val);
        if (top == null) {
            top = newNode;
        } else {
            StackNode temp = top;
            top = newNode;
            newNode.next = temp;
        }
        System.out.println(val + " is pushed to stack.");
    }
    
    public int pop() {
        if (top == null) {
            System.out.println("Stack is Empty.");
            return Integer.MIN_VALUE;
        }
        int popped = top.val;
        top = top.next;
        return popped;
    }

    在push中,我们先创建新的节点newNode,如果栈为空,那么直接将newNode赋给top。如果不为空,就将新元素的下一节点指向当前的top,并将newNode更新为top节点。在pop中,也要先检查栈是否为空,不为空的话,记录下top的数据作为返回值,并将top更新为自己的下一个节点。以下是链式栈peek和isEmpty的定义:

    public int peek() {
        if (top == null) {
            System.out.println("Stack is empty.");
            return Integer.MIN_VALUE;
        }
        return top.val;
    }
    
    public boolean isEmpty() {
        return top == null;
    }

    在栈不为空的情况下,peek只需查看top的值即可,isEmpty也只要查看top是否是null就可以了。不管是用数组还是链表来实现栈,我们都只要处理头节点top,所以栈的所有操作都为O(1)。

    队列(Queue)

    概念

    队列是很好理解的一种数据结构,顾名思义,队列数据结构就和我们平时排队一样,先进入的元素先出,后进入的元素后出。队列的两端都是开的,一段负责插入新元素,另一端负责删除元素。

    Queue Example

    队列主要支持以下两种操作:

    • 入队(enqueue):增加一个新的元素
    • 出队(dequeue):删除一个元素

    还支持其他辅助操作:

    • peek – 查看队列最前端的元素
    • isFull – 查看队列是否满了
    • isEmpty – 查看队列是否为空

    数组队列实现

    队列和堆栈一样,也可以使用两种实现方式,一种是使用数组,叫做顺序队列,另一种是使用链表实现,叫做链式队列。以下我们会先实现一种更常见的数组队列,叫做循环队列,它和基础的顺序队列相比较,更能有效地利用数组空间。以下是循环队列的定义:

    public class ArrayQueue {
        int front, rear, size;
        int capacity;
        int array[];
    
        public ArrayQueue(int capacity) {
            this.capacity = capacity;
            front = rear = size = 0;
            array = new int[capacity];
        }
    }

    在循环队列中,我们需要capacity来限制队列的长度,并创建两个指针front和rear,front用来指向队列的头部,而rear指向队列的尾部。队列总是从头部取出元素,从尾部插入新元素,在操作队列时,我们只需要移动front和rear两个指针即可。我们还需要一个额外的size变量来记录元素的数量,front,rear和size都初始化为0 。

    以下是enqueue和dequeue的定义:

    public void enqueue(int item) {
        if (isFull()) return;
        array[rear] = item;
        rear = (rear + 1) % capacity;
        size++;
        System.out.println(item + " is enqueued.");
    }
    
    public int dequeue() {
        if (isEmpty()) return Integer.MIN_VALUE;
        int item = array[front];
        front = (front + 1) % capacity;
        size --;
        return item;
    }

    在新元素入队的时候,我们需要先判断队列是否已满(isFull的代码在下一段)。如果未满,那么就把元素插入rear的位置,并将rear加1,并与capacity取模,然后增加size。在出队的时候,先要检查队列是否为空(isEmpty的代码在下一段),记录下删除元素的值后,我们将front指针增加1,与capacity取模,然后将size减少1。以下是辅助操作的定义:

    public int peek() {
        if (isEmpty()) return Integer.MIN_VALUE;
        return array[front];
    }
    
    public boolean isFull() {
        return size == capacity;
    }
    
    public boolean isEmpty() {
        return size == 0;
    }

    peek只要查看front指针指向的值即可,isFull要检查size是否和容量capacity相同,isEmpty直接查看size是否等于0。

    链式队列实现

    用链表实现队列也很简单,和数组实现相似,也需要两个指针(front和rear)来实现。以下是ListQueue和QueueNode的定义:

    public class ListQueue {
    
        QueueNode front;
        QueueNode rear;
    
        static class QueueNode {
            int value;
            QueueNode next;
            public QueueNode(int value) {
                this.value = value;
            }
        }
    }

    在链式队列中,我们需要定义节点QueueNode,QueueNode中含有两个值:一个是节点的数值value,另一个是指向下一个节点的next指针。在链式队列ListQueue中,我们只需要两个节点front和rear,front用来指向队列最前端的节点,而rear用来指向尾节点。以下是两个重要操作enqueue和deuque的实现:

    public void enqueue(int value) {
        QueueNode newNode = new QueueNode(value);
        if (this.rear == null) { // Queue is empty
            this.front = this.rear = newNode;
            return;
        }
        this.rear.next = newNode;
        this.rear = newNode;
    }
    
    public int dequeue() {
        if (this.front == null) {
            System.out.println("The queue is empty.");
            return Integer.MIN_VALUE;
        }
        QueueNode frontNode = this.front;
        this.front = this.front.next;
        if (this.front == null) {
            this.rear = null;
        }
        return frontNode.value;
    }

    在enqueue中,我们先创建一个新的节点,如果队列为空,那么将头节点和尾节点同时指向新节点,结束操作。如果队列不为空,只要尾节点的next指针指向新节点,然后将尾节点指向新节点。在dequeue中,如果头节点front为空,直接返回默认数值。队列不为空的情况下,记录下front的数值作为返回值,并将头节点更新为下一节点。

    完整代码

    GitHub完整代码

    Leetcode相关练习

    Stack相关:

    Queue相关:

  • 相关阅读:
    正则表达式工具RegexBuddy使用教程(原创自Zjmainstay)
    基于nodejs实现js后端化处理
    深入正则表达式应用
    如何利用火狐控制台下载网页图片
    Ajax实现提交表单时验证码自动验证(原创自Zjmainstay)
    PHP cURL应用实现模拟登录与采集使用方法详解
    程序猿教你怎样记密码
    我眼里的正则表达式(入门)
    博客园文章markdown实现
    jQuery实现菜单点击隐藏(上下左右)
  • 原文地址:https://www.cnblogs.com/qiu-hua/p/14880217.html
Copyright © 2011-2022 走看看