• 单链表原理及应用举例


    单链表是一种链式存取的数据结构,单链表中的数据是以结点的形式存在,每一个结点是由数据元素和下一个结点的存储的位置组成。单链表与数组相比的最大差别是:单链表的数据元素存放在内存空间的地址是不连续的,而数组的数据元素存放的地址在内存空间中是连续的,这也是为什么根据索引无法像数组那样直接就能查询到数据元素。

    单链表的逻辑结构如下(带头节点):

     链表是有序的列表,他在内存中的存储结构如下:

     1.链表是以节点的方式来存储,是链式存储

     2.每个节点都包含data域和next域:指向下一个节点

     3.链表的各个节点不一定是连续存储的

     4.链表分带头节点的链表和没有头节点的链表(根据实际需求来确定)

     5.head节点:不存放具体的数据,作用就是表示单链表的头

    应用举例

    使用带头节点的单链表实现水浒英雄排名,进行基本增删改查操作

    添加思路分析:

    第一种:直接在链表尾部添加新英雄

    第二种:通过排名(no)来指定插入英雄位置

    分析示意图如下:

     

    1.head节点不能动,因此我们需要定义一个temp辅助节点找到待添加的节点的前一个节点,通过遍历实现
    2.新节点.next=temp.next
    3.temp.next=新节点.next

    代码实现
    package com.atxihua;
    
    import java.util.Stack;
    
    public class SingleLinkedListDemo {
        public static void main(String[] args) {
            //测试
            //先创建节点
            HeroNode hero4=new HeroNode(4,"林冲","豹子头");
            HeroNode hero3=new HeroNode(3,"吴用","智多星");
            HeroNode hero2=new HeroNode(2,"卢俊义","玉麒麟");
            HeroNode hero1=new HeroNode(1,"宋江","及时雨");
            SingleLinkedList singleLinkedList=new SingleLinkedList();
            //添加到链表
    //        singleLinkedList.add(hero1);
    //        singleLinkedList.add(hero2);
    //        singleLinkedList.add(hero3);
    //        singleLinkedList.add(hero4);
    
    
    
            //按照编号排序添加
            singleLinkedList.addByOrder(hero1);
            singleLinkedList.addByOrder(hero2);
            singleLinkedList.addByOrder(hero3);
            singleLinkedList.addByOrder(hero4);
    
            //显示链表
            singleLinkedList.list();
    
            //测试修改节点信息
            //修改吴用的nickname
            HeroNode newHeroNode=new HeroNode(3,"吴用","智多星偏偏无用");
           singleLinkedList.update(newHeroNode);
            System.out.println("修改后的链表");
            singleLinkedList.list();
            //删除豹子头林冲
            singleLinkedList.del(4);
            System.out.println("删除节点后的链表");
            singleLinkedList.list();
    
            //后面的测试方法为大厂面试题
            System.out.println("求单链表的节点个数");
            System.out.println(singleLinkedList.getLength(singleLinkedList.getHead()));
    
            //测试一下看看是否得到了倒数第K个节点
            HeroNode res=singleLinkedList.findLastKNode(singleLinkedList.getHead(),1);
            System.out.println(res);
    
            //反转单链表
          singleLinkedList.reverseList(singleLinkedList.getHead());
          singleLinkedList.list();
    
            System.out.println("逆向打印单链表");
            singleLinkedList.reversePrint(singleLinkedList.getHead());
        }
    }
    //定义HeroNode,每个HeroNode对象就是一个节点
    class HeroNode{
        public int no;
        public String name;
        public String nickname;
        //指向下一个节点
        public HeroNode next;
        //构造器
        public  HeroNode(int hNo,String hName,String hNickName){
            this.no=hNo;
            this.name=hName;
            this.nickname=hNickName;
        }
        //为了显示方便,重写toString
        @Override
        public String toString() {
            return "HeroNode{" +
                    "no=" + no +
                    ", name='" + name + '\'' +
                    ", nickname='" + nickname + '\'' +
                    ", next=" + next +
                    '}';
        }
    
    }
    //定义一个SingleLinkedList来管理英雄
    class SingleLinkedList{
        //先定义一个头节点,头节点不要动,不存放具体的数据
        private HeroNode head=new HeroNode(0,"","");
        //添加节点到单链表中
        //思路,当不考虑编号顺序时
        //1.找到当前链表的最后一个节点
        //2.将最后这个节点的next指向新节点
        public void add(HeroNode heroNode){
            //因为head节点不能动,因此我们需要一个辅助遍历temp
            HeroNode temp=head;
            //遍历链表,找到最后一个节点
            while (true){
                if(temp.next==null){
                    break;
                }
                //如果没有找到最后一个节点,将temp后移
                temp=temp.next;
            }
            //当退出while循环时,temp就指向了链表的最后
            //将最后这个节点的next指向新节点
            temp.next=heroNode;
        }
        //第二种方式在添加英雄时,根据排名将英雄插入到指定位置
        //如果有这个排名,则添加失败并给出提升
        public void addByOrder(HeroNode heroNode){
            //因为head节点不能动,因此我们需要一个辅助遍历temp
            HeroNode temp=head;
            //flag标志添加的编号是否存在,默认为false
            boolean flag=false;
            //遍历链表,找到最后一个节点
            while (true){
                if(temp.next==null){
                    //说明temp已经在链表的最后
                    break;
                }
                if(temp.next.no>heroNode.no){
                    //说明位置已找到,就在temp后面插入
                    break;
                }else if(temp.next.no==heroNode.no){
                    //说明该位置排名已有其他英雄,该编号已存在
                    flag=true;
                    break;
                }
                //如果没有找到,将temp后移
                temp=temp.next;
            }
            if(flag){//不能添加,说明编号存在
                System.out.println("该英雄排名的位置已有其他英雄!!!"+heroNode.no);
            }else {
                //插入到链表中,temp的后面
                heroNode.next=temp.next;
                temp.next=heroNode;
            }
        }
        //修改节点信息,根据编号no来修改,即no是不能修改的
        public void update(HeroNode newHeroNode){
            //判断链表是否为空
            if(head.next==null){
                System.out.println("链表为空");
                return;
            }
            //根据no找到需要修改的节点
            //定义一个辅助变量
            HeroNode temp=head.next;
            boolean flag=false;//是否找到该节点
            while (true){
                if(temp==null){
                    break;//已经遍历完链表
                }
                if(temp.no==newHeroNode.no){
                    //找到该节点
                    flag=true;
                    break;
                }
                //未找到,后移
                temp=temp.next;
            }
            if(flag){
                temp.name= newHeroNode.name;
                temp.nickname= newHeroNode.nickname;
            }else{
                System.err.println("该英雄不存在!!!请检查输入排名是否正确");
            }
        }
        //删除节点
        //1.head不能动,因此我们需要定义一个temp辅助节点找到待删除的节点的前一个节点
        //2.我们在比较时,是比较temp.next.no和需要删除节点的no
        public void del(int no){
            //判断链表是否为空
            if(head.next==null){
                System.out.println("链表为空");
                return;
            }
            HeroNode temp=head;
            //flag标志是否找到待删除的节点
            boolean flag=false;
            //遍历链表,找到最后一个节点
            while (true){
                if(temp.next==null){
                    //说明temp已经在链表的最后
                    break;
                }
                if(temp.next.no>no){
                    //说明位置已找到
                    break;
                }else if(temp.next.no==no){
                    //说明找到待删除节点的前一个节点temp
                    flag=true;
                    break;
                }
                //如果没有找到,将temp后移,遍历
                temp=temp.next;
            }
            if(flag){//找到,可以删除
                temp.next=temp.next.next;
            }else {
                System.err.println("未找到该英雄的信息!!!请确定排名编号是否正确");
            }
        }
        //显示链表,遍历
        public void list(){
            //判断链表是否为空
            if(head.next==null){
                System.out.println("链表为空");
                return;
            }
            //定义一个辅助变量
            HeroNode temp=head.next;
            while (true){
                //判断链表是否遍历到最后
                if(temp==null){
                    break;
                }
                //输出节点信息
                System.out.println(temp);
                //将temp后移,一定小心
                temp=temp.next;
            }
        }
    
        public HeroNode getHead() {
            return head;
        }
    
        //求链表的有效节点个数
        public static int getLength(HeroNode head){
            if(head.next==null){//空链表
                return 0;
            }
            int length=0;
            //定义一个辅助变量
            HeroNode cur=head.next;
            while (cur!=null){
                length++;
                cur=cur.next;//遍历
            }
            return length;
        }
        //查找单链表中的倒数第K个节点(新浪面试题)
        //编写一个方法,接受head节点,同时接收一个K
        //先把链表从头到尾遍历,得到链表的总长度getLength
        //得到长度size后,我们从链表的第一个开始遍历(size-K)个就可以了
        //如果找到,则返回该节点,否则返回null
        public  static HeroNode findLastKNode(HeroNode head,int K){
            //判断链表是否为空,返回null
            if(head.next==null){
                return  null;//没找到
            }
            //遍历得到链表长度
            int size=getLength(head);
            //第二次遍历size-K的位置
            //先做校验
            if(K<=0||K>size){
                return null;
            }
            //定义给辅助变量,for循环定位到倒是K的位置
            HeroNode cur=head.next;
            for(int i=0;i<size-K;i++){
                cur=cur.next;
            }
            return cur;
        }
    
        /*
        单链表的反转,腾讯面试题
        * 思路:
        * 先定义一个节点reverseHead=new HeroNode();
        * 从原来的链表从头遍历每一个节点,将其取出,并放在新的链表reverseHead的最前端
        * 原来的链表的head.next=reverseHead.next
        * */
        public static  void reverseList(HeroNode head){
            //如果当前链表为空,或者只有一个节点,无需反转,直接返回
            if(head.next==null||head.next.next==null){
                return;
            }
            //定义一个辅助变量,帮助我们遍历原链表
            HeroNode cur =head.next;
            HeroNode next=null;//指向当前节点[cur]的下一个节点
            HeroNode reverseHead=new HeroNode(0,"","");
            while (cur!=null){
                next=cur.next;//先暂时保存当前节点的下一个节点,因为后面需要使用
                cur.next=reverseHead.next;//将cur的下一个节点指向新的链表的最前端
                reverseHead.next=cur;//将cur连接到新链表上
                cur=next;//让cur后移
            }
            //将head.next指向reveseHead.next,实现单链表的反转
            head.next=reverseHead.next;
        }
        /*
        * 从头到尾打印单链表(百度)
        * 思路
        * 1.先将单链表进行反转操作,然后遍历即可,这样做会破坏原来的单链表结构,不建议
        * 2.可以利用栈这个数据结构,将各个节点压入到栈中,利用栈先进后出的特点,就实现了逆序打印
        * */
        public static  void reversePrint(HeroNode head){
            if(head.next==null){
                return;//空链表,无法打印
            }
            //创建一个栈,将各个节点压入栈中
            Stack<HeroNode> stack=new Stack<HeroNode>();
            HeroNode cur=head.next;
            //将链表中的所有节点压入栈中
            while (cur!=null){
                stack.push(cur);
                cur=cur.next;//cur后移,继续压入下一节点
            }
            //将栈中的节点进行打印,pop出栈
            while (stack.size()>0){
                System.out.println(stack.pop());//先进后出
            }
        }
        }
    
    
    

    结果展示:

    结果与预期结果一致,达到使用单链表实现的预期结果。

    后面的大厂面试题结果没有展示,只有反转单链表有点麻烦。

    思路分析图如下:

     

     代码分析

    public static  void reverseList(HeroNode head){
            //如果当前链表为空,或者只有一个节点,无需反转,直接返回
            if(head.next==null||head.next.next==null){
                return;
            }
            //定义一个辅助变量,帮助我们遍历原链表
            HeroNode cur =head.next;
            HeroNode next=null;//指向当前节点[cur]的下一个节点
            HeroNode reverseHead=new HeroNode(0,"","");
            while (cur!=null){
                //先暂时保存当前节点的下一个节点,因为后面需要使用,此时,cur=数据2,next=数据5
                next=cur.next;
                //将cur的下一个节点指向新的链表的最前端,此时,新的reverseHead头指针指向数据5
                cur.next=reverseHead.next;
                //将cur连接到新链表上,此时reverHead的头指针指向数据5,在数据5之后增加了节点2,此时单链表为reverseHead=>5=>2
                reverseHead.next=cur;
                //让cur后移,此时cur=5,重复上述操作,最终得到reverseHead=>9=>5=>2
                cur=next;
            }
            //将head.next指向reveseHead.next,实现单链表的反转,
            // 此时reverseHead单链表已经实现反转,
            // 原来的单链表结构已经破坏,将原来的单链表的头指针,指向reverseHead的第一个节点,即得到反转后的单链表
            head.next=reverseHead.next;
        }
  • 相关阅读:
    linux下拼接字符串的代码
    postgresql实现插入数据返回当前的主键ID
    记录一个linux下批处理的代码
    iptables
    mybatis获得执行insert的返回值
    git commit之后撤销
    仿照CIFAR-10数据集格式,制作自己的数据集
    C/C++ 在处理文件所在路径下创建子目录
    C/C++ 图像二进制存储与读取
    C/C++ 文件路径解析
  • 原文地址:https://www.cnblogs.com/ftx3q/p/15699377.html
Copyright © 2020-2023  润新知