week2: Stacks and Queues & Elementary Sorts
泛型与可迭代的数据结构
泛型
在java中实现泛型十分简单,只需定义类时在类名后加上<类型名>
,然后在类中用到此类型数据时通通使用类型名
修饰即可。如
public class Stack<Item> {
private Item[] s;
...
}
可迭代
若想自定义的数据结构是可迭代的,那么就需要实现Iterable
接口,重写iterator()
方法,其会返回一个迭代器,定义如下:
// Iterable interface, built in to java
public interface Iterable<Item> {
Iterator<Item> iterator();
}
当然,我们需要自己定义这个迭代器是如何遍历我们所写的数据结构的。java提供了Iterator
接口,我们实现它即可。
// Iterator interface, built in to java
public interface Iterator<Item> {
boolean hasNext();
Item next();
void remove(); // optional; use at your own risk
}
在client程序中,可以使用范围for循环来进行迭代遍历:
for (String s : stack)
StdOut.println(s);
其等价于:
Iterator<String> i = stack.iterator();
while (i.hasNext()) {
String s = i.next();
StdOut.println(s);
}
基础排序算法
回调(callback)
我们的排序方法在不知道传入的数据类型是什么时,是如何在排序中比较各个元素的呢?使用回调函数。
客户程序传入需要排序的数组,我们的排序方法在比较元素时会调用该元素对象的compareTo()
方法来完成比较。
即对于一个以比较元素为基础的排序方法而言,传进来的对象需自己是可比较的(Comparable)
,在java中就是需要实现Comparable
接口,重写compareTo()
方法。
// Comparable interface, built in to java
public interface Comparable<Item> {
public int compareTo(Item that);
}
然后我们的排序方法将传进来的数组当成可比较对象的数组即可,如:
public static void sort(Comparable[] a) {
...
}
插入排序
在这几个基础排序算法中,我们着重讨论下插入排序。
插入排序是时间复杂度为$O(n^2)$、稳定(stable)且原地(in place)的排序算法,它在数组接近有序和数组较小时常被选用来排序。为什么呢?
- 数组接近有序。插入排序是每次将当前元素与前面的元素一一比较,直到找到小于等于它的元素就在该位置插入。若数组接近有序,那么插入排序比较和交换的次数就很少,能在$O(n)$的时间内完成。
- 数组较小。当数组较小时,插入排序是很快的,因为它是原地的(in place),没有较多的开销。此时它常用来代替归并排序(merge sort)和快速排序(quick sort),因为这两个排序的递归调用对带来额外的栈开销。
Assignment: Deques and Randomized Queues
代码见这里。在这主要说明下完成作业的几个关键点:
- loitering。在链表实现deque的过程中,由于有
next
和prev
两个引用,稍有不慎就会出现loitering。以addFirst
函数为例,添加一个新的first节点后,如果仅仅设置了该节点的next
和prev
引用,是会出现loitering的。若链表不止有这一个新增的节点,那么原来的first节点的prev
应该指向它,即oldFirst.prev = first
。 - 随机队列的
dequeue
函数。由于随机队列是随机删除一个元素,若用数组实现,那一下标直接就空了,如果置之不理,仍然是在后面入列,则出列操作不好实现。为了解决这一问题,我们可以交换删除元素位置的值和队尾元素的值。 - bonus:bonus的问题我也不会,是在论坛上了解到的方法——水塘抽样。水塘抽样是一种统计的随机算法,在这里不作探讨,不了解的朋友可以参见:Reservoir sampling(水塘抽样)。
参考
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!