zoukankan      html  css  js  c++  java
  • 数据结构与算法4——栈和队列

      本章涉及到三种数据存储类型:栈、队列和优先级队列。首先介绍着三种结构与数组的区别,然后依次介绍每种数据结构。

    1 不同的结构类型                                                   

      本章所讲的数据结构和算法与前面章节提到的有很大不同,下面是三个区别。

    1.1 程序员的工具                

      数组是前面介绍过的数据存储结构,和本书后面介绍的其他结构(链表、树)一样,都适用于数据库应用中做数据记录。它常用于记录那些对应于现实世界的对象和活动的数据,如职员档案、目录和商务数据等等。这些结构便于数据的访问:它们易于进行插入、删除和查找特定数据项的操作。
      然而,本章要讲解的数据结构和算法更多的是作为程序员的工具来运用,它们主要作为构思算法的辅助工具,而不是完全的数据存储工具,这些数据结构的生命周期比那些数据库类型的结构要短得多。在程序操作执行期间它们才被创建,通常用它们去执行某项特殊的任务,当完成任务之后,它们就被销毁。

    1.2 受限访问                     

      在数组中,若知道数据项的下标,便可以立即访问该数据项,或者通过顺序搜索数据项,访问到数组中的各数据项。而在本章中的数据结构中,访问是受限制的,即在特定时刻只有一个数据项可以被读取或者被删除。

    1.3 更加抽象                     

      栈、队列和优先级队列是比数组和其他数据存储结构更为抽象的结构。主要通过接口对栈、队列和优先级队列进行定义,这些接口表明通过它们可以完成的操作,而它们的主要实现机制对用户来说是不可见的。
      例如,栈的主要机制可以用数组来实现,本章的示例就是这样处理的;但它也可以用链表来实现,优先级队列的内部实现可以用数组或一种特别的树————来实现。

    2 栈                                                                          

      栈只允许访问一个数据项:即最后插入的数据项。移除这个数据项后才能访问倒数第二个插入的数据项,依次类推。就像条状装的阿尔卑斯糖一样,每一个小的圆的阿尔卑斯糖叠加,然后用纸包好成条状,只能先吃最后放的那个糖,然后才能吃倒数第二个糖。本节中将看到如何利用栈来检验源程序中的小括号、中括号和大括号是否匹配的问题。
      栈也是那些应用了相当复杂的数据结构算法的便利工具。
      大部分微处理器运用基于栈的体系结构,当调用一个方法时,把它的返回地址和参数压入栈,当方法结束返回时,那些数据出栈,栈操作就嵌入在微处理器中。
      栈的操作有New(新建)、Push(入栈)、Pop(出栈)和Peek(查看)
      这里的栈是利用数组来实现的,因此可以看到存储数据项的数组。虽然它是基于数组的,但是因为栈的访问是受限访问,所以不能利用下标访问各数据项。实际上,栈的概念和实现它的内部数据结构应该是完全分开的,栈也可以利用其他的存储结构比如链表来实现。栈的容量通常会比较小,是临时的数据结构,解析一个很长的算术表达式只需要一个十几个单元的栈就够了。

    2.1 栈的实现                      

      下面利用数组实现一个栈:

     1 package chapter4;
     2 
     3 class StackX {
     4     //栈的最大空间
     5     private int maxSize;
     6     //用于实现栈的数组
     7     protected long[] stackArray;
     8     //栈顶
     9     private int top;
    10     public StackX(int maxSize){
    11         this.maxSize = maxSize;
    12         //创建数组
    13         stackArray = new long[maxSize];
    14         //top = -1表示栈里没有元素
    15         top = -1;
    16     };
    17     //入栈操作
    18     public void push(long i){
    19         //先把栈顶指针向上移动一个位置
    20         ++top;
    21         //把新值赋给栈顶指针指向的位置
    22         stackArray[top] = i;
    23     };
    24     //出栈操作
    25     public long pop(){
    26         //返回栈顶指针当前指向的值,也就是即将被"移除"的值
    27         //然后将栈顶指针回移一个位置即可
    28         return stackArray[top--];
    29     };
    30     //查看栈
    31     public long peek(){
    32         //返回当前栈顶指针所指的值
    33         return stackArray[top];
    34     };
    35     //判断栈是否为空
    36     public boolean isEmpty(){
    37         return (top == -1);
    38     }
    39     //判断栈是否已满
    40     public boolean isFull(){
    41         return (top == this.maxSize - 1);
    42     }
    43 };
    44 
    45 public class StackApp{
    46     public static void main(String[] args){
    47         //创建一个新栈
    48         StackX s = new StackX(10);
    49         //将一些值推入栈中
    50         s.push(2);
    51         s.push(7);
    52         s.push(27);
    53         //输出入栈后的结果
    54         System.out.println("入栈后的值:");
    55         for(int i = 0; i < s.stackArray.length; i++){
    56             System.out.print(s.stackArray[i] + " ");
    57         }
    58         System.out.println();
    59         //查看当前栈的情况
    60         System.out.println("当前栈指针指向值:" + s.peek());
    61         //将元素出栈
    62         System.out.println("出栈的值:" + s.pop());
    63         //查看当前栈的情况
    64         System.out.println("当前栈指针指向值:" + s.peek());
    65     }
    66 };
    View Code

    输出结果:
    入栈后的值:
    2 7 27 0 0 0 0 0 0 0
    当前栈指针指向值:27
    出栈的值:27
    当前栈指针指向值:7

      程序分析:上面利用数组实现了一个栈,并进行了创建栈、入栈、出栈、查看栈的四种基本操作,但是没有进行异常处理。比如满栈添加元素,或者空栈弹出一个数据项。只需要利用isFull来判断即可。

    栈实例1:单词逆序

      因为栈是后入先出的,所以就可以利用这个特性很容易将单词逆序,具体方法就是利用pop函数将元素逆序喷出~~
    我们将上面的程序稍微改造一下即可:

    package chapter4;
    
    import java.io.*;
    
    class StackX {
        //栈的最大空间
        private int maxSize;
        //用于实现栈的数组
        protected char[] stackArray;
        //栈顶
        private int top;
        public StackX(int maxSize){
            this.maxSize = maxSize;
            //创建数组
            stackArray = new char[maxSize];
            //top = -1表示栈里没有元素
            top = -1;
        };
        //入栈操作
        public void push(char i){
            //先把栈顶指针向上移动一个位置
            ++top;
            //把新值赋给栈顶指针指向的位置
            stackArray[top] = i;
        };
        //出栈操作
        public char pop(){
            //返回栈顶指针当前指向的值,也就是即将被"移除"的值
            //然后将栈顶指针回移一个位置即可
            return stackArray[top--];
        };
        //查看栈
        public char peek(){
            //返回当前栈顶指针所指的值
            return stackArray[top];
        };
        //判断栈是否为空
        public boolean isEmpty(){
            return (top == -1);
        }
        //判断栈是否已满
        public boolean isFull(){
            return (top == this.maxSize - 1);
        }
    };
    
    class Reverser{
        private String input;
        private String output;
        public Reverser(String input){
            this.input = input;
        };
        //进行逆序
        public String doRev(){
            int stackSize = input.length();
            StackX s = new StackX(stackSize);
            for(int i = 0; i < stackSize; i++){
                char ch = input.charAt(i);
                s.push(ch);
            };
            output = "";
            while(!(s.isEmpty())){
                char ch = s.pop();
                output = output + ch;
            }
            return output;
        }
    }
    
    public class StackApp{
        public static String getString()throws IOException{
            InputStreamReader isr = new InputStreamReader(System.in);
            BufferedReader br = new BufferedReader(isr);
            String s = br.readLine();
            return s;
        }
        public static void main(String[] args)throws IOException{
            String input, output;
            while(true){
                System.out.println("请输入一个字符串:");
                System.out.flush();
                //读取一个字符串
                input = getString();
                //遇到Enter键则表示输入完毕
                if (input.equals("")){
                    break;
                }
                //进行逆转
                Reverser rs = new Reverser(input);
                output = rs.doRev();
                //输出逆序结果
                System.out.println("逆序后结果为:" + output); 
            }
        }
    };
    View Code

    输出结果:
    请输入一个字符串:
    Hello World
    逆序后结果为:dlroW olleH
    请输入一个字符串:

    栈实例2:分隔符匹配

      栈通常用于解析某种类型的文本串,通常,文本串是用于计算机语言写的代码行,而解析它们的程序就是编译器。比如说一个表达式a{b[c]},这里有两个大括号和两个中括号。
    原理分析:
      分隔符匹配程序从字符串中不断读取字符,每次读取一个字符,若发现它是左分隔符,将它压入栈中。当从输入中读到一个右分隔符时,弹出栈顶的左分隔符,并且查看它是否和右分隔符相匹配。如果它们不匹配(比如,一个左大括号和一个右大括号),则程序保存。如果没有做分隔符和右分隔符匹配,或者一直存在着没有被匹配的分隔符,程序被报错,分隔符没有被匹配表现为把所有的字符读入之后,栈中仍留有着分隔符。注意:非分隔符是不插入栈中的。
    以a{b[c]}为例

        所读字符        栈中字符
        a                
        {                    {
        b                    {
        [                    {[
        c                    {[
        ]                    {
        }                    

      这个方法的可行性在于,最后出现的左边分隔符需要最先匹配,符合栈后进先出的特点。

    package chapter4;
    
    import java.io.*;
    
    class StackX {
        //栈的最大空间
        private int maxSize;
        //用于实现栈的数组
        protected char[] stackArray;
        //栈顶
        private int top;
        public StackX(int maxSize){
            this.maxSize = maxSize;
            //创建数组
            stackArray = new char[maxSize];
            //top = -1表示栈里没有元素
            top = -1;
        };
        //入栈操作
        public void push(char i){
            //先把栈顶指针向上移动一个位置
            ++top;
            //把新值赋给栈顶指针指向的位置
            stackArray[top] = i;
        };
        //出栈操作
        public char pop(){
            //返回栈顶指针当前指向的值,也就是即将被"移除"的值
            //然后将栈顶指针回移一个位置即可
            return stackArray[top--];
        };
        //查看栈
        public char peek(){
            //返回当前栈顶指针所指的值
            return stackArray[top];
        };
        //判断栈是否为空
        public boolean isEmpty(){
            return (top == -1);
        }
        //判断栈是否已满
        public boolean isFull(){
            return (top == this.maxSize - 1);
        }
    };
    
    class BracketChecker{
        private String input;
        public BracketChecker(String input){
            this.input = input;
        };
        //分隔符检查
        public void check(){
            int stackSize = input.length();
            StackX s = new StackX(stackSize);
            for(int i = 0; i < stackSize; i++){
                char ch = input.charAt(i);
                switch(ch){
                    case '{':
                    case '[':
                    case '(':
                        s.push(ch);
                        break;
                    case '}':
                    case ']':
                    case ')':
                        if(!(s.isEmpty())){
                            char  chx = s.pop();
                            if((ch == '{' && chx != '}')||
                               (ch == '[' && chx != ']')||
                               (ch =='(' && chx != ')')
                                    ){
                                System.out.println("Error: " + ch + " at" + i);
                            }
                        }else{
                            System.out.println("Error: " + ch + " at" + i);
                        }
                        break;
                    default:{
                        break;
                    }
                }
                if(!(s.isEmpty())){
                    System.out.println("Error:missing the right delimiter");
                }
            };
        }
    }
    
    public class StackApp{
        public static String getString()throws IOException{
            InputStreamReader isr = new InputStreamReader(System.in);
            BufferedReader br = new BufferedReader(isr);
            String s = br.readLine();
            return s;
        }
        public static void main(String[] args)throws IOException{
            String input, output;
            while(true){
                System.out.println("请输入一个字符串:");
                System.out.flush();
                //读取一个字符串
                input = getString();
                //遇到Enter键则表示输入完毕
                if (input.equals("")){
                    break;
                }
                //进行逆转
                BracketChecker bc = new BracketChecker(input);
                bc.check();
            }
        }
    };
    View Code

    2.2 栈的效率                       

      上面用数组实现了栈,数据项入栈和出栈的时间复杂度都为常数O(1)。这也就是说,栈操作所耗时间都不依赖于栈中数据项的个数,因此操作时间很短,栈不需要比较和移动操作。

    3 队列                                                                    

      队列(queue)这个单词是英国人说的排(line)(一种等待服务的方式)。队列是一种数据结构,有点类似栈,只是在队列中第一个插入的数据项也会最先被移除,也就是先进先出FIFO。而在栈中,最后插入的数据项最先移除,最先插入的最后移除,LIFO。队列就像排队买票一样,最先到达的最先买票。
      队列也可以模拟真实世界的环境,在计算机或网络操作系统中,有各种队列工作,打印作业有队列等待,键盘敲击输入,有一个存储键入内容的队列。

      队列有两个指针,一个是队头指针,一个是队尾指针,队头指针用于控制数据项的删除,删除时,指针往后移,队尾指针用于控制数据项的插入,插入时指针往后移动。这样就可以控制先进先出。这样设计有一个问题是 队尾指针很快会移动到数组的末端(数组下标最大),并且数组开始的地方因为已经移除了部分数据项,所以开头会有空数据项,这样新的数据项就无法从队尾插入。为了避免队列没有满,但是却无法插入新数据项的问题,可以让队头队尾指针绕回数组开始的位置,这就是循环队列。

    3.1 循环队列                         

      循环队列的特点是队头指针和队尾指针都是循环式的,当插入更多的数据项时,队尾指针如预计的那样向上移动。注意在队尾指针回绕之后,它限制处在队头指针的下面,这就颠倒了初始的位置。
      删除足够多的数据项后,对头指针也回绕,这时队列的指针回到了初始运行时的位置状态,队头指针在队尾指针的下面。总之,插入看队尾指针,删除看队头指针,无论怎么绕来绕去,这个是不变的。

    3.2 循环队列的数组实现        

      下面我们来实现队列的新建、插入、删除和查看四种操作。

     1 package chapter4;
     2 
     3 class Queue{
     4     private int maxSize;
     5     private long[] queueArray;
     6     private int front;
     7     private int rear;
     8     private int nElems;
     9     //初始化队列
    10     public Queue(int maxSize){
    11         this.maxSize = maxSize;
    12         queueArray = new long[this.maxSize];
    13         front = 0;
    14         rear = -1;
    15         nElems = 0;
    16     };
    17     //用队尾指针标识插入数据项
    18     public void insert(long i){
    19         queueArray[++rear] = i;
    20         //如果队尾指针已经达到数组末端,则循环至开头
    21         if(rear == this.maxSize - 1){
    22             rear = -1;
    23         }
    24         nElems++;
    25     };
    26     //删除数据项
    27     public long remove(){
    28         long temp = queueArray[front++];
    29         if(front == this.maxSize){
    30             front = 0;
    31         }
    32         nElems--;
    33         return temp;
    34     };
    35     //查看队列
    36     public long peek(){
    37         return queueArray[front];
    38     };
    39     //检查队列是否已满
    40     public boolean isFull(){
    41         return (nElems == this.maxSize);
    42     };
    43     //检查队列是否为空
    44     public boolean isEmpty(){
    45         return (nElems == 0);
    46     };
    47     public int size(){
    48         return nElems;
    49     };
    50 };
    51 public class QueueApp {
    52     public static void main(String[] args){
    53         //创建一个队列
    54         Queue q = new Queue(5);
    55         //插入5个数据项
    56         q.insert(2);
    57         q.insert(7);
    58         q.insert(27);
    59         q.insert(77);
    60         q.insert(87);
    61         //删除数据项
    62         while(!(q.isEmpty())){
    63             long n = q.remove();
    64             System.out.println(n);
    65         }
    66     };
    67 };
    View Code

    输出结果:
    2
    7
    27
    77
    87
    上面我们用数组实现了一个循环队列,并且从运行结果来看,插入顺序和删除的顺序是一致的。这就是队列的特点。另外,插入时候没有进行队列是否已满检查。

    3.2.1 队列的效率 

      和栈一样,队列中插入数据项和移除数据项的时间复杂度为O(1)

     

    3.1 循环队列                         

     

      循环队列的特点是队头指针和队尾指针都是循环式的,当插入更多的数据项时,队尾指针如预计的那样向上移动。注意在队尾指针回绕之后,它限制处在队头指针的下面,这就颠倒了初始的位置。
      删除足够多的数据项后,对头指针也回绕,这时队列的指针回到了初始运行时的位置状态,队头指针在队尾指针的下面。总之,插入看队尾指针,删除看队头指针,无论怎么绕来绕去,这个是不变的。

     

     

    3.2 循环队列的数组实现        

     

      下面我们来实现队列的新建、插入、删除和查看四种操作。

     

    View Code

     

    输出结果:
    2
    7
    27
    77
    87
    上面我们用数组实现了一个循环队列,并且从运行结果来看,插入顺序和删除的顺序是一致的。这就是队列的特点。另外,插入时候没有进行队列是否已满检查。

     

    3.2.1 队列的效率 

     

      和栈一样,队列中插入数据项和移除数据项的时间复杂度为O(1)

     

    3.3 优先级队列                  

     

      优先级队列是比栈和队列更专用的数据结构。但是在很多情况下都很有用。和普通队列一样,优先级队列有一个队头和一个队尾,并且也是从队头移除数据项。优先级队列应该称为有序队列。因为插入时候会按照顺序将数据项插入,这样队头总是最小的数据项。优先级队列在某些计算机系统中也有很多应用,例如抢先式多任务操作系统中,程序排列在优先级队列中,这样优先级最高的程序就会得到时间片并得以执行。
      优先级队列可以通过堆和数组实现,这里介绍数组的实现。

     

    View Code

     

    输出结果:
    2
    7
    26
    27
    65
    77
    87
    我们看到,插入后的队列总是按顺序的。

     

    4 小结                                                                        

     

    1.栈、队列和优先级队列是经常用于简化某些程序操作的数据结构。
    2.在这些数据结构中,只有一个数据项可以被访问。
    3.栈允许访问最后一个插入的数据项
    4.栈中重要的操作是在栈顶插入一个数据项,以及从栈顶移除一个数据项。
    5.队列只允许访问第一个插入的数据项。
    6.队列的重要操作是在队尾插入数据项和在队头移除数据项。
    7.队列可以实现为循环队列,它基于数组,数组下标可以从数组末端回绕到数组的开始位置。
    8.优先级队列允许访问最小或者最大的数据项,优先级插入数据时候是有序插入的。
    9.这些数据结构可以用数组实现,也可以用其他机制,比如链表来实现。

     

  • 相关阅读:
    面向对象和网络编程补充
    元类与网络编程
    类的继承
    面向过程
    身为一个小白,看到一篇值得看的文章。讲述小白学习python的6个方法。
    2019.08.02 学习整理
    2019.08.01学习整理
    2017.07.31 学习整理
    2019.07.30 学习整理
    2019.7.29学习整理python
  • 原文地址:https://www.cnblogs.com/people/p/3255563.html
Copyright © 2011-2022 走看看