数组是编程语言中最常见也是很有用的数据结构,允许随机存取,相比于链表,数组能节约空间。但是他有两个缺陷:
(1).数组的长度是固定的,改变数组的大小意味着需要创建一个新的数组,并从原数组中拷贝数据到新数组中;
(2).数组数据在内存中一次连续存储,在实现插入删除操作会移动其他数据,使得开销变大。
采用链式结构可以很好的克服这一局限,链式结构是存储数据和其他节点的链指针的节点集合。
1.单向链表
一个节点只包含节点数据和指向后继节点的指针,这样的链表成为单向链表。
一个节点的java定义:
public class IntNode{
public int info;
public IntNode next;
public IntNode(int i){
this(i,null);
}
public IntNode(int i,IntNode n){
info = i;
next = n;
}
}
一个节点只包含两个域:info域 和 next域,前者用来存储信息,后者用来把节点连接在一起形成链表。
有了节点的定义,要创建一个新的节点只需要给他 new 一下就好:
IntNode p = new IntNode(10); //创建了一个数据域为10的节点
p.next = new IntNode(9); //创建p的后继节点,数据域为9
p.next.next = new IntNode(8); //创建第三个节点;
现在出现一个问题,如果我想创建10000个节点,那岂不是得写9999个next,这也太复杂了吧
一个简单的解决方案:在链表中同时保存两个指针: 一个指向链表的第一个节点;另一个指向链表的最后一个节点。
单向链表封装如下:
//*****************************IntSLList.java****************************** // singly linked list class to store integers public class IntSLList { private IntNode head, tail; public IntSLList() { head = tail = null; } public boolean isEmpty() { return head == null; } //在链表头添加节点 public void addToHead(int e1) { head = new IntNode(e1,head); if(tail == null) { tail = head; } } //在链表尾添加节点 public void addToTail(int e1) { if(!isEmpty()) { tail.next = new IntNode(e1); tail = tail.next; } else { head = tail = new IntNode(e1); } } //删除链表头节点并返回数据 public int deleteFromHead() { int e1 = head.info; if(head == tail) { head = tail = null; }else { head = head.next; } return e1; } //从链表尾删除节点并返回数据 public int deleteFromTail() { int e1 = tail.info; if(head == tail) { head = tail = null; }else { IntNode temp; for(temp = head;temp.next != tail;temp = temp.next) { tail = temp; tail.next = null; } } return e1; } //打印链表节点 public void printAll() { for(IntNode t = head; t!=null;t = t.next) { System.out.println(t.info + " "); } } //查找节点 public boolean isInList(int e1) { IntNode t; for(t = head; t != null && t.info != e1; t = t.next) { } return t != null; } //删除节点,该节点在链表任何位置 public void delete(int e1) { if(!isEmpty()) { if(head == tail && e1 == head.info) { head = tail = null; }else if(e1 == head.info){ head = head.next; }else { IntNode pred,tmp; for(pred = head, tmp = head.next; tmp != null && tmp.info != e1; pred = pred.next,tmp = tmp.next) { } if(tmp != null) { pred.next = tmp.next; if(tmp == tail) { tail = pred; } } } } } }
上面的封装给出了整型链表的一般操作,包括插入,删除,查找,判断非空等,示例存储的数据类型是整型的,推广的一般的链表可以直接把info域声明为Object。
单项链表的一个主要缺陷:由于节点只包含指向后继节点的指针,不能访问一个节点的前驱,使得在删除队尾数据时不得不遍历整个链表,对于一个包含很多数据的长链表来说,无疑会降低链表的操作速度。
2.双向链表
双向链表每个节点包含两个指针域,一个指向前驱节点,另一个指向后继节点。双向链表对于删除链表尾节点是十分方便的
下面给出简单的整型双向链表的封装:
//+++++++++++++++++++++++++++IntDLLNode.java++++++++++++++++++++++++++++++ public class IntDLLNode{ public int info; public IntDLLNode next, prev; public IntDLLNode(int e1) { this(e1,null,null); } public IntDLLNode(int e1,IntDLLNode n,IntDLLNode p) { info = e1; next = n; prev = p; } } //+++++++++++++++++++++++++++IntDLList.java++++++++++++++++++++++++++++++ public class IntDLList { private IntDLLNode head, tail; public IntDLList() { head = tail = null; } //判断非空与否? public boolean isEmpty() { return head != null; } //在表尾添加节点 public void addToTail(int e1) { if(!isEmpty()) { tail = new IntDLLNode(e1,null,tail); tail.prev.next = tail; }else { head = tail = new IntDLLNode(e1); } } //删除表尾的节点并返回该节点值 public int removeFromTail() { int e1 = tail.info; if(head == tail) { head = tail = null; //表中只有一个节点 }else { tail = tail.prev; tail.next = null; } return e1; } }
3.循环链表
将节点连接成环形成循环链表:表的节点必须是有限的,而且每个节点都必须有一个后继。
3.1循环单向链表
3.2循环双向链表