栈和队列(Stack、Vector源码分析、逆波兰表达式、队列)2

 栈:栈是限定仅在表尾进行插入和删除操作的线性表。

    队列:队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表。


    允许插入和删除的一端为栈顶(Top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出的线性表。

 Stack和Vector
    Stack继承Vector,Stack也是用数组来实现的。 Stack整个类的方法就5个,push(E item)入栈,pop()出栈,peek()获取栈顶元素,enpty()判断是否为空栈,search(Object o)查询栈内元素位置。每个方法声明上都有synchronized修饰。因此Stack是线程安全的。而大部分的业务逻辑是调用父类Vector的方法实现的。

    public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    //数据存储
    protected Object[] elementData;
    //元素数量
    protected int elementCount;
    //扩容增长因子
    protected int capacityIncrement;
    public Vector(int initialCapacity, int capacityIncrement) {
      
}
    通过源码我们可以看到Vector是由Object[]实现的,扩容因子变量capacityIncrement和数组长度initialCapacity是可以通过构造方法设置的,如果不设置initialCapacity默认是10,capacityIncrement默认是0。
   public synchronized E peek() {
        ...
        return elementAt(len - 1);
    }
    public synchronized E elementAt(int index) {
          ...
        return elementData(index);
    }
  E elementData(int index) {
        return (E) elementData[index];
    }
    peek()方法只是获取栈顶的元素,并不会删除元素,数组的下标0为栈底,数组的下表length-1为栈顶。源码很简单很清晰。
    public synchronized E pop() {
         ...
        obj = peek();
        removeElementAt(len - 1);
        return obj;
    }
    public synchronized void removeElementAt(int index) {
          ...
        elementCount--;
        elementData[elementCount] = null; /* to let gc do its work */
    }
    pop()出栈是先调用peek()获取栈顶元素,然后elementCount元素数量减1,elementData栈顶元素设置为空。

    出栈后,elementData数组的大小没有改变,也就是说没有缩小容量。只是把元素的数量减1了。

  public E push(E item) {
        addElement(item);
        return item;
    }
    public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }
  private void ensureCapacityHelper(int minCapacity) {
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
   private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
    push(E item)入栈直接调用父类Vector的addElement(E obj)方法,然后调用ensureCapacityHelper(int minCapacity)方法判断是否要扩容。注意:elementData.length是数组的长度,elementCount是元素的个数,如果elementCount+1>elementData.length我们需要调用grow(int minCapacity)方法扩容了。如果扩容因子capacityIncrement大于0,就扩容capacityIncrement大小。如果小于0就扩容一倍。如果扩容之后大小还是不够,就用元素数量的大小来扩容。而hugeCapacity(int minCapacity)方法一般是调用不到的,我们不会用到0x7fffffff-8这么大的容量。然后我们回到addElement(E obj)方法,扩容结束后,把obj放入栈顶,整个入栈就结束了。search(Object o)的业务逻辑也很简单,从栈顶往栈底遍历元素,一个一个比较。返回第一个相等元素的坐标。我就不贴源码了。

逆波兰表达式
    栈最经典的应用就是逆波兰表达式,逆波兰表达式也叫后缀表达式。例如:783+3*+102/+;而8+(2-1)*10,这是中缀表达式。中缀表达式是我们计算用的,而后缀表达式是计算机采用的。具体运算方式:

中缀表达式运算:7+(8+1)*3+10/2=39;

后缀表达式运算:7 8 1+3*+10 2/+ 

                            7 9 3*+10 2/+          8 1+运算结果为9

                            7 27+10 2/+            9 3*运算结果27

                            34 10 2 /+               7 27+运算结果34

                            34 5+                      10 2 /运算结果5

                            39                           34 5+ 运算结果39

    后缀运算是碰到符号,把符号前两个数运算。中缀表达式转后缀表达式就是利用的栈结构。让我们来看看是怎么转的。

    上图就是中缀表达式转后缀表达式,看着是不是有点迷糊,很难找到规律。其实是有规律的,记住这个口诀:数字输出,运算符进栈,括号匹配出栈,栈顶优先级低出栈。大概意思就是遇到数字就打印,遇到运算符运算符进栈,当匹配到闭合括号时括号内的运算符就出栈,如果栈顶的优先级比下面的低,优先级高的出栈,运算符低的入栈。同级的运算符栈底比栈顶高。最后数字都输出完了栈内符号按优先级全部输出。下面看看计算机是怎么运算的:

队列
    队列(queue):只允许在一端进行插入操作,而在另一端进行删除操作的线性表。插入的一段称为队尾,删除的一段称为队头。

    队列的顺序结构

    缺点:出队复杂度高o(n),容易假溢出。front队头,rear队尾。队列出栈时是把队头置为空,其他数据元素不动。假溢出就是队头还有空间,队尾没有空间。

    2.      循环队列

    把队列的这种头尾相接的顺序存储结构称为循环队列。

    顺序存储效率低,容易假溢出。队列最好的存储方式还是链式存储。

    3.     队列的链式存储及结构模式

    队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已。

    如果要出栈只要把头结点指针指向a2,a1就出栈了。如果新元素要入栈的话,只要尾节点指针指向新节点元素就可以了。

    查看java源码会发现,Queue是个接口,有趣的是它的实现类有LinkedList,也就是说LinkedList可以当队列使用。你会发现LinkedList里面有offer()入队方法,在队尾添加元素。poll()出队方法,删除队头元素。   

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值