zoukankan      html  css  js  c++  java
  • 栈和队列

    在学习栈和队列前,先补充两个概念:物理结构和逻辑结构。

    1.什么是数据存储的物理结构呢?

      举个易懂的例子,如果把数据结构比作活生生的人,那么物理结构就是人的血肉和骨骼,看得见,摸得着,实实在在。那么,数组和链表就是实实在在的存储结构。

    2.什么是数据存储的逻辑结构呢?

      就刚刚的例子来说,人体之上,还存在着人的思想和精神,它看不见摸不着。那么,栈和队列以及数和图等数据结构都是属于逻辑结构。虽然栈和队列是属于逻辑结构的,但是他们的物理实现既可以利用数组,也可以利用链表来实现。

    3.什么是栈?

      栈(stack)是一种线性数据结构,它就像一个放羽毛球的圆筒容器,栈中的元素只能先入后出,最早进入的元素存放的位置叫做栈底(bottom),最后进入的元素存放的位置叫做栈顶(top)。

      3.1 栈的基本操作

        3.1.1 入栈

          入栈操作(push)就是把新元素放入栈中,只允许从栈顶一侧放入元素,新元素的位置将会称为新的栈顶。

        3.1.2 出栈

          出栈操作(pop)就是把元素从栈中弹出,只有栈顶元素才允许出栈,出栈元素的前一个元素将会称为新的栈顶。

          java自己封装的栈:

     1 package com.algorithm.test;
     2 
     3 import java.util.Stack;
     4 
     5 /**
     6  * @Author Jack丶WeTa
     7  * @Date 2020/7/28 12:06
     8  * @Description 关于栈的简单操作(入栈和出栈)
     9  */
    10 public class StackTest {
    11     public static void main(String[] args) {
    12         Stack<Integer> stack = new Stack<>();
    13 
    14         for (int i = 0; i <= 10; i++) {
    15             stack.push(i);
    16         }
    17 
    18         while (!stack.isEmpty()) {
    19             System.out.println("栈:" + stack.toString() + "	栈大小为:" + stack.size() + "	出栈元素为:" + stack.pop());
    20         }
    21     }
    22 }

          我们自己也可以用数组来简单实现一下:

    package com.algorithm.test;
    
    
    /**
     * @Author Jack丶WeTa
     * @Date 2020/7/28 12:06
     * @Description 关于栈的简单操作(入栈和出栈)
     */
    public class MyStackTest {
    
        private int[] array;
        private int top; //栈顶
    
        public MyStackTest(int capacity){
            this.array = new int[capacity];
        }
    
        public void push(int element){
            if(top >= array.length){
                throw new IndexOutOfBoundsException("满栈了,无法进行入栈操作!");
            }
            array[top] = element;
            top++;
        }
    
        public int pop() {
            if(top <= 0){
                throw new RuntimeException("栈为空,无法进行出栈操作!");
            }
            int popElement = array[top-1];
            top--;
            return popElement;
        }
    
        public void output(){
            for (int i = 0; i < top; i++){
                System.out.print(array[i] + ",");
            }
        }
        public static void main(String[] args) {
            MyStackTest myStackTest = new MyStackTest(6);
            myStackTest.push(5);
            myStackTest.push(2);
            myStackTest.push(4);
            myStackTest.push(8);
            myStackTest.push(0);
            myStackTest.push(9);
            myStackTest.output();
            System.out.println();
            System.out.println("-----开始出栈操作-----");
    
            for (int i = 0; i < 6; i++){
                System.out.print("出栈第" + (i+1) + "次:");
                myStackTest.pop();
                myStackTest.output();
                System.out.println();
            }
        }
    }

        总结下栈操作的时间复杂度。由于入栈和出栈指挥影响到最后一个元素,不涉及到其他元素的整体移动,所以无论是以数组还是链表实现,入栈和出栈的时间复杂度都是O(1)。

    4.什么是队列呢?

      队列(queue)是一种线性数据结构,它的特征和行驶车辆的单行隧道很相似。不同于栈的先入后出,队列中的元素只能先入先出。队列的出口端叫做队头(front),队列的入口端叫队尾(rear)。

      4.1 队列的基本操作

        4.1.1 入队

          入队(enqueue)就是把新元素放入队列中,只允许在队尾的位置放置元素,新元素的下一个位置将会成为新的队尾。

        4.1.2 出队

          出队(dequeue)就是把元素移出队列,只允许在对头一侧移出元素,出队元素的后一个元素将会成为新的对头。但是,如果像这样子不断出队的话,对头左边的空间将失去作用,那队列的容量岂不是越来越小了?那么,我们就需要采取一些措施,用数组实现的队列可以采用循环队列的方式来维持队列容量的恒定。

    5.什么是循环队列呢?

      假设队列经过反复的入队和出队操作,还剩下2个元素,在“物理”上分布于数组的末尾位置,这时又有一个新元素将要入队。在数组不做扩容的前提下,如何让新元素入队并确定新的队尾位置呢?我们可以利用已出队元素留下的空间,让队尾指针重新指回数组的首位。这样一来,整个队列的元素就“循环”起来了。在物理存储上,队尾的位置也可以在对头之前。当再有元素入队时,将其放在数组的首位,队尾指针继续后移即可。一直到(队尾下标+1)%数组长度 = 对头下标 时,代表此队列真的已经满了。需要注意的是,队尾指针指向的位置永远空出1位,所以队列最大容量比数组长度小1。

      那么我们也可以用代码自己实现一下所谓的循环队列:

    package com.algorithm.test;
    
    
    /**
     * @Author Jack丶WeTa
     * @Date 2020/7/28 14:57
     * @Description 循环队列的实现demo
     */
    public class MyQueueTest {
        private int[] array;
        //队头
        private int front;
        //队尾
        private int rear;
    
        public MyQueueTest(int capacity) {
            this.array = new int[capacity];
        }
    
        /**
         * @param element 入队的元素
         * @throws Exception
         */
        public void enQueue(int element) throws Exception {
            if ((rear + 1) % array.length == front) { //  (队尾下标+1)% 数组长度 == 队头下标
                //队列已经满了
                throw new Exception("队列已满!");
            }
            array[rear] = element;
            rear = (rear + 1) % array.length; //入队后,队尾向后挪一位
        }
    
        /**
         * @return 出队的元素
         * @throws Exception
         */
        public int deQueue() throws Exception {
            if (rear == front) {
                throw new Exception("队列已空!");
            }
            int deQueueElement = array[front]; //出队的元素
            front = (front + 1) % array.length; //出队后,队头向后挪1位
            return deQueueElement;
        }
    
        /**
         * 输出队列
         */
        public void output() {
            for (int i = front; i != rear; i = (i+1)%array.length){
                System.out.print(array[i] + ",");
            }
        }
    
        public static void main(String[] args) throws Exception {
            MyQueueTest myQueueTest = new MyQueueTest(6);
            myQueueTest.enQueue(3);
            myQueueTest.enQueue(5);
            myQueueTest.enQueue(6);
            myQueueTest.enQueue(8);
            myQueueTest.enQueue(1);
            myQueueTest.output();
            System.out.println("--------------------------------------");
    
            myQueueTest.deQueue();
            myQueueTest.deQueue();
            myQueueTest.deQueue();
            myQueueTest.output();
            System.out.println("--------------------------------------");
    
            myQueueTest.enQueue(2);
            myQueueTest.enQueue(4);
            myQueueTest.enQueue(9);
            myQueueTest.output();
        }
    }

        循环队列不仅充分利用了数组的空间,还避免了数组元素整体移动的麻烦,那么入队和出队的时间复杂度也同样是O(1)。

    6.栈和队列的应用

      6.1 栈的应用

        栈的输出顺序和输入顺序相反,所以栈通常用于对“历史”的回溯,也就是逆流而上追溯“历史”。

        例如实现递归的逻辑,就可以用栈来代替,因为栈可以回溯方法的调用链。

        栈还有一个著名的应用场景是面包屑导航,使用户在浏览页面时可以轻松的回溯到上一级或更上一级的页面。

      6.2 队列的应用

        队列的输出顺序和输入顺序相同,所以队列通常用于对“历史”的回放,也就是按照“历史”的顺序把“历史”重演一遍。

        例如在多线程中,争夺公平锁的等待队列,就是按照访问顺序来决定线程在队列中的次序的。

        再如网络爬虫实现网络抓取时,也是把抓取的网站URL存入队列中,在按照存入队列的顺序来依次抓取和解析的。

      6.3 双端队列

        双端队列这种数据结构可以说是综合了栈和队列的优点,对双端队列来说,从对头一端可以入队或出队,从队尾一端也可以入队或出队。

      6.4 优先队列

        还有一种队列,它遵循的不是先入先出,而是谁的优先级最高,谁先出队。这种队列叫做优先队列。有限队列已经不属于线性数据机构的范畴了,他是基于二叉堆来实现的。

      总结一下,本次主要学习栈和简单的队列的概念和运用,6.3和6.4的知识后面会进行研究和学习的!

  • 相关阅读:
    TBalloonHint 提示
    Delphi 结构体常量的定义
    editplus的用法
    Delphi中的容器类
    delphi XE5 UnicodeString的由来
    Delphi:TObject简要说明-对象的创建流程
    Java 反射之私有字段和方法详细介绍
    Java之画图板浅析
    java中的AlgorithmParameterSpec接口
    Java抽象类简单学习
  • 原文地址:https://www.cnblogs.com/JackWeTa/p/13391835.html
Copyright © 2011-2022 走看看