(死磕数据结构与算法——链表(JAVA实现)(单向链表、双向链表、环形链表)。才疏学浅,如有错误,及时指正!

单链表

1. 基本概念

链表是有序的列表,以节点的方式来进行存储,属于链式存储。
每个节点包含data域和next域,next域用来指向下一个节点。
链表的各个节点并不一定是连续存储。
链表分为带头节点的链表和不带头节点的链表。

2. 带头节点链表的示意图

3. 功能详解

1. 添加节点到链表(添加到最后)

  1. 创建辅助变量temp,用于遍历整个节点
  2. 找到链表的最后,把节点添加到链表的最后

2. 添加节点到链表(有序)

  1. 创建辅助变量temp,用于遍历整个节点
  2. 找到要添加节点的前一个位置
  3. 新的节点.next = temp.next
  4. temp.next = 新的节点

3. 修改节点的信息

  1. 创建辅助变量temp,用于遍历整个节点
  2. 首先判断链表是否为空,若为空,则修改失败,结束函数
  3. 对链表进行遍历,找到要修改的节点
  4. 找到要修改的节点后,进行修改信息

4. 删除某个节点的信息

  1. 创建辅助变量temp,用于遍历整个节点.
  2. 对链表进行遍历,找到要删除的节点。
  3. temp.next = temp.next.next;
  4. 被删除的节点,将不会有其它引用指向,会被垃圾回收机制回收。

4. 示意代码

package LinkedList;

public class LinkedListDemo1 {
    
    
    public static void main(String[] args) {
    
    
        //测试
        //创建一个链表
        SingleLinkedList linkedList = new SingleLinkedList();

        //创建几个学生
        StudentNode s1 = new StudentNode( 5,"小明" );
        StudentNode s2 = new StudentNode( 2,"小红" );
        StudentNode s3 = new StudentNode( 4,"小黄" );
        StudentNode s4 = new StudentNode( 3,"小离" );
        StudentNode s5 = new StudentNode( 1,"小三" );

        //将学生节点添加至链表中
        //第一种方式(添加到末尾)
        /*linkedList.add( s1 );
        linkedList.add( s2 );
        linkedList.add( s3 );
        linkedList.add( s4 );
        linkedList.add( s5 );*/

        //第二种方式(按顺序)
        linkedList.addByOrder( s1 );
        linkedList.addByOrder( s2 );
        linkedList.addByOrder( s3 );
        linkedList.addByOrder( s4 );
        linkedList.addByOrder( s5 );

        //测试修改节点信息
        linkedList.updateNode( new StudentNode( 3,"刘海军真帅" ) );

        //测试删除节点
        linkedList.remove( 3 );
        //显示链表
        linkedList.show();
    }

}

/**
 *
 */
class StudentNode {
    
    
    public int num;
    public String name;
    public StudentNode next;
    //创建构造器
    StudentNode (int num, String name){
    
    
        this.num = num;
        this.name = name;
    }

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

/**
 * 定义一个链表类,来对我们的学生链表进行管理
 */
class SingleLinkedList {
    
    
    //初始化头节点
    StudentNode head = new StudentNode(  0,"");

    //添加节点到单向链表(不考虑顺序)
    //步骤:
    //1. 找到最后一个节点
    //2. 让最后一个节点的next域指向要添加的节点
    public void add(StudentNode studentNode){
    
    
        //头节点不能动,因此需要一个辅助对象temp来进行遍历
        StudentNode temp = head;
        //遍历链表,找到最后一个节点
        while (true) {
    
    
            //找到链表的最后,结束循环
            if(temp.next == null){
    
    
                break;
            }
            //没有找到链表的最后,将temp后移
            temp =  temp.next;
        }
        temp.next = studentNode;
    }

    //添加节点到指定位置
    public void addByOrder (StudentNode studentNode) {
    
    
        //头节点不能动,因此需要一个辅助对象temp来进行遍历
        StudentNode temp = head;
        boolean flag = false;  //标志原链表中是否存在要插入的节点
        //遍历链表
        while(true){
    
    
            if(temp.next == null) {
    
    
                //说明temp在链表的最后,退出循环
                break;
            }
            if(temp.next.num > studentNode.num) {
    
    
                //找到位置,退出循环
                break;
            }else if(temp.next.num == studentNode.num){
    
    
                //说明编号存在
                flag = true;
                break;
            }
             temp = temp.next;  //后移,遍历当前链表
        }
        //判断flag
        if(flag == false){
    
    
            studentNode.next = temp.next;
            temp.next = studentNode;
        }else{
    
    
            //插入失败,编号已经存在
            System.out.printf( "准备插入的学生编号%d已经存在", studentNode.num );
        }
    }

    //修改节点的信息
    public void updateNode (StudentNode studentNode) {
    
    
        if(head.next == null){
    
    
            System.out.println("链表为空!");
            return;
        }
        //1. 遍历,找到要修改的节点
        StudentNode temp = head; //辅助节点,便于遍历
        boolean flag = false;
        while(true) {
    
    
            if(temp.num == studentNode.num){
    
    
                //找到了要修改的节点,结束遍历
                flag = true;
                break;
            }
            if(temp.next == null) {
    
    
                //找到链表的最后都找不到要修改的节点,
                break;
            }
            temp = temp.next; //继续遍历
        }
        if(flag == false) {
    
    
            System.out.printf( "修改%d编号的节点失败,不能修改\n", studentNode.num );
        }
        else {
    
    
            //修改节点
            temp.name = studentNode.name;
        }
    }

    //删除节点
    public void remove (int num) {
    
    
        //找到要删除的节点,然后进行操作
        StudentNode temp = head;
        boolean flag = false;
        //遍历
        while(true) {
    
    
            if(temp.next.num == num){
    
    
                flag = true;
                break;
            }
            if(temp.next == null){
    
    
                //找到链表的最后,结束循环
                break;
            }
            temp = temp.next;  //继续遍历
        }
        if(flag == true){
    
    
            //进行删除操作
            temp.next = temp.next.next;
        }else{
    
    
            System.out.println("要删除的节点不存在!!!");
        }
    }

    //显示链表(遍历)
    public void show() {
    
    
        //遍历
        StudentNode temp = head;  //辅助节点,用于遍历
        if(temp.next == null){
    
    
            System.out.println("目前是一个空链表");
            return;
        }
        while(true) {
    
    
            if(temp.next == null) {
    
    
                //结束
                break;
            }
            temp = temp.next;
            System.out.println(temp);
        }
    }
}

2. 双向链表

1. 基本概念

单向链表的缺点
查找时只能是一个方向,而双向链表可以向前或者向后查找。
单向链表不能实现自我删除,而必须依靠一个辅助节点才能完成。双向链表可以实现自我删除。
什么是双向链表
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。

2. 示意图

3. 功能详解

1. 遍历:和单链表一样,只是比单链表多了向前和向后遍历两个方向。

2. 添加(添加到最后):先找到双向链表的最后节点,然后temp.next = newStudentNode, newStudentNode.pre = temp;

3. 修改:与单链表的思路一致。

4. 删除:找到要删除的节点,temp.pre.next = temp.next, temp.next.pre = temp.pre;

4. 示意代码

package LinkedList;

public class LinkedListDemo2 {
    
    
    public static void main(String[] args) {
    
    
        //测试
        //创建一个链表
        DoubleLinkedList linkedList = new DoubleLinkedList();

        //创建几个学生
        StudentNode1 s1 = new StudentNode1( 5,"小明" );
        StudentNode1 s2 = new StudentNode1( 2,"小红" );
        StudentNode1 s3 = new StudentNode1( 4,"小黄" );
        StudentNode1 s4 = new StudentNode1( 3,"小离" );
        StudentNode1 s5 = new StudentNode1( 1,"小三" );
        linkedList.add( s1 );
        linkedList.add( s2 );
        linkedList.add( s3 );
        linkedList.add( s4 );
        linkedList.add( s5 );

        linkedList.remove( 2 );

        linkedList.updateNode(  new StudentNode1( 3,"hapi" ));
        linkedList.show();
    }

}

/**
 *
 */
class StudentNode1 {
    
    
    public int num;
    public String name;
    public StudentNode1 next;
    public StudentNode1 pre;
    //创建构造器
    StudentNode1 (int num, String name){
    
    
        this.num = num;
        this.name = name;
    }

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

/**
 * 定义一个链表类,来对我们的学生链表进行管理
 */
class DoubleLinkedList {
    
    
    //初始化头节点
    StudentNode1 head = new StudentNode1(  0,"");

    //添加节点到单向链表(不考虑顺序)
    //步骤:
    //1. 找到最后一个节点
    //2. 让最后一个节点的next域指向要添加的节点,让要添加节点的pre域指向最后一个节点。
    public void add(StudentNode1 studentNode1){
    
    
        //头节点不能动,因此需要一个辅助对象temp来进行遍历
        StudentNode1 temp = head;
        //遍历链表,找到最后一个节点
        while (true) {
    
    
            //找到链表的最后,结束循环
            if(temp.next == null){
    
    
                break;
            }
            //没有找到链表的最后,将temp后移
            temp =  temp.next;
        }
        temp.next = studentNode1;
        studentNode1.pre = temp;
    }

    //修改节点的信息
    public void updateNode (StudentNode1 studentNode1) {
    
    
        if(head.next == null){
    
    
            System.out.println("链表为空!");
            return;
        }
        //1. 遍历,找到要修改的节点
        StudentNode1 temp = head; //辅助节点,便于遍历
        boolean flag = false;
        while(true) {
    
    
            if(temp.num == studentNode1.num){
    
    
                //找到了要修改的节点,结束遍历
                flag = true;
                break;
            }
            if(temp.next == null) {
    
    
                //找到链表的最后都找不到要修改的节点,
                break;
            }
            temp = temp.next; //继续遍历
        }
        if(flag == false) {
    
    
            System.out.printf( "修改%d编号的节点失败,不能修改\n", studentNode1.num );
        }
        else {
    
    
            //修改节点
            temp.name = studentNode1.name;
        }
    }

    //删除节点
    public void remove (int num) {
    
    
        //找到要删除的节点,然后进行操作
        StudentNode1 temp = head;
        if(temp.next == null){
    
    
            System.out.println("链表为空,无法删除");
            return;
        }
        boolean flag = false;
        //遍历
        while(true) {
    
    
            if(temp.num == num){
    
    
                flag = true;
               break;
            }
            if(temp.next == null){
    
    
                //找到链表的最后,结束循环
                break;
            }
            temp = temp.next;  //继续遍历
        }
        if(flag == true){
    
    
            //进行删除操作
            temp.pre.next = temp.next;
            if(temp.next != null){
    
    
                temp.next.pre = temp.pre;
            }
        }else{
    
    
            System.out.println("要删除的节点不存在!!!");
        }
    }

    //显示链表(遍历)
    public void show() {
    
    
        //遍历
        StudentNode1 temp = head;  //辅助节点,用于遍历
        if(temp.next == null){
    
    
            System.out.println("目前是一个空链表");
            return;
        }
        while(true) {
    
    
            if(temp.next == null) {
    
    
                //结束
                break;
            }
            temp = temp.next;
            System.out.println(temp);
        }
    }
}

3. 单向环形链表

1. 基本概念

什么是单向环形链表:把单链表的最后一个节点的next指向头节点而不是null,就构成了一个单向环形链表。
**解决的问题:**约瑟夫问题(Josephu)

2. 示意图

3. 功能详解

1. 构建:先创建第一个节点,让first指向该节点,形成环形。

当我们每创建一个新的节点时,把该节点加入到已有的环形链表中即可。

2. 遍历: 先创建一个辅助指针temp,执行frist节点。

然后遍历链表以temp.next == frist 作为结束条件。

4. 示意代码

package LinkedList;

public class CircleLinkedListDemo1 {
    
    
    public static void main(String[] args) {
    
    

        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.add( 5 );
        circleSingleLinkedList.show();
        circleSingleLinkedList.countStudent( 1, 2,5 );
    }
}
class Student {
    
    
    public int num;
    public Student next;

    public Student (int num){
    
    
        this.num = num;
    }
}

/**
 * 创建环形的单向链表
 */
class CircleSingleLinkedList {
    
    
    //创建一个first节点。
    Student first = null;

    //添加节点到单向环形链表中
    public void add(int nums){
    
    
        //数据校验
        if(nums < 1){
    
    
            System.out.println("nums的值不正确");
            return;
        }
        //创建一个辅助指针,用来构建环形单链表
        Student temp = null;

        for (int i = 1; i <= nums; i++) {
    
    
            //根据i的值,创建学生节点
            Student student = new Student( i );
            //如果i == 1
            if(i == 1){
    
    
                first = student;
                temp = student;
                first.next = first;  //构成环
            }else {
    
    
                temp.next = student;
                student.next = first;
                temp = student;
            }
        }
    }

    //遍历环形链表
    public void show (){
    
    
        //判断链表是否为空
        if(first == null){
    
    
            System.out.println("链表为空,无需遍历");
            return ;
        }
        //因为first一直不能动,所以创建一个辅助变量temp来完成遍历。
        Student temp = first;
        while(true){
    
    
            System.out.printf("小孩的编号为:%d", temp.num);
            System.out.println();
            if(temp.next == first){
    
    
                //结束循环
                break;
            }
            temp = temp.next;// temp后移
        }
    }

    /**
     * 根据用户的输入,计算出小孩的出圈顺序
     * @param startNum 开始数的编号
     * @param countNUm 表示数几下
     * @param nums  学生的初始值
     */
    public void countStudent(int startNum, int countNUm, int nums){
    
    
        if(first == null || startNum < 1 || startNum > nums){
    
    
            System.out.println("输入参数有误");
            return;
        }
        //创建辅助指针,帮助学生出圈。
        Student temp = first;
        //先遍历,到开始数数的地方结束,就是环形链表的最后
        while(true) {
    
    
            if(temp.next == first){
    
    
                break;
            }
            temp = temp.next;
        }
        //先让first和temp移动startNum -1 次
        for (int i = 0; i < startNum -1 ; i++) {
    
    
            first =  first.next;
            temp = temp.next;
        }
        //开始报数,让temp和first同时移动countNUm-1次,然后进行出圈操作
        while(true){
    
    
            if(temp == first){
    
      //圈中只有一个节点
                break;
            }
            //让first和temp同时移动countNUm-1次
            for (int i = 0; i < countNUm-1; i++) {
    
    
                first =  first.next;
                temp = temp.next;
            }
            //这时first指向的节点,就是要出圈的节点。
            System.out.printf( "学生%d出圈\n", first.num );
            first = first.next;
            temp.next = first;
        }
        System.out.printf( "最后留在圈中的小孩是%d", first.num );
    }

}

总结:*

本文通过Java代码以及介绍的方式来实现对链表的模拟,包含了对链表的增、删、等基本操作的说明以及代码实现。对单向链表,双向链表,环形链表有了更深一步的认知。

持续学习!

猜你喜欢

转载自blog.csdn.net/qq_41497756/article/details/108696111
今日推荐