表的学习笔记
主要是ArrayList和LinkedList的手动实现
3.3 Java 集合中的表
概念
- 在Java中包含着一些普通数据结构的实现,该部分称为CollectionAPI,位于java.util包中
3.3.1 Collection接口
Collection位于java.util包中,实现了Iterator接口,实现Iterator接口的可以拥有增强for循环,下面是Collection接口提供的一些重要方法
public interface Collection<T> extends Iterable<T>{
int size();
boolean isEmpty();
void clear();
boolean contains(T t);
boolean add(T t);
boolean remove(T t);
java.util.Iterator<T> iterator();
}
3.3.2 Iterator接口
实现Iterator接口的集合必须提供一个称为iterator的方法,该方法返回一个Iterator类型的对象。下面是Iterator接口的定义
public interface Iterator<T>{
boolean hasNext();
T next();
void remove();
}
Iterator接口实现的思路是通过iterator方法,每个集合均可以创建并且返回给客户一个实现Iterator接口的对象,并将当前位置的概念在对象内部存储起来。
每次对next的调用都给出集合的下一项,第一次调用给出第一项,第n次调用第n项。使用hasNext()告诉是否还有下一项。
Iterator接口还包含remove()的方法,该方法可以删除由next返回的最新项。它的优点在于,Collection的remove方法必须首先找出被删除的项
当直接使用Iterator,而不是使用增强for循环时的基本法则:如果对正在被迭代的集合上进行结构上的改变,即对该集合使用add、remove或者clear方法,那么迭代器就不再合法。但是在使用Iterator自己的remove方法时,那么他仍然是合法的。
3.3.3 List接口、ArrayList类和LinkedList类
下面是List接口,他指定listIterator方法作为迭代器
public interface List<T> extends Collection<T>{
T get(int index);
T set(int index,T t);
void add(int index,T t);
void remove(int index);
ListIterator<T> listIterator(int pos);
}
List ADT有两种流行的实现方式。ArrayList类提供了List ADT的一种可增长的数组实现。
使用ArrayList的优点是get和set花费常数时间,缺点是新项加入和现有项删除花费O2时间,除非变动是在末端实现的。LinkedList则提供了List ADT的双链表实现。
使用LinkedList的优点是新项加入和现有项删除开销很小,缺点是LinkedList不容易做索引,对get的调用花费时间很长,除非调用非常接近表的端点。
对下面求List和的算法来说,对ArrayList是O,对LinkedList是O2,但是如果使用增强for循环,那么他们两个都是O2。因为迭代器可以有效的从一项推进到下一项
public int sum(List<Integer> lst){
int total=0;
for(int i=0;i<N;i++){
total+=lst.get(i);
}
return total;
}
3.3.4 例子:删除LinkedList中的偶数项
方法一
public void method1(List<Integer> list){
int i=0;
while(i<list.size()){
if(lst.get(i)%2==0){
list.remove(i);
}
}
}
方法二
public void method2(List<Integer> list){
for(Integer i:list){
if(i%2==0){
list.remove(i);
}
}
}
方法三
public void method3(List<Integer> list){
Iterator<Integer> iter=list.iterator();
while(iter.hasNext()){
if(iter.next()%2==0){
iter.remove();
}
}
}
分析:
方法1,是最基本的方法,但是对于LinkedList来说,get方法很低效,remove方法也不高效,算法复杂度O2
方法2,想用迭代器规避get的低效,但是改变了结构,会使迭代器失效,方法二会直接报错。
方法3,转化为Iterator,使用迭代器保证了高效访问与删除,因为相当于直接知道位置了。算法复杂度O
3.4 ArrayList的实现
下面是技术要求
-
MyArrayList将保持基础数组,数组的容量以及存储在MyArrayList中的当前项数
-
MyArrayList将将提供一种机制改变便基础数组的容量,获得一个新数组,把老数组数据copy到新数组来改变数组容量,允许JVM回收老数组
-
MyArrayList将提供get和set实现
-
MyArrayList将提供基本的例程,如size isEmpty clear,还提供remove 以及两种add,如果数组大小和容量相同,这两个add例程都将增加容量。
-
MyArrayList将提供一个实现Iterator接口的类。这个类将存储迭代序列中的下一项的下标,并且提供next hasNext remove等方法的实现,MyArrayList的迭代器方法直接返回实现Iterator接口的该类的新构造的实例。
import java.util.Iterator;
public class MyArrayList<T> implements Iterable<T> {
private static final int DEFAULT_CAPACITY=10;
private int theSize;
private T[] theItems;
/**
* 初始化ArrayList
*/
public MyArrayList(){
clear();
}
//大小
public int size(){
return theSize;
}
//清空
public void clear(){
theSize=0;
ensureCapacity(DEFAULT_CAPACITY);
}
public boolean isEmpty(){
return size()==0;
}
public void ensureCapacity(int newCapacity){
if(newCapacity<theSize){
return;
}
T[] old=theItems;
theItems=(T[])new Object[newCapacity];
for(int i=0;i<size();i++){
theItems[i]=old[i];
}
}
public T get(int index){
if(index>size()||index<0){
throw new ArrayIndexOutOfBoundsException();
}
return theItems[index];
}
public T set(int index,T newValue){
if(index<0||index>size()){
throw new ArrayIndexOutOfBoundsException();
}
T old=theItems[index];
theItems[index]=newValue;
return old;
}
public T remove(int index){
T item=theItems[index];
for(int i=index;i<size()-1;i++){
theItems[i]=theItems[i+1];
}
theSize--;
return item;
}
public void add(T t){
add(size(),t);
}
public void add(int index,T t){
if(theItems.length==theSize){
ensureCapacity(theSize*2+1);
}
for(int i=theSize;i>index;i--){
theItems[i]=theItems[i-1];
}
theItems[index]=t;
theSize++;
}
public java.util.Iterator<T> iterator(){
return new ArrayListIterator<T>(this);
}
private static class ArrayListIterator<T> implements java.util.Iterator<T>{
private int current=0;
private MyArrayList<T> list;
public ArrayListIterator(MyArrayList<T> list) {
this.list=list;
}
public boolean hasNext(){
return current<list.size();
}
public T next(){
if(!hasNext()){
throw new java.util.NoSuchElementException();
}
return list.theItems[current++];
}
public void remove(){
list.remove(--current);
}
}
}
3.5 LinkedList的实现
下面是技术要求
-
提供MyLinkedList类本身,它包含到两端的链、表的大小以及一些方法
-
Node类,它可能是一个私有的嵌套类。一个节点包含数据以及到前后的链,还有一些适当的构造方法
-
LinkedListIterator类,该类抽象了位置的概念,是一个私有类,并实现接口Iterator.它提供了next、hasNext和remove实现。
为了排除一些特殊情形,在表的终端创建一个额外的节点来表示终端标记,这些节点称为标记节点,在前端的节点也叫做头节点(header node),而在末端的节点有时候也叫尾节点(tail node)
private static class Node<T>{
public T data;
public Node<T> prevNode;
public Node<T> nextNode;
public Node(T data,Node<T> prevNode,Node<T> nextNode){
this.data=data;
this.prevNode=prevNode;
this.nextNode=nextNode;
}
}
下面是基本骨架
public class MyLinkedList<T> implements Iterable<T> {
private int theSize;
private int modCount=0;
private Node<T> beginMarker;
private Node<T> endMarker;
private static class Node<T>{
public T data;
public Node<T> prevNode;
public Node<T> nextNode;
public Node(T data,Node<T> prevNode,Node<T> nextNode){
this.data=data;
this.prevNode=prevNode;
this.nextNode=nextNode;
}
}
public MyLinkedList(){
clear();
}
public void clear(){
}
public int size(){
return theSize;
}
public boolean isEmpty(){
return size()==0;
}
public boolean add(T newNode){
//Todo
}
public void add(int index,T newNode){
//Todo
}
public T get(int index){
//Todo
}
public T set(int index,T newNode){
//Todo
}
public T remove(int index){
//Todo
}
public java.util.Iterator<T> iterator(){
return new LinkedListIterator();
}
private class LinkedListIterator implements java.util.Iterator<T>{
//Todo
}
...
}
各部分实现
- 链表初始化
首先是双链表的初始化,使用clear来实现,让头节点为null,尾节点的前一个结点为头节点,后一个节点为null,数值也为null,然后让头节点的下一个节点为尾节点,使当前大小theSize=0,修改次数加1
public void clear(){
beginMarker=new Node<>(null,null,null);
endMarker=new Node<>(null,beginMarker,null);
beginMarker.nextNode=endMarker;
theSize=0;
modCount++;
}
- 节点添加
然后是双链表的节点添加。为了方便起见,定义一个私有函数addBefore(Node node,T value)实现在某个位置前面添加节点,为了得到这个位置原来的节点,再实现一个获取的函数getNode(int index)
//添加节点
public boolean add(T newNode){
add(size(),newNode);
return true;
}
public void add(int index,T newNode){
addBefore(getNode(index),newNode);
}
/**
*
* @param node 这个Node是插入位置的原来的Node,值是新的值
* @param value
*/
private void addBefore(Node<T> node,T value){
Node<T> newNode=new Node<>(value,node.prevNode,node.nextNode);
newNode.prevNode.nextNode=newNode;
node.prevNode=newNode;
theSize++;
modCount++;
}
private Node<T> getNode(int index){
Node<T> node;
if(index<0||index>size()-1){
throw new IndexOutOfBoundsException();
}
if(index<size()/2){
node=beginMarker.nextNode;
for(int i=0;i<index;i++){
node=node.nextNode;
}
}else {
node=endMarker;
for(int i=size();i>index;i--){
node=node.prevNode;
}
}
return node;
}
- 节点删除
节点删除就是断开那个节点的键,首先就要获取节点getNode,然后操作
public T remove(int index){
Node<T> node=getNode(index);
node.nextNode.prevNode=node.prevNode;
node.prevNode.nextNode=node.nextNode;
theSize--;
modCount++;
return node.data;
}
- 获取节点的值和设置节点的值
获取节点对象后对其修改即可
public T get(int index){
return getNode(index).data;
}
public T set(int index,T newValue){
Node<T> node=getNode(index);
T oldValue=node.data;
node.data=newValue;
return oldValue;
}
- 迭代器的实现
迭代器内部需要实现next hasNext 和remove方法.exceptedModCount是为了防止在使用迭代器时改变链表结构而做的标志
private T remove(Node<T> node){
node.nextNode.prevNode=node.prevNode;
node.prevNode.nextNode=node.nextNode;
theSize--;
modCount++;
return node.data;
}
public java.util.Iterator<T> iterator(){
return new LinkedListIterator();
}
private class LinkedListIterator implements java.util.Iterator<T>{
private Node<T> current=beginMarker.nextNode;
private int exceptedModCount=modCount;
private boolean canRemove=false;
public boolean hasNext() {
return current!=endMarker;
}
public T next() {
if(modCount!=exceptedModCount){
throw new ConcurrentModificationException();
}
if(!hasNext()){
throw new NoSuchElementException();
}
T value=current.data;
current=current.nextNode;
canRemove=true;
return value;
}
public void remove() {
if(modCount!=exceptedModCount){
throw new ConcurrentModificationException();
}
if(!canRemove){
throw new IllegalStateException();
}
MyLinkedList.this.remove(current.prevNode);
exceptedModCount++;
canRemove=false;
}
}