手把手带你用Java实现单向链表

一、链表介绍

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
在内存中的存储
链表分为带头节点的链表和没有头节点的链表,根据实际的需求确定
在这里插入图片描述

二、链表的应用示例

使用带head头的单向链表实现输入学生信息,查询时根据学生id顺序显示

  1. 添加节点:根据学生id将学生信息插入到指定位置(若id存在则提醒添加失败)
    在这里插入图片描述

添加(创建)
1.创建一个head头节点,作为单链表的头部
2.找到新添加节点的位置,可以通过辅助变量(指针)完成
3.新的节点.next = temp.next
4.temp.next = 新的节点.
注意:第三步与第四步顺序不能反,若先将当前节点的next指向新节点,则原链表截断后的元素则丢失。

  1. 修改节点

1.找到将要修改的节点
2.将信息给原节点

  1. 删除节点

1.先找到需要删除的节点的前一个节点
2.temp.next = temp.next.next

  1. 查询所有节点

通过遍历进行查询

三、代码实现

public class SingleLinkedListDemo {
    //进行测试
    public static void main(String[] args) {
       //创建节点
        //顺序添加
//        StudentNode node1 = new StudentNode(1,"唐僧");
//        StudentNode node2 = new StudentNode(2,"孙悟空");
//        StudentNode node3 = new StudentNode(3,"猪八戒");
//        StudentNode node4 = new StudentNode(4,"沙僧");

        //乱序添加
        StudentNode node1 = new StudentNode(1,"唐僧");
        StudentNode node3 = new StudentNode(3,"猪八戒");
        StudentNode node2 = new StudentNode(2,"孙悟空");
        StudentNode node4 = new StudentNode(4,"沙僧");

        //创建链表
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        System.out.println("创建后查询:");
        singleLinkedList.show();

        //将节点添加到链表中
        singleLinkedList.add(node1);
        singleLinkedList.add(node3);
        singleLinkedList.add(node2);
        singleLinkedList.add(node4);
        System.out.println("添加后查询:");
        singleLinkedList.show();

        //修改节点
        StudentNode node5 = new StudentNode(4,"沙和尚");
        singleLinkedList.update(node5);
        System.out.println("修改后查询:");
        singleLinkedList.show();

        //删除节点
        singleLinkedList.delete(4);
        System.out.println("删除后查询:");
        singleLinkedList.show();

    }
}

/**
 * 链表
 */
class SingleLinkedList{
    //初始化一个头节点,头节点不存放具体数据
    private StudentNode head = new StudentNode(0,"");

    /**
     *  添加节点到单向链表
     *  思路:
     *  1.找到新添加节点的位置
     *  2.将新节点的下一个节点指向到新节点的next中
     *  3.将当前节点的next指向到新节点
     *  注意:第二步与第三步顺序不能反,若先将当前节点的next指向新节点,则原链表截断后的节点则丢失。
     * @param studentNode
     */
    public void add(StudentNode studentNode){
        //因为头节点不能动,所有我们需要一个辅助指针(变量)来协助找到添加的位置
        //单链表结构,我们找到的temp是位于  添加位置  的前一个节点。
        StudentNode temp = head;
        //标志着添加编号是否存在,默认为false;
        boolean flag = false;
        while(true){
            //说明temp已经是链表的最后一个
            if(temp.next == null){
                break;
            }
            //找到要添加的位置,在temp后边添加节点
            if(temp.next.id > studentNode.id){
                break;
            }
            //说明该id已存在,更改验证存在标识为true
            else if(temp.next.id == studentNode.id){
                flag = true;
                break;
            }
            //指针后移,遍历当前链表
            temp = temp.next;
        }
        //判断是否进行添加操作
        if(flag){
            System.out.printf("待添加的学生id %d 已被占用,不可添加",studentNode.id);
        }else{
            //将新节点的下一个节点指向到新节点的next中
            studentNode.next = temp.next;
            //将当前节点的next指向到新节点
            temp.next = studentNode;
            System.out.println("添加成功!");
        }
    }

    /**
     * 修改节点,根据id进行修改,因此id不可更改
     * 思路:
     *  1.找到将要修改的节点
     *  2.将信息给原节点
     * @param studentNode
     */
    public void update(StudentNode studentNode){
        //判断当前链表是否为空
        if(head.next == null){
            System.out.println("链表为空  ~~~~~  ");
            return;
        }
        //找到将要修改的节点
        StudentNode temp = head.next;
        //标识是否找到该节点
        boolean flag = false;
        while(true){
            //说明temp已经是链表的最后一个
            if(temp == null){
                break;
            }
            //将节点进行比较
            if(temp.id == studentNode.id){
                flag = true;
                break;
            }
            temp = temp.next;
        }
        //判断是否可以进行修改操作
        if(flag){
            temp.name = studentNode.name;
        }else {
            System.out.printf("没有找到 id为 %d 的节点,不可修改\n",studentNode.id);
        }
    }

    /**
     * 删除节点  ,根据id删除节点
     * 思路:
     * 1.如果指针指向了要删除的节点则无法删除,所有需要找到要删除的节点的上一个节点
     * 2.temp.next = temp.next.next
     * @param id
     */
    public void delete(int id){
        //因为头节点不能动,所有我们需要一个辅助指针(变量)来协助找到添加的位置
        //单链表结构,我们找到的temp是位于  添加位置  的前一个节点。
        StudentNode temp = head;
        //标识是否找到待删除的节点
        boolean flag = false;
        while (true){
            //说明temp已经是链表的最后一个
            if(temp == null){
                break;
            }
            //将节点进行比较
            if(temp.next.id == id){
                flag = true;
                break;
            }
            temp = temp.next;
        }
        //判断是否可以进行删除操作
        if(flag){
            temp.next = temp.next.next;
        }else {
            System.out.printf("没有找到 id为 %d 的节点,无法删除\n",id);
        }
    }

    /**
     * 显示链表中所有节点
     */
    public void show(){

        //判断当前链表是否为空
        if(head.next == null){
            System.out.println("链表为空  ~~~~~  ");
            return;
        }
        System.out.println("-----------------显示链表开始-------------------");
        //因为头节点不能动,所有我们需要一个辅助指针(变量)来协助找到添加的位置
        //单链表结构,我们找到的temp是位于  添加位置  的前一个节点。
        StudentNode temp = head;
        while (true){
            //说明temp已经是链表的最后一个
            if(temp == null){
                break;
            }
            //将节点进行打印
            System.out.println(temp);
            temp = temp.next;
        }
        System.out.println("-----------------显示链表结束-------------------");
    }
}

/**
 * 定义一个StudentNode, 每个StudentNode对象就是一个链表中的节点
 */
class StudentNode{
    public int id;
    public String name;
    public StudentNode next;

    public StudentNode(int id,String name){
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "StudentNode{id=" + id +", name='" + name + "'}";
    }
}

四、拓展功能

  1. 统计当前链表有效节点数
 //测试代码
 		System.out.println("当前链表有效节点数为:"+singleLinkedList.size());
//实现代码
	/**
     * 获取当前链表有效节点数
     * 思路:遍历链表中的所有节点
     * @return
     */
    public int size(){
        //空链表
        if(head.next == null){
            return 0;
        }
        int size = 0;
        //定义一个辅助指针(变量)
        StudentNode temp = head.next;
        while (temp != null){
            size++;
            //遍历当前链表下的节点
            temp = temp.next;
        }
        return size;
    }
  1. 找到单链表中倒数第index个节点
//测试代码
        //查找倒数第n个节点
        System.out.println("倒数第1个节点为:"+singleLinkedList.findLastIndexNode(1));
//实现代码
	/**
     * 找到单链表中倒数第index个节点
     * 思路:
     *  1.判断节点是否存在
     *  2.从第一个节点开始遍历,遍历(size - index)个节点
     * @param index
     * @return
     */
    public StudentNode findLastIndexNode(int index){
        //空链表
        if(head.next == null){
            return null;
        }
       //判断节点数是否存在
        if(index<=0 || index > size()){
            return null;
        }
        //遍历第size - index的位置,该位置就是我们要找的节点
        StudentNode temp = head.next;
        for (int i = 0;i < size() - index;i++){
            temp = temp.next;
        }
        return temp;
    }
  1. 反转链表
//测试代码
		//反转当前链表
        System.out.println("反转后查询:");
        singleLinkedList.reverseList();
        singleLinkedList.show();
        
//实现代码
	/**
     * 反转当前链表
     */
    public void reverseList(){
        //空链表或只有一个节点直接返回
        if(head.next == null || head.next.next == null){
            return;
        }
        //定义一个辅助指针(变量)
        StudentNode temp = head.next;
        //指向当前节点的下一个节点,避免节点丢失
        StudentNode next = null;
        //创建临时链表头节点
        StudentNode reverseHead = new StudentNode(0,"");
        //遍历原链表中的每一个节点将其取出放在临时链表reverseHead的最前端
        while (temp != null){
            //暂时保存当前节点后的节点
            next = temp.next;
            //将temp的下一个链表指向新链表的最前端
            temp.next = reverseHead.next;
            //将temp连接到新链表上
            reverseHead.next = temp;
            //遍历当前链表下的节点(指针后移)
            temp = next;
        }
        head.next = reverseHead.next;
    }
  1. 反转打印链表
//测试代码
        //反转打印当前链表
        System.out.println("反转打印:");
        singleLinkedList.reverseShow();
//实现代码
	/**
     * 反向打印单链表
     * 使用Stack栈,利用Stack栈先进后出原则进行反向打印,反向打印不影响源数据结构
     */
    public void reverseShow(){
        //空链表
        if(head.next == null){
            return;
        }
        //创建一个栈,将各个节点压入栈
        Stack<StudentNode> stack = new Stack<StudentNode>();
        StudentNode temp = head.next;
        while(temp != null){
            stack.add(temp);
            //指针后移
            temp = temp.next;
        }
        while (stack.size()>0){
            //打印节点信息
            System.out.println(stack.pop());
        }
    }
  1. 合并链表,并保持有序
//测试代码
		//合并链表
        //创建第二个链表
        SingleLinkedList singleLinkedList2 = new SingleLinkedList();
        StudentNode node11 = new StudentNode(6,"白骨精");
        StudentNode node12 = new StudentNode(7,"牛魔王");
        StudentNode node13 = new StudentNode(2,"孙悟空");
        singleLinkedList2.add(node11);
        singleLinkedList2.add(node12);
        singleLinkedList2.add(node13);

        //进行合并
        singleLinkedList.addAll(singleLinkedList2);

        System.out.println("合并后打印:");
        singleLinkedList.show();
        
//实现代码
	/**
     * 合并/添加一个有序的单链表,合并后依然有序
     */
    public void addAll(SingleLinkedList singleLinkedList){
        //判断待添加链表有效节点数的数量
        if(singleLinkedList.size()==0)
        {
            return;
        }
        //将待添加链表加入当前链表中
        while (singleLinkedList.size()>0){
            StudentNode temp = singleLinkedList.findLastIndexNode(1);
            singleLinkedList.delete(temp.id);
            this.add(temp);
        }
    }

附完整代码

import java.util.Stack;

/**
 * @author 张家宝
 * @date 2020/4/7
 */
public class SingleLinkedListDemo {
    //进行测试
    public static void main(String[] args) {
       //创建节点
        //顺序添加
//        StudentNode node1 = new StudentNode(1,"唐僧");
//        StudentNode node2 = new StudentNode(2,"孙悟空");
//        StudentNode node3 = new StudentNode(3,"猪八戒");
//        StudentNode node4 = new StudentNode(4,"沙僧");

        //乱序添加
        StudentNode node1 = new StudentNode(1,"唐僧");
        StudentNode node3 = new StudentNode(3,"猪八戒");
        StudentNode node2 = new StudentNode(2,"孙悟空");
        StudentNode node4 = new StudentNode(4,"沙僧");

        //创建链表
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        System.out.println("创建后查询:");
        singleLinkedList.show();

        //将节点添加到链表中
        singleLinkedList.add(node1);
        singleLinkedList.add(node3);
        singleLinkedList.add(node2);
        singleLinkedList.add(node4);
        System.out.println("添加后查询:");
        singleLinkedList.show();

        //修改节点
        StudentNode node5 = new StudentNode(4,"沙和尚");
        singleLinkedList.update(node5);
        System.out.println("修改后查询:");
        singleLinkedList.show();

        //删除节点
        singleLinkedList.delete(2);
        System.out.println("删除后查询:");
        singleLinkedList.show();

        //统计当前链表有效节点数
        System.out.println("当前链表有效节点数为:"+singleLinkedList.size());

        //查找倒数第n个节点
        System.out.println("倒数第1个节点为:"+singleLinkedList.findLastIndexNode(1));

        //反转当前链表
        System.out.println("反转后查询:");
        singleLinkedList.reverseList();
        singleLinkedList.show();

//        //反转打印当前链表
        System.out.println("反转打印:");
        singleLinkedList.reverseShow();

        //合并链表
        //创建第二个链表
        SingleLinkedList singleLinkedList2 = new SingleLinkedList();
        StudentNode node11 = new StudentNode(6,"白骨精");
        StudentNode node12 = new StudentNode(7,"牛魔王");
        StudentNode node13 = new StudentNode(2,"孙悟空");
        singleLinkedList2.add(node11);
        singleLinkedList2.add(node12);
        singleLinkedList2.add(node13);

        //进行合并
        singleLinkedList.addAll(singleLinkedList2);

        System.out.println("合并后打印:");
        singleLinkedList.show();

    }
}

/**
 * 链表
 */
class SingleLinkedList{
    //初始化一个头节点,头节点不存放具体数据
    private StudentNode head = new StudentNode(0,"");

    /**
     *  添加节点到单向链表
     *  思路:
     *  1.找到新添加节点的位置
     *  2.将新节点的下一个节点指向到新节点的next中
     *  3.将当前节点的next指向到新节点
     *  注意:第二步与第三步顺序不能反,若先将当前节点的next指向新节点,则原链表截断后的节点则丢失。
     * @param studentNode
     */
    public void add(StudentNode studentNode){
        //因为头节点不能动,所有我们需要一个辅助指针(变量)来协助找到添加的位置
        //单链表结构,我们找到的temp是位于  添加位置  的前一个节点。
        StudentNode temp = head;
        //标志着添加编号是否存在,默认为false;
        boolean flag = false;
        while(true){
            //说明temp已经是链表的最后一个
            if(temp.next == null){
                break;
            }
            //找到要添加的位置,在temp后边添加节点
            if(temp.next.id > studentNode.id){
                break;
            }
            //说明该id已存在,更改验证存在标识为true
            else if(temp.next.id == studentNode.id){
                flag = true;
                break;
            }
            //指针后移,遍历当前链表
            temp = temp.next;
        }
        //判断是否进行添加操作
        if(flag){
            System.out.printf("待添加的学生id %d 已被占用,不可添加\n",studentNode.id);
        }else{
            //将新节点的下一个节点指向到新节点的next中
            studentNode.next = temp.next;
            //将当前节点的next指向到新节点
            temp.next = studentNode;
//            System.out.println("添加成功!");
        }
    }

    /**
     * 修改节点,根据id进行修改,因此id不可更改
     * 思路:
     *  1.找到将要修改的节点
     *  2.将信息给原节点
     * @param studentNode
     */
    public void update(StudentNode studentNode){
        //判断当前链表是否为空
        if(head.next == null){
            System.out.println("链表为空  ~~~~~  ");
            return;
        }
        //找到将要修改的节点
        StudentNode temp = head.next;
        //标识是否找到该节点
        boolean flag = false;
        while(true){
            //说明temp已经是链表的最后一个
            if(temp == null){
                break;
            }
            //将节点进行比较
            if(temp.id == studentNode.id){
                flag = true;
                break;
            }
            temp = temp.next;
        }
        //判断是否可以进行修改操作
        if(flag){
            temp.name = studentNode.name;
        }else {
            System.out.printf("没有找到 id为 %d 的节点,不可修改\n",studentNode.id);
        }
    }

    /**
     * 删除节点  ,根据id删除节点
     * 思路:
     * 1.如果指针指向了要删除的节点则无法删除,所有需要找到要删除的节点的上一个节点
     * 2.temp.next = temp.next.next
     * @param id
     */
    public void delete(int id){
        //因为头节点不能动,所有我们需要一个辅助指针(变量)来协助找到添加的位置
        //单链表结构,我们找到的temp是位于  添加位置  的前一个节点。
        StudentNode temp = head;
        //标识是否找到待删除的节点
        boolean flag = false;
        while (true){
            //说明temp已经是链表的最后一个
            if(temp == null){
                break;
            }
            //将节点进行比较
            if(temp.next.id == id){
                flag = true;
                break;
            }
            temp = temp.next;
        }
        //判断是否可以进行删除操作
        if(flag){
            temp.next = temp.next.next;
        }else {
            System.out.printf("没有找到 id为 %d 的节点,无法删除\n",id);
        }
    }

    /**
     * 显示链表中所有节点
     */
    public void show(){

        //判断当前链表是否为空
        if(head.next == null){
            System.out.println("链表为空  ~~~~~  ");
            return;
        }
        System.out.println("-----------------显示链表开始-------------------");
        //因为头节点不能动,所有我们需要一个辅助指针(变量)来协助找到添加的位置
        //单链表结构,我们找到的temp是位于  添加位置  的前一个节点。
        StudentNode temp = head;
        while (true){
            //说明temp已经是链表的最后一个
            if(temp == null){
                break;
            }
            //将节点进行打印
            System.out.println(temp);
            temp = temp.next;
        }
        System.out.println("-----------------显示链表结束-------------------");
    }

    /*----------- 拓展 ------------*/

    /**
     * 获取当前链表有效节点数
     * 思路:遍历链表中的所有节点
     * @return
     */
    public int size(){
        //空链表
        if(head.next == null){
            return 0;
        }
        int size = 0;
        //定义一个辅助指针(变量)
        StudentNode temp = head.next;
        while (temp != null){
            size++;
            //遍历当前链表下的节点
            temp = temp.next;
        }
        return size;
    }

    /**
     * 找到单链表中倒数第index个节点
     * 思路:
     *  1.判断节点是否存在
     *  2.从第一个节点开始遍历,遍历(size - index)个节点
     * @param index
     * @return
     */
    public StudentNode findLastIndexNode(int index){
        //空链表
        if(head.next == null){
            return null;
        }
       //判断节点数是否存在
        if(index<=0 || index > size()){
            return null;
        }
        //遍历第size - index的位置,该位置就是我们要找的节点
        StudentNode temp = head.next;
        for (int i = 0;i < size() - index;i++){
            temp = temp.next;
        }
        return temp;
    }

    /**
     * 反转当前链表
     */
    public void reverseList(){
        //空链表或只有一个节点直接返回
        if(head.next == null || head.next.next == null){
            return;
        }
        //定义一个辅助指针(变量)
        StudentNode temp = head.next;
        //指向当前节点的下一个节点,避免节点丢失
        StudentNode next = null;
        //创建临时链表头节点
        StudentNode reverseHead = new StudentNode(0,"");
        //遍历原链表中的每一个节点将其取出放在临时链表reverseHead的最前端
        while (temp != null){
            //暂时保存当前节点后的节点
            next = temp.next;
            //将temp的下一个链表指向新链表的最前端
            temp.next = reverseHead.next;
            //将temp连接到新链表上
            reverseHead.next = temp;
            //遍历当前链表下的节点(指针后移)
            temp = next;
        }
        head.next = reverseHead.next;
    }

    /**
     * 反向打印单链表
     * 使用Stack栈,利用Stack栈先进后出原则进行反向打印,反向打印不影响源数据结构
     */
    public void reverseShow(){
        //空链表
        if(head.next == null){
            return;
        }
        //创建一个栈,将各个节点压入栈
        Stack<StudentNode> stack = new Stack<StudentNode>();
        StudentNode temp = head.next;
        while(temp != null){
            stack.add(temp);
            //指针后移
            temp = temp.next;
        }
        while (stack.size()>0){
            //打印节点信息
            System.out.println(stack.pop());
        }
    }

    /**
     * 合并/添加一个有序的单链表,合并后依然有序
     */
    public void addAll(SingleLinkedList singleLinkedList){
        //判断待添加链表有效节点数的数量
        if(singleLinkedList.size()==0)
        {
            return;
        }
        //将待添加链表加入当前链表中
        while (singleLinkedList.size()>0){
            StudentNode temp = singleLinkedList.findLastIndexNode(1);
            singleLinkedList.delete(temp.id);
            this.add(temp);
        }
    }
}

/**
 * 定义一个StudentNode, 每个StudentNode对象就是一个链表中的节点
 */
class StudentNode{
    public int id;
    public String name;
    public StudentNode next;

    public StudentNode(int id,String name){
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "StudentNode{id=" + id +", name='" + name + "'}";
    }
}
发布了27 篇原创文章 · 获赞 11 · 访问量 4397

猜你喜欢

转载自blog.csdn.net/qq_26020387/article/details/105372965