数据结构与算法之栈和队列

放在前面的话

本章所讲的数据结构和算法与大家所熟悉的数组(数组也是一种数据结构)有很大的不同,虽然它们都适用于数据库应用中作数据记录,栈和队列常用于记录那些对应现实世界的对象和活动的数据(如:职员档案、目录和商务数据等等)。
然而,本章要讲解的数据结构和算法更多的是作为程序员的工具来使用。它们主要作为构思和算法的辅助工具,而不是完全的数据存储工具。这些数据结构的生命周期比那些数据库类型的结构要短很多。在程序操作执行期间才被创建,通常用它们去执行某项特殊的任务,当任务被完成之后,它们就会被销毁。


受限的访问

在数组中,如果我们知道某项数据的下标,便可以立即访问该数据项,或者通过遍历数据项,也可以访问到数组中的数据。而在本章中的数据结构中,访问时受限制的,也就是说在特定时刻只有一个数据项可以被读取或者删除(除非“作弊”)。
这些数据结构的接口设计增强了这种受限访问。访问其他数据项(理论上)是不允许的。



更加的抽象

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



栈只允许访问一个数据项,也就是最后插入的那个数据项。只有移除最后一个数据项才能访问倒数第二个数据项,以此类推。这种机制在不少的编程环境中都非常的有用。接下来我们将详细的说一下栈,并写一个demo帮助大家更熟悉怎么使用栈。利用栈来检验源程序中的小括号、中括号和大括号是否匹配的问题,最后会讲到栈在解析算法表达式的时候起到的极为重要的作用,比如:3*(2+9)这种算法。
栈也是那些应用了相当复杂的数据结构算法的便利工具,在接下来的文章会进行详解,比如“二叉树”中,用栈来辅助遍历树的节点,还有利用栈来辅助查找“图”的顶点,当然“二叉树”和“图”都是另一种数据结构。
大部分微处理器运用基于栈的体系结构。当调用一个方法时,把它的返回地址和参数压入栈,当方法结束返回时,那些数据出栈。可见栈操作就嵌入在微处理器中。
一些比较老的便携式的计算器也用基于栈的体系结构。它们不是输入带括号的算术表达式,而是把中间结果先存入栈中。当然,在本章的最后部分解析算术表达式问题的时候将更详细的讲述这种方法。


邮政模拟例

为了更进一步的理解栈的思想,本章从美国邮政服务的一个模拟例子入手。许多人在工作的时候会收到信件,会随手将它放在大厅桌子上的信堆上,或者把它投入到一个“专门的”信件筐中。等他们有空的时候会从上到下的处理这堆邮件。他们首先打开的是堆积在最上面的信件,然后做出处理,第一封信件处理完之后,接下来会处理倒数第二封,以此类推,但是每次处理的都是剩下的所有邮件的最上面的那一封。
当然,很多人并不会这么生硬的遵循从上到下的顺序。比如:他们可能先拿栈底的邮件,从最早收到信件看起。或者他们还可以在处理信件之前,打乱了信件的顺序,将最紧急的信件放在了最上面,在这些情况下,这些人的信件系统就不再是计算机科学含义中的栈了。如果从栈底拿信件然后处理,就是队列的结构如果区分优先级次序,那就是优先级队列结构
当然还有其他的模拟例子,大家可以百度搜索。


栈的Java代码


下面来看一个程序,stack.java,它用StackX类实现了栈,下面的代码包括这个类和一个很短的main方法来

测试它。

public class StackX {//利用数组的方式实现栈

    private int maxSize;
    private long[] stackArray;
    private int top;

    public StackX(int s) {
        maxSize = s;//设置数组长度
        stackArray = new long[maxSize];//实例化数组
        top = -1;//没有数据项
    }

    public void push(long i) {//把数据项放在栈顶
        //记住,一定要先移动top所在的项,然后才能给该项赋值,也就是必须要先++top
        stackArray[++top] = i;
    }

    public long pop() {//返回栈顶的数据项(也就是移除栈顶数据项)
        //同样的道理,栈顶的数据项移除之后,一定要将top移动到下一个数据项上面去,也就是top--
        return stackArray[top--];
    }

    public long peek() {//查看当前的栈顶数据项,这时候只是查看并不需要移除栈顶数据项
        return stackArray[top];
    }

    public boolean isEmpty() {//如果堆栈为空,则为true。top为-1也就是栈里面没有数据项了
        return (top == -1);
    }

    如果堆栈满了,则为true。因为是用数组实现的栈,所以总会有满的时候,但ADT实现的栈是不会满的
    public boolean isFull() {
        return (top == maxSize - 1);
    }
}

class StackApp {
    public static void main(String[] are) {
        StackX stackX = new StackX(10);

        //插入数据项到栈
        stackX.push(10);
        stackX.push(20);
        stackX.push(30);
        stackX.push(4);
        stackX.push(5);
        stackX.push(60);

        long value;
        while (!stackX.isEmpty()) {
            value = stackX.pop();//拿出栈顶的数据项
            System.out.println("此时栈顶的数据项为" + value);
        }
    }
}

代码中有详细的注释,大家有什么不懂的可以加群:499956265,互相交流。
StackApp类中的main方法创建一个可以容纳10个数据项的栈,然后将多个数据项压入栈,接着通过出栈将所有数据项依次显示出来,一直到栈空。大家可以复制代码自己运行一下。
可以看到显示数据的顺序和压入数据的顺序正好是相反的。这是因为最后入栈的数据项最先弹出。
在这个版本中,StackX类里面数据项的类型为long。当然这个类型大家可以随便改,甚至可以改为对象类型。
StackX类中构造方法根据参数创建了一个新栈(用数组的方式实现),栈的域包括表示最大值的变量(也就是数组的大小)、数组本身以及变量top,它存储栈顶元素的下标。(注意:由于栈是用数组实现的,需要先规定栈的大小。但是,如果使用链表来实现栈,就不需要先规定栈的容量
push方法中将top值增加一,使它指向原顶端数据项上面的一个位置,并在这个位置上存储一个数据项。再次提醒,top要在插入数据项之前增加一!
peek方法仅仅是返回top所指向的数据项的值(也就是当前栈顶的值),并不做其他操作。
pop方法返回栈顶的值,并将top减一,也就是移除栈顶的值。(提示:虽然数据项仍然还在数组中(直到有新的数据项来覆盖它),但是已经不能访问这个数据项了,所以可以认为是已经将这个数据项移除了当前栈)
isEmpty方法和isFull方法分别在栈空和栈满时返回true。栈空是top变量为-1(top的初始值),栈满时top变量为maxSize-1(数组下标的最大值)。
出错时的处理方式

有不同的方法来处理栈的错误。当向已经栈满的栈再添加一个数据项时,或者从栈空的栈中弹出一个数据项时,会发生什么事呢?
可以把处理这些错误的工作交给用户(程序员).用户在插入数据项之前必须要确定栈不是满的,或者在弹出栈顶值得时候先判断栈是不是空的。
有的栈的push和pop的时候会自己判断当前栈是否是满的还是空的!对于栈来说发现这些错误的一个好的方法是抛出异常,然后让用户来捕获并处理。
为了程序的简单起见,我并没有在main方法中添加这些操作,大家见谅。


栈的实例1:单词逆序

栈的第一个demo很简单,是做一个单词的逆序。运行程序,提示输入一个单词,然后回车。程序会输出该单词的逆序。
思路分析:首先字母从输入的字符串中一个接一个地提取出来并且压入栈中。接着让它们依次的弹出,并显示出来。因为栈的现金后出的特点,所以字母的顺序会自动的被颠倒过来。代码如下

public class Stackx {
    private int maxSize;
    private char[] stackArray;
    private int top;

    public Stackx(int a) {
        maxSize = a;
        stackArray = new char[a];
        top = -1;
    }

    public void push(char b) {
        stackArray[++top] = b;
    }

    public char pop() {
        return stackArray[top--];
    }

    public char peek() {
        return stackArray[top];
    }

    public boolean isEmpty() {
        return top == -1;
    }

    public boolean isFull() {
        return top == maxSize - 1;
    }
}

class Reverser {
    public static void main(String[] aaa) throws Exception {
        Stackx stackx;
        while (true) {
            String input, output = "";
            System.out.println("让我们开始吧");
            System.out.flush();
            input = getString();
            if ("".equals(input)) {
                break;
            }
            System.out.println("该单词是" + input);
            stackx = new Stackx(input.length());
            for (int i = 0; i < input.length(); i++) {
                stackx.push(input.charAt(i));
            }
            for (int i = 0; i < input.length(); i++) {
                output += stackx.pop();
            }
            System.out.println("该单词的逆序是" + output);

        }

    }

    public static String getString() throws Exception {
        InputStreamReader inputStreamReader = new InputStreamReader(System.in);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        String s = bufferedReader.readLine();
        return s;
    }
}

以上就是该demo的所有代码了。
大家还可以百度查一下关于stack的其他demo。

栈的效率

StackX类中实现的栈,数据项入栈和出栈的时间复杂度都为常数O(1)。也就是说,栈的操作所消耗的时间不依赖于栈中数据项的个数,因此操作时间很短。栈不需要向数组一样进行比较和移动操作,所以操作时间消耗要比数组短很多!






  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值