⭐链表⭐【JAVA】

本文围绕链表数据结构展开,介绍了链表以节点存储、链式存储等特点。详细阐述单链表的不排序添加遍历、有序添加、修改、删除操作及相关例题,还讲解了双向链表和环形链表的操作思路,如双向链表的遍历、添加、删除、修改,环形链表的构建、遍历及小孩出圈问题。

1.链表的概述

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

2.每个节点包含data域,next域(指向下一个节点)

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

4.带头节点链表和不带头结点链表

5.在内存中的存储结构如下图:

注意:链表的各个节点不一 定是连续存


2.单链表

例子-1:不排序添加遍历

使用带头结点的单向链表,实现-英雄的排行榜管理!

思路:

 head节点:表示单链表的头,不存放具体数据 

 next:指向下一个节点,最后不在指向,默认为null

 1.添加:①先创建一个head节点,作用表示单链表的头②每增加一个节点,直接加到链表的后面

 2.遍历:通过一个辅助变量,帮助遍历整个链表

public class SingLinkedListDemo {
    public static void main(String[] args) {
        //测试
        //创建节点
        HeroNode h1 = new HeroNode(1, "张飞", "射手");
        HeroNode h2 = new HeroNode(2, "赵云", "辅助");
        HeroNode h3 = new HeroNode(3, "关羽", "中单");

        //创建一个链表
        SingLinkedList singLinkedList = new SingLinkedList();
        //加入
        singLinkedList.add(h2);
        singLinkedList.add(h1);
        singLinkedList.add(h3);
        //显示
        singLinkedList.list();

    }
}

/**
 * 定义链表,管理英雄
 */
class SingLinkedList {

    //设置头节点,头节点不能动(代表整个链表),不存放具体数据
    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.next;
        }
        //当退出循环时,temp就指向最后
        //将next指向新的节点
        temp.next = heroNode;
    }
    //显示链表【遍历】

    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);
            //将next后移,不后移是死循环
            temp = temp.next;
        }

    }

}


/**
 * 定义一个节点,每个HeroNode 对象就是一个节点
 */
class HeroNode {
    public int no;
    public String name;
    public String nickname;
    public HeroNode next; //指向下一个节点

    public HeroNode() {
    }

    public HeroNode(int no, String name, String nickname) {
        this.no = no;
        this.name = name;
        this.nickname = nickname;
    }

    //显示方便,重写toString
    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickname='" + nickname + '\''+"}";
    }
}

显示的英雄排序和自己添加的先后顺序有关,没有考虑排序~

例子-2:有序添加

需要按照编号添加

1.首先找到新添加节点的位置,通过辅助变量(遍历)

2.新的节点.next=temp.next

3.将temp.next=新的节点

 public void add2(HeroNode heroNode) {
        //头节点不能动,通过辅助变量找到添加的位置
        //我们的辅助变量temp,找的是添加位置的前一个节点,否则查入不了
        HeroNode temp = head;
        Boolean flag = false;//标识添加的编号是否存在,默认false
        while (true) {
            if (temp.next == null) {//说明temp已经是链表尾
                break;
            }
            if (temp.next.no > heroNode.no) {//说明位置应尽找到
                break;
            } else if (temp.next.no == heroNode.no) {//已经存在
                flag = true;//标识节点已经存在
                break;
            }
            temp = temp.next;//后移遍历
        }
        if (flag == true) {//说明存在,不再添加
            System.out.println("该英雄已经存在,不能加入!编号:"+heroNode.no);
        }else {
            //加入链表
            heroNode.next=temp.next;
            temp.next=heroNode;
        }

    }

例子-3:修改

1.定义一个辅助变量

2.根据编号进行查找

 public void update(HeroNode heroNode) {
        //判断列表是否为空
        if (head.next == null) {
            System.out.println("列表为空~");
            return;
        }
        //找到需要修改的节点,根据编号(no)
        //定义一个辅助变量
        HeroNode temp = head;
        boolean flag = false;//标识是否找到
        while (true) {
            if (temp == null) {//链表的最后
                break;
            }
            if (temp.no == heroNode.no) {
                flag = true;
                break;
            }
            //指针后移
            temp = temp.next;
        }
        //根据flag判断是否找到修改的节点
        if (flag == true) {
            temp.name = heroNode.name;
            temp.nickname = heroNode.nickname;
        } else {
            System.out.println("链表中没有编号为:" + heroNode.no + "的英雄");
        }
    }

例子-4:删除

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

2.temp.next=temp.next.next (temp指向节点的下下一个节点)

3.被删除的节点,不会有其他引用指向,被垃圾回收机制回收

 public void delete(int no) {

        HeroNode temp = head;
        Boolean flag = false;//标识是否找到节点
        while (true) {
            if (temp.next == null) {//到链表的最后
                break;
            }
            if (temp.next.no == no) {//找到待删除节点的前一个结点
                flag = true;
                break;
            }
            //temp后移,继续寻找
            temp = temp.next;
        }
        if (flag == true) {
            temp.next = temp.next.next;
        } else {
            System.out.println("删除的英雄不存在~");
        }
    }

例题

1.求单链表中有效节点的个数?

 public  static int getLength(HeroNode head){
        if (head.next==null){//链表为空
            return 0;
        }
        int length=0;
        HeroNode temp=head.next;
        while (temp!=null){
            length++;
            temp=temp.next;
        }
        return length;
    }

要是测试的话,记得 给 head 声明 get方法哦~

2.查找单链表中倒数第n个节点?

public static HeroNode findLastIndex(HeroNode head,int index){//倒数第index节点
        if (head.next==null){//链表为空
            return null;//没有找到
        }
        //第一次遍历
        int size=getLength(head);//获取链表节点的个数

        //第二次遍历,(size-index)就是倒数的第n个节点---->下表从零开始
        //判断index是否有效
        if (index<=0||index>size){
            return null;
        }
        //定义辅助变量
        HeroNode temp=head.next;//指向第一个有效数据的节点
        for (int i=0;i<size-index;i++){
            temp=temp.next;
        }
        return temp;
    }

3.单链表如何反转?

注意理解:temp.next = reverseHead.next
  public static void reverseList(HeroNode head) {
        if (head.next == null || head.next.next == null) {//为空或者只有一个节点
            return;
        }
        //定义辅助指针,帮助遍历原来的链表
        HeroNode temp = head.next;
        HeroNode next = null;//指向当前节点的下一个节点
        HeroNode reverseHead = new HeroNode(0, "", "");

        //从头遍历原先的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端
        while (temp != null) {
            next = temp.next;//先保留当前节点的下一个节点,不然会断掉
            temp.next = reverseHead.next;//将temp的下一个节点指向新的链表的最前端
            reverseHead.next=temp;//将将temp连接到新的链表上
            temp = next;//让temp指向下一个节点
        }
        //将reverseHead在换成head
        head.next = reverseHead.next;

    }

3.双向链表

1.双链表可以向前或者向后查找

2.双向链表可以自我删除

3.遍历、添加、删除、修改的操作思路

        遍历:和单链表一样,只是可以向前、也可以向后查找

        添加(默认添加到最后):1.先找到双向链表的最后这个节点。                                                                                          2.temp.next=newHeroNode。                        

                                                3.newHeroNode.pre=temp;

        修改:和单向链表一样

        删除:1.因为是双向链表,可以自我删除某个节点

                   2.直接找到要删除的节点,比如temp

                   3.temp.pre.next=temp.next

                   4.temp.next.pre=temp.pre

  代码实现:

package linklist.doublelinklist;

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

        //创建测试节点
        HeroNode h1=new HeroNode(1,"张飞","辅助");
        HeroNode h2=new HeroNode(2,"马超","射手");
        HeroNode h3=new HeroNode(3,"赵云","打野");
        HeroNode h4=new HeroNode(4,"刘备","中单");
        HeroNode h5=new HeroNode(5,"关羽","上单");

        HeroNode h6=new HeroNode(5,"曹操","打野");

        //创建一个双链表
        DoubleLink doubleLink=new DoubleLink();

        doubleLink.add(h1);
        doubleLink.add(h2);
        doubleLink.add(h3);
        doubleLink.add(h4);
        doubleLink.add(h5);
        doubleLink.list();

        System.out.println("修改后=======");
        doubleLink.update(h6);
        doubleLink.list();

        System.out.println("删除后=======");
        doubleLink.delete(2);
        doubleLink.list();

    }
}

/**
 * 创建一个双链表的类
 */
class DoubleLink {

    //初始化一个头节点,头节点不要动,不存放具体数据
    private HeroNode head = new HeroNode(0, "", "");


    /**
     * 删除-----直接找到要删除的节点,找到后自我删除即可
     */
    public void delete(int no) {
        //判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空~");
            return;
        }

        //head头节点不能动,借用辅助接点temp
        HeroNode temp = head.next;
        boolean flag = false;//标志是否找到待删除的节点
        while (true) {
            if (temp == null) {//已经到链表最后节点的next
                break;
            }
            if (temp.no == no) {
                flag = true;//找到待删除的节点
                break;
            }
            //未找到,temp后移
            temp = temp.next;
        }
        if (flag) {//表示找到待删除的节点
            temp.pre.next = temp.next;

            //如果是最后一个节点,就不需要执行,否则出席那空指针
            if (temp.next != null) {
                temp.next.pre = temp.pre;
            }
        } else {
            System.out.println("要删除的节点【" + no + "】不存在~");
        }

    }


    /**
     * 修改 --- 和单向链表一样
     */
    public void update(HeroNode heroNode) {
        //判断链表是否为空
        if (head.next == null) {
            return;
        }

        //head不能动,需要一个辅助节点temp
        HeroNode temp = head;
        boolean flag = false;//表示是否找到该节点
        while (true) {
            //到链表最后
            if (temp == null) {
                break;
            }
            if (temp.no == heroNode.no) {
                flag = true; //找到修改节点
                break;
            }
            //未找到,temp后移
            temp = temp.next;
            ;
        }
        //根据flag判断是否找到要修改的节点
        if (flag) {
            temp.name = heroNode.name;
            temp.nickname = heroNode.nickname;
        } else {
            System.out.println("未找到要修改的节点~");
        }

    }


    /**
     * 添加:默认添加到链表尾部
     */
    public void add(HeroNode heroNode) {
        //因为head节点不能动,因此我们需要一个辅助节点temp
        HeroNode temp = head;
        while (true) {
            //找到链表尾部
            if (temp.next == null) {
                break;
            }
            //如果没有找到将temp后移
            temp = temp.next;
        }
        //退出循环,temp指向链表最后
        //形成一个双向链表
        temp.next = heroNode;
        heroNode.pre = temp;
    }

    /**
     * 遍历双向链表
     */
    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;
    }

}


/**
 * 定义HeroNode,每个HeroNode对象就是一个节点
 */
class HeroNode {
    public int no;
    public String name;
    public String nickname;
    public HeroNode next;//指向下一个节点,默认为null
    public HeroNode pre;//指向前一个结点,默认为null


    public HeroNode() {
    }

    public HeroNode(int no, String name, String nickname) {
        this.no = no;
        this.name = name;
        this.nickname = nickname;
    }

    //显示方便,重写toString
    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickname='" + nickname + '\'' + "}";
    }
}

4.环形链表

Josephu(与瑟夫)问题:

 

构建环形链表的思路:

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

2.后面没创建一个新的节点,就把该节点加入到环形链表中

遍历环形链表:

1.让一个辅助指针cur,指向first节点

2.然后while遍历该环形链表 ,cur.next==first结束

小孩儿出圈的思路:

1.创建一个辅助指针helper,刚开始指向环形链表的最后这个节点,

2.当小孩报数前,先让first和helper移动k-1次,报数时,让first和helper指针同时移动m-1次

3.这时就可以将first指向的小孩儿节点出圈

3.1first=first.next;

3.2helper.next=first;

出圈的顺序:2-4-1-5-3

代码实现:

public class JosepFu {
    public static void main(String[] args) {
        CircleSingleLinked circleSingleLinked = new CircleSingleLinked();
        circleSingleLinked.addBoy(125);

        circleSingleLinked.count(10,20,125);

    }
}


//创建一个环形单向链表
class CircleSingleLinked {
    //创建一个first节点,当前无编号
    private Boy first = null;

    /**
     * 添加节点,构建成环形链表
     */
    public void addBoy(int nums) {
        //判断nums
        if (nums < 1) {
            System.out.println("nums的值不正确!!!");
            return;
        }
        Boy cur = null;//辅助指针,帮助构建环形链表
        //使用for循环创建环形链表
        for (int i = 1; i <= nums; i++) {
            //根据编号创建小孩儿节点
            Boy boy = new Boy(i);
            //第一个节点
            if (i == 1) {
                first = boy;
                first.setNext(first);//构成环,只有一个节点
                cur = first;//让cur指向第一个节点
            } else {
                cur.setNext(boy);//指向新的节点
                boy.setNext(first);//新的节点指向第一个节点
                cur = boy;//辅助节点后移
            }
        }
    }

    /**
     * 遍历环状链表
     */
    public void list() {
        if (first == null) {
            System.out.println("链表为空");
            return;
        }
        //first头节点不动,借用辅助指针
        Boy temp = first;
        while (true) {
            System.out.println("当前节点:" + temp.getNo());
            if (temp.getNext() == first) {
                break;
            } else {
                temp = temp.getNext();//指针后移
            }
        }
    }

    /**
     * 根据 用户的输入,计算出小孩出圈的顺序
     * strNo:从第几个节点开始数数
     * countNo:表示数几次
     * nums:表示有几个节点
     */
    public void count(int strNo, int countNo, int nums) {
        //对数据进行校验
        if (first == null || strNo < 1 || strNo > nums) {
            System.out.println("参数输入有误~~");
            return;
        }
        //创建一个辅助指针
        Boy helper = first;
        while (true) {
            if (helper.getNext() == first) {//说明helper时最后有个节点
                break;
            } else {
                helper = helper.getNext();
            }
        }//结束循环找到helper指向最后这个节点
        //报数时,让first和helper指针同时移动k-1次
        for (int i=0;i<strNo-1;i++){
            first=first.getNext();
            helper=helper.getNext();
        }
        //让小孩报数,让first和helper指针同时移动m-1次,然后出圈,知道圈中只有一个节点
        while (true){
            if (helper==first){//说明圈中只有一个节点
                break;
            }
            //让first和helper同时移动countNo-1次
            for (int i=0;i<countNo-1;i++){
                first=first.getNext();
                helper=helper.getNext();
            }//for循环结束后,这时的first这个节点就是要移除的节点
            System.out.println("小孩儿:"+first.getNo()+"出圈");
            //这时让first出圈
            first=first.getNext();
            helper.setNext(first);//helper指向新的first
        }//退出while循环时,只有一个节点
        System.out.println("最后的节点为:"+first.getNo());
    }

}

//创建一个Boy类,表示一个节点
class Boy {
    private int no;//编号
    private Boy next;//指向下一个节点

    public Boy(int no) {
        this.no = no;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public Boy getNext() {
        return next;
    }

    public void setNext(Boy next) {
        this.next = next;
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会敲代码的小张

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值