栈
栈在java中的实现方式是:
public class Stack<E> extends Vector<E>
Vector向量可以理解为高级的数组,因为它里面通过一个Object[]数组来维护所有元素,并且实现了该数组的管理以及动态增长。所以Stack本质上就是一个数组,通过继承Vector,同时提供Stack特性的几个方法push,pop,peek等,原理比较简单。
队列
》一般队列
队列的特点是先进先出(FIFO),java中已经封装好Queue类。
队列的效率:添加和移除数据项时间为O(1)。注意这里的添加是往队尾添加数据项,移除的是队头的数据项,不是同一个。
队列在java中的实现方式是:
public interface Queue<E> extends Collection<E> public interface Collection<E> extends Iterable<E>
注意这里是一个接口,也就是说可以通过实现该接口得到自己的队列。Collection接口中提供了诸如add,remove,isEmpty等方法,同时继承了Iterable可以获取迭代器。Queue中则新增了一些针对队列的一些方法。
》双端队列
双端队列顾名思义,就是可以在两端同时进行添加和移除操作的队列。在java中的实现如下
public interface Deque<E> extends Queue<E>
也是一个接口,通过继承Queue,并提供对双端的额外操作,addFirst,addLast,removeFirst,removeLast等等。这是一个综合体,因为可以操作一端,你也可以当成栈来使用,同时还是个队列。
》优先级队列
优先级队列中的元素按照关键字的值有序。数据项在插入的时候会插入到合适的位置维持队列的顺序。在java中的实现如下:
//PriorityQueue继承了AbstractQueue
class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serializable //抽象类AbstractQueue 继承自AbstractCollection并实现Queue接口 public abstract class AbstractQueue<E> extends AbstractCollection<E> implements Queue<E> //抽象类AbstractCollection实现了Collection接口 public abstract class AbstractCollection<E> implements Collection<E>
看了源码你会发现PriorityQueue是一个具体的类,之前的Queue等接口我们是看不到队列里的元素是什么的。只有实现接口的子类中才有。比如这里PriorityQueue中也使用Object[]数组来保存队列元素。
同时,抽象类AbstractQueue中还是只有add, remove等操作。跟优先级相关的内容肯定是在PriorityQueue中的,它是如何实现的呢?我们知道优先级队列在添加元素的时候维持顺序,所以直接看add方法。源码如下:
public boolean add(E e) {
return offer(e);
}
//offer源码如下,我们具体看一下
public boolean offer(E e) {
//排除空指针
if (e == null)
throw new NullPointerException();
/**modCount的解释是The number of times this priority queue has been structurally modified,不用管*/
modCount++;
//当数量超出数组大小时,通过grow动态新增数组空间
int i = size;
if (i >= queue.length)
grow(i + 1);
//size++
size = i + 1;
//如果是第一次插入数据则直接放入
if (i == 0)
queue[0] = e;
else //如果已经存在数据则调用siftup,这里面是某种排序
siftUp(i, e);
return true;
}
//siftUp源码如下:
private void siftUp(int k, E x) {
//comparator不会陌生,如果构造器里提供了比较器就用比较器,没有就默认处理。siftUpUsingComparator与siftUpComparable算法一样就是比较器不一样
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
//就看siftUpComparable。x是要插入的元素,k是插入前队列的长度
private void siftUpComparable(int k, E x) {
//获取默认比较器,这里注意一定是可排序的元素才能使用优先级队列
Comparable<? super E> key = (Comparable<? super E>) x;
//下面是一段神祕代码,但是我们知道它一定实现了某种排序
while (k > 0) {
//其实就是(k-1)/2
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (key.compareTo((E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}
下面我们分析一下这段神祕代码,对于我们这些算法战五渣只知道插入排序什么的,这里明显不是啊。如果我们自己按照它的程序模拟插入会发现一个奇怪的结果,有点不对劲啊。我写了个简单的例子,记录了它的储存过程。
PriorityQueue<Integer> queue = new PriorityQueue<>();
queue.add(1);
System.out.println(queue.toString());
queue.add(5);
System.out.println(queue.toString());
queue.add(2);
System.out.println(queue.toString());
queue.add(3);
System.out.println(queue.toString());
然后运行,结果如下:
[1]
[1, 5]
[1, 5, 2]
[1, 3, 2, 5]
wtf,这排了序?兄台莫慌,会有这样的疑问很正常,因为我们的惯性思维就是排好序的就是1,2,3,4。。。然而,这里的数据结构是一个队列,不是数组,应该以队列的方式验证它的排序,这叫在其位谋其政。队列的方式就是进去的时候无序,出来的时候是有序的。怎么出来?
while(!queue.isEmpty()){
System.out.print(queue.poll()+" ");
}
结果是:1 2 3 5 。完全正确
看来这段神祕代码确实有效,只不过排序的过程比较特别,刚才我们解锁的姿势不对。那么这段神祕代码是什么?其实从parent,siftUp这些字眼能看出来这是一个堆排序,建立在二叉树的基础上。
相关知识我们后面了解,今天的话题是栈和队列在java中是如何实现的。我们可以看到存储可以用数组,在存储的基础上实现各中对应操作。优先级队列底层用了堆排等等。
上一节:数据结构与算法之二(栈常见案例)
下一节:数据结构与算法之四(链表)