单向链表是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始。链表是使用指针进行构造的列表,并且是由一个个结点组装起来的,因此又称为结点列表。

图中结构很清晰,一条链表是由多个节点通过指针next指向下一个节点串连而成的,每个节点中都包含了data域和next域,data的域作用是保存数据,next域则是作为指针指向下一个节点的地址,另外还有一个头指针节点head,它表示一个链表头,此节点data域无数据。当指针next=null时,链表终止下一个节点,最终返回的是next=null这个节点到链表头指针head节点长度的单向链表。

案例

  下面我想通过一个简单案例来描述一个单向链表的结构及使用方式,这个案例也很简单,输出此案例旨在更好的理解单向链表的数据结构。通过单向链表实现水浒英雄人物增删改查的操作,不做过多的文字赘述,直接上代码,代码中附有更详细的注释。

一、无序添加节点(数据)
  • 首先需要在单链表中定义节点Node,节点中包含data域(存放对象的相关属性数据)和next域(指向下一个Node的指针);
  • 通过Node创建一个head头,这个head中的data域不需要数据,另外需要定义一个临时辅助指针temp,令temp=head;
  • 对于被添加的(数据)节点node,当temp.next=null,表示终止当前单链表,否则继续指向下一个节点:temp=temp.next;
  • 当退出链表时,拿到最后一个数据:temp.next=node。

 public static void main(String[] args) {
        //创建链表
        AtomSingleLinkedList list = new AtomSingleLinkedList();
        //创建节点
        HeroNode h1 = new HeroNode(1, "宋江", "及时雨", "翻天覆地");
        HeroNode h2 = new HeroNode(2, "卢俊义", "玉麒麟", "翻江倒海");
        HeroNode h3 = new HeroNode(3, "吴用", "智多星", "风卷残云");
        HeroNode h4 = new HeroNode(4, "林冲", "豹子头", "千军万马");
        //打乱顺序的添加节点
        list.add(h3);
        list.add(h1);
        list.add(h4);
        list.add(h2);
        //显示数据
        list.queryList();
     
        /**
        * 打印结果:
        * HeroNode{number=3, name='吴用', nickName='智多星', skill='风卷残云'}
        * HeroNode{number=1, name='宋江', nickName='及时雨', skill='翻天覆地'}
        * HeroNode{number=4, name='林冲', nickName='豹子头', skill='千军万马'}
        * HeroNode{number=2, name='卢俊义', nickName='玉麒麟', skill='翻江倒海'}
        */
 }

 
 class AtomSingleLinkedList {
     //1、先创建一个头节点,头节点不存放数据,头节点不能乱动,否则后面无法查找链表
      private final HeroNode head = new HeroNode(-1, "", "", "");
     
      public void add(HeroNode heroNode) {
         HeroNode temp = head;
          while (temp.next != null) {
            //说明找到链表最后一个节点,退出死循环
            //如果未找到,temp指针的next向后移动
            temp = temp.next;
         }
        //退出循环时,此时的temp指向了链表的最后一个节点
        temp.next = heroNode;
      }
 }
 
 /**
 * 定义一个HeroNode,每一个HeroNode就是一个节点,里面
 * 定义了英雄的属性数据
 */
class HeroNode {
    public int number;
    public String name;
    public String nickName;
    public String skill;
    public HeroNode next;
    
    //其他代码就不去写了。。。
}
       

二、有序添加节点(数据)
  • 首先需要在单链表中定义Node节点类,节点中包含data域(存放对象的相关属性数据)和next域(指向下一个Node的指针)
  • 通过Node创建一个head头,这个head不许要传参,另外需要定义一个临时辅助指针temp,用来存放head的下一个指向的节点
  • 满足单向链表排序添加数据的条件有以下几点: a、定义一个temp临时指针存放head节点 Node temp=head; b、对于新增的数据(节点)node,当temp.next=0时,则表示链表终止,当前要被添加的数据 node会被移到temp的后面; c、当temp.next.num>node.num时,说明待加入的节点在单链表中找到位置,则有: node.next=temp.next; temp.next=node d、当temp.next.node=node.num时,说明这个位置已经存在了一个一样的的数据,无需添加 e、每次判断循环必须将指针指向下一个节点:temp=temp.next

 public static void main(String[] args) {
        //创建链表
        AtomSingleLinkedList list = new AtomSingleLinkedList();
        //创建节点
        HeroNode h1 = new HeroNode(1, "宋江", "及时雨", "翻天覆地");
        HeroNode h2 = new HeroNode(2, "卢俊义", "玉麒麟", "翻江倒海");
        HeroNode h3 = new HeroNode(3, "吴用", "智多星", "风卷残云");
        HeroNode h4 = new HeroNode(4, "林冲", "豹子头", "千军万马");
        //打乱顺序的添加
        list.addOrderByNum(h3);
        list.addOrderByNum(h1);
        list.addOrderByNum(h4);
        list.addOrderByNum(h2);
     
        //显示数据
        list.queryList();
        /**
        * 打印结果(发现即便是添加时是乱序的,但最终结果都是有序的排列):
        * HeroNode{number=1, name='宋江', nickName='及时雨', skill='翻天覆地'}
        * HeroNode{number=2, name='卢俊义', nickName='玉麒麟', skill='翻江倒海'}
        * HeroNode{number=3, name='吴用', nickName='智多星', skill='风卷残云'}
        * HeroNode{number=4, name='林冲', nickName='豹子头', skill='千军万马'}
        */
 }

 /**
 *有序添加数据
 */
public void addOrderByNum(HeroNode heroNode) {
        HeroNode temp = head;
        while (true) {
            if (temp.next == null) {
               //没有数据,直接添加到head后
                heroNode.next = null;
                temp.next = heroNode;
                break;
            }
               //说明要插入链表中的位置找到
            if (temp.next.number > heroNode.number) {
                //先将heroNode的指针指向heroNode后面一个元素
                heroNode.next = temp.next;
                //然年后heroNode前面一个元素的指针指向heroNode
                temp.next = heroNode;
                break;
            } else if (temp.next.number == heroNode.number) {
                System.out.print("当前水浒人物" + heroNode.name + "已存在,无法再加入序列\n");
                break;
            }
            //指针下移
            temp = temp.next;
        }
三、根据序列号删除节点
  • 同样需要一个临时指针temp(Node temp=head),当temp.next=null时,则说明链表中无可删除节点;
  • 当temp.next.num=num(需要删除的节点序号)时,此时令temp的指针指向下下个节点,即:temp.next=temp.next.next;这时直接跳过序号为num的节点来达到删除节点的目的,有人会问,没有被指向的那个num序号节点(被删除的这个节点)不会占内存吗?不会,因为没有被指针next指向的节点是不可达对象(非GC Root对象),最终会被GC回收!
  • 每做一个判断指针next必须下移:temp=temp.next。
 public static void main(String[] args) {
        //创建链表
        AtomSingleLinkedList list = new AtomSingleLinkedList();
        //创建节点
        HeroNode h1 = new HeroNode(1, "宋江", "及时雨", "翻天覆地");
        HeroNode h2 = new HeroNode(2, "卢俊义", "玉麒麟", "翻江倒海");
        HeroNode h3 = new HeroNode(3, "吴用", "智多星", "风卷残云");
        HeroNode h4 = new HeroNode(4, "林冲", "豹子头", "千军万马");
        //打乱顺序的添加
        list.addOrderByNum(h3);
        list.addOrderByNum(h1);
        list.addOrderByNum(h4);
        list.addOrderByNum(h2);
        //删除序号为2的节点
        list.deleteByNum(2);
     
        //显示数据
        list.queryList();
        /**
        * 打印结果(序号为2的英雄被删除):
        * HeroNode{number=1, name='宋江', nickName='及时雨', skill='翻天覆地'}
        * HeroNode{number=3, name='吴用', nickName='智多星', skill='风卷残云'}
        * HeroNode{number=4, name='林冲', nickName='豹子头', skill='千军万马'}
        */
 }


  /**
  * 根据序号删除节点
  */
public void deleteByNum(int num) {
        HeroNode temp = head;
        while (true) {
            if (temp.next == null) {
                System.out.printf("编号--%d的数据不存在,无法为您删除数据~",num);
                break;
            }
            if (temp.next.number == num) {
                temp.next = temp.next.next;
                System.out.println(temp + "  删除成功!!\n\n");
                break;
            }
            temp = temp.next;
        }
    }

四、修改数据

  • 首先需要一个临时指针temp,令temp=head,当temp.next=null时,说明链表中没有数据,退出修改;
  • 对于传入的修改数据node,这里需要注意的是数据是根据序列号num来做修改的,当temp.next.num=node.num时,对temp.next中的数据进行修改,将node的数据赋值给temp.next中(序列号除外);
  • 指针要下移,保证遍历可以正常进行,否则会出现死循环:temp=temp.next。
 public static void main(String[] args) {
        //创建链表
        AtomSingleLinkedList list = new AtomSingleLinkedList();
        //创建节点
        HeroNode h1 = new HeroNode(1, "宋江", "及时雨", "翻天覆地");
        HeroNode h2 = new HeroNode(2, "卢俊义", "玉麒麟", "翻江倒海");
        HeroNode h3 = new HeroNode(3, "吴用", "智多星", "风卷残云");
        HeroNode h4 = new HeroNode(4, "林冲", "豹子头", "千军万马");
        //打乱顺序的添加
        list.addOrderByNum(h3);
        list.addOrderByNum(h1);
        list.addOrderByNum(h4);
        list.addOrderByNum(h2);
        //删除序号为2的节点
        list.updateNode(new HeroNode(2, "小卢", "玉麒麟!!", "天崩地裂") );
     
        //显示数据
        list.queryList();
        /**
        * 打印结果(序号为2的英雄被修改):
        * HeroNode{number=1, name='宋江', nickName='及时雨', skill='翻天覆地'}
        * HeroNode{number=2, name='小卢', nickName='玉麒麟!!', skill='天崩地裂'}
        * HeroNode{number=3, name='吴用', nickName='智多星', skill='风卷残云'}
        * HeroNode{number=4, name='林冲', nickName='豹子头', skill='千军万马'}
        */
 }

 /**
 *修改数据
 */
public void updateNode(HeroNode updateNode) {
        HeroNode temp = head;
        while (true) {
            if (temp.next == null) {
                System.out.printf("没有找到编号: %d\n", updateNode.number);
                break;
            }
            if (temp.next.number == updateNode.number) {
                //把需要修改的节点 temp.next的数据替换成updateNode的数据
                temp.next.name = updateNode.name;
                temp.next.nickName = updateNode.nickName;
                temp.next.skill = updateNode.skill;
                break;
            }
            //指针下移
            temp = temp.next;
        }
    }

五、查询数据

  • 同样需要一个临时变量temp,令temp=head.next,当temp=null时,终止链表,则表示无数据;
  • 当temp!=null时,在死循环中遍历temp,取出temp的值,指针要下移:temp=temp.next;当遍历到temp=null时退出死循环。

public static void main(String[] args) {
        //创建链表
        AtomSingleLinkedList list = new AtomSingleLinkedList();
        //创建节点
        HeroNode h1 = new HeroNode(1, "宋江", "及时雨", "翻天覆地");
        HeroNode h2 = new HeroNode(2, "卢俊义", "玉麒麟", "翻江倒海");
        HeroNode h3 = new HeroNode(3, "吴用", "智多星", "风卷残云");
        HeroNode h4 = new HeroNode(4, "林冲", "豹子头", "千军万马");
        //打乱顺序的添加
        list.addOrderByNum(h3);
        list.addOrderByNum(h1);
        list.addOrderByNum(h4);
        list.addOrderByNum(h2);
     
        //显示数据
        list.queryList();
        /**
        * 打印结果:
        * HeroNode{number=1, name='宋江', nickName='及时雨', skill='翻天覆地'}
        * HeroNode{number=2, name='卢俊义', nickName='玉麒麟', skill='翻江倒海'}
        * HeroNode{number=3, name='吴用', nickName='智多星', skill='风卷残云'}
        * HeroNode{number=4, name='林冲', nickName='豹子头', skill='千军万马'}
        */
 }



/**
 * 显示链表中的数据
 */
public void queryList() {
    //因为head不可动
    HeroNode temp = head.next;

    if (temp == null) {
        System.out.println("当前链表为空,没数据可显示~~");
        return;
    }
   
    while (temp != null) {
        //当temp指针指向null,则代表是最后一个节点,退出当前死循环
        System.out.println(temp);
        //temp指针一定要后移,否则一直会处于死循环中
        temp = temp.next;
    }
}