在底层结构上,单向链表经过指针将一组零散的内存块串联在一块儿。其中,咱们把内存块称为链表的“结点”。为了将全部的结点串起来,每一个链表的结点除了存储数据以外,还须要记录链上的下一个结点的地址。以下图所示,咱们把这个记录下个结点地址的指针叫做后继指针 next。java
从画的单链表图中,你应该能够发现,其中有两个结点是比较特殊的,它们分别是第一个结点和最后一个结点。咱们习惯性地把第一个结点叫做头结点,把最后一个结点叫做尾结点。其中,头结点用来记录链表的基地址。有了它,咱们就能够遍历获得整条链表。而尾结点特殊的地方是:指针不是指向下一个结点,而是指向一个空地址 NULL,表示这是链表上最后一个结点。node
1. 插入操做算法
2. 删除操做数据库
删除操做的时间复杂度和插入操做的时间复杂度相似。编程
3. 更新操做浏览器
4. 查询操做缓存
因为链表的底层数据是不连续的,因此不能经过随机访问进行数据寻址。只能经过遍历进行查找数据。编程语言
package com.csx.algorithm.link; public class SinglyLinkedList<E> { public static void main(String[] args) { SinglyLinkedList<Integer> list = new SinglyLinkedList<>(); //尾部插入,遍历链表输出 System.out.println("尾部插入[1-10]"); for (int i = 1; i <= 10; i++) { list.addLast(Integer.valueOf(i)); } list.printList(); //头部插入,遍历链表输出 System.out.println("头部插入[1-10]"); for (int i = 1; i <= 10; i++) { list.addFirst(Integer.valueOf(i)); } list.printList(); //在指定节点后面插入 System.out.println("在头节点后面插入[100]"); list.addAfter(100, list.head); list.printList(); System.out.println("在头节点前面插入[100]"); list.addBefore(100, list.head); list.printList(); System.out.println("在尾节点前面插入[100]"); list.addBefore(100, list.tail); list.printList(); System.out.println("在尾节点后面插入[100]"); list.addAfter(100, list.tail); list.printList(); System.out.println("------------删除方法测试-----------"); System.out.println("删除头节点"); list.removeFirst(); list.printList(); System.out.println("删除尾节点"); list.removeLast(); list.printList(); System.out.println("删除指定节点"); list.removeNode(list.head.next); list.printList(); } private Node head; private Node tail; public SinglyLinkedList() { } public SinglyLinkedList(E data) { Node node = new Node<>(data, null); head = node; tail = node; } public void printList() { Node p = head; while (p != null && p.next != null) { System.out.print(p.data + "-->"); p = p.next; } if (p != null) { System.out.println(p.data); } } public void addFirst(E data) { //容许节点值为空 //if(data==null){ // return; //} Node node = new Node(data, head); head = node; if (tail == null) { tail = node; } } public void addLast(E data) { Node node = new Node(data, null); if (tail == null) { head = node; tail = node; } else { tail.next = node; tail = node; } } /** * @param data * @param node node节点必须在链表中 */ public void addAfter(E data, Node node) { if (node == null) { return; } Node newNode = new Node(data, node.next); node.next = newNode; if(tail==node){ tail = newNode; } } /** * @param data * @param node node节点必须在链表中 */ public void addBefore(E data, Node node) { if (node == null) { return; } Node p = head; if (p == null) { throw new RuntimeException("node not in LinkedList..."); } if (p == node) { Node newNode = new Node(data, node); head = newNode; return; } while (p.next != null) { if (p.next == node) { break; } p = p.next; } if (p.next == null) { throw new RuntimeException("node not in LinkedList..."); } Node newNode = new Node(data, node); p.next = newNode; } public void removeFirst() { if (head == null) { return; } if (head == tail) { head = null; tail = null; } else { head = head.next; } } public void removeLast() { if (tail == null) { return; } if (head == tail) { head = null; tail = null; } else { Node p = head; while (p.next != tail) { p = p.next; } p.next = null; tail = p; } } public void removeNode(Node node) { if (node == null) { return; } Node p = head; if (p == null) { return; } while (p.next != null && p.next != node) { p = p.next; } if (p.next != null) { p.next = node.next; } } private static class Node<E> { E data; Node next; public Node(E data, Node next) { this.data = data; this.next = next; } } }
若是你使用高级编程语言,通常都会有现成的单向链表实现。好比你使用的是Java,其中的LinkedList就能够实现单向链表功能(虽然LinkedList底层是双向链表,可是双向链表能够实现单向链表的全部功能)。性能
有时候你可能只是想实现一个链表的结构,并不想暴露太多的操做API给用户。这时候使用LinkedList可能不太能知足你的需求,由于LinkedList除了链表相关的操做,还暴露了其余的一些接口,这样可能会给用户太多的操做权限。测试
其实这个问题也不是太大,咱们是要作下适当的封装就好了。
package com.csx.algorithm.link; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.Set; import java.util.function.Predicate; public class SinglyLinkedList2<E> { private LinkedList<E> list; public SinglyLinkedList2() { this.list = new LinkedList<>(); } public SinglyLinkedList2(E data){ Set<E> singleton = Collections.singleton(data); this.list = new LinkedList<>(singleton); } public SinglyLinkedList2(Collection<? extends E> c){ this.list = new LinkedList<>(c); } // ----------------------------------新增方法--------------------------------------- public void addFirst(E data){ list.addFirst(data); } public void addLast(E data){ list.addLast(data); } // 在链表末尾添加 public boolean add(E date){ return list.add(date); } public boolean addAll(Collection<? extends E> collection){ return list.addAll(collection); } public boolean addBefore(E data,E succ){ int i = list.indexOf(succ); if(i<0){ return false; } list.add(i,data); return true; } public boolean addAfter(E data,E succ){ int i = list.indexOf(succ); if(i<0){ return false; } if((i+1)==list.size()){ list.addLast(data); return true; }else { list.add(i+1,data); return true; } } // ---------------------------------- 删除方法--------------------------------------- // 删除方法,默认删除链表头部元素 public E remove(){ return list.remove(); } // 删除方法,删除链表第一个元素 public E removeFirst(){ return list.removeFirst(); } // 删除方法,删除链表最后一个元素 public E removeLast(){ return list.removeLast(); } // 删除链表中第一次出现的元素,成功删除返回true // 对象相等的标准是调用equals方法相等 public boolean remove(E data){ return list.remove(data); } // 逻辑和remove(E data)方法相同 public boolean removeFirstOccur(E data){ return list.removeFirstOccurrence(data); } // 由于LinkedList内部是双向链表,因此时间复杂度和removeFirstOccur相同 public boolean removeLastOccur(E data){ return list.removeLastOccurrence(data); } // 批量删除方法 public boolean removeAll(Collection<?> collection){ return list.removeAll(collection); } // 按照条件删除 public boolean re(Predicate<? super E> filter){ return list.removeIf(filter); } // ----------------------------- 查询方法---------------------------- // 查询链表头部元素 public E getFirst(){ return list.getFirst(); } // 查询链表尾部元素 public E getLast(){ return list.getLast(); } // 查询链表是否包含某个元素 // 支持null判断 // 相等的标准是data.equals(item) public boolean contains(E data){ return list.contains(data); } public boolean containsAll(Collection<?> var){ return list.containsAll(var); } }
由于双向链表、循环链表都能实现单链表的功能,因此这边举例的使用场景不单单是针对单链表的,使用其余链表也能够实现。
链表一个经典的链表应用场景就是 LRU 缓存淘汰算法。
缓存是一种提升数据读取性能的技术,在硬件设计、软件开发中都有着很是普遍的应用,好比常见的 CPU 缓存、数据库缓存、浏览器缓存等等。
缓存的大小有限,当缓存被用满时,哪些数据应该被清理出去,哪些数据应该被保留?这就须要缓存淘汰策略来决定。常见的策略有三种:先进先出策略 FIFO(First In,First Out)、最少使用策略 LFU(Least Frequently Used)、最近最少使用策略 LRU(Least Recently Used)。
使用单链表实现LRU算法的大体思路是:
维护一个有序单链表(链表长度有限),越靠近链表尾部的结点是越早以前访问的。当有一个新的数据被访问时,咱们从链表头开始顺序遍历链表。
若是此数据以前已经被缓存在链表中了,咱们遍历获得这个数据对应的结点,并将其从原来的位置删除,而后再插入到链表的头部。
若是此数据没有在缓存链表中,又能够分为两种状况:
实现上面算法的时间复杂度是O(n)。