zoukankan      html  css  js  c++  java
  • 链表(LinkedList)

    链表可能是继数组之后第二种使用最广泛的通用存储结构。

    •     单链表

    •     双端链表

    •     有序链表

    •     双向列表

    •     有迭代器的列表

       链表与数组一样,都作为数据的基本存储结构,但是在存储原理上二者是不同的。在数组中,数据是存储在一段连续的内存空间中,我们可以通过下标来访问数组中的元素;而在链表中,元素是存储在不同的内存空间中,前一个元素的位置维护了后一个元素在内存中的地址,在Java中,就是前一个元素维护了后一个元素的引用。在本教程我们,我们将链表中的每个元素称之为一个节点(Node)。对比数组, 链表的数据结构可以用下图表示

    Image.png

    这张图显示了一个链表的数据结构,链表中的每个Node都维护2个信息:一个是这个Node自身存储的数据Data,另一个是下一个Node的引用,图中用Next表示。对于最后一个Node,因为没有下一个元素了,所以其并没有引用其他元素,在图中用紫色框来表示。

    这张图主要显示的是链表中Node的内部结构和Node之间的关系。一般情况下,我们在链表中还要维护第一个Node的引用,原因是在链表中访问数据必须通过前一个元素才能访问下一个元素,如果不知道第一个Node的话,后面的Node都不可以访问。事实上,对链表中元素的访问,都是从第一个Node中开始的,第一个Node是整个链表的入口;而在数组中,我们可以通过下标进行访问元素。

    Node.Java:

    1. public class Node {
    2.     //Node中维护的数据
    3.        private Object data;
    4.        //下一个元素的引用
    5.        private Node next;
    6.     
    7.     // setters and getters
    8. }

    一、单链表Java实现

    本节介绍单链表的Java实现,我们用SingleLinkList来表示。

    分析:

    1、SingleLinkList中要维护的信息:维护第一个节点(firstNode)的引用,作为整个链表的入口;

    2、插入操作分析:基于链表的特性,插入到链表的第一个位置是非常快的,因为只要改变fisrtNode的引用即可。因此对于单链表,我们会提供addFirst方法。

    3、查找操作分析:从链表的fisrtNode开始进行查找,如果确定Node中维护的data就是我们要查找的数据,即返回,如果不是,根据next获取下一个节点,重复这些步骤,直到找到最后一个元素,如果最后一个都没找到,返回null。

    4、删除操作分析

         首先查找到要删除的元素节点,同时将这个节点的上一个节点和下一个节点也要记录下来,只要将上一个节点的next引用直接指向下一个节点即可,这就相当于 删除了这个节点。如果要删除的是第一个节点,直接将LinkList的firstNode指向第二个节点即可。如果删除的是最后一个节点,只要将上一个节 点的next引用置为null即可。上述分析,可以删除任意节点,具有通用性但是效率较低。通常情况下,我们还会提供一个removeFirst方法,因为这个方法效率较高,同样只要改变fisrtNode的引用即可。  

    此外,根据情况而定,可以选择是否要维护链表中元素的数量size,不过这不是实现一个链表必须的核心特性。

    SingleLinkList.java

    1. public class SingleLinkList<T> {
    2.     //链表中第一个节点
    3.     protected Node firstNode=null;
    4.  
    5.     //链表中维护的节点总量
    6.     protected int size;
    7.  
    8.     /**
    9.      * 添加到链表的最前面
    10.      * @param element
    11.      */
    12.     public void addFirst(T element){
    13.         Node node=new Node();
    14.         node.setData(element);
    15.         Node currentFirst=firstNode;
    16.         node.setNext(currentFirst);
    17.         firstNode=node;
    18.         size++;
    19.     }
    20.  
    21.     /**
    22.      * 如果链表中包含要删除的元素,删除第一个匹配上的要删除的元素,并且返回true;
    23.      * 如果没有找到要删除的元素,返回false
    24.      * @param element
    25.      */
    26.     public boolean remove(T element){
    27.         if(size==0){
    28.             return false;
    29.         }
    30.         if(size==1){
    31.             firstNode=null;
    32.             size--;
    33.         }
    34.  
    35.         Node pre=firstNode;
    36.         Node current=firstNode.getNext();
    37.         while(current!=null){
    38.             /*如果当前节点中维护的值就是要删除的值,
    39.             直接将上一个节点pre的next应用指向当前节点current的下一个节点接口*/
    40.             if((current.getData()==null&&element==null)
    41.                     ||(current.getData().equals(element))){
    42.                 pre.setNext(current.getNext());
    43.                 size--;
    44.                 return true;
    45.             }
    46.  
    47.             //如果当前元素不是要删除的元素,继续循环
    48.             pre=current;
    49.             current=current.getNext();
    50.         }
    51.         return false;
    52.     }
    53.  
    54.     /**
    55.      * 如果包含返回true,如果不包含,返回false
    56.      * @param element
    57.      * @return
    58.      */
    59.     public boolean contains(Object element){
    60.         if(size==0){
    61.             return false;
    62.         }
    63.         Node current=firstNode;
    64.         while(current!=null){
    65.             if((current.getData()==null&&element==null)
    66.                     ||(current.getData().equals(element))){
    67.                 return true;
    68.             }
    69.  
    70.             //如果当前元素不是要删除的元素,继续循环
    71.             current=current.getNext();
    72.         }
    73.         return false;
    74.     }
    75.  
    76.     public boolean isEmpty(){
    77.         return size==0;
    78.     }
    79.  
    80.     public int size(){
    81.         return size;
    82.     }
    83.  
    84.     /**
    85.      * 打印出所有的元素
    86.      */
    87.     public void display(){
    88.         if(!isEmpty()){
    89.             Node current=firstNode;
    90.             while(current!=null){
    91.                 System.out.print(current.getData()+" ");
    92.                 current=current.getNext();
    93.             }
    94.         }
    95.     }
    96.     /**
    97.      * 删除第一个元素
    98.      */
    99.     public T removeFisrt() {
    100.         Node result=null;
    101.         if(size!=0) {
    102.             result = firstNode.getNext();
    103.             firstNode= result;
    104.             return (T) result.getData();
    105.         }
    106.        return null;
    107.     }
    108.  
    109.     public T getFirst() {
    110.         return (T) firstNode.getData();
    111.     }
    112. }

    测试添加addFirst

    1. @Test
    2. public void testAddFisrt() {
    3.     SingleLinkList<Integer> linkList=new SingleLinkList<Integer>();
    4.     for (int i = 0; i < 10; i++) {
    5.         linkList.addFirst(i);
    6.     }
    7.     linkList.display();
    8. }

    控制台输出:

    9    8    7    6    5    4    3    2    1    0

    因为总是添加到最前面,因此时降序的。

    需要注意的是:在本案例中,不能同时调用addFirst,addLast。因为我们在addFirst方法中并没有维护lastNode的信息,因此同时使用这两种方法可能会出错,有待继续完善。

    测试删除任意元素:

    1. @Test
    2. public void testRemove() {
    3.     SingleLinkList<Integer> linkList=new SingleLinkList<Integer>();
    4.     for (int i = 0; i < 10; i++) {
    5.         linkList.addFirst(i);
    6.     }
    7.     if(!linkList.isEmpty()){
    8.         linkList.remove(5);
    9.     }
    10.     linkList.display();
    11. }

    控制要输出:

    0    1    2    3    4    6    7    8    9

    可以看到5的确没有了

    测试删除第一个元素:

    1. @Test
    2. public void testRemoveFisrt() {
    3.     SingleLinkList<Integer> linkList=new SingleLinkList<Integer>();
    4.     for (int i = 0; i < 10; i++) {
    5.         linkList.addFirst(i);
    6.     }
    7.     linkList.removeFisrt();
    8.     linkList.display();
    9. }

    控制台输出:

    1    2    3    4    5    6    7    8    9


    测试包含:

    1. @Test
    2. public void testContains() {
    3.     SingleLinkList<Integer> linkList=new SingleLinkList<Integer>();
    4.     for (int i = 0; i < 10; i++) {
    5.         linkList.addFirst(i);
    6.     }
    7.     System.out.println(linkList.contains(5));
    8.     System.out.println(linkList.contains(10));
    9. }

    控制台输出:

    true
    false

    结果显示,包含5,不包含10.

    二、双端链表Java实现

    本节介绍双端链表的Java实现,我们用DoubleLinkJava来表示。

    双端链表与传统的链表非常类似,但是它有一个新增的特性:即对链表中最后一个节点的引用lastNode。我们可以像在单链表中在表头插入一个元素一样,在链表的尾端插入元素。如果不维护对最后一个节点的引用,我们必须要迭代整个链表才能得到最后一个节点,然后再插入,效率很低。因此我们在双链表中添加一个addLast方法,用于添加节点到末尾。

    addLast方法分析:直接将链表中维护的lastNode的next引用指向新的节点,再将lastNode的引用指向新的节点即可。

    因为单链表中,大部分的代码在双端链表中都可以重用,所以此处我们编写的DoubleLinkList只要继承SingleLinkList,添加必要的属性和方法支持从尾部操作即可。

    DoubleLinkList.java

    1. package com.tianshouzhi.algrithm.list;
    2.  
    3. public class DoubleLinkList<T> extends SingleLinkList<T>{
    4.     //链表中的最后一个节点
    5.     protected Node lastNode=null;
    6.      /**
    7.      * 添加到链表的最后
    8.      * @param element
    9.      */
    10.     public void addLast(T element){
    11.         Node node=new Node();
    12.         node.setData(element);
    13.         
    14.         if(size==0){//说明没有任何元素,说明第一个元素
    15.             firstNode=node;
    16.         }else{//如果有元素,将最后一个节点的next指向新的节点即可
    17.             /*这里有一个要注意的地方:
    18.                 当size=1的时候,firstNode和lastNode指向同一个引用
    19.                 因此lastNode.setNext时,fisrtNode的next引用也会改变;
    20.                 当size!=1的时候,lastNode的next的改变与firstNode无关*/
    21.             lastNode.setNext(node);
    22.         }
    23.         
    24.         //将lastNode引用指向新node
    25.         lastNode=node;
    26.         size++;
    27.         
    28.     }
    29.     
    30.     /**
    31.      * 当链表中没有元素时,清空lastNode引用
    32.      */
    33.     @Override
    34.     public boolean remove(T element) {
    35.         boolean result=super.remove(element);
    36.         if(size==0){
    37.             lastNode=null;
    38.         }
    39.         return result;
    40.     }
    41.  
    42.     /**
    43.      * 因为在SingleLinkList中并没有维护lastNode的信息,我们要自己维护
    44.      */
    45.     @Override
    46.     public Node addFirst(T element) {
    47.         Node node=super.addFirst(element);
    48.         if(size==1){//如果链表为size为1,将lastNode指向当前节点
    49.             lastNode=node;
    50.         }
    51.         return node;
    52.     }
    53.     
    54.     
    55. }

    测试addLast

    1. @Test
    2.     public void testAddFisrt() {
    3.         DoubleLinkList<Integer> linkList=new DoubleLinkList<Integer>();
    4.         for (int i = 0; i < 5; i++) {
    5.             linkList.addFirst(i);
    6.         }
    7.         for (int i = 0; i < 5; i++) {
    8.             linkList.addLast(i);
    9.         }
    10.         linkList.display();
    11.     }

    控制台输出:

    4    3    2    1    0    0    1    2    3    4

    从输出中我们可以到,前五个元素因为是addFirst添加的,所以是降序的,而后面五个元素是addLast添加的,所以是升序的。

    三、有序链表Java实现

    本节讲解有序链表,使用SortedLinkList表示。

    所谓有序链表,就是链表中Node节点之间的引用关系是根据Node中维护的数据data的某个字段为key值进行排序的。

    为了在一个有序链表中插入,算法必须首先搜索链表,直到找到合适的位置:它恰好在第一个比它大的数据项前面。

        当算法找到了要插入的数据项的位置,用通常的方式插入数据项:把新的节点Node指向下一个节点,然后把前一个节点Node的next字段改为指向新的节点。然而,需要考虑一些特殊情况,连接点有可能插入在表头或者表尾。

    在本例中,我们创建一个类Person表示插入的数据,我们希望链表中数据是按照Person的enName属性升序排列的。  

    三、Java中的双端链表实现LinkedList

    java中已经提供了双端链表的实现,java.util.LinkedList,以下是这个类部分方法摘要,相信不需要介绍,根据方法名字,你就可以知道其含义:

      1. public class LinkedList<E>
      2.     extends AbstractSequentialList<E>
      3.     implements List<E>, Deque<E>, Cloneable, java.io.Serializable
      4. {
      5. public E getFirst() ;
      6. public E getLast() ;
      7. public E removeFirst();
      8. public E removeLast();
      9. public void addFirst(E e);
      10. public void addLast(E e);
      11. public boolean contains(Object o);
      12. public int size();
      13. public boolean add(E e);//等价于addLast
      14. public boolean remove(Object o);
      15. public void clear() ;
      16. ....
      17. }
  • 相关阅读:
    过滤器实例——字符编码Filter
    pcap文件格式分析
    jsp常见获取地址函数之间的不同
    将抓到的pcap文件中Http包转换为可读的txt格式
    DBA入门之Oracle数据库参数文件
    查询session status各项统计数据的前三名
    查询正在做的排序操作
    DBUtils的handler
    DBA入门之认识检查点(Checkpoint)
    show_space_by_tom
  • 原文地址:https://www.cnblogs.com/muzhongjiang/p/13672509.html
Copyright © 2011-2022 走看看