public class MyArrayList<T> implements Iterable<T> {
private static final int DEFAULT_CAPACITY = 10;
private T[] theItems; private int currentSize;
private int modCount = 0;
public MyArrayList() { doClear(); } public int size() { return currentSize; }
public boolean isEmpty() { return currentSize == 0; }
public T get(int idx) { if (idx < 0 || idx >= theItems.length) { throw new ArrayIndexOutOfBoundsException(); } return theItems[idx]; }
public T set(int idx, T x) { if (idx < 0 || idx >= theItems.length) { throw new ArrayIndexOutOfBoundsException(); } T oldVal = theItems[idx]; theItems[idx] = x; return oldVal; }
private void ensureCapacity(int newSize) { if (newSize <= currentSize) { return; } T[] oldItems = theItems; theItems = (T[]) new Object[newSize]; for (int i = 0; i < currentSize; i++) { theItems[i] = oldItems[i]; } oldItems = null; }
public boolean add(T x) { add(size(), x); return true; }
private void add(int idx, T x) { if (currentSize == theItems.length) { ensureCapacity(currentSize * 2 + 1); } for (int i = currentSize; i > idx; i--) { theItems[i] = theItems[i - 1]; } theItems[idx] = x; modCount++; currentSize++; }
public T remove(int idx) { if (idx < 0 || idx >= theItems.length) { throw new ArrayIndexOutOfBoundsException(); } T removeItem = theItems[idx]; for (int i = idx; i < currentSize - 1; i++) { theItems[i] = theItems[i + 1]; } currentSize--; modCount++; return removeItem; } public void clear() { doClear(); }
private class ArrayListIterator implements Iterator<T> {
private int expectedModCount = 0;
private boolean isOkToRemove = false; private int current;
@Override public boolean hasNext() { if (expectedModCount != modCount) { throw new ConcurrentModificationException(); } return current < currentSize; }
@Override public T next() { if (expectedModCount != modCount) { throw new ConcurrentModificationException(); } if (!hasNext()) { throw new NoSuchElementException(); } T val = theItems[current++]; isOkToRemove = true; return val; }
@Override public void remove() { if (expectedModCount != modCount) { throw new ConcurrentModificationException(); } if (!isOkToRemove) { throw new IllegalStateException(); } MyArrayList.this.remove(--current); expectedModCount++; isOkToRemove = false; } } @Override public Iterator<T> iterator() { return new ArrayListIterator(); } }
简单链表
为了避免插人和删除的线性开销,我们需要保证表可以不连续存储,否则表的每个部分都可能需要整体移动。下图指出链表的一般想法。 为了执行printList或find(x),只要从表的第一个节点开始然后用一些后继的next链遍历该表即可。这种操作显然是线性时间的,和在数组实现时一样,不过其中的常数可能会比用数组实现时要大。findKth 操作不如数组实现时的效率高;findKth(i)花费O(i)的时间并以这种明显的方式遍历链表而完成。在实践中这个界是保守的,因为调用findKth常常是以(按i)排序后的方式进行。例如,findKth (2), findKth (3),findKth (4) 以及 findKth(6)可通过对表的一次扫描同时实现。 remove 方法可以通过修改一个 next 引用来实现。下图给出在原表中删除第三个元素的结果。 Insert 方法需要使用 new操作符从系统取得一个新节点,此后执行两次引用的调整。其一般想法在图 3-3 中给出,其中的虚线表示原来的 next 引用。 我们看到,在实践中如果知道变动将要发生的地方,那么向链表插人或从链表中删除一项的操作不需要移动很多的项,而只涉及常数个节点链的改变。 在表的前端添加项或删除第一项的特殊情形此时也属于常数时间的操作,当然要假设到链表前端的链是存在的。只要我们拥有到链表最后节点的链,那么在链表末尾进行添加操作的特殊情形(即让新的项成为最后一项)可以花费常数时间。因此,典型的链表拥有到该表两端的链。删除最后一项比较复杂,因为必须找出指向最后节点的项,把它的 next 链改成 null,然后再更新持有最后节点的链。在经典的链表中,每个节点均存储到其下一节点的链,而拥有指向最后节点的链并不提供最后节点的前驱节点的任何信息。 保留指向最后节点的节点的第3个链的想法行不通,因为它在删除操作期间也需要更新。我们的做法是,让每一个节点持有一个指向它在表中的前驱节点的链,如下图所示,我们称之为双链表(doubly linked list)。
public T get(int idx) { return getNode(idx).data; }
public T set(int idx, T newVal) { Node<T> node = getNode(idx); T oldVal = node.data; node.data = newVal; return oldVal; } private Node<T> getNode(int idx) { return getNode(idx, 0, size() - 1); }
private Node<T> getNode(int idx, int lower, int upper) {
if (idx < lower || idx > upper) { throw new IndexOutOfBoundsException(); }
Node<T> p;
if (idx < size() / 2) { p = beginMarker.next; for (int i = 0; i < idx; i++) { p = p.next; } } else { p = endMarker; for (int i = size(); i > idx; i--) { p = p.prev; } } return p; }
public T remove(int idx) { return remove(getNode(idx)); }
public T remove(Node<T> p) { p.prev.next = p.next; p.next.prev = p.prev; modCount++; currentSize--; return p.data; }
@Override public Iterator<T> iterator() { return new LinkedListIterator(); }
private class LinkedListIterator implements Iterator<T> { private int expectedModCount = modCount; private boolean isOkToRemove = false; private Node<T> current = beginMarker.next;
@Override public boolean hasNext() { return current != endMarker; }
@Override public T next() {
if (expectedModCount != modCount) { throw new ConcurrentModificationException(); } if (!hasNext()) { throw new NoSuchElementException(); } T nextItem = current.data; current = current.next; isOkToRemove = true; return nextItem; }
@Override public void remove() { if (expectedModCount != modCount) { throw new ConcurrentModificationException(); } if (!isOkToRemove) { throw new IllegalStateException(); } isOkToRemove = false; expectedModCount++; MyLinkedList.this.remove(current.prev); } } }
一些情况下的时间复杂度对比
1 2 3 4 5
public static void makeList1 (List <Integer> 1st, int N){ 1st. Clear (); for (inti=0; i <N; i++) Ist.add(i); }
public static void makeList2 (List<Integer> lst, int N){ lst.clear (); for (inti=0; i <N; i++) lst. Add (0,i); }
那么,对于 LinkedList 它的运行时间是 O (N),但是对于 ArrayList 其运行时间则是 O (N2),因为在 ArrayList 中,在前端进行添加是一个 0 (N)操作。下一个例程是计算 List 中的数的和:
1 2 3 4 5
public static int sum (List <Integer> lst){ int total = 0; for (inti=0; i <N; i++) total += lst.get(i); }
这里,ArrayList 的运行时间是 O (N),但对于LinkedList来说,其运行时间则是0(N2),因为在 LinkedList 中,对 get 的调用为 0 (N)操作。可是,要是使用一个增强的 for 循环,那么它对任意 List 的运行时间都是0(N),因为迭代器将有效地从-项到下-项推进。