带你走进Java数据结构与算法(四)

107 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第十五天,点击查看活动详情

🤗前言:上文学习了队列,本文带领大家走进单链表~

链表

Linked List

  1. 带头节点的链表
  2. 没有头节点的链表
  • 有序的列表,链表以节点方式存储,链式存储
  • 每个节点包含 data 域 ,next 域:指向下一个节点
  • 链表的节点之间不一定是连续的

单链表

带头结点(单链表)逻辑结构示意图

image-20220807215722911

内存布局图

image-20220807215621747

水浒传英雄排行榜管理

分析

  1. 完成CRUD功能

  2. 添加英雄方式

    1. 直接添加到链表尾部
    2. 根据排名将英雄插入到指定位置(英雄存在,则无效)
  3. 单链表创建示意图

    image-20220807221131311

单链表添加节点

  1. 先创建一个head头节点,单链表的头
  2. 每添加一个节点,就直接加入到链表的最后遍历

遍历

  1. 通过一个变量遍历,帮助遍历整个链表
package com.linkedlist;
​
/**
 * @author Kcs 2022/8/7
 */
public class SingleLinkedListDemo {
​
    public static void main(String[] args) {
        //测试
        //创建节点
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
​
        //添加进入链表
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        singleLinkedList.add(hero1);
        singleLinkedList.add(hero2);
        singleLinkedList.add(hero3);
        singleLinkedList.add(hero4);
​
        //显示
        singleLinkedList.list();
    }
​
}
​
/**
 * 定义一个SingleLinkedList管理英雄
 */
class SingleLinkedList {
    /**
     * 初始化一个头节点
     */
    private HeroNode head = new HeroNode(0, "", "");
​
    /**
     * 添加节点到单向链表
     * 找当前链表的最后节点
     * 最后节点的next 指向新的节点
     */
    public void add(HeroNode heroNode) {
​
        //添加一个临时变量temp
        HeroNode temp = head;
        //遍历链表,找到最后
        while (true) {
            //找到链表的最后
            if (temp.next == null) {
                break;
            }
            //如果没有找到,temp后移
            temp = temp.next;
​
        }
        //退出循坏,temp就指向链表的最后
        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);
            //将temp后移,不然就是死循环
            temp = temp.next;
        }
    }
}
​
​
/**
 * 定义一个HeroNode,每个heroNode对象就是一个节点
 */
class HeroNode {
    /**
     * 编号
     */
    public int no;
    /**
     * 姓名
     */
    public String name;
    /**
     * 昵称
     */
    public String nickname;
    /**
     * 指向下一个节点
     */
    public HeroNode next;
​
    /**
     * 构造器
     * @param no
     * @param name
     * @param nickname
     */
    public HeroNode(int no, String name, String nickname) {
        this.no = no;
        this.name = name;
        this.nickname = nickname;
    }
​
​
    /**
     * 重写toString
     */
    @Override
    public String toString() {
        return "Heroes{" +
                "no=" + no +
                ", name='" + name + ''' +
                ", nickname='" + nickname + ''' +
                '}';
    }
}

以上代码,添加的顺序改变是无序的,就按添加的代码进行排序(也就是第一种方式)

方式二添加英雄方式

  • 根据排名(编号)将英雄插入到指定位置(英雄存在,则无效)

分析

  1. 首先找到新添加的节点的位置,通过辅助变量(指针),通过遍历
  2. 新的节点.next = temp.next
  3. temp.next = 新的接点
package com.linkedlist;
​
import com.sun.xml.internal.ws.util.StreamUtils;
​
/**
 * @author Kcs 2022/8/7
 */
public class SingleLinkedListDemo {
​
    public static void main(String[] args) {
        //测试
        //创建节点
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
​
        //添加进入链表
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        // singleLinkedList.add(hero1);
        // singleLinkedList.add(hero2);
        // singleLinkedList.add(hero3);
        // singleLinkedList.add(hero4);
​
        //根据编号添加
        singleLinkedList.addHeroByOrder(hero4);
        singleLinkedList.addHeroByOrder(hero1);
        singleLinkedList.addHeroByOrder(hero3);
        singleLinkedList.addHeroByOrder(hero2);
        
        
        //显示
        singleLinkedList.list();
    }
​
}
​
/**
 * 定义一个SingleLinkedList管理英雄
 */
class SingleLinkedList {
    /**
     * 初始化一个头节点
     */
    private HeroNode head = new HeroNode(0, "", "");
    
    /**
     * 根据排名(编号)将英雄插入到指定位置(英雄存在,则无效)
     */
    public void addHeroByOrder(HeroNode heroNode) {
        //设置一个临时遍历,找到要添加的位置 ,temp位于添加位置的前一个节点,否则插入失败
        HeroNode temp = head;
        //标志编号是否存在
        boolean flag = false;
​
        while (true) {
            //到链表的最后,为空
            if (temp.next == null) {
                break;
            }
            //确定添加位置
            if (temp.next.no > heroNode.no) {
                break;
            } else if (temp.next.no == heroNode.no) {
                //添加的编号已存在,无效
                flag = true;
                break;
            }
            //后移,继续遍历
            temp = temp.next;
        }
​
        //判断flag
        if (flag) {
            //已存在编号
            System.out.printf("该英雄编号%d已存在,不能添加\n", heroNode.no);
        } else {
            //插入链表中,temp的后面
            heroNode.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);
            //将temp后移,不然就是死循环
            temp = temp.next;
        }
    }
}
​
​
/**
 * 定义一个HeroNode,每个heroNode对象就是一个节点
 */
class HeroNode {
    /**
     * 编号
     */
    public int no;
    /**
     * 姓名
     */
    public String name;
    /**
     * 昵称
     */
    public String nickname;
    /**
     * 指向下一个节点
     */
    public HeroNode next;
​
    /**
     * 构造器
     * @param no
     * @param name
     * @param nickname
     */
    public HeroNode(int no, String name, String nickname) {
        this.no = no;
        this.name = name;
        this.nickname = nickname;
    }
​
​
    /**
     * 重写toString
     */
    @Override
    public String toString() {
        return "Heroes{" +
                "no=" + no +
                ", name='" + name + ''' +
                ", nickname='" + nickname + ''' +
                '}';
    }
}

单链表修改节点

修改英雄的信息

/**
 * 修改节点信息,根据编号修改,编号不能修改
 * 根据newHeroNode 的 no 来修改信息
 */
public void update(HeroNode updateHeroNode) {
    //判断链表是否空
    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 == updateHeroNode.no) {
            flag = true;
            break;
        }
​
        //后移
        temp = temp.next;
    }
    //判断是否为要修改的节点
    if (flag) {
        temp.name = updateHeroNode.name;
        temp.nickname = updateHeroNode.nickname;
    } else {
        //没有找到
        System.out.printf("没有找到编号 %d的信息,不能修改\n", updateHeroNode.no);
    }
}

单链表删除节点

  1. 找到要删除节点的前一个节点temp
  2. temp.next = temp.next.next (跳过的节点,就是直接删除掉,被垃圾回收了)
/**
 * 删除节点,不能改变head 节点,通过temp找到待删除的节点的前一个节点
 * temp.next.no 与删除节点的no比较
 */
​
public void delete(int no) {
    HeroNode temp = head;
    //标志是否找到待删除的节点
    boolean flag = false;
    while (true) {
        //temp到链表最后
        if (temp.next == null) {
            break;
        }
        if (temp.next.no == no) {
            //找到要删除节点的前一个节点
            flag = true;
            break;
        }
        //后移
        temp = temp.next;
    }
    //判断flag
    if (flag) {
        //找到 ,删除
        temp.next = temp.next.next;
    } else {
        System.out.println("要删除的节点%d不存在\n" + no);
    }
}

单链表查询节点

/**
 * 查找英雄信息
 */
public void search(int no) {
    //判断链表是否为空
    HeroNode temp = head;
    if (temp.next == null) {
        System.out.println("链表为空!!!");
        return;
    }
    //判断是否找到节点
    boolean flag = false;
    while (true) {
        //temp到链表最后
        if (temp.next == null) {
            break;
        }
        if (temp.no == no) {
            //找到要查找节点的前一个节点
            flag = true;
            break;
        }
        //后移
        temp = temp.next;
    }
    //判断flag
    if (flag) {
        //查找到
        System.out.println(temp);
    } else {
        System.out.println("要查找的节点%d不存在\n" + no);
    }
}

单链表面试题

  • 单链表(节点)反转

    1. 定义一个节点 reverse = new HeroNode()
    2. 从头到尾遍历原来的链表,每遍历一个节点,并放在新的链表的最前端,最前端的next域指向上一个最前端的地方,原来的链表的第一个就没有next
    3. 原来的链表的 head.next = reverseHead.next
    //将链表反转
    public static void reverseList(HeroNode head) {
        //如果链表为空,或者只有一个节点,无须反转,直接返回
        if (head.next == null || head.next.next == null) {
            return;
        }
        //辅助变量
        HeroNode cur = head.next;
        //指向当前节点(cur节点)的下一个节点,
        HeroNode next = null;
        HeroNode reverseHead = new HeroNode(0, "", "");
    ​
        //遍历原来的链表,每遍历一个新的节点,就将其取出,放在新的链表reverseHead 的最前端
        while (cur != null) {
            //暂存当前节点的下一个节点
            next = cur.next;
            //将cur的下一个节点的指向新的链表的最前端
            cur.next = reverseHead.next;
            //cur链接到新的链表上
            reverseHead.next = cur;
            //cur后移,不能是cur.next 因为反转后的next以及不存在了
            cur = next;
        }
        //将原始链表的head.next指向reverseHead.next 实现反转
        head.next = reverseHead.next;
    }