(一)线性数据结构
前面提及的动态数组、栈、队列的底层都依托静态数组,靠resize()解决固定容量的问题;而链表是真正的动态数据结构,其优点如下:
(1)最简单的动态数据结构
(2)更深入理解引用(或者指针)
(3)更深入的理解递归
(4)辅助组成其他数据结构
(二)链表 Listed List
数据存储在“节点”(Node)中:
class Node {
E e;
Node next;
}
优点:真正的动态,不需要处理固定容量的问题
缺点:丧失了随机访问的能力
(三)数组和链表的对比
数组最好用于索引有语意的情况,其最大的优点就是支持快速查询;而链表不适合用于索引有语意的情况,其最大的优点就是动态。
(四)链表的实现
- 声明
public class LinkedList<E> { private class Node{ public E e; public Node next; public Node(E 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(); } } private Node dummyHead;//虚拟头结点; 没有设置虚拟头结点:private Node head; int size; public LinkedList(){ dummyHead = new Node(null,null);//head = null; size = 0; } //获取链表中的元素个数 public int getSize() { return size; } //返回链表是否为空 public boolean isEmpty(){ return size == 0; } @Override public String toString(){ StringBuilder res = new StringBuilder(); // Node cur = dummyHead.next; while (cur != null){ res.append(cur + "->"); cur = cur.next; } //循环意思同上 for (Node cur = dummyHead.next; cur != null; cur = cur.next) res.append(cur + "->"); res.append("Null"); return res.toString(); } }
- 在链表中添加新元素e(增)
- 在链表index(0-based)位置添加新元素e
//在链表index(0-based)位置添加新的元素e //在链表中不是一个常用的操作,练习用 public void add(int index, E e){ if (index < 0 || index > size)//可以取到size的原因:可以在链表的末尾添加元素 throw new IllegalArgumentException("add failed,Illegal index"); /**链表头之前没有节点,无虚拟头结点的表示方法 * if (index == 0)//链表头之前没有节点,无虚拟头结点 addFirst(e); else{ Node prev = head; for (int i = 0; i < index - 1; i++)//将prev移动到index的前一个节点 prev = prev.next; // Node node = new Node(e); // node.next = prev.next; // prev.next = node; prev.next = new Node(e,prev.next);//代替上面三行注释代码 size ++; } */ //链表头设置虚拟头结点的表示方法 Node prev = dummyHead; for (int i = 0; i < index; i++)//将prev移动到index的前一个节点 prev = prev.next; // Node node = new Node(e); // node.next = prev.next; // prev.next = node; prev.next = new Node(e,prev.next);//代替上面三行注释代码 size ++; }
- 在链表头添加新元素e
//在链表头添加新元素e public void addFirst(E e){ //未添加虚拟头结点的表示方法 // Node node = new Node(e); // node.next = head; // head = node; // head = new Node(e,head);//取代上面注释的三句话 // size ++; //添加虚拟头结点的表示方法 add(0,e); }
- 在链表的末尾添加新元素e
//在链表的末尾添加新的元素e public void addLast(E e){ add(size,e); }
- 在链表index(0-based)位置添加新元素e
- 获取链表中index位置的元素(查)
- 获得链表index(0-based)位置的元素
//获得链表index(0-based)位置的元素 //在链表中不是一个常用的操作,练习用 public E get(int index){ if (index < 0 || index >= size) throw new IllegalArgumentException("Get failed,Illegal index"); Node cur = dummyHead.next; for (int i = 0; i < index; i ++) cur = cur.next; return cur.e; }
- 获取链表的第一个位置的元素
//获取链表的第一个位置的元素 public E getFirst(){ return get(0); }
- 获取链表中的最后一个位置的元素
//获取链表的最后一个位置的元素 public E getLast(){ return get(size - 1); }
- 查找链表中是否有元素e
//查找链表中是否有元素e public boolean contains(E e){ Node cur = dummyHead.next; while (cur != null){ if (cur.e.equals(e)) return true; cur = cur.next; } return false; }
- 获得链表index(0-based)位置的元素
- 更新链表中的元素为e(改)
- 更新链表的第index(0-based)个位置的元素为e
//更新链表的第index(0-based)个位置的元素为e //在链表中不是一个常用的操作,练习用 public void set(int index, E e){ if (index < 0 || index >= size) throw new IllegalArgumentException("Get failed,Illegal index"); Node cur = dummyHead.next; for (int i = 0; i < index; i++) cur = cur.next; cur.e = e; }
- 更新链表的第index(0-based)个位置的元素为e
- 删除链表中的元素,并返回删除的元素(删)
- 删除链表中索引为index(0-based)的元素,并返回删除的元素
//删除链表中索引为index(0-based)的元素,返回删除的元素 public E remove(int index){ if (index < 0 || index > size) throw new IllegalArgumentException("remove failed,Illegal index"); Node prev = dummyHead; for (int i = 0; i < index; i++) prev = prev.next;//找到待删除节点之前的节点 Node retNode = prev.next; prev.next = retNode.next; retNode.next = null; size --; return retNode.e; }
- 删除链表中的第一个元素,并返回删除的元素
//删除链表中的第一个元素,并返回删除的元素 public E removeFirst(){ return remove(0); }
- 删除链表中的最后一个元素,并返回删除的元素
//删除链表中的最后一个元素,并返回删除的元素 public E removeLast(){ return remove(size - 1); }
- 删除链表中索引为index(0-based)的元素,并返回删除的元素
(五)链表的时间复杂度分析
- 添加操作 O(n)
- addList(e) O(n)
- addFirst(e) O(1)
- add(index,e) O(n/2) = O(n)
- 删除操作 O(n)
- removeLast(e) O(n)
- removeFirst(e) O(1)
- remove(index,e) O(n/2) = O(n)
- 修改操作 O(n)
- set(index,e) O(n)
- 查找操作 O(n)
- get(index) O(n)
- contains(e) O(n)
(六)使用链表实现栈
- 栈的接口:
public interface Stack<E> { //栈的接口
int getSize();
boolean isEmpty();
void push(E e);
E pop();
E peek();
}
- 声明:
public class LinkedListStack<E> implements Stack<E> { private LinkedList<E> list ; public LinkedListStack(){ list = new LinkedList<>(); } }
- 获取链表栈的大小
@Override public int getSize(){ return list.getSize(); }
- 判断链表栈是否为空
@Override public boolean isEmpty(){ return list.isEmpty(); }
- 往栈中添加元素e
/**链表的头相当于栈的栈顶*/ @Override public void push(E e){ list.addFirst(e); }
- 获取栈中的第一个元素
@Override public E peek() { return list.getFirst(); }
- 删除栈中的元素
@Override public E pop(){ return list.removeFirst(); }
(七)使用链表实现队列
- 声明
public class LinkedListQueue<E> implements Queue<E> { private class Node{ public E e; public Node next; public Node(E 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(); } } private Node head, tail; private int size; public LinkedListQueue(){ head = null; tail = null; size = 0; } @Override public String toString(){ StringBuilder res = new StringBuilder(); res.append("Queue: front "); Node cur = head; while (cur != null){ res.append(cur + "->"); cur = cur.next; } res.append("null tail"); return res.toString(); } }
- 获取队列的大小
@Override public int getSize(){ return size; }
- 判断队列是否为空
@Override public boolean isEmpty() { return size == 0; }
- 往队列中添加元素e
@Override public void enqueue(E e){ if(tail == null){//当队列为空的时候 tail = new Node(e); head = tail; }else{ tail.next = new Node(e); tail = tail.next; } size ++; }
- 删除队列中的元素,并返回删除的元素
@Override public E dequeue(){ if (isEmpty()) throw new IllegalArgumentException("cannot dequeue from an empty queue."); Node retNode = head;//出队的节点 head = head.next; retNode.next = null; if (head == null)//当只有一个节点的时候 tail = null; size --; return retNode.e; }
- 获取队列的第一个元素,并返回该元素
@Override public E getFront(){ if (isEmpty()) throw new IllegalArgumentException("cannot dequeue from an empty queue."); return head.e; }