单链表原理及应用举例

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

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

 

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

 

 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;
    }

 

posted @ 2021-12-16 22:22  活在记忆里的人  阅读(830)  评论(0编辑  收藏  举报