一、栈
线性表中的元素具有一一对应的关系,而栈是一种特殊的线性表。也是先入后出的有序列表。
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.总结:有了这些介绍,我觉得会对这些数据结构的使用更熟练。
三、扩展:
后续考虑引入循环队列的实现和一些更深入的介绍,谢谢。