栈1:栈的特征和常用操作

Posted 纵横千里,捭阖四方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了栈1:栈的特征和常用操作相关的知识,希望对你有一定的参考价值。

​从今天开始我们进入第二个专题:三种特殊的线性结构:栈,队和Hash。

栈是很多表达式、符号等运算的基础,也是递归的底层实现。理论上递归能做的题目,栈都可以,只是有些问题用栈会非常复杂。

队列的考察比较特殊,直接考察队列本身的题目不多。在面试时重点在两个方面发力,一个是java中的队列,特别是阻塞队列,这个是多线程和并发框架JUC的重要组成部分。而在手写算法中,队列主要集中在于广度遍历有关的场景,例如二叉树的层次遍历,N叉树的广度优先遍历等等。广度优先的问题我们后面再看。

Hash的应用之广也不必多说,在各类应用中也大量见到。而HashMap和支持并发的ConcurrentHashMap的原理本身也是面试的重点。我们知道Spring的核心是管理bean的容器,而这个容器本身就是ConcurrentHashMap结构,另外Dubbo等代码中也都大量使用ConcurrentHashMap。

但是比较尴尬的是,因为Hash要开辟O(n)的空间,很多用Hash写出来的算法,面试官总是会问是否有更好的算法。所以在应用中Hash是扛把子,而在很多算法面试题里,Hash只是个备胎。

还有一点就是很多问题一旦使用hash就没什么技术含量了,例如在链表部分我们介绍的如何判断链表中是否有环,如何确定两个链表是否交叉等等。在应用中我们优先采用Hash,而在算法中则应重点设计那些烧脑的方法。

我们接下来就逐步拆解这三大结构以及相关的算法题。

1.栈的定义和特征

栈本质上仍然是线性表,底层实现也是链表或者顺序表,栈与线性表的最大区别是数据的存取的操作被限制了,其插入和删除操作只允许在线性表的一端进行。一般而言,把允许操作的一端称为栈顶(Top),不可操作的一端称为栈底(Bottom),同时把插入元素的操作称为入栈(Push),删除元素的操作称为出栈(Pop)。若栈中没有任何元素,则称为空栈,栈的结构如下图:

栈的操作主要有5个:

push(E):增加一个元素Epop();弹出元素Epeek():显示栈顶元素,但是不出栈empty():判断栈是否为空search():在栈中搜索元素

我们在设计自己的栈的时候,不管用数组还是链表,都要实现上面几个方法,不过search()这个用的不多,前面四个是必不可少的。

由图我们可看到,栈只能从栈顶存取元素,先进入的元素反而是后出,而栈顶永远指向栈内最顶部的元素。因此栈也称为后进先出(Last In First Out,LIFO)或先进后出(First In Last Out FILO)的线性表。栈的基本操作创建栈,判空,入栈,出栈,获取栈顶元素等,但是不支持对指定位置进行删除,插入。

2.java中的栈

java的util中就提供了栈Stack,其使用也不难,看一个例子就够了:

public class MainTest {    public static void main(String[] args) {        Stack<Integer> stack=new Stack();        stack.push(1);        stack.push(2);        stack.push(3);        System.out.println("栈顶元素为:"+stack.peek());        while(!stack.empty()){          if(stack.peek()%2==0){             System.out.println(stack.pop());          }           }    }}

接下来,我们看一下如何自己实现,如果要自己实现栈,可以有数组,链表和LinkedList三种基本方式,如果想熟练搞定这方面的题,都要会才行。

3.基于数组实现栈

采用顺序表实现的的栈,内部以数组为基础,实现对元素的存取操作,

这里需要注意的一个点是:每次入栈之前先判断栈的容量是否够用,如果不够用就用Arrays.copyOf()进行扩容。

基于数组设计的栈,入栈过程看这个图就行了

注意入栈之后要更新栈顶top指向要变化

出栈的过程看这张图就够了:

完整的实现代码是:

import java.util.Arrays;/** * 数组实现栈 */class Mystack<T> {    //实现栈的数组    private Object[] stack;    //数组大小    private int size;    Mystack() {    //初始容量为10    stack = new Object[10];    }     //判断是否为空    public boolean isEmpty() {        return size == 0;    }    //返回栈顶元素    public T peek() {        T t = null;        if (size > 0)            t = (T) stack[size - 1];        return t;    }        public void push(T t) {        expandCapacity(size + 1);        stack[size] = t;        size++;    }     //出栈    public T pop() {        T t = peek();        if (size > 0) {            stack[size - 1] = null;            size--;        }        return t;    }     //扩大容量    public void expandCapacity(int size) {        int len = stack.length;        if (size > len) {            size = size * 3 / 2 + 1;//每次扩大50%            stack = Arrays.copyOf(stack, size);        }    }}}

然后写一个测试类看看上面的方法好不好用:

public class ArrayStack {    public static void main(String[] args) {        Mystack<String> stack = new Mystack<>();        System.out.println(stack.peek());        System.out.println(stack.isEmpty());        stack.push("java");        stack.push("is");        stack.push("beautiful");        stack.push("language");        System.out.println(stack.pop());        System.out.println(stack.isEmpty());        System.out.println(stack.peek());    }

4.基于链表实现栈

链表也可以实现栈,关键点是插入和删除都在头结点进行。

具体来说,添加元素的过程如下图:

删除元素的过程如下图:

代码实现也不复杂,完整实现如下:

/** * 链表实现栈 */class Mystack<T> {    //定义链表    class Node<T> {        private T t;        private Node next;    }    private Node<T> head;    //构造函数初始化头指针    Mystack() {        head = null;    }    //入栈    public void push(T t) {        if (t == null) {            throw new NullPointerException("参数不能为空");        }        if (head == null) {            head = new Node<T>();            head.t = t;            head.next = null;        } else {            Node<T> temp = head;            head = new Node<>();            head.t = t;            head.next = temp;        }    }    //出栈    public T pop() {        T t = head.t;        head = head.next;        return t;    }    //栈顶元素    public T peek() {        T t = head.t;        return t;    }
    //栈空    public boolean isEmpty() {        if (head == null)            return true;        else            return false;    }}​

同样写一个测试类:

public class LinkStack {    public static void main(String[] args) {        Mystack stack = new Mystack();        System.out.println(stack.isEmpty());        stack.push("Java");        stack.push("is");        stack.push("beautiful");        System.out.println(stack.peek());        System.out.println(stack.peek());        System.out.println(stack.pop());        System.out.println(stack.pop());        System.out.println(stack.isEmpty());        System.out.println(stack.pop());        System.out.println(stack.isEmpty());    }}

5.基于LinkedList实现栈

LinkedList是java做了初步封装的顺序表,因此我们使用起来更为方便。例如栈的基本操作基于LinkedList就是这样的:

push-----addFirst()pop-------removeFirst()peek-----getFirst()isEmpty-isEmpty()

实现代码就是:

import java.util.LinkedList;/** * LinkedList实现栈 * @param <T> */class ListStack<T> {    private LinkedList<T> ll = new LinkedList<>();    //入栈    public void push(T t) {        ll.addFirst(t);    }    //出栈    public T pop() {        return ll.removeFirst();    }    //栈顶元素    public T peek() {        T t = null;        //直接取元素会报异常,需要先判断是否为空        if (!ll.isEmpty())            t = ll.getFirst();        return t;    }    //栈空    public boolean isEmpty() {        return ll.isEmpty();    }}

还是写一个测试类吧:

 public class LinkedListStack {    public static void main(String[] args) {        ListStack<String> stack = new ListStack();        System.out.println(stack.isEmpty());        System.out.println(stack.peek());        stack.push("java");        stack.push("is");        stack.push("beautiful");        System.out.println(stack.peek());        System.out.println(stack.pop());        System.out.println(stack.isEmpty());        System.out.println(stack.peek());    }}

以上是关于栈1:栈的特征和常用操作的主要内容,如果未能解决你的问题,请参考以下文章

数据结构学习笔记(栈队列)整理与总结

数据结构学习笔记(栈队列)整理与总结

c++里关于栈的函数哪些常用

栈的实现及其典型应用

数据结构 栈的简单理解和基本操作

栈是常用的一种数据结构,有 n 个元素在栈顶端一侧等待进栈,栈顶端另一侧是 出栈序列。你已经知道栈的操作有两种:push 和 pop,前者是将一个元素进栈,后 者是将栈顶元素弹出。现在要使用这两种操作