Java数据结构之单链表的实现与面试题汇总

2022-10-25 19:19:42
目录
1 单链表1.1 单链表介绍1.2 单链表的实现思路分析1.3 实现代码2 单链表的面试题2.1 统计单链表中有效节点数量2.2 新浪–倒数第k个节点2.3 腾讯–单链表的反转2.4 百度–逆序打印单链表

1>

1.1>

由于顺序表的插入删除操作需要移动大量的元素,影响了运行效率,因此引入了线性表的链式存储——单链表。单链表通过一组任意的存储单元来存储线性表中的数据元素,不需要使用地址连续的存储单元,因此它 不要求在逻辑上相邻的两个元素在物理位置上也相邻。

物理结构示意图:

逻辑结构示意图:

关于单链表的一些说明:

    链表是以节点的方式存储的,每个节点包含data和next域,分别表示存储的数据和指向下一个节点;链表的各个节点不一定是连续存储的;可以根据实际需求来构造是否带有头节点的链表。

    1.2>

    1.2.1 单链表的创建与遍历

    单链表的创建:

    先创建一个 head 头节点,表示单链表的头;

    每添加一个节点就直接加入链表的最后;

    遍历的思路:

    创建一个辅助指针,用于帮助遍历整个链表;

    当指针指向的节点的next域为null,说明当前节点为最后一个,遍历完成。 1.2.2 单链表节点的插入与修改

    示意图如下:

      首先需要通过遍历找到需要添加节点的位置,图中示意的为a1的位置;新的节点的next指向a1.next;将该位置,即a1.next指向新的节点。

      修改操作相当于上述过程的简化,只需要找到对应的节点直接修改节点对应的属性即可,这里不再赘述。

      1.2.3 单链表节点的删除

      删除序号为 “2” 的节点示意图如下:

      思路如下:

        找到待删除节点的前一个节点,示例中则找到序号为1的节点;让该节点的 temp.next = temp.next.next,即可;由于被删除的节点没有其他的指向,则会由Java的垃圾回收机制进行回收,无需处理。

        1.3>

        StudentNode.java 节点类:

        /**
         * @author 兴趣使然黄小黄
         * @version 1.0
         * 链表的节点类,包含学生信息和next
         */
        public class StudentNode {
            public String no; //学号
            public String name; //姓名
            public int age; //年龄
            public StudentNode next; //指向下一个节点
        
            //构造器
            public StudentNode(String no, String name, int age ){
                this.no = no;
                this.name = name;
                this.age = age;
            }
        
            //为了显示方便
            @Override
            public String toString() {
                return "StudentNode{" +
                        "no='" + no + '\'' +
                        ", name='" + name + '\'' +
                        ", age=" + age +
                        '}';
            }
        }
        

        StudentLinkedList.java 链表的实现类:

        /**
         * @author 兴趣使然黄小黄
         * @version 1.0
         * 链表的实现类,用于管理众多StudentNode节点
         */
        public class StudentLinkedList {
            //初始化头节点
            private StudentNode head = new StudentNode("", "", 0);
        
        	  //获取头节点
            public StudentNode getHead() {
                return head;
            }
        
            //添加节点
            //1.找到当前链表的最后节点
            //2.将最后节点的next指向新的节点
            public void add(StudentNode studentNode) {
                StudentNode temp = head;
                //遍历链表找到最后的节点
                while (temp.next != null) {
                    //没有找到,就后移
                    temp = temp.next;
                }
                //最后的节点的next指向新节点
                temp.next = studentNode;
            }
        
            //遍历 显示链表
            public void showList(){
                //判断链表是否为空
                if (head.next == null){
                    System.out.println("当前链表为空");
                    return;
                }
                //遍历 使用辅助指针
                StudentNode temp = head;
                while (temp != null){
                    //更新指针
                    temp = temp.next;
                    if (temp.next == null){
                        System.out.print(temp);
                        break;
                    }
                    System.out.print(temp + "--->");
                }
            }
        
            //插入节点
            //根据学号顺序查找添加的位置, 如果存在, 则提示错误信息
            public void addByOrder(StudentNode studentNode){
                //寻找的temp应该为添加位置的前一个节点
                StudentNode temp = head;
                boolean flag = false; //标识新添加的no是否已经存在
                while (true){
                    if (temp.next == null){
                        //已经在链表的尾部
                        break;
                    }
                    if (Integer.parseInt(temp.next.no) > Integer.parseInt(studentNode.no)){
                        //位置找到 插入到temp后
                        break;
                    }else if (Integer.parseInt(temp.next.no) == Integer.parseInt(studentNode.no)){
                        //已经存在
                        flag = true;
                        break;
                    }
                    //移动指针
                    temp = temp.next;
                }
                if (flag){
                    System.out.println("\n准备插入的学生信息: " + studentNode.no + ",该学号已经存在,不可添加!");
                }else {
                    studentNode.next = temp.next;
                    temp.next = studentNode;
                }
            }
        
            //根据no学号修改学生信息
            public void update(StudentNode studentNode){
                if (head.next == null){
                    System.out.println("当前链表为空, 无法修改");
                    return;
                }
                StudentNode temp = head.next;
                boolean flag = false; //表示是否找到节点
                while (true){
                    if (temp == null){
                        break;
                    }
                    if (temp.no == studentNode.no){
                        flag = true;
                        break;
                    }
                    temp = temp.next;
                }
                if (flag){
                    temp.name = studentNode.name;
                    temp.age = studentNode.age;
                }else {
                    System.out.println("没有找到");
                }
            }
        
            //删除节点
            public void delete(String no){
                StudentNode temp = head;
                boolean flag = false; //标志是否找到
                //查找到待删除节点的前一个节点进行删除操作
                while (true){
                    if (temp.next == null){
                        //到达尾部
                        break;
                    }
                    if (temp.next.no == no){
                        //找到了
                        flag = true;
                        break;
                    }
                    //遍历
                    temp = temp.next;
                }
                //删除操作
                if (flag){
                    temp.next = temp.next.next;
                    System.out.println("删除成功!");
                }else {
                    System.out.println("要删除的节点不存在!");
                }
            }
        }
        

        测试类:

        /**
         * @author 兴趣使然黄小黄
         * @version 1.0
         * 测试链表
         */
        public class StudentListTest {
        
            public static void main(String[] args) {
                StudentNode node1 = new StudentNode("1", "黄小黄", 21);
                StudentNode node2 = new StudentNode("2", "懒羊羊", 21);
                StudentNode node3 = new StudentNode("3", "沸羊羊", 22);
                //创建单链表 录入数据 输出
                StudentLinkedList list = new StudentLinkedList();
                list.add(node1);
                list.add(node2);
                list.add(node3);
                System.out.println("遍历链表:");
                list.showList();
                //测试插入数据方法
                StudentNode node5 = new StudentNode("5", "美羊羊", 19);
                StudentNode node4 = new StudentNode("4", "暖羊羊", 19);
                list.addByOrder(node5);
                list.addByOrder(node4);
                System.out.println("\n依次插入学号为5、4的学生后:");
                list.showList();
                //测试修改方法
                System.out.println("\n测试修改方法:");
                list.update(new StudentNode("1", "祢豆子", 10));
                list.showList();
                //测试删除方法
                System.out.println("\n测试删除方法:");
                list.delete("1");
                list.delete("5");
                list.showList();
            }
        }
        

        实现结果:

        遍历链表:
        StudentNode{no='1', name='黄小黄', age=21}--->StudentNode{no='2', name='懒羊羊', age=21}--->StudentNode{no='3', name='沸羊羊', age=22}
        依次插入学号为5、4的学生后:
        StudentNode{no='1', name='黄小黄', age=21}--->StudentNode{no='2', name='懒羊羊', age=21}--->StudentNode{no='3', name='沸羊羊', age=22}--->StudentNode{no='4', name='暖羊羊', age=19}--->StudentNode{no='5', name='美羊羊', age=19}
        测试修改方法:
        StudentNode{no='1', name='祢豆子', age=10}--->StudentNode{no='2', name='懒羊羊', age=21}--->StudentNode{no='3', name='沸羊羊', age=22}--->StudentNode{no='4', name='暖羊羊', age=19}--->StudentNode{no='5', name='美羊羊', age=19}
        测试删除方法:
        删除成功!
        删除成功!
        StudentNode{no='2', name='懒羊羊', age=21}--->StudentNode{no='3', name='沸羊羊', age=22}--->StudentNode{no='4', name='暖羊羊', age=19}
        Process finished with exit code 0

        2>

        2.1>
         /**
             * 
             * @param head 头节点
             * @return 返回有效节点个数
             */
            public static int getLength(StudentNode head){
                if (head.next == null){
                    return 0;
                }
                int length = 0;
                StudentNode temp = head.next;
                while (temp != null){
                    length++;
                    temp = temp.next;
                }
                return length;
            }
        

        2.2>

        查找链表中倒数第k个节点

        思路分析:

          编写一个方法,接收head头节点和index,index表示k;链表从头到尾遍历,求出长度(链表节点个数)size;从第一个节点,遍历size-length次,即可找到倒数第k个节点。

          参考代码:

          /**
               * 获取单链表中倒数第k个节点
               * @param head 链表的头节点
               * @param index 倒数第 k 个元素
               * @return 返回倒数第 k 个元素,或者 null
               */
              public static StudentNode findLastIndexNode(StudentNode head, int index){
                  //如果链表为空
                  if (head.next == null){
                      return null;
                  }
                  //得到链表的长度(节点个数)
                  int size = getLength(head);
                  //遍历 size-index次 得到倒数第index个节点
                  //数据校验
                  if (index <= 0 || index > size){
                      return null;
                  }
                  //遍历
                  StudentNode current = head.next;
                  for (int i = 0; i < size - index; i++) {
                      current = current.next;
                  }
                  return current;
              }
          

          2.3>

          反转单链表

          思路分析:

            可以使用头插法;以原链表为模板,每遍历一个节点,取出,并接在新链表的最前端;原head头节点,指向新的节点;直到遍历完为止。

            参考代码:

            	/**
                 * 头插法反转链表
                 * @param head 接收待反转的链表
                 * @return 返回一个反转后的新链表
                 */
                public static StudentLinkedList reverseList(StudentNode head){
                    if (head.next == null){
                        return null;
                    }
                    StudentNode old = head.next; //用于遍历旧链表
                    //创建新链表,新链表根据原链表遍历得到
                    StudentLinkedList newList = new StudentLinkedList();
                    StudentNode newHead = newList.getHead(); //新链表的头节点
                    //遍历构造
                    boolean flag = true; //标记是否为第一次添加
                    while (old != null){
                        //头插法加入到新链表中
                        StudentNode newNode = new StudentNode(old.no, old.name, old.age);
                        if(flag){
                            newHead.next = newNode;
                            newNode.next = null;
                            flag = false;
                        }else {
                            newNode.next = newHead.next;
                            newHead.next = newNode;
                        }
                        old = old.next;
                    }
                    return newList;
                }
            

            以上方式虽然可以实现链表的反转,但是是以返回一个新的反转链表的形式,并没有真正意义上实现原地反转,下面介绍另一种方式:

            双指针:

            	/**
                 * 双指针就地反转链表
                 * @param head 接收链表的头节点,方法中会将链表反转
                 */
                public static void reverse(StudentNode head){
                    //如果当前链表为空 或者只有一个节点 直接返回即可
                    if (head.next == null || head.next.next == null){
                        return;
                    }
                    //辅助指针遍历原来的链表
                    StudentNode cur = head.next; //当前节点
                    StudentNode next = null; //指向cur的下一个节点
                    StudentNode reverseHead = new StudentNode("", "", 0);
                    //遍历原来的链表,每遍历一个节点,就取出,放在新链表的最前端
                    while (cur != null){
                        next = cur.next; //暂时保存当前节点的下一个节点
                        cur.next = reverseHead.next; //讲cur下一个节点放在链表最前端
                        reverseHead.next = cur;
                        cur = next; //cur后移动
                    }
                    head.next = reverseHead.next;
                    return;
                }
            

            2.4>

            从尾到头打印单链表

            方式一: 先将单链表反转,然后再打印。但是这样会破坏掉原有单链表的结构,而题目要求仅仅是打印,因此不建议!

            方式二: 利用栈模拟

            将单链表的各个节点压入栈中,利用栈先进后出的特点,实现逆序打印。

            参考代码:

                /**
                 * 利用栈模拟 实现链表的逆序打印
                 * @param head 链表的头节点
                 */
                public static void reversePrintList(StudentNode head){
                    if (head.next == null){
                        return; //空链表无法打印
                    }
                    //创建栈模拟逆序打印
                    Stack<StudentNode> stack = new Stack<>(); //栈
                    StudentNode cur = head.next;
                    //将链表的所有节点压入栈
                    while (cur != null){
                        stack.push(cur);
                        cur = cur.next;
                    }
                    //逆序打印
                    while (!stack.empty()){
                        //出栈
                        System.out.println(stack.pop());
                    }
                    return;
                }
            

            以上就是Java数据结构之单链表的实现与面试题汇总的详细内容,更多关于Java单链表的资料请关注易采站长站其它相关文章!