【java算法】稀疏数组/队列/单双链表

线性和非线性结构

在这里插入图片描述

稀疏数组

前言

引入
在这里插入图片描述
稀疏数组的介绍
在这里插入图片描述
在这里插入图片描述
二维数组和稀疏数组互转的思路
在这里插入图片描述

代码

二维数组转稀疏数组

public class SparseArray {
    public static void main(String[] args) {
        //创建一个原始的二维数组 11*11
        //0:表示没用棋子 1:表示黑子 2:表示蓝子
        int chessArr1[][] = new int[11][11];
        chessArr1[1][2] = 1;
        chessArr1[2][4] = 2;
        chessArr1[4][3] = 1;

        //输出原始的二维数组
        System.out.println("原始的二维数组:");
        //#1.java中的双循环特殊写法 --把下面的双循环更换看看效果--一样的
        for(int[] row : chessArr1){
            for (int data:row){
                System.out.printf("%d\t",data);
            }
            System.out.println();
        }
        //将二维数组 转为 稀疏数组
        //1.先遍历二维数组得到非0数据个数
        int sum = 0;
        for (int i = 0;i<11;i++){
            for (int j = 0;j<11;j++){
                if (chessArr1[i][j] != 0){
                    sum++;
                }
            }
        }
//        //新双循环版本
//        for (int[] row : chessArr1){
//            for (int data:row){
//                if (data != 0){
//                    sum++;
//                }
//            }
//        }
        System.out.println("sum = "+sum);

        //2.创建对应的稀疏数组
        int sparseArr[][] = new int[sum + 1][3];
        //给稀疏数组赋值
        sparseArr[0][0] = 11;
        sparseArr[0][1] = 11;
        sparseArr[0][2] = sum;
        //遍历二维数组,将非0的值存放到sparseArr中
        int count = 0;//用于记录是第几个非0的数据
        for (int i = 0; i < 11; i++) {
            for (int j = 0; j < 11; j++) {
                if (chessArr1[i][j] != 0){
                    count++;
                    sparseArr[count][0] = i;
                    sparseArr[count][1] = j;
                    sparseArr[count][2] = chessArr1[i][j];
                }
            }
        }

        //输出稀疏数组的形式
        System.out.println();
        System.out.println("得到稀疏数组为:");
        //#2.多维数组的length是行吗?
        for (int i = 0; i < sparseArr.length; i++) {
            //#3.只有printf可以处理类似%d的转义字符
            System.out.printf("%d\t%d\t%d\t\n",sparseArr[i][0],sparseArr[i][1],sparseArr[i][2]);
        }
        System.out.println();
    }
}

稀疏数组转为二维数组

System.out.println("=================================");

        //稀疏数组转为二维数组
        //1.先读取稀疏数组的第一行,工具第一行的数据,创建原始的二维数组
        int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];


        //2.在读取稀疏数组后几行的数据(从第二行开始),并赋值 原始的二维数组
        for (int i = 1; i < sparseArr.length; i++) {
            chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
        }

        //输出恢复后的二维数组
        System.out.println();
        System.out.println("恢复后的二维数组");

        for (int[] row : chessArr2){
            for (int data : row){
                System.out.printf("%d\t",data);
            }
            System.out.println();
        }

在这里插入图片描述

刷类型题

【题目】
稀疏数组搜索。有个排好序的字符串数组,其中散布着一些空字符串,编写一种方法,找出给定字符串的位置。
【示例1】
输入: words = [“at”, “”, “”, “”, “ball”, “”, “”, “car”, “”, “”,“dad”, “”, “”], s = “ta”
输出:-1
说明: 不存在返回-1
【示例2】
输入:words = [“at”, “”, “”, “”, “ball”, “”, “”, “car”, “”, “”,“dad”, “”, “”], s = “ball”
输出:4
【提示】
words的长度在[1, 1000000]之间

思路
思路
1.一维字符型数组转稀疏数组
稀疏数组构成:
num sum
13 1
pos ch
4 ball
2.拿ball去遍历查找稀疏数组
3.找到输出sparsearray[i][0]的值

问题:
1.没搞懂怎么自行输入空的元素
2.count要注意他的写法

关于这道题的快速解法
JAVA练习195-稀疏数组搜索

package sparseArrayDemo;

import java.util.Scanner;

public class T1 {
    public static void main(String[] args) {
        //创建一个一维字符数组
        String words[] = new String[13];
        //给原数组赋值
        words[0] = "at";
        words[4] = "ball";
        words[7] = "car";
        words[10] = "dad";

        //输入s的值
        Scanner scanner = new Scanner(System.in);
        String s = scanner.next();

        //转换为稀疏数组

        //获取sum值
        int sum = 0;
        for (int i = 0; i < words.length; i++) {
            //# 数组中空元素的表示
            if (words[i] != null){
                sum++;
            }
        }
        String sparseArr[][] = new String[sum+1][2];
        //给稀疏数组赋值
        //# toString的效率最高
        sparseArr[0][0] = Integer.toString(words.length);
        sparseArr[0][1] = Integer.toString(sum);

        int count = 0;
        for (int i = 0; i < words.length; i++) {
            if (words[i] != null){
                count++;
                sparseArr[count][0] = Integer.toString(i);
                sparseArr[count][1] = words[i];
            }
        }

        //输出稀疏数组 ok
//        for (int i = 0; i < sum+1; i++) {
//            for (int j = 0; j < 2; j++) {
//                //# %s表示
//                System.out.printf("%s\t",sparseArr[i][j]);;
//            }
//            System.out.println();
//        }

        //查找输入的s的位置
        boolean flag = false;
        for (int i = 1; i < sum; i++) {
            if (sparseArr[i][1].equals(s)){
                System.out.println(sparseArr[i][0]);
                flag = true;
            }
        }
        if (flag == false){
            System.out.println(-1);
        }
    }
}

在这里插入图片描述

队列

非环形队列

队列的概念
在这里插入图片描述
数组模拟队列
在这里插入图片描述数组模拟队列–存入分析
在这里插入图片描述
演示程序:

package ArrayQueueDemo;

import java.util.Scanner;

public class queueDY {
    public static void main(String[] args) {
        //创建一个队列
        ArrayQueue queue = new ArrayQueue(3);
        char key = ' '; //接收用户输入
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;    //构造菜单循环

        //输出菜单
        while (loop){
            System.out.println("s(show):显示队列");
            System.out.println("e(exit):退出程序");
            System.out.println("a(add):添加数据到队列");
            System.out.println("g(get):从队列取出数据");
            System.out.println("h(head):查看队列头的数据");

            key = scanner.next().charAt(0); //接收一个字符
            switch (key){
                case 's':
                    queue.showQueue();
                    break;
                case 'a':
                    System.out.println("输入一个数");
                    int value = scanner.nextInt();
                    queue.addQueue(value);
                    break;
                case 'g':
                    try{
                        int res = queue.getQueue();
                        System.out.printf("取出的数据是%d\n",res);
                    }catch (Exception e){
                        //TODO:handle exception
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try{
                        int res = queue.headQueue();
                        System.out.printf("取出的数据是%d\n",res);
                    }catch (Exception e){
                        //TODO:handle exception
                        System.out.println(e.getMessage());
                    }
                case 'e':
                    //#
                    scanner.close();
                    loop = false;
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序退出!");
    }

}
//使用数组模拟队列--编写一个ArrayQueue类
class ArrayQueue{
    private int maxSize;    //表示数组的最大容器
    private int front;         //队列头
    private int rear;       //队列尾
    private int[] arr;      //该数组用于存放数据,mono队列

    //创建队列的构造器
    public ArrayQueue(int arrMaxSize){
        maxSize = arrMaxSize;
        arr = new int[maxSize];
        front = -1; //指向队列的头部,(front是指向队列头的前一个位置)
        rear = -1;  //指向队列尾,(指向队列的最后一个数据)
    }

    //判断队列是否满
    public boolean isFull(){
        return rear == maxSize - 1;
    }

    //判断队列是否为空
    public boolean isEmpty(){
        return rear == front;
    }

    //添加数据到队列
    public void addQueue(int n){
        //判断队列是否满
        if (isFull()){
            System.out.println("队列满,不能加入到队列");
            return;
        }
        rear++; //让rear后移
        arr[rear] = n;
    }

    //获取队列的数据,出队列
    public int getQueue(){
        //判断队列是否空
        if (isEmpty()){
            //通过抛出异常
            throw new RuntimeException("队列空,不能取数据");
        }
        front++;    //front后移
        return arr[front];
    }

    //显示队列的所有数据
    public void showQueue(){
        //遍历
        if (isEmpty()){
            System.out.println("队列空,不能加入到队列!");
            return;
        }

        for (int i = 0; i < arr.length; i++) {
            System.out.printf("arr[%d]=%d\n",i,arr[i]);
        }
    }

    //显示队列的头数据,注意不是取出数据
    public int headQueue(){
        //判断
        if (isEmpty()){
            throw new RuntimeException("队列空,不能取数据");
        }
        return arr[front+1];
    }
}

bug:
1 showQueue()内i从0开始,所以取了数据也查看不到,只能用headQueue查看情况
2 现在用过的地方,就算是空的,也不能再用了,因为front和rear一直在++,所以肯定会超出
因为需要对数组进行取模,构成环形队列

环形队列

在这里插入图片描述
代码

1 showQueue改为i从front,解决bug1
2 取模变成了环形队列,解决bug2

package ArrayQueueDemo;

import java.util.Scanner;

public class CircleArrayQueue {
    public static void main(String[] args) {
        //创建一个队列
        //此时多了一个月定位置,所以需要+1
        CircleArray queue = new CircleArray(4);
        char key = ' '; //接收用户输入
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;    //构造菜单循环

        //输出菜单
        while (loop){
            System.out.println("s(show):显示队列");
            System.out.println("e(exit):退出程序");
            System.out.println("a(add):添加数据到队列");
            System.out.println("g(get):从队列取出数据");
            System.out.println("h(head):查看队列头的数据");

            key = scanner.next().charAt(0); //接收一个字符
            switch (key){
                case 's':
                    queue.showQueue();
                    break;
                case 'a':
                    System.out.println("输入一个数");
                    int value = scanner.nextInt();
                    queue.addQueue(value);
                    break;
                case 'g':
                    try{
                        int res = queue.getQueue();
                        System.out.printf("取出的数据是%d\n",res);
                    }catch (Exception e){
                        //TODO:handle exception
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try{
                        int res = queue.headQueue();
                        System.out.printf("队列的头数据是:%d\n",res);
                    }catch (Exception e){
                        //TODO:handle exception
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'e':
                    //#
                    scanner.close();
                    loop = false;
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序退出!");
    }


}
class CircleArray{
    private int maxSize;        //表示数组的最大容器
    private int front;          //队列头
    private int rear;           //队列尾
    private int[] arr;          //该数组用于存放数据,mono队列

    public CircleArray(int arrMaxSize){
        maxSize = arrMaxSize;
        arr = new int[maxSize];
        front = 0;
        rear = 0;
        //front和rear默认值为0,所以也可以不写
    }

    //判断队列是否满
    public boolean isFull(){
        return (rear + 1) % maxSize == front;
    }

    //判断队列是否为空
    public boolean isEmpty(){
        return rear == front;
    }

    //添加数据到队列
    public void addQueue(int n){
        //判断队列是否满
        if (isFull()){
            System.out.println("队列满,不能加入到队列");
            return;
        }
        //直接将数据加入
        arr[rear] = n;
        //将rear后移,这里必须考虑取膜
        rear = (rear + 1) % maxSize;
    }

    //获取队列的数据,出队列
    public int getQueue(){
        //判断队列是否空
        if (isEmpty()){
            //通过抛出异常
            throw new RuntimeException("队列空,不能取数据");
        }
        int value = arr[front];
        front = (front + 1) % maxSize;
        return value;
    }

    //球当前队列的有效数据个数
    public int size(){
        return (rear + maxSize - front) % maxSize;
    }

    //显示队列的所有数据
    public void showQueue(){
        //遍历
        if (isEmpty()){
            System.out.println("队列空,没有数据!");
            return;
        }
        //i从front开始,因为
        for (int i = front; i < front+size(); i++) {
            System.out.printf("arr[%d]=%d\n",i % maxSize,arr[i % maxSize]);
        }
    }

    //显示队列的头数据,注意不是取出数据
    public int headQueue(){
        //判断
        if (isEmpty()){
            throw new RuntimeException("队列空,不能取数据");
        }
        return arr[front];
    }
}

刷题

没看到只有关队列的题

单链表

单链表的定义

在这里插入图片描述
在这里插入图片描述

案例演示–代码

在这里插入图片描述
代码

1.按照顺序添加

package linkedlist;

public class SingleLinkedListD3emo {
    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.add(hero1);
        singleLinkedList.add(hero3);
        singleLinkedList.add(hero4);
        singleLinkedList.add(hero2);

        //显示操作
        singleLinkedList.list();

    }
}

//定义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就指向了链表的最后
        temp.next = heroNode;
    }

    //显示链表
    public void list(){
        //判断链表是否为空
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        //引入辅助变量temp
        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;   //指向下一个节点

    //构造器

    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 + '\'' +
                ", next=" + next +
                '}';
    }
}

要求:按照顺序添加
在这里插入图片描述

2.按英雄排名插入

链表的插入
在这里插入图片描述
代码:
(在SingleLinkedList类中添加方法addByOrder)

//第二种添加方式:按排名添加
    public void addByOrder(HeroNode heroNode){
        //因为头节点不能动,因此我们需要一个辅助指针temp
        //因为是单链表,所以我们找的temo是位于添加位置前一个节点,否则插入失败
        HeroNode temp = head;
        boolean flag = false;   //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){//说明希望添加的heroNode的标号已经存在
                flag = true;//说明标号存在
                break;
            }
            temp = temp.next;
        }
        if (flag){
            //说明编号存在
            System.out.printf("准备插入的英雄的编号%d已经存在了,不能加入\n",heroNode.no);
        }else {
            //插入到链表中(temp的后面)
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }

不论插入顺序,输出仍然保持有序:
在这里插入图片描述

3.根据no编号来修改节点信息

代码:(在SingleLinkedList类中添加方法)

//修改节点的信息,根据no编号来修改(no本身不能改
    //1.根据newHeroNode 的 no 来修改即可
    public void upDate(HeroNode newHeroNode){
        //判断是否为空
        if (head.next == null){
            System.out.println("链表为空!");
            return;
        }
        //找到需要修改的节点
        HeroNode temp = head.next;
        boolean flag = false;
        while (true){
            if (temp == null){
                break; //已经遍历完链表
            }
            if (temp.no == newHeroNode.no){
                //找到
                flag = true;
                break;
            }
            temp = temp.next;
        }
        //根据flag 判断是否找到要修改的节点
        if (flag){
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        }else {//没找到
            System.out.printf("没找到编号%d的节点,不能修改\n",newHeroNode.no);
        }
    }

在这里插入图片描述

4.删除节点

在这里插入图片描述SingleLinkedList类中

//删除节点
    public void del(int no){
        HeroNode temp = head;
        boolean flag = false;
        while (true){
            if (temp.next == null){
                //已经到链表的最后
                break;
            }
            if (temp.next.no == no){
                //找到的待删除节点的前一个节点temp
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag){
            //找到
            temp.next = temp.next.next;
        }else {
            System.out.printf("要删除的%d节点不存在\n",no);
        }
    }

main方法中

//删除一个节点
        singleLinkedList.del(1);
        singleLinkedList.del(4);
        singleLinkedList.del(2);
        singleLinkedList.del(3);
        System.out.println("删除后链表的情况:");

显示:
在这里插入图片描述

单链表刷题

在这里插入图片描述

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

在主类,但main外部设置方法:

    //获取单链表中有效节点的个数
    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;
    }

在main内部:

        System.out.println("有效的节点个数:"+getLength(singleLinkedList.getHead()));//4

在这里插入图片描述

2.查找单链表中的倒数第k个节点

自己版:

//查找单链表中的倒数第k个节点
    public static void getReK(int len,int k,HeroNode head){
        //转变为正数下的顺序数
        int no = len - k + 1;

        //去找no序号,并输出其信息
        HeroNode temp = head;
        boolean flag = false;
        while (true){
            if (temp.next == null){
                //已经走到最后都没找到
                break;
            }
            if (temp.next.no == no){
                //找到no序号,去输出其信息
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag){
            //找到
            //输出节点的信息
            System.out.println(temp.next);

        }else {
            System.out.printf("单链表中的不存在倒数第%d个节点!",k);
        }
    }
System.out.println("去查找单链表中的倒数第k个节点,k==");
        int k = scanner.nextInt();
        getReK(len,k,singleLinkedList.getHead());

老师版:

妙处:
1.方法内部的互相调用
2.声明此方法类型为HeroNode

//老师版
    //index: 倒数第K
    public static HeroNode findLastIndexNode(HeroNode head,int index){
        //如果链表为空
        if (head.next == null)
                return null;
        //得到链表的长度
        int size = getLength(head);
        //正数第K个节点的位置:size - index
        //先做一个index检验
        if (index <= 0 || index > size){
            return null;
        }
        HeroNode cur = head.next;
        for (int i = 0;i<size - index;i++){
            cur = cur.next;
        }
        return cur;
    }
HeroNode res = findLastIndexNode(singleLinkedList.getHead(),1);
        System.out.println("res="+res);

3.单链表的反转

思路:
在这里插入图片描述
代码:(老师版)
main方法内:

System.out.println("反转单链表:");
        reverseList(singleLinkedList.getHead());
        singleLinkedList.list();

main方法外主类内:

//将单链表反转
    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,"","");
        //遍历原来的链表,没遍历一个节点,就取出放在新链表reverseHead的最前端
        while (cur != null){
            next = cur.next;    //先暂时保存当前节点的下一个节点
            //完整的连接操作
            cur.next = reverseHead.next;//将cur的下一个节点指向链表的最前端
            reverseHead.next = cur;

            cur = next;//让cur后移
        }
        //将head.next指向reverseHead.next,实现反转的最后一步
        head.next = reverseHead.next;
    }

显示:
在这里插入图片描述

4.从尾到头打印单链表【方式1: 反向遍历。 方式2: Stack栈】

在这里插入图片描述

//可以利用栈这个数据结构,将各个节点压入栈中,
    // 然后利用栈的先进后出的特点,就实现了逆序打印的效果
    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()); //stack特点是先进后出
        }
    }

main中

//方式2:打印反转的单链表
        System.out.println("逆序打印单链表,但没有改变链表的结构:");
        reversePrint(singleLinkedList.getHead());

显示:
在这里插入图片描述

5.合并两个有序的单链表,合并之后的链表依然有序

在这里插入图片描述

【单链表四道经典例题(必会)】

链表的相交问题

(1)判断两个链表是否相交?
(2)如果相交请找到相交的节点。

思想:
在这里插入图片描述
解答:

Java 判断两个链表是否相交

判断链表是否存在回文

java判断单链表是否是回文链表


双向链表

思路:
在这里插入图片描述

双向连表的建立:

//创建一个双线链表的类
class DoubleLinkedList{
    //先初始化一个头节点,头节点不动,不存放
    private HeroNode2 head = new HeroNode2(0,"","");

    public HeroNode2 getHead() {
        return head;
    }

    //删除双向链表节点
    public void del(int no){

        //判断当前链表是否为空
        if (head.next == null){
            System.out.println("链表空,无法删除!");
            return;
        }
        HeroNode2 temp = head.next;
        boolean flag = false;
        while (true){
            if (temp.next == null){
                //已经到链表的最后
                break;
            }
            if (temp.no == no){
                //找到的待删除节点的前一个节点temp
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag){
            //找到
//            temp.next = temp.next.next;
            temp.pre.next = temp.next;
            //如果是最后一个节点,就不需要执行下面这句话,否则出现空指针
            if (temp.next != null){
                temp.next.pre = temp.pre;
            }
        }else {
            System.out.printf("要删除的%d节点不存在\n",no);
        }
    }

    //修改的操作和单向链表一样
    //只是节点类型改为HeroNode2
    public void upDate(HeroNode2 newHeroNode){
        //判断是否为空
        if (head.next == null){
            System.out.println("链表为空!");
            return;
        }
        //找到需要修改的节点
        HeroNode2 temp = head.next;
        boolean flag = false;
        while (true){
            if (temp == null){
                break; //已经遍历完链表
            }
            if (temp.no == newHeroNode.no){
                //找到
                flag = true;
                break;
            }
            temp = temp.next;
        }
        //根据flag 判断是否找到要修改的节点
        if (flag){
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        }else {//没找到
            System.out.printf("没找到编号%d的节点,不能修改\n",newHeroNode.no);
        }
    }

    public void add(HeroNode2 heroNode){
        //输出和使用temp的时候都有变.next,所以不用修改为head.next
        HeroNode2 temp = head;
        while (true){
            if (temp.next == null){
                break;
            }
            temp = temp.next;
        }
        //此时temp指向链表的最后
        //形成一个双向链表
        temp.next = heroNode;
        heroNode.pre = temp;
    }

    //显示链表,遍历方法同单向链表
    public void list(){
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        HeroNode2 temp = head.next;
        while (true){
            if (temp == null){
                break;
            }
            System.out.println(temp);
            temp = temp.next;
        }
    }


}
class HeroNode2{
    public int no;
    public String name;
    public String nickname;
    public HeroNode2 next;//指向下一个节点 默认为null
    public HeroNode2 pre;//指向下一个节点 默认为null

    //构造器
    public HeroNode2(int no, String name, String nickname) {
        this.no = no;
        this.name = name;
        this.nickname = nickname;
    }

    @Override
    public String toString() {
        return "HeroNode2{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickname='" + nickname + '\'' +
                '}';
    }
}

修改双向链表:

//先创建节点
        HeroNode2 hero1 = new HeroNode2(1,"宋江","及时雨");
        HeroNode2 hero2 = new HeroNode2(2,"卢俊义","玉麒麟");
        HeroNode2 hero3 = new HeroNode2(3,"吴用","智多星");
        HeroNode2 hero4 = new HeroNode2(4,"林冲","豹子头");

        //创建一个双线链表
        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
        doubleLinkedList.add(hero1);
        doubleLinkedList.add(hero2);
        doubleLinkedList.add(hero3);
        doubleLinkedList.add(hero4);

        doubleLinkedList.list();

        //修改
        HeroNode2 newHeroNode = new HeroNode2(4,"公孙胜","入云龙");
        doubleLinkedList.upDate(newHeroNode);
        System.out.println("修改后链表情况:");
        doubleLinkedList.list();

在这里插入图片描述

删除双向链表:

//删除
        doubleLinkedList.del(3);
        System.out.println("删除链表的情况:");
        doubleLinkedList.list();

在这里插入图片描述

小测–按排名添加

自己代码:
main 外:

//按照编号顺序添加
    public static void addByOdser(HeroNode2 heroNode,HeroNode2 head){
        HeroNode2 temp = head;
        boolean flag = false;   //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){//说明希望添加的heroNode的标号已经存在
                flag = true;//说明标号存在
                break;
            }
            temp = temp.next;
        }
        if (flag){
            //说明编号存在
            System.out.printf("准备插入的英雄的编号%d已经存在了,不能加入\n",heroNode.no);
        }else {
            //插入到链表中(temp的后面)
            heroNode.pre = temp;
            temp.next = heroNode;
        }
    }

main内:

//按编号no排序添加
        addByOdser(hero1,doubleLinkedList.getHead());
        addByOdser(hero2,doubleLinkedList.getHead());
        addByOdser(hero3,doubleLinkedList.getHead());
        addByOdser(hero4,doubleLinkedList.getHead());

输出:
在这里插入图片描述

发现问题:单向链表add与addByOder添加时
语句不同的原因
在这里插入图片描述
在这里插入图片描述
问题类似双向链表中的,del处理删除最后一个节点的情况
在这里插入图片描述

总结:
时刻注意着节点在最后一位时,它的next是否为空节点的情况

单向环形链表

介绍

在这里插入图片描述

应用场景–约瑟夫问题

在这里插入图片描述
在这里插入图片描述

代码实现

创建单向环形链表&环形单链表的添加

  • 思路

注意:
boy:不是指针,是一个节点对象
curboy:是一个指针

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 代码
package linkedlist;

public class Josepfu {
    public static void main(String[] args) {
        //测试-构建 环形链表 和 遍历输出
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(10);
        circleSingleLinkedList.showBoy();
    }
}
//创建一个环形单向链表
class CircleSingleLinkedList {
    //创建一个first急待你,当前没有编号(相当于赋初值,后面才给它确切的值
    private Boy first = new Boy(-1);

    //添加小孩节点,构建一个环形链表
    public void addBoy(int nums) {
        //nums做一个 数据检验
        if (nums < 1) {
            System.out.println("nums值不正确");
            return;
        }
        Boy curBoy = null;  //辅助指针,帮助构建
        //使用for来创建我们的环形链表
        //???为啥单向链表不能用for来创建
        for (int i = 1; i <= nums; i++) {
            //根据编号 创建小孩节点
            Boy boy = new Boy(i);
            //如果是第一个小孩
            if (i == 1) {
                first = boy;
                first.setNext(first);//构成环
                curBoy = first; //让curBoy指向第一个小孩
            } else {
                //一般添加节点操作
                curBoy.setNext(boy);
                boy.setNext(first);
                curBoy = boy;
            }
        }
    }

    //遍历当前环形链表
    public void showBoy() {
        //判断链表是否为空
        if (first == null) {
            System.out.println("没有任何小孩!");
            return;
        }
        //因为first不能动,所以仍然需要一个curBoy完成遍历
        Boy curBoy = first;
        while (true) {
            System.out.printf("小孩的编号 %d \n", curBoy.getNo());
            if (curBoy.getNext() == first) {//说明已经遍历完成
                break;
            }
            curBoy = curBoy.getNext();//curBoy后移
        }
    }
}
class Boy{
    private int no; //编号
    private Boy next;   //指向下一个节点,默认null

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

输出
在这里插入图片描述

环形单链表的出圈

在这里插入图片描述
CircleSingleLinkedList内方法:

//出圈小孩节点
    public void countBoy(int startNo,int countNum,int nums){
        //先对数据进行检验
        if (first == null || startNo < 1 || startNo > nums){
            System.out.println("参数输入有误,请重新输入");
            return;
        }
        //创建辅助指针
        Boy helper = first;
        //helper指针应该事先,指向环形链表的最后节点
        while (true){
            if (helper.getNext() == first){
                break;
            }
            helper = helper.getNext();
        }
        //小孩报数前,先让first和helper移动k - 1次
        for (int j = 0;j < startNo - 1;j++){
            first = first.getNext();
            helper = helper.getNext();
        }

        //当小孩报数时,让first和helper指针同时移动m - 1次,然后出圈
        //way:喜欢操作,直到全栈只有一个节点
        while (true){
            if (helper == first){
                //说明圈中只有一个节点
                break;
            }
            //让first和helper指针同时移动countNum - 1(m - 1)
            for (int i = 0; i < countNum - 1; i++) {
                first = first.getNext();
                helper = helper.getNext();
            }
            //这时forst指向的节点就是,要出圈的小孩节点
            System.out.printf("小孩%d出圈\n",first.getNo());
            //将first指向的节点出圈
            first = first.getNext();
            //???这样不就forst和helper指向同一个节点了? --理解错误
            //这是一整套完整的出圈操作  等价于:helper.next = first(还是不懂就去看图)
            helper.setNext(first);
        }
        System.out.printf("最后留在全栈的小孩编号%d\n",first.getNo());
    }

main内:

//测试-小孩出圈是否正常
        circleSingleLinkedList.countBoy(1,2,5);

在这里插入图片描述

刷题

2288 蓝桥杯2018年第九届真题 约瑟夫环

在这里插入图片描述
自己代码:(正确
main外

//刷题1
    public static int getLatNo(int k,int n,Boy first){
        int lastNo;//最后一个剩下的人编号
        //让helper指针应实现走,指向环形链表的最后节点
        Boy helper = first;
        while (true){
            if (helper.getNext() == first){
                break;
            }
            helper = helper.getNext();
        }

        //开始报数,让forst和helper同时移动k - 1,然后出圈
        while (true){
            if (helper == first){
                //说明圈中只剩下一个节点
                break;
            }
            //开始移动m - 1
            for (int i = 0; i < k - 1; i++) {
                first = first.getNext();
                helper = helper.getNext();
            }
            //让forst指向的节点出圈
            first = first.getNext();
            helper.setNext(first);
        }
        lastNo = first.getNo();
        return lastNo;
    }

小知识点

String类中的replaceAll方法:public String replaceAll(String regex,String replacement)
在这里插入图片描述

java中的双循环特殊写法

for(int[] row : chessArr1){
   for (int data:row){
        System.out.printf("%d\t",data);
    }
    System.out.println();
}

多维数组的length是行数
sparseArr.length

只有printf可以处理类似%d的转义字符

System.out.printf("%d\t%d\t%d\t\n",sparseArr[i][0],sparseArr[i][1],sparseArr[i][2]);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值