栈与队列数据结构分析(Java底层源码分析)

一、栈

线性表中的元素具有一一对应的关系,而栈是一种特殊的线性表。也是先入后出的有序列表。

1.栈的基本功能

基本功能:1)栈顶入栈,栈顶出栈。称之为先入先出。

                  2)通过指针记录栈中的元素个数。

                  3)反映栈是否已满,如果满了是否要进行栈的底层扩容。

                  4)栈是否已空,如果空,再调用出栈函数是否要抛出异常,考虑异常处理。

应用:1)调用子程序 2)递归调用  3)二叉树遍历  4)表达式的转换;实现逆波兰计算器。

2.介绍Java中SE标准类库中的Stack类底层实现

下面是Stack底层源码分析
//继承于Vector Vector的底层是数组,由于Vector是线程安全的,具有同步方法,该数据结构使用效率低
//因为Stack继承了Vector,因而具有了丰富的方法,不仅能让我们在栈中的元素畅快的访问,也能满足我们 
//利用栈的特性解决问题的需求!
public class Stack<E> extends Vector<E> {
    /**
     * 创建一个空栈,构造器
     */
    public Stack() {
    }

   /**
    *添加元素,调用的是父类添加元素的方法,父类中添加元素的方法是根据索引按序添加
    *时间复杂度o(1)
   */
    public E push(E item) {
        addElement(item);

        return item;
    }

    /**
     * 调用父类的方法移除末尾元素
     * 其父类的底层是数组
     * 本身数组删除元素的平均时间复杂度为o(n),但这里的底层方法是更有效率的o(1)
     */
    public synchronized E pop() {
        E       obj;
        int     len = size();

        obj = peek();
        removeElementAt(len - 1);

        return obj;
    }

    /**
     * 调用父类方法,根据末尾元素索引返回栈顶元素
     * 时间复杂度为o(1)
     */
    public synchronized E peek() {
        int     len = size();

        if (len == 0)
            throw new EmptyStackException();
        return elementAt(len - 1);
    }

    /**
     * 根据父类维护的size元素个数,判断自己的元素个数是否为0,为0则空栈
     */
    public boolean empty() {
        return size() == 0;
    }

    /**
     * 这应该是不属于栈的基本方法了
     * 返回数组中最后一次出现该元素的位置
     * 注意,若入栈的是对象,此时该重写对象所属类中的equals方法
     */

    // 这里查找的效率不高,没有哈希,应该是只凭借equals遍历了
    public synchronized int search(Object o) {
        int i = lastIndexOf(o);

        if (i >= 0) {
            return size() - i;
        }
        return -1;
    }

    //序列号,版本号
    private static final long serialVersionUID = 1224463164541339165L;
}
下面是Vector部分源码分析

1)入栈时调用addElement方法

 
public synchronized void addElement(E obj) {
        modCount++;
        //确保容量足够,否则扩容机制
        //一旦扩容会消耗时间和空间资源
        ensureCapacityHelper(elementCount + 1);
        //在末尾添加元素
        elementData[elementCount++] = obj;
    }

2)出栈时调用removeElementAt方法

请注意,这里稍微复杂,读者可记住以下几点:

数组的删除元素是通过覆盖来实现的,这里其实基本原理也不例外,调用了本地方法实现了更有效率的覆盖,System.arraycopy这个本地方法能在多个内存空间同时覆盖,比传统的o(n)移位覆盖更有效率。                                     

public synchronized void removeElementAt(int index) {
        modCount++;
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                     elementCount);
        }
        else if (index < 0) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        int j = elementCount - index - 1;
        if (j > 0) {
            System.arraycopy(elementData, index + 1, elementData, index, j);
        }
        elementCount--;
        elementData[elementCount] = null; /* to let gc do its work */
    }

  3.总结:         

因为Stack继承了Vector,因而具有了丰富的方法,不仅能让我们在栈中的元素畅快的访问,也能满足我们利用栈的特性解决问题的需求!

二、队列

1.基本介绍:队列是一个先进先出的有序列表,可以用链表或数组实现

2.下面介绍JavaSE标准类库中的队列底层实现

介绍Queue和Deque接口

介绍Queue、Deque接口的两个实现类

--->Linkedlist

--->ArrayDeque

再介绍Queue接口实现类

--->PriorityQueue优先级队列

3.介绍Queue、Deque接口

1)Queue继承于Collection接口,这里定义了队列的一些基本功能的增删改的抽象方法。
public interface Queue<E> extends Collection<E> {
    boolean add(E e);

    boolean offer(E e);

    E remove();

    E poll();

    E element();

    E peek();
}
2)Deque这个接口又继承于Queue接口,提供了更加丰富的抽象方法,集中于对元素的访问、添加、删除、遍历

能访问队首和队尾元素,也能判断元素是否包含于队列。

另外,提供了push方法,向队首添加元素,又和队首出队的方法组合,让他的实现类可以实现栈的功能,这里要注意,防止用出问题,也就是说ArrayList、Linklist是栈也能是队列!

public interface Deque<E> extends Queue<E> {
    
    boolean offerFirst(E e);

   
    boolean offerLast(E e);

    E removeFirst();

   
    E removeLast();

   
    E pollFirst();

    
    E pollLast();

   
    E getFirst();

    E getLast();

    E peekFirst();

    E peekLast();

    
    boolean removeLastOccurrence(Object o);

    boolean add(E e);

    
    boolean offer(E e);

    
    E remove();

    E poll();

    E element();

    
    E peek();


   
    void push(E e);

    E pop();


   
    boolean contains(Object o);

   
    public int size();

    
    Iterator<E> iterator();

    Iterator<E> descendingIterator();

}

4.介绍实现类们

3)LinkedLst

继承和实现结构:实现了Deque和Collection,是集合也是一个双端队列(能在两端进行添加和删除),由于是链表实现所以没有容量限制。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

举例几个实现方法

都比较简单,下面我简单叙述一下。

查询方法:

头结点和末尾结点不为空,则返回对应的结点元素值

删除方法:

头结点和末尾结点不为空,则删除对应结点,链表的删除时间复杂度低,为常数级o(1)

   //举例几个方法
    public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }

    public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }

    public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

    
    public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }

   
    public void push(E e) {
        addFirst(e);
    }

LinkedList是集合,看得出来既可以当普通队列使用还可以当做集合使用。在当队列使用时我们就具有了更加丰富的方法,方便对结果集进行处理。

4)ArrayDeque

---> 是一个双端队列,能在两端高效的进行添加和删除。实际上是一个环形队列

--->有扩容机制,没有容量限制

实现了Deque接口

public class ArrayDeque<E> extends AbstractCollection<E>
                           implements Deque<E>, Cloneable, Serializable

 head = (h + 1) & (elements.length - 1)与运算  这种实现环形队列的方式,比我们用取模法实现环形队列更高效,这里是有点复杂的,大家可以考虑深入了解,其实我熟知的方法是取模法实现环形队列。

取模法实现环形队列我后续可以在文章中添加,后面我也可能继续深入说明这个高效率的方法

public E pollFirst() {
        int h = head;
        @SuppressWarnings("unchecked")
        E result = (E) elements[h];
        // Element is null if deque empty
        if (result == null)
            return null;
        elements[h] = null;     // Must null out slot
        head = (h + 1) & (elements.length - 1);
        return result;
    }
5)PriorityQueue优先级队列

继承于 AbstractQueue类,实现Queue接口,没有实现Deque接口!因此它仅仅是队列

是一个优先级队列,底层使用堆实现。

堆是一个完全二叉树,从队首(堆顶)取元素,从队尾送入元素。

在Java中这个类底层默认是小顶堆。使用对象时需要自己定义equals方法,用Java比较器重写,这样可以自己定义是小顶堆还是大顶堆。

下面是比较器的使用举例

 //创建优先级队列对象,设置排列规则
	   PriorityQueue<Integer> priorityQueue = new PriorityQueue<Integer>(
            new  Comparator<Integer>() {
			@Override
			public int compare(Integer o1, Integer o2) {
				//底层用小顶堆转换为用大顶堆
				// TODO Auto-generated method stub
				return o2-o1;
			}
		});    

5.总结:有了这些介绍,我觉得会对这些数据结构的使用更熟练。

三、扩展:

后续考虑引入循环队列的实现和一些更深入的介绍,谢谢。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值