介绍
数据存储在节点中,与数组相比,是真正的动态,不需要考虑固定容量的问题;但是也因此使其不能随机访问。
链表类
创建一个链表类LinkList,在该类中创建一个私有类Node,该类中有两个公有属性e和指向下一个节点的next。
public class LinkedList<E> {
private Node head:
private int size;
private class Node{
public E e;
public Node next;
public Node(Node e,Node next){
this.e=e;
this.next=next;
}
public Node(E e){
this(e,null);
}
public Node(){
this(null,null);
}
@Override
public String toString(){
return e.toString();
}
}
}
添加新节点
添加第一个元素时,已经存在这样的一个空链表,要做的就是生成一个新节点,然后将这个节点的next指向head节点,再让新节点作为head节点。head=new Node(e,head);可以分拆为Node node=new Node(e); node.next=head; head=node;
。
可以这么理解,head节点在类初始化时就已经存在了,new Node(e,head) 操作是让新节点和已经存在的head节点建立关联;关联后在给head属性赋予新的指向,即新node。
public void addFirst(E e){
head=new Node(e,head);
size++;
}
向index位置添加节点
需要注意的是preNode节点位置的变化过程。当 i = 0
的时候preNode是i + 1
位置的元素;因此若让preNode是index -1
位置的元素,需要循环index-1-1
次。
public void add(int index,E e){
if(index<0||index>size){
throw new IllegalArgumentException("Add failed, illegal index .");
}
if (index == 0) {
addFirst(e);
}
Node preNode=head;
for(int i=0;i<index-1;i++){
preNode=preNode.next;
}
preNode.next=new Node(e,preNode.next);
size++;
}
链表的虚拟头结点
在一般性的添加节点时,我们需要找到添加位置的前一个位置的元素。但是在添加头结点时,对其做了特殊处理,因为头结点没有前一个位置。为了让添加头结点和添加其他位置的节点过程一般化,引入了虚拟头结点。也就是head指向的是一个e为null的节点。
需要在原基础上更改链表类的构造方法,如下。
public LinkedList(){
dummyHead=new Node(null,null);
size=0;
}
即头结点的e和next都是null;
然后修改add方法。
public void add(int index,E e){
if(index<0||index>size){
throw new IllegalArgumentException("Add failed, illegal index .");
}
/*if (index == 0) {
addFirst(e);
}*/
Node preNode=dummyHead;
for(int i=0;i<index;i++){
preNode=preNode.next;
}
preNode.next=new Node(e,preNode.next);
size++;
}
不再对在0位置添加节点做特殊化处理,同时因为head是0位置之前的一个节点,所以循环条件也做了变化。
这样当添加第一个结点时,只要让index=0
就可以了。
链表的增删改查
主要看一下新增、删除和修改。
增
代码同上。
删
public E delete(int index){
if(index<0||index>size){
throw new IllegalArgumentException("Add failed, illegal index .");
}
Node preNode=dummyHead;
for(int i=0;i<index;i++){
preNode=preNode.next;
}
Node retNode=preNode.next;
preNode.next=retNode.next;
retNode.next=null;
return retNode.e;
}
改
public void set(int index,E e){
if(index<0||index>size){
throw new IllegalArgumentException("Add failed, illegal index .");
}
Node cur=dummyHead;
for(int i=0;i<=index;i++){
cur=cur.next;
}
cur.e=e;
}
时间复杂度分析
可以查看链表的整体时间复杂度一般要比数组高,所以它更适宜只对链表头做操作。
使用链表实现栈
这比较简单,只要把栈的底层实现,即数组改为链表就可以了。从理论上来看,链表和数组实现栈的时间复杂度是一样的;如果进行大量的增删操作,可能链表实现的栈相对快一些,这是因为每新增一个节点,链表栈都要去寻找一个新的空间供新节点使用;但是实际上链表栈和数组栈的效率差异并不会像普通队列和循环队列那样十分的大。