数据结构与算法 - 链表(java)

本文详细介绍了链表的概念,包括单向链表、双向链表和单向环形链表的介绍、操作(增、删、改、查)以及相关代码实现。着重讲解了链表的存储方式、节点结构,并通过实例展示了链表操作的逻辑。

链表介绍
链表是有序的列表,但是它在内存中是存储如下
在这里插入图片描述

小结上图:

  1. 链表是以节点的方式来存储,是链式存储
  2. 每个节点包含data域,next域:指向下一个节点.
  3. 如图:发现链表的各个节点不一定是连续存储.
  4. 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定

单向链表

介绍

单链表(带头结点)逻辑结构示意图如下
在这里插入图片描述

案例:实现链表 增 删 改 查 操作

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


1、直接添加到链表尾部
在这里插入图片描述

说明:

  1. 先创建一个head 头节点,不存放具体的数据,作用就是表示单链表的表头
  2. 后面每添加一个节点,就直接添加到链表的最后
    最后一个.next = 新结点

2、添加到链表中间(如排序)

在这里插入图片描述

说明:

  1. 首先找到新添加的节点的位置, 是通过辅助变量(temp), 遍历来搞定
  2. 新的节点.next = temp.next
  3. temp.next = 新的节点


在这里插入图片描述

说明:

  1. 先找到需要删除的这个节点的前一个节点 temp
  2. temp.next = temp.next.next
  3. 被删除的节点,将不会有其它引用指向,会被垃圾回收机制回收


1、先找到该节点,通过遍历
2、修改节点中的数据


通过遍历找到该结点

代码

【节点数据】

/* 链表数据 */
class Student{
    int id;
    String name;
    Student next; // 指向下一个节点

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student[ 学号 = " + id + ", 姓名 = " + name + ']';
    }
}

【单链表】

/* 单链表 */
class MyLinkedList{

    //初始化头结点,不存放具体数据
    private Student head = new Student(0,"");

    //遍历链表
    public void info(){
        //判断是否为空
        if (head.next == null){
            System.out.println("单向链表为空。");
            return;
        }
        //遍历
        Student temp = head.next; //辅助变量
        while (true){
            if (temp == null){//判断是否是最后一个
                break;
            }
            //输出
            System.out.println(temp);
            //temp 后移
            temp = temp.next;
        }
    }

    //添加结点,不考虑编号(id)顺序
    public void add(Student student){
        Student temp = head;//head 头结点不能动,temp 辅助遍历
        while (true){
            //找到最后一个
            if (temp.next == null){ //链表的最后一个为空
                break;
            }
            //没找到, temp后移
            temp = temp.next;
        }
        //将最后节点的 next 指向新的节点
        temp.next = student;
    }

    //添加结点,考虑编号(id)顺序
    public void addById(Student student){
        Student temp = head; //辅助变量
        boolean isFalse = false;//添加的编号(id)是否存在
        while (true){
            if (temp.next == null){ //链表最后
                break;
            }
            if (temp.next.id > student.id){ //编号比较,位置找到
                break;
            }
            if (temp.next.id == student.id){//要新添加的id值已经存在
                isFalse = true;//表示编号存在
                break;
            }
            //temp 后移
            temp = temp.next;
        }
        //判断id值是否存在
        if (isFalse){//存在
            System.out.println("准备添加的数据已存在,学号为 = " + student.id);
        }else{//不存在
            //插入 - 两个指向的顺序不要换
            student.next = temp.next; //①
            temp.next = student; //②
        }
    }

    //删 - 根据id值来删除节点
    public void delete(int id){
        Student temp = head; //辅助变量
        boolean flag = false; //是否找到要删除的节点
        while (true){
            if (temp.next == null){ //链表最后
                break;
            }
            if (temp.next.id == id) {//找到
                flag = true;
                break;
            }
            //temp 后移
            temp = temp.next;
        }
        //判断 是否找到
        if (flag){
            //找到 - 删除
            temp.next = temp.next.next;
        }else {
            //没找到
            System.out.println("要删除的节点学号("+ id +")不存在");
        }
    }

    //改 - 根据id值来修改节点数据
    public void upadte(Student student,String name){
        //是否为空
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }

        Student temp = head.next;
        boolean flag = false; //是否找到修改的节点
        while (true){
            if (temp == null) {//链表最后
                break;
            }
            if (temp.id == student.id){ //找到
                flag = true;
                break;
            }
            temp = temp.next;//后移
        }
        if (flag){
            temp.name = name;
        }else {
            System.out.println("没有找到要修改的节点,即不存在学号为:" + student.id + "的学生");
        }
    }

    //查 - 根据id值来查找节点
    public void select(int id){
        //判断是否为空
        if (head == null){
            System.out.println("链表为空");
            return;
        }
        Student temp = head.next;
        while (true){
            if (temp == null){
                break;
            }
            if (temp.id == id){
                System.out.println("找到了");
                System.out.println("学号 = " + temp.id + ";姓名 = " + temp.name);
                return;
            }
            temp = temp.next;
        }
        System.out.println("没有找到学号为" + id + "的学生");
    }
}

【测试】

/* 测试*/
public class SingleLinkedList {

    public static void main(String[] args) {
        //创建链表
        MyLinkedList list = new MyLinkedList();
        //创建节点
        Student st1 = new Student(1,"小小");
        Student st2 = new Student(2,"大大");
        Student st3 = new Student(3,"芊芊");
        Student st4 = new Student(4,"后后");
        Student st5 = new Student(5,"佐佐");
        
        //添加
        list.addById(st5);
        list.addById(st1);
        list.addById(st2);
        list.addById(st4);
        list.addById(st3);

        //删除
        list.delete(1);
        list.delete(6);

        //修改
        list.upadte(st2,"星星");

        //查
        list.select(2);

        //遍历
        list.info();
    }
}

双向链表

介绍

管理单向链表的缺点分析:
1)单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
2)单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除。

在这里插入图片描述

实现双向链表 增 删 改 查 操作

【增】
方式一: 默认添加到双向链表的最后
在这里插入图片描述


temp.next = newStudent; newStudent.pre = temp;

方式二:按编号顺序添加
在这里插入图片描述


添加在链表最后:
temp.next = newStudent; newStudent.pre = temp;
添加在中间:
1、newStudent.next = temp.next; temp.next.pre = newStudnet;
2、temp.next = newStudent; newStudent.pre = temp;

【删】
在这里插入图片描述


1、删除的是中间的节点
temp.pre.next = temp.next; temp.next.pre = temp.pre;
2、删除的是最后一个节点
temp.pre.next = temp.next;

【改】
1、先找到该节点,通过遍历
2、更改节点中的数据

【查】
通过遍历找到该结点

代码

【节点数据】

class MyStudent{
    int id;
    String name;
    MyStudent next; // 指向下一个节点
    MyStudent pre;//指向前一个节点

    public MyStudent(int id, String name) {
        this.id = id;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student[ 学号 = " + id + ", 姓名 = " + name + ']';
    }
}

【双向链表】

class DoubleList{
    MyStudent head = new MyStudent(0,"");//头结点

    //遍历
    public void show(){
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        MyStudent temp = head.next;
        while (temp != null){
            System.out.println(temp);//输出
            temp = temp.next;//后移
        }
    }

    //添加:不考虑编号顺序
    public void add(MyStudent student){
        MyStudent temp = head;
        //找到最后一个节点
        while (true){
            if (temp.next == null){
                break;
            }
            temp = temp.next;
        }
        //添加到最后
        temp.next = student;
        student.pre = temp;
    }

    //添加:考虑编号顺序
    public void addById(MyStudent student){
        MyStudent temp = head;
        while (true){
            //添加在链表最后
            if (temp.next == null){
                temp.next = student;
                student.pre = temp;
                break;
            }
            //添加在中间
            if (student.id < temp.next.id){
                student.next = temp.next;
                temp.next.pre = student;
                temp.next = student;
                student.pre = temp;
                break;
            }
            //要添加的编号已存在
            if (student.id == temp.id){
                System.out.println("添加编号" + student.id + "已存在,添加失败");
                break;
            }
            temp = temp.next;
        }
    }

    //修改
    public void update(int index,String name){
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        MyStudent temp = head.next;
        while (true){
            //遍历完整个链表
            if (temp == null){
                System.out.println("不存在这个节点编号");
                break;
            }
            // 修改
            if (index == temp.id){
                temp.name = name;
                break;
            }
            temp = temp.next;
        }
    }

    //删除
    public void delete(int index){
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        MyStudent temp = head.next;
        while (true){
            if (temp == null){
                System.out.println("不存在这个节点");
                return;
            }
            if (temp.id == index){
                break;
            }
            temp = temp.next;
        }
        //删除
        //1、删除中间节点 2、删除最后节点
        if (temp.next == null){ //最后
            temp.pre.next = temp.next;
        }else {//中间
            temp.pre.next = temp.next;
            temp.next.pre = temp.pre;
        }
    }

    //查找
    public void select(int index){
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        MyStudent temp = head.next;
        while (true){
            if (temp == null){
                System.out.println("没找到编号为" + index + "的学生信息");
                return;
            }
            if (temp.id == index){
                break;
            }
            temp = temp.next;
        }
        System.out.println("找到编号为" + temp.id +"的信息;" + temp);
    }

}

【测试】

public class DoubleLinkedList {
    public static void main(String[] args) {
        DoubleList list = new DoubleList();
        MyStudent st1 = new MyStudent(1,"小小");
        MyStudent st2 = new MyStudent(2,"大大");
        MyStudent st3 = new MyStudent(3,"芊芊");
        MyStudent st4 = new MyStudent(4,"后后");
        MyStudent st5 = new MyStudent(5,"佐佐");

        //添加
        list.addById(st5);
        list.addById(st1);
        list.addById(st2);
        list.addById(st4);
        list.addById(st3);

        //删除
        list.delete(1);
        list.delete(5);

       //修改
        list.update(1,"星星");

        //查
        list.select(1);

        //遍历
        list.show();

    }
}

单向环形链表

单向环形链表 - 约瑟夫问题

介绍

Josephu问题为
设编号为1,2,…n的n个人围坐一圈,
约定编号为k(1<=k<=n)的人从1开始报数,
数到m的那个人出列,它的下一位又从1开始报数,
数到m的那个人又出列,依次类推,
直到所有人出列为止,由此产生一个出队编号的序列。

提示
用一个不带头结点的循环链表来处理Josephu问题:
先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,
计到m时,对应结点从链表中删除,
然后再从被删除结点的下一个结点又从1开始计数,
直到最后一个结点从链表中删除算法结束。

代码

【节点】

class Boy{
    public int no;//编号
    public Boy next;//指向下一个节点

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

【链表】

class CircleLinkedList{
    //创建first节点
    private Boy first = null; //第一个节点

    //添加小孩形参环
    public void add(int num){
        //校验
        if (num < 1){
            System.out.println("小孩人数要大于1");
            return;
        }
        Boy temp = null;//辅助指针
        for (int i = 1; i <= num; i++) {
            Boy boy = new Boy(i);//根据编号创建小孩节点
            //第一个小孩
            if (i == 1){
                first = boy;
                first.next = first; //形成环
                temp = first;
            }else {
                temp.next = boy;
                boy.next = first;
                temp = boy;
            }
        }
    }

    //遍历
    public void show(){
        //判断是否为空
        if (first == null){
            System.out.println("链表为空");
            return;
        }
        Boy temp = first;//first不能动
        while (true){
            System.out.println("小孩编号为:" + temp.no);
            if (temp.next == first){//遍历完成
                return;
            }
            temp = temp.next;//temp后移
        }
    }

    /**
     * 根据用户的输入,计算出小孩出圈的顺序
     * @param no 表示从第几个小孩开始数数
     * @param count 表示数几下
     * @param num 表示最初有多少小孩在圈中
     */
    public void counts(int no,int count,int num){
        //验证
        if (no < 1 || first == null || no > num || count < 0){
            System.out.println("参数输入有误,重新输入");
            return;
        }
        
        Boy helper = first;//辅助指针,指向最后节点
        while (true){
            if (helper.next == first){
                break;
            }
            helper = helper.next;
        }

        //指向传入的第几个小孩节点(no)
        for (int i = 0; i < no - 1; i++) {
            first = first.next;
            helper = helper.next;
        }

        //出圈
        while (true){
            if (helper == first){//当前链表中只有一个节点
                break;
            }
            //指针移动count - 1 下
            for (int i = 0; i < count - 1; i++) {
                first = first.next;
                helper = helper.next;
            }
            //first 指向要出圈的节点
            System.out.println("小孩" + first.no + "出圈");
            first = first.next;
            helper.next = first;
        }
        System.out.println("最后留在圈中的小孩编号为" + first.no);
    }
}

【测试】

public class Josepfu {
    public static void main(String[] args) {
        //测试
        CircleLinkedList list = new CircleLinkedList();
        list.add(5);
        list.show();
        System.out.println("\n -------出圈------");
        //出圈
        list.counts(1,2,7);
        System.out.println("\n -------遍历------");
        list.show();
    }
}

学习记录- - 千

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值