Java数据结构学习1--List

寒假看了数据结构与算法分析Java语言描述这本书,但并没有静下心来仔细阅读,准备毕业前重新认认真真看完。想到的第一件事就是通过写博客来加深理解,虽然会花费不少时间,但我觉得利肯定是大于弊的,加油每一天。
Java语言中包含一些基本数据结构。今天总结的是表ADT。
表ADT就是list,在Java中表是由List接口所实现。List接口继承了Conllection接口。先说关于Collection接口。列举一些比较重要的方法:
public interface Collection<T> extends  Iterable<T>{
int size();
boolean isEmpty();
void clear();
boolean  contains(T x);
boolean  add(T x);
boolean  remove(T x);
java.util.Iterator<T>  iterator();
}

那么Iterable接口是个什么鬼,这是Collection接口必须继承的方法,他只有一个方法,就是

public interface<T> {
Iterator<T> iterator();
}

也就是我们上面见到的第七个方法。
那Iterator又是什么,Iterator也是一个接口,实现了三个方法:

public  interface  Iterator<T> {
boolean  hasNext();
T next();
void remove();
}

Collection接口扩展了Iterable接口。实现Iterable接口的类可以拥有增强的for循环(foreach).
Iterator的思想是:通过iterator方法,每个集合均可以创建并返回给客户一个实现了iterator接口的对象,并将当先位置的概念在对象内部存储下来.每次对next的调用都给出集合的下一项,然后用hasNext来告诉是否存在下一项。当编译器见到一个用于Iterator的对象的增强的for循环时,它用对iterator方法的调用来代替增强的for循环来得到一个Iterator对象。(数据结构与算法分析 java语言描述 p47)
Iterator接口中的方法只有三个,一般只能用来遍历,不过remove方法很值得去尝试使用,相对于Collection里的remove方法来言好处多多:
Collection中的remove方法需要先找出被删除项。那么久需要先获取其位置,之前说过,通过iterator方法返回一个实现了iterator接口的对象,并且当前位置的概念在对象内部存储下来,那么就知道了所删除项的位置,开销就减少了。比如说我们要在一个集合里每隔一项删除一项,最好的方法就是使用迭代器:

public static void remove(List<Integer> list){
Iterator<Integer> itr = list.iterator();
while(itr.hasNext()){
if(itr.next() % 2 == 0)
itr.remove();
}
}
不过需要注意的是:对正在迭代的集合进行结构上的改变是不合法的,其后使用该迭代器时会有ConcurrentModificationException异常被抛出。所以也就是只有在需要一个迭代器时我们才需要获取迭代器,不过如果迭代器调用了它自己的remove方法,那它就依旧是合法的,这也是我们更愿意使用迭代器的remove方法的第二个原因,而这也是为什么上面例程里为什么不用增强的for循环去解决问题的原因(从而会出现list.remove()这种情况),我们不能期待其懂得循环只有在一项不被删除时它才会向前推进,这里会发生ConcurrentModificationException异常。
现在我们可以回到我们今天的主题:List。List接口继承了Collection接口,所以它获取了Collection接口的所有方法,以及其他一些方法,比如说其中一些重要的:
public interface  List<T>  extends Collection<T>{
T  get(int idx);
T  set(int idx, T x);
void add(int idx, T x);
void remove(int idx);

ListIterator<AnyType>  listIterator(int pos);
}

那么又有疑惑了,ListIterator是什么,查看一下jdk,就会很快知道,ListIterator是一个继承了Iterator接口的接口:

public  interface  ListIterator<T>  extends Iterator<T> {
public boolean hasNext();
T next();
boolean hasPrevious();
T previous();
int nextIndex();
int previousIndex();
void remove();
void set(T);
add(T);
}

previous和hasPrevious使得对表从后向前的遍历得以完成。add方法将一个新的项以当前位置放入表中,当前项是通过把迭代器对next的调用所给出的项和对previous的调用所给出的项之间而抽象出来的概念。set方法改变被迭代器看到的最后一个值,对LinkedList很难实现。关于ArrayList和LinkedList,下面即将开始讲。

List 有两种流行的实现方式:ArrayList和LinkedList,具体的继承关系直接看jdk源码。使用ArrayList的好处在于对get和set花费常数时间,但是对于插入和删除代价昂贵,除非变动在ArrayList末尾。LinkedList提供了双链表实现,所以在插入删除方面代价较小(假设变动项位置已知),在表头或表尾添加删除花费常数时间,LinkedList提供了想要的方法addFirst,removeFirst,addLast,removeLast,getFirst,getLast.但缺点却是对get的调用是昂贵的,除非在靠近端点处。
下面例程计算List中的数之和:

public static  int sum(List<Integer> lst){
int total = 0;
for(int i = 0; i < list.size(); i++){
total += lst.get(i);
}
}

在ArrayList中运行时间为O(n),但对于LinkedList则需要O(n^2),因为LinkedList实现get(int idx)需要花费O(n)时间,但解决办法不是没有,使用增强的for循环,从而使整体运行时间为O(n)。之前说过,实现了Iterable接口的类可以拥有增强的for循环,该循环施于这些类之上以观察它们所有的项。其本质是增强的for循环底层实现了iterator迭代,迭代有效地从一项到下一项推进(通过调用它的next方法)。

那么就开始尝试去了解一下ArrayList和LinkedList的源码。但是jdk源码中比较长,我们抽取其中的一部分比较重要的方法,自己去写一个(照着源码写),写出来是对源码最好的分析(这个在书中有, 但为了更好理解,可以同时参考JDK源码一起看)。

public class MyArrayList<E>  implements Iterable<E>{
//源码中ArrayList并不是直接实现Iterable接口,直接看jdk源码可知
   private static final int DEFAULT_CAPACITY = 10;
   private int theSize;
   private E[] theItems;

   public MyArrayList(){
      clear();
   }

   public clear(){
      theSize = 0;
      ensureCapacity(DEFAULT_CAPACITY);
   }

   public int size(){
      return theSize;
   }

   public  boolean isEmpty {
      return size() == 0;
   }

   public void trimeToSize(){
      ensureCapacity(size());
   }

   public {

   }

   public E  get(int index){
      if(index < 0 || index >= size())
         throw new ArrayIndexOutOfBoundsException();
      return theItems[index];
   }

   public E set(int index, E item){
      if(index < 0 || index >= size())
         throw new ArrayIndexOutOfBoundsException();
      E old = theItems[index];
      theItems[index] = item;
      return old;  
   }

   public ensureCapacity(int size){
      E[] old = theItems;
      theItems = (E[])new Object[size];
      for(int i = 0; i<size(); i++){
      theItems[i] = old[i];
      }

   public void add(int index, E x){
      if(size() == theItems.length){
         ensureCapacity(size() * 2 +1)
      }
      for(int i = theSize; i > index; i-- ){
         theItems[i] = theItems[i - 1];
      }
      theItems[index] = x;
      theSize ++;
   }

   public E remove(int index){
      E x = theItems[index];
      for(int i = theSize; i < size(); i+ ){
         theItems[i] = theItems[i + 1];
      }
      theSize --;   
      return x;
   }

   public java.util.Iterator<E>  iterator(){
      return new ArrayListIterator();
   }

   private class ArrayListIterator implements java.util.Iterator<E> {
      private int current = 0;
      public boolean hasNext(){
         return current < size();
      }

      public E next(){
         if(! hasNext){
            throw new java.util.NoSuchElementException();
         }
         return theItems[current ++];
      }

      public void remove(){
      //感受内部类的好处
         MyArrayList.this.remove(--current);
      }
   }

}
LinkedList则有些不同,LinkedList是由双链表来实现,而且还需要保留该表两端的引用。只要操作发生在已知位置(端点或者是迭代器指定的位置),我们的操作(add或remove)将花费常数时间。
在下面这个精简的LinkedList类MyLinkedList类中,将会包含以下三个类:
1.MyLinkedList类本身,包括两端的链,表的大小以及一些方法。
2.Node类
3.LinkedListIterator类。

在表的前端我们创建一个头节点作为逻辑上开始的标志,在末端创建节点叫做尾节点。使用这些额外的节点的优点在于,排除了特殊情况简化代码。比如说删除第一个节点,因为删除算法需要访问被删除节点前面的节点。

关于Node类,是MyLinkedList类的一个静态内部类,具体实现:
private static class Node<E>{

   public E d;
   public Node<E> previous;
   public Node<E> next;
   public Node(E d, Node<E> previous, Node<E> next){
      this.d = d;
      this.previous = preivous;
      this.next = next;
   }
}
现在我们探究MyLinkedList类本身,包含以下几个成员变量和一些重要方法:
private int modCount;
private int theSize;
private Node<E> startFlag;
private NOde<E> endFalg;

public void clear(){
  theSize = 0;
  startFlag = new Node<E>(null, null, null);
  endFlag = new Node<E>(null, startFlag, null);
  startFlag.next = endFlag;
  modCount++;
}

public void add(int index, E x){
  addBefore(getNode(index), x);
}

private void addBefore(Node<E> p, E x ){
  Node<E> newNode = new Node<E>(x, p.previous, p);
  newNode.previous.next = newNode;
  p.previous = newNode;
  modCount ++;
  theSize ++;
}

private E remove(Node<E> p, E x){
  p.next.previous = p.previous;
  p.previous.next = p.next;
  theSize ++;
  modCount ++;
  return p.data
}

private Node<E> getNode(int index){
  Node<E> p;
  if(index <0 ||  index >=size()){
    throw new IndexOutOfBoundsException();
  }
  if(index < size()/2){
  //这里只是遍历查找,并不是做结构上的变化
    p = startFlag.next;
    for(int i =0; i < index; i++){
      p = p.next;
    }
  }else{
    ......
  }
  return p;
}

LinkedListIterator类似ArrayListIterator,但添加了错误检测。

private  class  LinkedListIterator implements  java.util.Iterator<E>{
  private Node<E> current = startFlag.next;
  private int expectedModCount = modCount;
  private boolean okToRemove = false;

  public boolean hasNext(){
    return current != endFlag;
  }

  public E next(){
    if(modCount != expectedModCount){
      throw new ConcurrentModificationException();
    }
    if(! hasNext()){
      throw new NoSuchElementException();
    }
    E nextItem = current.data;
    current = current.next;
    okToRemove = true;
    return nextItem;
  }

  public void remove(){
     if(modCount != expectedModCount){
      throw new ConcurrentModificationException();
     }
     if(! okToRemove){
       throw new IllegalStateException();
     }
     MyLinkedList.this.remove(current.previous);
     okToRemove = false;
     expectedModCount++;
  } 
}

最后还有一句话:remove方法里的current是保持不变的,因为current正在观察的节点不受前面被删除的节点的影响。

好了,list就到此为止了,但我所学会的这些只是基础,以后还应更深入地学习,加油每一天。下一篇准备做栈和队列的笔记。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值