- package LinkedList;
- /**
- * <p><strong>我的Java单链表练习</strong></p>
- * <p>单链表提供了在列表头的高效插入和删除操作,不过在单链表的末尾的插入操作效率很低.</p>
- * <p>单链表指针域保存着下一节点的引用,尾结点的指针域等于null</p>
- * @author baby69yy2000
- */
- public class SingleLinkedList<T> {
- /**
- * 结点类
- */
- private static class Node<T> {
- T nodeValue; // 数据域
- Node<T> next; // 指针域保存着下一节点的引用
- Node(T nodeValue, Node<T> next) {
- this.nodeValue = nodeValue;
- this.next = next;
- }
- Node(T nodeValue) {
- this(nodeValue, null);
- }
- }
- // 下面是SingleLinkedList类的数据成员和方法
- private Node<T> head, tail;
- public SingleLinkedList() {
- head = tail = null;
- }
- /**
- * 判断链表是否为空
- */
- public boolean isEmpty() {
- return head == null;
- }
- /**
- * 创建头指针,该方法只用一次!
- */
- public void addToHead(T item) {
- head = new Node<T>(item);
- if(tail == null) tail = head;
- }
- /**
- * 添加尾指针,该方法使用多次
- */
- public void addToTail(T item) {
- if (!isEmpty()) { // 若链表非空那么将尾指针的next初使化为一个新的元素
- tail.next = new Node<T>(item); // 然后将尾指针指向现在它自己的下一个元素
- tail = tail.next;
- } else { // 如果为空则创建一个新的!并将头尾同时指向它
- head = tail = new Node<T>(item);
- }
- }
- /**
- * 打印列表
- */
- public void printList() {
- if (isEmpty()) {
- System.out.println("null");
- } else {
- for(Node<T> p = head; p != null; p = p.next)
- System.out.println(p.nodeValue);
- }
- }
- /**
- * 在表头插入结点,效率非常高
- */
- public void addFirst(T item) {
- Node<T> newNode = new Node<T>(item);
- newNode.next = head;
- head = newNode;
- }
- /**
- * 在表尾插入结点,效率很低
- */
- public void addLast(T item) {
- Node<T> newNode = new Node<T>(item);
- Node<T> p = head;
- while (p.next != null) p = p.next;
- p.next = newNode;
- newNode.next = null;
- }
- /**
- * 在表头删除结点,效率非常高
- */
- public void removeFirst() {
- if (!isEmpty()) head = head.next;
- else System.out.println("The list have been emptied!");
- }
- /**
- * 在表尾删除结点,效率很低
- */
- public void removeLast() {
- Node<T> prev = null, curr = head;
- while(curr.next != null) {
- prev = curr;
- curr = curr.next;
- if(curr.next == null) prev.next = null;
- }
- }
- /**
- * <p>插入一个新结点</p>
- * <ul>插入操作可能有四种情况:
- * <li>①表为空, 返回false</li>
- * <li>②表非空,指定的数据不存在</li>
- * <li>③指定的数据是表的第一个元素</li>
- * <li>④指定的数据在表的中间</li></ul>
- * @param appointedItem 指定的nodeValue
- * @param item 要插入的结点
- * @return 成功插入返回true;
- */
- public boolean insert(T appointedItem, T item) {
- Node<T> prev = head, curr = head.next, newNode;
- newNode = new Node<T>(item);
- if(!isEmpty()) {
- while((curr != null) && (!appointedItem.equals(curr.nodeValue))) { //两个判断条件不能换
- prev = curr;
- curr = curr.next;
- }
- newNode.next = curr; //②③④
- prev.next = newNode;
- return true;
- }
- return false; //①
- }
- /**
- * <p>移除此列表中首次出现的指定元素</p>
- * <ul>删除操作可能出现的情况:
- * <li>①prev为空,这意味着curr为head. head = curr.next; --> removeFirst();</li>
- * <li>②匹配出现在列表中的某个中间位置,此时执行的操作是 --> prev.next = curr.next;,</li></ul>
- * <p>在列表中定位某个结点需要两个引用:一个对前一结点(prev左)的引用以及一个对当前结点(curr右)的引用.</p>
- * prev = curr;
- * curr = curr.next;
- */
- public void remove(T item) {
- Node<T> curr = head, prev = null;
- boolean found = false;
- while (curr != null && !found) {
- if (item.equals(curr.nodeValue)) {
- if(prev == null) removeFirst();
- else prev.next = curr.next;
- found = true;
- } else {
- prev = curr;
- curr = curr.next;
- }
- }
- }
- /**
- * 返回此列表中首次出现的指定元素的索引,如果列表中不包含此元素,则返回 -1.
- */
- public int indexOf(T item) {
- int index = 0;
- Node<T> p;
- for(p = head; p != null; p = p.next) {
- if(item.equals(p.nodeValue))
- return index;
- index++;
- }
- return -1;
- }
- /**
- * 如果此列表包含指定元素,则返回 true。
- */
- public boolean contains(T item) {
- return indexOf(item) != -1;
- }
- public static void main(String[] args) {
- SingleLinkedList<String> t = new SingleLinkedList<String>();
- t.addToHead("A");
- //t.addFirst("addFirst");
- t.addToTail("B");
- t.addToTail("C");
- System.out.println(t.indexOf("C")); // 2
- System.out.println(t.contains("A")); // true
- //t.addLast("addLast");
- //t.removeLast();
- //t.insert("B", "insert");
- //t.removeFirst();
- //t.remove("B"); // A C
- t.printList(); // A B C
- }
- }
按链表的组织形式分有ArrayList和LinkList两种。ArrayList内部其实是用数组的形式实现链表,比较适合链表大小确定或较少对链表进行增删操作的情况,同时对每个链表节点的访问时间都是constant;而LinkList内部以一个List实现链表,比较适合需要频繁对链表进行操作的情况,对链表节点的访问时间与链表长度有关O(N)。
另外,根据实现形式可以分为直接式(想不出什么合适的名字,姑且这样吧)和使用Iterator(迭代模式)两种方法。直接式的实现方法和C/C++中的写法差不多;而使用Iterator时,需要实现java.lan中的Iterable接口(或者也可以自己在链表内部定义自己的Iterator method)
关于直接实现链表好,还是使用迭代模式好这个问题,有两个帖子可以看看:
1. http://topic.csdn.net/t/20050722/17/4162226.html这篇帖子,虽然短小,但也说出了一些使用迭代模式设计链表的好处;
2.后来又看到了http://www.java63.com/design_pattern/iterator_pattern.html这篇帖子,觉得说的比较清楚了。
我这里再捡主要的说一下:
使用迭代模式的优点:
1,实现功能分离,简化容器接口。让容器只实现本身的基本功能,把迭代功能委让给外部类实现,符合类的设计原则。
2,隐藏容器的实现细节。
3,为容器或其子容器提供了一个统一接口,一方面方便调用;另一方面使得调用者不必关注迭代器的实现细节。
4,可以为容器或其子容器实现不同的迭代方法或多个迭代方法。
我觉得第4点说的很好,对于一堆数据而言,不同的人(或业务逻辑)使用它的方式也不尽相同,定义一个theIterator继承Iterator,不仅提供next,hasNext 以及remove这个最小的操作集合,同时也可以提供更多的其它方法。在theIterator的实现类中,具体实现不同的迭代方法,比如顺序、逆序或根据其它语义进行遍历等,再通过类厂的方式将一个theIterator实现的对象交给用户使用。
下面我给出两个例子:
首先是直接式实现链表的例子,这个例子只提供了几种链表操作的基本方法,仅用于示意:
public class MyList<AnyType> {
private class Node<AnyType>{
public Node<AnyType> pre;
public Node<AnyType> next;
public AnyType data;
public Node(AnyType d, Node<AnyType>p, Node<AnyType> n){}
public Node(){}
}
private int theSize;
private Node<AnyType> Header;
private Node<AnyType> Tail;
public MyList(){}
public void add(AnyType item){}
public boolean isEmpty(){}
public int size(){}
public AnyType get( int idx){}
public void print(){}
}
Node<AnyType>类定义了双向链表中节点的结构,它是一个私有类,而其属性和构造函数都是公有的,这样,其父类可以直接访问其属性,而外部类根本不知道Node类的存在。Data是节点中的数据与,pre指向前一个Node节点,next指向后一个Node节点。其构造函数的实现如下,不解释:
public Node(AnyType d, Node<AnyType>p, Node<AnyType> n){
this.data = d;
this.pre = p;
this.next = n;
}
public Node(){
this.data = null;
this.pre = null;
this.next = null;
}
下面我们看一下链表的构造函数实现:
public MyList(){
theSize = 0;
Header = new Node<AnyType>(null,null,null);
Tail = new Node<AnyType>(null,Header,null);
Header.next = Tail;
}
我们构造了一个带有头、尾节点的双向链表,头节点的Next指向尾节点,为节点的pre指向头节点。链表长度起始为0。
继续贴上链表类其它方法的实现,不解释了,应该比较清楚:
public void add(AnyType item){
Node<AnyType> aNode = new Node<AnyType>(item,null,null);
Tail.pre.next = aNode;
aNode.pre = Tail.pre;
aNode.next = Tail;
Tail.pre = aNode;
theSize++;
}
public boolean isEmpty(){
return ( theSize == 0);
}
public int size(){
return theSize;
}
public AnyType get( int idx){
if(idx > theSize-1 || idx < 0)
throw new IndexOutOfBoundsException();
Node<AnyType> current = new Node<AnyType>(null,Header,null);
for(int i = 0; i<idx; i++)
current = current.next;
return current.data;
}
public void print(){
Node<AnyType> current = Header.next;
while(current.next != null){
//如果AnyType是你自己定义 //的数据类型,那么请务必提供
//一个toString方法,要么就不
//要在链表里实现print方法。
System.out.println(current.data.toString());
current = current.next;
}
}
第二个例子是用迭代方法实现链表的例子,下面是类定义:
public class MyListItr<Type> implements Iterable<Type> {
private class Node<Type>{
public Type data;
public Node<Type> pre;
public Node<Type> next;
public Node(){}
public Node(Type d, Node<Type> p, Node<Type> n){}
}
private Node<Type> Header;
private Node<Type> Tail;
private int theSize;
public MyListItr(){}
public void add(Type item){}
public void print(){}
public int size(){}
@Override
public Iterator<Type> iterator() {}
private class MyListIterator implements Iterator<Type>{
@Override
public boolean hasNext() {}
@Override
public Type next() {}
@Override
public void remove() {}
}
}
这里主要说一下与前面例子不同的地方:MyListItr类实现了java.lan.Itrable接口,因此我们必须实现其public Iterator<Type> iterator() {}方法,它的返回值是一个迭代器对象,那么我就定义一个内部私有类——MyListIterator,这个类实现了iterator接口。在public Iterator<Type> iterator() 方法中,我就构造这么一个MyListIterator的对象并返回。
Iterator接口有三个方法是我们必须实现的,hasNext,next和remove,这是迭代操作的最小集合了。对于不同的迭代器执行方式,我们可以定义多个类似MyListIterator这样的实现类,只要在链表类中的iterator() 方法中把具体实现类的对象返回给用户就OK了。
罗嗦两句:我感觉如果我们定义链表类时,如果不继承Iterable接口,而是直接在类内部提供 public theIterator Iterator() 方法,theIterator是一个自定义接口,继承自Iterator接口,这样我们可以提供遵循theIterator接口的各种迭代方式的不同实现类,并通过一个类厂方法在链表的public theIterator Iterator() 方法中把具体迭代实现类的对象交给用户,以此就可以根据不同的需求,提供链表的多种迭代方式。我觉得这种方法是可行的,但并没有具体实验。
下面只贴出链表iterator()方法和迭代实现类的MyListIterator代码,其它方法就不重复贴了:
@Override
public Iterator<Type> iterator() {
return new MyListIterator();
}
private class MyListIterator implements Iterator<Type>{
Node<Type> current = Header.next;
@Override
public boolean hasNext() {
return (current != Tail);
}
@Override
public Type next() {
if(!hasNext())
throw new IndexOutOfBoundsException();
Type item = current.data;
current = current.next;
return item;
}
@Override
public void remove() {
if( !hasNext())
throw new NoSuchElementException();
current.pre.next = current.next;
current.next.pre = current.pre;
current = current.next;
theSize--;
}
}
测试代码:
public class MyListTest {
/**
* @param args
*/
public static void main(String[] args) {
MyList<Integer> intList = new MyList<Integer>();
for (int i = 0; i < 10; i++) {
intList.add(i);
}
intList.print();
MyListItr<ItemInfo> itrList = new MyListItr<ItemInfo>();
for (int i = 0; i < 10; i++) {
ItemInfo aItem = new ItemInfo("name_" + i, "score_" + i);
itrList.add(aItem);
}
itrList.print();
System.out.println("=======print using iterator type_1==========");
Iterator<ItemInfo> Itor = itrList.iterator();
for (ItemInfo str : itrList)
System.out.println(str);
System.out.println("=======print using iterator type_2==========");
while(Itor.hasNext())
System.out.println(Itor.next().toString());
System.out.println("-------------------after remove----------------");
while (Itor.hasNext())
Itor.remove();
while(Itor.hasNext())
System.out.println(Itor.next().toString());
}
}
其中 ItemInfo的定义如下:
public class ItemInfo {
String name;
String score;
public ItemInfo(String n, String s){
name = n; score = s;
}
public ItemInfo(){
name = null; score = null;
}
public String toString(){
String str = name +" "+ score;
return str;
}
}