Java实现链表结构

  1. package LinkedList;     
  2.     
  3. /**   
  4.  * <p><strong>我的Java单链表练习</strong></p>   
  5.  * <p>单链表提供了在列表头的高效插入和删除操作,不过在单链表的末尾的插入操作效率很低.</p>   
  6.  * <p>单链表指针域保存着下一节点的引用,尾结点的指针域等于null</p>   
  7.  * @author baby69yy2000   
  8.  */    
  9. public class SingleLinkedList<T> {     
  10.          
  11.     /**   
  12.      * 结点类   
  13.      */    
  14.     private static class Node<T> {     
  15.         T nodeValue; // 数据域     
  16.         Node<T> next; // 指针域保存着下一节点的引用     
  17.              
  18.         Node(T nodeValue, Node<T> next) {     
  19.             this.nodeValue = nodeValue;     
  20.             this.next = next;     
  21.         }     
  22.              
  23.         Node(T nodeValue) {     
  24.             this(nodeValue, null);     
  25.         }     
  26.     }     
  27.     
  28.     // 下面是SingleLinkedList类的数据成员和方法     
  29.     private Node<T> head, tail;     
  30.          
  31.     public SingleLinkedList() {     
  32.         head = tail = null;     
  33.     }     
  34.          
  35.     /**   
  36.      * 判断链表是否为空   
  37.      */    
  38.     public boolean isEmpty() {     
  39.         return head == null;     
  40.     }     
  41.          
  42.     /**   
  43.      * 创建头指针,该方法只用一次!   
  44.      */    
  45.     public void addToHead(T item) {     
  46.         head = new Node<T>(item);     
  47.         if(tail == null) tail = head;     
  48.     }     
  49.          
  50.     /**   
  51.      * 添加尾指针,该方法使用多次   
  52.      */    
  53.     public void addToTail(T item) {     
  54.         if (!isEmpty()) { // 若链表非空那么将尾指针的next初使化为一个新的元素     
  55.             tail.next = new Node<T>(item); // 然后将尾指针指向现在它自己的下一个元素     
  56.             tail = tail.next;     
  57.         } else { // 如果为空则创建一个新的!并将头尾同时指向它     
  58.             head = tail = new Node<T>(item);           
  59.         }     
  60.     }     
  61.          
  62.     /**   
  63.      * 打印列表   
  64.      */    
  65.     public void printList() {     
  66.         if (isEmpty()) {     
  67.             System.out.println("null");     
  68.         } else {     
  69.             for(Node<T> p = head; p != null; p = p.next)     
  70.                 System.out.println(p.nodeValue);     
  71.         }     
  72.     }     
  73.          
  74.     /**   
  75.      * 在表头插入结点,效率非常高   
  76.      */    
  77.     public void addFirst(T item) {     
  78.         Node<T> newNode = new Node<T>(item);     
  79.         newNode.next = head;     
  80.         head = newNode;     
  81.     }     
  82.          
  83.     /**   
  84.      * 在表尾插入结点,效率很低   
  85.      */    
  86.     public void addLast(T item) {     
  87.         Node<T> newNode = new Node<T>(item);     
  88.         Node<T> p = head;     
  89.         while (p.next != null) p = p.next;     
  90.         p.next = newNode;     
  91.         newNode.next = null;     
  92.     }     
  93.          
  94.     /**   
  95.      * 在表头删除结点,效率非常高   
  96.      */    
  97.     public void removeFirst() {     
  98.         if (!isEmpty()) head = head.next;     
  99.         else System.out.println("The list have been emptied!");     
  100.     }     
  101.          
  102.     /**   
  103.      * 在表尾删除结点,效率很低   
  104.      */    
  105.     public void removeLast() {     
  106.         Node<T> prev = null, curr = head;     
  107.         while(curr.next != null) {     
  108.             prev = curr;     
  109.             curr = curr.next;     
  110.             if(curr.next == null) prev.next = null;     
  111.         }     
  112.     }     
  113.          
  114.     /**   
  115.      * <p>插入一个新结点</p>   
  116.      * <ul>插入操作可能有四种情况:   
  117.      * <li>①表为空, 返回false</li>   
  118.      * <li>②表非空,指定的数据不存在</li>   
  119.      * <li>③指定的数据是表的第一个元素</li>   
  120.      * <li>④指定的数据在表的中间</li></ul>   
  121.      * @param appointedItem 指定的nodeValue   
  122.      * @param item 要插入的结点   
  123.      * @return 成功插入返回true;   
  124.      */    
  125.     public boolean insert(T appointedItem, T item) {     
  126.         Node<T>  prev = head, curr = head.next, newNode;     
  127.         newNode = new Node<T>(item);     
  128.         if(!isEmpty()) {     
  129.             while((curr != null) && (!appointedItem.equals(curr.nodeValue))) { //两个判断条件不能换     
  130.                 prev = curr;     
  131.                 curr = curr.next;     
  132.             }     
  133.             newNode.next = curr; //②③④     
  134.             prev.next = newNode;     
  135.             return true;      
  136.         }     
  137.         return false//①     
  138.     }     
  139.          
  140.     /**   
  141.      * <p>移除此列表中首次出现的指定元素</p>   
  142.      * <ul>删除操作可能出现的情况:   
  143.      * <li>①prev为空,这意味着curr为head. head = curr.next; --> removeFirst();</li>   
  144.      * <li>②匹配出现在列表中的某个中间位置,此时执行的操作是 --> prev.next = curr.next;,</li></ul>   
  145.      * <p>在列表中定位某个结点需要两个引用:一个对前一结点(prev左)的引用以及一个对当前结点(curr右)的引用.</p>   
  146.      * prev = curr;   
  147.      * curr = curr.next;   
  148.      */    
  149.     public void remove(T item) {     
  150.         Node<T> curr = head, prev = null;     
  151.         boolean found = false;     
  152.         while (curr != null && !found) {     
  153.             if (item.equals(curr.nodeValue)) {     
  154.                 if(prev == null) removeFirst();     
  155.                 else prev.next = curr.next;     
  156.                 found = true;     
  157.             } else {     
  158.                 prev = curr;     
  159.                 curr = curr.next;     
  160.             }     
  161.         }     
  162.     }     
  163.          
  164.     /**   
  165.      * 返回此列表中首次出现的指定元素的索引,如果列表中不包含此元素,则返回 -1.   
  166.      */    
  167.     public int indexOf(T item) {     
  168.         int index = 0;     
  169.         Node<T> p;     
  170.         for(p = head; p != null; p = p.next) {     
  171.             if(item.equals(p.nodeValue))     
  172.                 return index;     
  173.             index++;     
  174.                      
  175.         }     
  176.         return -1;     
  177.     }     
  178.          
  179.     /**   
  180.      * 如果此列表包含指定元素,则返回 true。   
  181.      */    
  182.      public boolean contains(T item) {     
  183.          return indexOf(item) != -1;     
  184.      }     
  185.          
  186.     public static void main(String[] args) {     
  187.         SingleLinkedList<String> t = new SingleLinkedList<String>();     
  188.         t.addToHead("A");     
  189.         //t.addFirst("addFirst");     
  190.         t.addToTail("B");     
  191.         t.addToTail("C");     
  192.         System.out.println(t.indexOf("C")); // 2     
  193.         System.out.println(t.contains("A")); // true     
  194.         //t.addLast("addLast");     
  195.         //t.removeLast();     
  196.         //t.insert("B", "insert");     
  197.         //t.removeFirst();     
  198.         //t.remove("B"); // A C     
  199.         t.printList(); // A B C     
  200.              
  201.     }     
  202.     
  203. }

 按链表的组织形式分有ArrayListLinkList两种。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,不仅提供nexthasNext 以及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节点。其构造函数的实现如下,不解释:

View Code
复制代码
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;

}
复制代码

 

下面我们看一下链表的构造函数实现:

View Code
复制代码
public MyList(){

theSize = 0;

Header = new Node<AnyType>(null,null,null);

Tail = new Node<AnyType>(null,Header,null);

Header.next = Tail;

}
复制代码

  我们构造了一个带有头、尾节点的双向链表,头节点的Next指向尾节点,为节点的pre指向头节点。链表长度起始为0

继续贴上链表类其它方法的实现,不解释了,应该比较清楚:

View Code
复制代码
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;

}

}
复制代码

 

第二个例子是用迭代方法实现链表的例子,下面是类定义:

View Code
复制代码
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接口有三个方法是我们必须实现的,hasNextnextremove,这是迭代操作的最小集合了。对于不同的迭代器执行方式,我们可以定义多个类似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--;
}

}
复制代码

测试代码:

View Code
复制代码
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的定义如下:

View Code
复制代码
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;

}

}
复制代码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值