计算机考研和中级工程师面试重点--栈与队列(Java实现)

简述

栈和队列结构是表结构基础上针对特定操作行为的数据结构。
特性:

  • 栈:先进后出。放入栈中的元素具有先进后出的特性。就像一队人走进了死胡同,后面的人必须先出去,这样前面的人才可以出来;
  • 队列:先进先出。队列中的元素先进先出的性质。与我们日常排队一样,先排队的人先出队。

栈和队列说明

我们使用数组来实现栈结构,从坐标0开始依次插入数据(入栈),从最后元素的坐标向0依次取出队列(出栈)。列出关键的两个操作入栈出栈
重点:

  1. 扩容
  2. size指向数组中最有一个元素的下一个位置
  3. 利用数组实现,栈底的元素索引一定为0
public class MyStack<E> {
    private Object[] elements;//数组实现栈结构
    private int size = 0;//当前栈中元素个数,也是最后一个元素坐标的下一个位置

    public MyStack(){//构造函数初始化容量为5的栈
        elements = new Object[5];
    }

    public MyStack(int initCapacity){//自定容量的栈
        elements = new Object[initCapacity];
    }

    public E push(E item) {//入栈
        if (size >= elements.length){//如果元素个数为数组容量,则需要扩容
            grow();//扩容操作
        }
        elements[size++] = item;//入栈,size自增1
        return item;
    }

    public E pop() {//出栈
        if (size > 0){//如果栈有元素出栈
            return (E)elements[--size];//size先减1,返回最有一个元素
        }
        return null;
    }

    private void grow(){//扩容操作
        int len = elements.length;//保持原有数组长度
        Object[] tmp = elements;//复制原有数组
        elements = new Object[len * 2];//扩容为当前容量的2倍
        System.arraycopy(tmp, 0, elements, 0, len);//复制数组
    }

    public static void main(String[] args){//测试用例
        MyStack<String> stack = new MyStack<String>();
        stack.push("1");
        stack.push("2");
        stack.push("3");
        stack.push("4");
        stack.push("5");
        String res = stack.push("6");
        String o1 = stack.pop();
        String o2 = stack.pop();
        String o3 = stack.pop();
        String o4 = stack.pop();
        String o5 = stack.pop();
        String o6 = stack.pop();

        stack.push("7");
        String o7 = stack.pop();
    }
}

队列

队列数组实现比起栈实现要相对复杂,这是由于队列先进先出导致的出队之后数组的头部位置元素无用却仍旧在内存中,而尾部位置仍需要不断入队,如果使用普通数组会导致数组占用内存越来越多,但数组的前部空间浪费。我们使用循环数组实现,循环数组就是数组的首尾相连,形成一个环,读取尾部元素之后再读取头部元素。循环数组的实现定义一个数组,我们利用指针遍历时按模取余,循环遍历数组元素。
重点:

  1. 循环数组实现队列,会损失一个元素空间。如果数组大小是5,则实际存储的元素个数为4,这是由需要定义两个索引分别表示队列的头和尾,为了区分队列满和空的情况导致。
  2. 空队列条件(也是初始化的条件):front == rear,头索引等于尾索引,两者在数组上重合表示没有元素。
  3. 满队列条件:(rear + 1) % elements.length == front,尾索引加1取模等于头索引,在循环数组中也就是尾指针刚好在头指针前一位。此时队列索引rear处不能放置元素,如果放置,rear加1取模之后就满足了空队列条件,而实际上此时是满队列的情况。
  4. 循环数组,定义一个数组,指针遍历时按模取余,循环遍历数组元素来实现。
public class MyQueue<E> {
    private Object[] elements;//数组实现队列
    private int front = 0;//头索引位置
    private int rear = 0;//尾索引位置

    public MyQueue(){//初始化数组容量为5
        elements = new Object[5];
    }

    public MyQueue(int initCapacity){//初始化设定的容量数组
        elements = new Object[initCapacity];
    }

    public boolean add(E item){//添加元素到队尾
        if ((rear + 1) % elements.length == front){//判断队列是满了
            return false;
        }
        elements[rear] = item;//插入元素在rear位置
        rear = (rear + 1) % elements.length;//rear自增1取模指向下一个位置索引
        return true;
    }

    public E remove(){//删除队头元素
        if (front == rear){//判断队列是否为空
            return null;
        }
        E ele = (E)elements[front];//返回front处元素
        front = (front + 1) % elements.length;//front自增1取模指向下一个索引位置
        return ele;
    }

    public static void main(String[] args){//测试用例
        MyQueue<String> queue = new MyQueue<String>();
        queue.add("1");
        queue.add("2");
        queue.add("3");
        queue.add("4");
        boolean r1 = queue.add("5");
        String s1 = queue.remove();
        String s2 = queue.remove();
        String s3 = queue.remove();
        String s4 = queue.remove();
        String s5 = queue.remove();

        queue.add("6");
        String s6 = queue.remove();
    }
}

补充说明

上面的栈实现代码存在着一个内存的泄露问题。如果栈先是快速增长到一个很大容量,然后在收缩,那么从栈中弹出来的对象将不会被当作垃圾回收,仍旧会一直驻留在内存中,这部分引用称为过期引用(obsolete reference)。只有当栈对象被回收时,过期引用才会得到回收,这类内存泄露问题很难被发现,因为实际上很难会表现出明显的失败导致系统崩溃。解决方法就是元素一旦出栈,清空其引用即可。有兴趣可以参考《Effective Java》第三版第7条,消除过期的对象引用。
代码如下:

public E pop() {//出栈
    if (size > 0){//如果栈有元素出栈
        E result = (E)elements[--size];//size先减1,返回最有一个元素
        elements[size] = null;
        return result;
    }
    return null;
}

总结

栈和队列实现简单,操作简单,但在实际中用途非常广泛,例如括号匹配问题前括号入栈,遇到后括号执行出栈操作,后括号与弹出的括号判断是否匹配;弹出异常时使用栈存储异常信息;系统不能全部马上响应时,把请求入队按照先到先执行原则从队列中依次执行请求;多个线程请求互斥资源,设置等待队列让线程依次等待,每次资源释放都从队列中取出最早排队的线程赋予资源。栈和队列在实际开发中占重要作用,是计算机数据结构学科的基础。

相关算法例题

判断括号有效

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342