单向链表的原理和基本实现(Java实现)

    技术2026-03-02  10

    单向链表的原理和基本实现(Java实现)

    源码链接:文章内源码

    基本原理

    链表是一种数据存储结构。在Java原生JDK中对于链表也进行了相应实现。 单向链表是链表的其中一种,其主要原理是:

    链表是以节点(Node)的形式存储数据,节点对象中存储了要保存的数据。单向链表中的每一个节点中都持有下一个节点的引用(Node next),通过上一个节点就可以找到下一个节点,依次串联,所以想要遍历整个单向链表就需要找到第一个节点。链表不同于数组,在内存中不一定是连续的空间,由于是节点存储,可以利用内存中零散的空间进行保存,只需持有下一个节点的地址即可。很多人都知道链表相较于List他增删快,查询慢,主要因为,链表的修改只需要将前一个节点和后一个节点中所持的前后节点的引用进行修改,而list在进行删除或者增加操作时,往往需要将集合中的元素进行移动,所以较为耗费时间较长,但是,实际上还是需要根据实际情况来判断,如果不考虑随机插入的情况,一直是在最后进行插入,list是非常快的,因为不需要进行元素的移动,但如果是随机插入或者是在靠前的位置进行插入,链表则有很大的优势,所以到底谁快谁慢还是要结合具体问题.

    基本实现

    在JDK中已经封装好了链表的集合容器,我们直接用就行了,但是手动的实现一些基本功能还是有助于更好的了解底层的原理加深对于这种数据结构的认识。

    1. 节点类(Node)的定义

    实现链表首先需要定义节点,根据上面的分析,单向链表中的节点应该要持有下个节点的引用和自身所要存储的数据值。在这里的数据层为了更好的通用,应该是要封装为Object对象,但为了方便这里没有进行封装。 /*** * 节点 */ class Node{ //************数据层************ //可封装为Object public int no; public String name ; public String nick; //****************************** //下一个节点的引用 public Node next; public Node(int no , String name , String nick){ this.no = no ; this.name = name ; this.nick = nick ; } @Override public String toString() { return "Node{" + "no=" + no + ", name='" + name + '\'' + ", nick='" + nick + '\'' + '}'; } }

    2.定义LinkedList链表类

    首先我们需要声明一个头结点Head,这个head不存放具体的数据,只是记录第一个节点的地址。

    public class LinkedList { //声明一个头结点.这个节点不放具体的数据,只记录第一个节点的地址 //根据上面的Node的构造方法初始化,next指针先不赋值. private Node head = new Node(0 , "" , ""); }

    2.1添加方法的实现

    思路分析首先我们需要声明一个第三变量temp来作为指针,开始时默认指向头结点,添加新节点时循环遍历链表,每循环一次,temp就后移一位,找到链表的最后一个节点,找到最后一个节点之后只需将原来的最后一个节点的next指向新加节点即可,判断是否是最后一个节点的条件应该是temp的next值为null,这样新加的节点就变成了最后一个节点,这里需要保证新加节点的next值为null. public class LinkedList { //声明一个头结点.这个节点不放具体的数据,只记录第一个节点的地址 private Node head = new Node(0 , "" , ""); /** * 添加方法,这里的添加需要保证插入的Node是新的next是null,如果next的值不为空之前使用过指向另一个 * Node地址的话会出现添加一个却把之前链表都连起来的情况,如果恰巧形成了闭环,首尾相连会出现死循环的情况 * @param newNode * @return */ public boolean add(Node newNode){ //声明一个第三变量用作指针,开始默认指向头结点 Node temp = head; //循环遍历链表,将新节点加入到next为null的节点后,就是直接加入到最后一个节点的后面 while(true){ //如果当前节点的下一个节点是null ,说明该节点是最后一个节点 if(temp.next == null){ //直接将新节点添加到最后一个节点之后,next指向新节点 temp.next = newNode; return true; } //将指针后移一位,指针指向下一个节点 temp = temp.next ; } } }

    2.2判断链表是否为空

    这个只需要判断头结点的next是否为null即可 /** * 判断链表是否为空的方法 * @return */ public boolean isEmpty(){ //如果头结点的下一个是空,就说明链表是空的 return head.next == null ; }

    2.3修改链表数据的方法

    思路首先要根据条件找到要修该的节点,这里用no值查询作为条件仍然是声明第三变量temp作为指针,循环链表,如果no值与链表中的相同就说明找到了要修改的节点.在这里修改有两种思路,第一种是temp指向当前节点,修改当前节点的数据,第二种是temp指向当前节点的上一个节点,也就是说temp.next才是要修改的节点,那么可以将temp.next指向新的节点,新的节点next指向temp.next.next,即原来要修改节点的next,就达到了替换的作用,这里采用第二种方式 public boolean update(Node newNode) { //首先需要找到要修改的节点 Node temp = head; //如果为空直接返回 if(isEmpty()){ System.out.println("链表为空"); return false; } //遍历链表 /* 如果当前节点的下一个节点的no值和要修改的节点的no值相同,说明当前节点的下一个节点就是要修改的 节点,此时的temp指针指向的是要修改节点的前一个节点,只需要将前一个节点的next指向新的节点,然后 将新的节点的next指向原来要修改的节点的next,就是将要修改的节点的next值赋给新的节点 */ while(temp.next != null){ //如果找到了相同的no值说明找到了要修改的节点 if(temp.next.no == newNode.no){ /* 首先将要修改节点的next值赋给新节点 , 此时temp.next指向的是要修改的节点,所以要修改节点的 next的值是temp.next.next */ newNode.next = temp.next.next; //然后将当前节点的next指向新的节点 temp.next = newNode; return true; } //指针后移 temp = temp.next; } System.out.println("没有找到该节点"); return false; }

    2.4删除方法

    思路首先需要明确,单向链表节点的删除是必须靠上一个节点来完成,因为,单向链表是单向的,只能找当前的后面的节点,不能找到前面的节点,而链表的删除则是需要将当前要删除的节点的上一个节点的next的值,指向要删除节点的下一个,由于无法通过当前节点找到上一个节点,所以这里的指针应该指向要删除节点的上一个节点,使用temp.next = temp.next.next的方式. /** * 删除方法,根据no值来删除 * 该方法与之前修改方法类似,只需将要删除节点的的上一个next直接指向要删除节点的next * @return */ public boolean del(int no){ //第三变量默认指向头结点 Node temp = head; if(isEmpty()){ System.out.println("链表为空"); return false; } while(temp.next != null){ //如果找到了要删除的节点 if(temp.next.no == no){ //就将当前节点的next指向要删除的的next //注意此时的temp指向的是要删除节点的前一个 temp.next = temp.next.next; return true ; } //指针后移 temp = temp.next; } System.out.println("没找到要删除的节点"); return false; }

    2.5遍历单向链表的方法

    思路循环,利用temp指针,每循环一次temp后移一位,当temp.next为null说明链表结束. /** * 遍历方法 */ public void showList(){ Node temp = head; if (isEmpty()){ System.out.println("链表为空"); } while(temp.next != null){ System.out.println(temp.next); temp = temp.next; } }

    2.6统计当前节点的个数,不包含头结点

    思路定义一个计数器,每循环一次,计数器++,仍然是当temp.next为空说明循环结束 /** * 统计头结点的个数,不包含头结点 */ public int size(){ int size = 0 ; Node temp = head.next; while(temp != null){ size++; temp = temp.next; } return size; }

    综上:已经基本实现了单向链表的增删改查的操作

    综合代码

    public class LinkedList { //声明一个头结点.这个节点不放具体的数据,只记录第一个节点的地址 private Node head = new Node(0 , "" , ""); /** * 添加方法,这里的添加需要保证插入的Node是新的next是null,如果next的值不为空之前使用过指向另一个 * Node地址的话会出现添加一个却把之前链表都连起来的情况,如果恰巧形成了闭环,首尾相连会出现死循环的情况 * @param newNode * @return */ public boolean add(Node newNode){ //声明一个第三变量用作指针,开始默认指向头结点 Node temp = head; //循环遍历链表,将新节点加入到next为null的节点后,就是直接加入到最后一个节点的后面 while(true){ //如果当前节点的下一个节点是null ,说明该节点是最后一个节点 if(temp.next == null){ //直接将新节点添加到最后一个节点之后,next指向新节点 temp.next = newNode; return true; } //将指针后移一位,指针指向下一个节点 temp = temp.next ; } } /** * 判断链表是否为空的方法 * @return */ public boolean isEmpty(){ //如果头结点的下一个是空,就说明链表是空的 return head.next == null ; } /** * 修改链表中数据的方法 * 需要提供一个新的节点,根据新节点的id值找到原来的节点,将原数据进行修改,id值不变 * @return */ public boolean update(Node newNode) { //首先需要找到要修改的节点 Node temp = head; //如果为空直接返回 if(isEmpty()){ System.out.println("链表为空"); return false; } //遍历链表 /* 如果当前节点的下一个节点的no值和要修改的节点的no值相同,说明当前节点的下一个节点就是要修改的 节点,此时的temp指针指向的是要修改节点的前一个节点,只需要将前一个节点的next指向新的节点,然后 将新的节点的next指向原来要修改的节点的next,就是将要修改的节点的next值赋给新的节点 */ while(temp.next != null){ //如果找到了相同的no值说明找到了要修改的节点 if(temp.next.no == newNode.no){ /* 首先将要修改节点的next值赋给新节点 , 此时temp.next指向的是要修改的节点,所以要修改节点的 next的值是temp.next.next */ newNode.next = temp.next.next; //然后将当前节点的next指向新的节点 temp.next = newNode; return true; } //指针后移 temp = temp.next; } System.out.println("没有找到该节点"); return false; } /** * 遍历方法 */ public void showList(){ Node temp = head; if (isEmpty()){ System.out.println("链表为空"); } while(temp.next != null){ System.out.println(temp.next); temp = temp.next; } } /** * 删除方法,根据no值来删除 * 该方法与之前修改方法类似,只需将要删除节点的的上一个next直接指向要删除节点的next * @return */ public boolean del(int no){ //第三变量默认指向头结点 Node temp = head; if(isEmpty()){ System.out.println("链表为空"); return false; } while(temp.next != null){ //如果找到了要删除的节点 if(temp.next.no == no){ //就将当前节点的next指向要删除的的next //注意此时的temp指向的是要删除节点的前一个 temp.next = temp.next.next; return true ; } //指针后移 temp = temp.next; } System.out.println("没找到要删除的节点"); return false; } /** * 统计头结点的个数,不包含头结点 */ public int size(){ int size = 0 ; Node temp = head.next; while(temp != null){ size++; temp = temp.next; } return size; } } /*** * 节点 */ class Node{ //************数据层************ public int no; public String name ; public String nick; //****************************** public Node next; public Node(int no , String name , String nick){ this.no = no ; this.name = name ; this.nick = nick ; } @Override public String toString() { return "Node{" + "no=" + no + ", name='" + name + '\'' + ", nick='" + nick + '\'' + '}'; } }

    测试自定义单向链表

    public class Test { public static void main(String[] args) { LinkedList linkedList = new LinkedList(); Node n1 = new Node(1 , "宋江" , "及时雨"); Node n2 = new Node(2 , "卢俊义" , "玉麒麟"); Node n3 = new Node(3 , "吴用" , "智多星"); Node n4 = new Node(4 , "李逵" , "黑旋风"); Node n5 = new Node(5 , "林冲" , "豹子头"); linkedList.addByOrder(n1); linkedList.addByOrder(n3); linkedList.addByOrder(n2); linkedList.addByOrder(n5); linkedList.addByOrder(n4); System.out.println("修改之后~~~~~~~"); Node n6 = new Node(5 , "冲冲" , "豹子头~~~"); linkedList.update(n6); linkedList.showList(); System.out.println("移除之后~~~~~~~"); linkedList.del(5); linkedList.del(1); linkedList.del(2); linkedList.del(3); linkedList.del(4); linkedList.showList(); //之前的节点不能使用,需要新建节点,保证next值为null Node n11 = new Node(1 , "宋江" , "及时雨"); Node n7 = new Node(2 , "卢俊义" , "玉麒麟"); Node n8 = new Node(3 , "吴用" , "智多星"); Node n9 = new Node(4 , "李逵" , "黑旋风"); Node n10 = new Node(5 , "林冲" , "豹子头"); System.out.println("添加之后~~~~~~~"); linkedList.add(n11); linkedList.add(n7); linkedList.add(n8); linkedList.add(n9); linkedList.add(n10); linkedList.showList();

    结果

    运行结果正确,可以实现基本功能

    Processed: 0.011, SQL: 9