带你手写一个栈,顺序表,链表,双栈表达式计算

栈的手写实现

在之前的文章中提到过,栈的实现方式有比较多的方式,主要有 顺序表链表两种方式,在带你手写一个栈之前,先来看看这两种基本的数据结构。

顺序表

顺序表

顺序表的底层是依靠数组实现的,由于数组具有高效的索引,我们可以在给定索引下标的情况下在时间复杂度为 O(1)的条件下,得到需要的数据,具有高效的随机访问性能。所以一般对于需要高效访问的需求,我们一般考虑使用顺序表这种数据结构。但是对于这种数据结构,在进行元素的增删时候,性能就比较低了,原因是:以插入为例,数组首先会确定需要操作的元素,这易操作,但是一旦将位置确定后,如果数组的容量能够满足该元素的插入,若满足,就需要将需要插入位置及以后的元素整体往后移动一个位置,在计算机底层,这种操作是比较费时费力的,进而降低了插入的性能;如果容量不满足,还需要重新申请一个比原来数组容量大的数组,将原本数据拷贝后,使用后面新的数组作为存储数组,之后再进行上述操作,同样是很费时的操作。

动画演示

带你手写一个栈,顺序表,链表,双栈表达式计算_第1张图片

总之

顺序表的优点是查询,缺点是增删。

链表

链表

数据结构

protected class Node {
    Element data;//节点数据
    Node next;//下一个结点的引用,(指针)
}

这种数据结构较顺序表而言,优缺点可以说是对调,二者是一种互补的关系,链表由于只保留了头结点的引用,如果需要得到给点位置的数据,就需要从头结点的位置依次的遍历,而这种遍历不像数组遍历,数组的遍历效率相对较高是因为在内存中,它的内存地址是连续的;而在链表中,他的存储位置在内存中不连续的,需要访问节点内部的 next才能得到下一个结点的内存地址,往往需要比较大的开销。

也可以形象的理解为,你在人群中寻找一个你的女朋友

场景:在一栋楼中,有100个隔间,有10层楼,每个隔间有2个状态:有人和没有人,你可以打开门查看,并且询问得到相应的信息,你的女朋友就在这栋楼中的某个隔间中。

顺序表假设:你在进入大楼时,就已经知道了你女朋友的房间号,你便可以直接地前往房间号找到她。

链表假设:你在进入大楼时,你只能从第一个房间开始询问,如果你运气逆天,第一个就是她,恭喜你,反之,你可以询问得到信息。大致类似于这种对话:

​ 你:请问你知道下一个房间号吗?

​ TA:10楼5号

​ 你:请问你知道下一个房间号吗?

​ TA:1楼5号

​ …(期间不允许打开其他房间,而且没有电梯,这就很难受啊!!!)

直到你找到你女朋友。

效果显而易见。

带你手写一个栈,顺序表,链表,双栈表达式计算_第2张图片

对于链表的插入,直接修改对应的指针就可以完成了,不需要额外的移动,效率相对顺序表更高。

手写栈

这次实现的栈,里面有链表和数组两组结构,可以根据需要实现,我这里采用的是 Object[]数组实现。

private static final int DEFAULT_SIZE = 10;//初始值
private transient Object[] table;//存储数组
private int size = 0;//元素数量
private int capacity;//容量
//可以更改为以Node为节点,建立以链表为数据结构的栈
protected class Node {
    Element data;
    Node next;

    Node(Element object) {
        data = object;
        next = null;
    }
    Element getData(){
        return data;
    }
}

push

/**
 * Push an item onto the top of the stack.
 * the same effect as:
 * addItem(item)
 *
 * @param item the item that to be pushed.
 * @return the {@code item} argument.
 */
public Element push(Element item) {
    addItem(item);
    return item;
}
private synchronized void addItem(Element item) {
    if(table == null){
        table = new Object[capacity];//懒加载
    }
    int length = table.length;
    if(size == length){
        grow();//自动扩容
    }
    table[(++size) - 1] = new Node(item);//可以直接以Element添加
}

grow : 自动扩容

/**
 * Automatic expansion
 */
private synchronized void grow() {
    capacity = size + (size >> 1);
    table = Arrays.copyOf(table, capacity);
}

peek / pop

/**
 * Looks at the object at the top
 * of this stack without removing it
 * from the stack.
 */
public Element peek() {
    return getItem();
}
/**
 * Removes the object at the top of this stack
 * and returns that object as the value of
 * this function.
 */
public Element pop() {
    return remove();
}
private synchronized Element remove() {
    if(size <= 0){
        return null;
    }
    Element element = getItem();
    table[(size--) - 1] = null;
    return element;
}
@SuppressWarnings("unchecked")
private Element getItem() {
    Node node = (Node)table[size - 1];
    return (node == null) ? null : node.getData();
}

到此,基本的实现就已经完成了,该栈是一个基于数组建立的,属于懒加载方式,即是在第一次 push item的时候,table才会初始化,基于数组实现,以数组最后一个元素为栈顶元素,可以通过栈中的元素数量得到栈顶的索引,进而实现相应的功能。

双栈表达式计算

需要提前准备两个栈,用来存储操作数和运算符。

这里我以

(1+(4×5))

为例,介绍具体的过程:

带你手写一个栈,顺序表,链表,双栈表达式计算_第3张图片
最后在操作数栈中的结果就为该表达式的值。

代码参考

package Solution;

/**
 * @Classname StackCalculator
 * @Description TODO
 * @Date 2020/7/22 10:03
 * @Created by Jason
 */
public class StackCalculator {

    public static void main(String[] args) {
        /*
         * 简单的计算程序:各种运算必须用小括号包括在内
         * 支持 + - * /
         * 举例:(1+2)     结果正确
         *      1+2       结果错误
         *      1+2+3     结果错误
         *      ((1+2)+3) 结果正确
         */
        String str = "(1+((2+3)*(4*(5/3))))";//请保证输入的表达式正确,该程序只做整数计算,不做核验
        Stack<Character> op = new Stack<Character>();
        Stack<Double> val = new Stack<Double>();
        char[] chars = str.toCharArray();
        for (char aChar : chars) {
            switch (aChar) {
                case '('://忽略左括号,靠右括号来确定运算顺序
                    break;
                case '+':
                case '-':
                case '*':
                case '/':
                    op.push(aChar);//压入到操作符栈
                    break;
                case ')'://取出两个操作数以及一个操作符,进行运算
                    double b = val.pop();
                    double a = val.pop();
                    char _op = op.pop();
                    val.push(doCalculator(a, b, _op));//将结果再次压入到操作数栈中
                    break;
                default://操作数入栈
                    val.push((double) aChar - 48.0);
                    break;
            }
        }
        System.out.println(str + " = " + val.peek());
        System.out.println("运算符栈中元素数: " + op.size());
        System.out.println("操作数栈中元素数: " + val.size());
    }

    private static Double doCalculator(double a, double b, char op) {
        double res = 0;
        switch (op){
            case '+': res = a + b ; break;
            case '-': res = a - b ; break;
            case '*': res = a * b ; break;
            case '/': res = a / b ; break;
            default:  throw new RuntimeException("不支持该操作符。");
        }
        return res;
    }

}

欢迎关注公众号

你可能感兴趣的:(数据结构和算法)