Java数据结构与算法学习 目前30170字


文章借鉴于【尚硅谷】数据结构与算法(Java数据结构与算法),在内容方面会做更改,但是核心依然是他们的学习视频。在这里声明。


1. 线性结构和非线性结构


1.1 线性结构

数据结构包括两大部分,一个叫 线性结构,一个叫 非线性结构

  • 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系。 比如数组, var[0] = 20; 存在一对一关系。

  • 线性结构有两种不同的存储结构,即顺序存储结构链式存储结构。顺序存储的线性表称为顺序表,顺序表中的元素是连续的。该元素指的是内存地址

  • 链式存储结构的线性表称为链表链表中存储的元素不一定是连续的, 元素节点中存放数据元素以及相邻元素的地址信息。

  • 线性结构常见的有:数组,队列,链表,栈


1.2 非线性结构

  • 非线性结构包括:二维数组,多维数组,广义表,树结构,图结构

2. 稀疏数组

定义: 当一个数组中,大部分元素都为0,或者大部分元素都为同一个数字的时候,可以使用稀疏数组来保存该数组。


2.1 稀疏数组的介绍

稀疏数组的处理方式是:

  • 记录一个数组有多少行,多少列,有多少个不同的值。
  • 把具有不同元素的行列记录在一个小规模的数组上(这个小规模的数组就是稀疏数组),从而缩小程序的规模。

在这里插入图片描述

左侧是原有的二维数组,右侧为压缩后的稀疏数组。在稀疏数组的第[0]索引位,第一个元素代表了这个原有的二维数组有几行,第二个代表了这个原有的数组有几列,第三个代表了除去非0以外的元素,还存在几个其他不同的元素。从第[1]号索引到第[8]号索引,他们分别记录的是该不同元素出现在二维数组上的位置所在。比如 元素22 出现在 索引为0 - 3 的地方,那么对应的具体坐标就为 1 - 4 也就是第一行第四个元素。那么,原有的 6行 7列的数组,就会变成 现在稀疏数组的 九行 三列,对应的元素分别是 6 * 7 = 42(压缩前) 和 3 * 9 = 27(压缩后),大大减少了使用空间。


2.2 稀疏数组的实操

那在具有上面的了解后,就可以来具体的学习一下怎么样把一个二维数组,转换为稀疏数组的思路。

  1. 遍历原始的二维数组,得到有效元素的个数 sum。
  2. 根据sum就可以创建稀疏数组sparseArray其中稀疏数组的大小为
    int[] [] sparseArray = new int[sum + 1][3]
  3. 将二维数组的有效数据,存入到稀疏数组中。

那么存入后,就需要还原二维数组。下面是稀疏数组转二维数组的思路。

  1. 先读取稀疏数组的第一行,第一行里面有二维数组的行和列。根据第一行的数据,创建原始的二维数组。 int[][] array = new int[sparse[0][1]] [sparse[0][2]]
  2. 然后读取稀疏数组后几行的数据,并且赋给原始的二维数组。即可。

现在就可以来写具体的代码实操。新建一个类,类名自定义,我这里的类名叫做SparseArray。接下来我直接粘贴代码。具体内容看代码,代码都具有注释。

package com.data_structure.sparse_array;

import java.util.Arrays;

/**
 * 数据结构
 * 稀疏数组的来回转换
 */
public class SparseArray {

    /***
     * 对二维数组的转置输出,观看更客观
     * @param arrays 对应的需要转置的二维数组
     */
    public static void Transpose_2D_Array(int[][] arrays) {
        for (int[] array : arrays) {
            System.out.println(Arrays.toString(array));
        }
    }

    /**
     * 遍历二维数组,得到非0元素的个数
     * @param arrays 传入的二维数组
     * @return 返回非0的个数
     */
    public static Integer Get_Oon_Empty_Element(int[][] arrays) {

        int sum = 0;

        for (int[] array : arrays) {
            for (int i : array) {
                if (i != 0) {
                    sum ++;
                }
            }
        }

        return sum;
    }

    /**
     * 稀疏数组转换二维数组
     * @param sparseArray 传入稀疏数组
     * @return 转换成功的二维数组
     */
    public static int[][] SparseArrayTo2DArray(int[][] sparseArray) {
        // 根据稀疏数组,还原对应二维数组的大小,当然,前提是判断稀疏数组是否可用

        if (sparseArray[0][0] != 0 && sparseArray[0][1] != 0) {

            // 创建一个新的二维数组
            int[][] new2DArray = new int[sparseArray[0][0]][sparseArray[0][1]];

            // 此时只需要循环遍历其他的值即可
            for (int i = 1; i <= sparseArray[0][2]; i++) {

                new2DArray[sparseArray[i][0]][sparseArray[i][1]] = sparseArray[i][2];

            }

            return new2DArray;

        }

        return null;
    }


    /**
     * 二维数组转换为稀疏数组
     */
    public static void Convert_2D_Array_To_Sparse_Array() {

        // 创建一个原始的11 * 11 的 二维数组模拟五子棋 0 表示空值, 1表示黑子,2表示白子
        int[][] arrays = new int[11][11];

        // 初始化棋盘 假设下了两个棋子,第二行第三个(黑色), 第三行第四个(白色)
        arrays[1][2] = 1;
        arrays[2][3] = 2;

        System.out.println("原始二维数组: ");

        // 对二维数组进行转置输出
        Transpose_2D_Array(arrays);

        // 得到非0的元素个数
        Integer integer = Get_Oon_Empty_Element(arrays);

        // 创建对应的稀疏数组 他的行为 对应有元素的个数 + 1 列为固定3
        int[][] sparseArray = new int[integer+1][3];

        //给稀疏数组赋值
        sparseArray[0][0] = 11;
        sparseArray[0][1] = 11;
        sparseArray[0][2] = integer;

        // 遍历原始数组,然后把对应不为0的元素,存储到稀疏数组中。
        int count = 0;
        for (int i = 0; i < 11; i++) {

            for (int j = 0; j < 11; j++) {

                // 如果不为0 代表有数据
                if (arrays[i][j] != 0) {

                    count ++;

                    sparseArray[count][0] = i;

                    sparseArray[count][1] = j;

                    sparseArray[count][2] = arrays[i][j];

                }

            }

        }

        // 输出稀疏数组的形式
        System.out.println("稀疏数组: ");

        Transpose_2D_Array(sparseArray);

        // 稀疏数组转换为 二维数组。以及知道系数组的数据位置,可以直接通过稀疏数组转换二维数组。
        int[][] newArrays = SparseArrayTo2DArray(sparseArray);

        System.out.println("由稀疏数组转换为二维数组: ");

        assert newArrays != null;
        Transpose_2D_Array(newArrays);

    }


    public static void main(String[] args) {

        Convert_2D_Array_To_Sparse_Array();

    }

}

上述代码是可以直接拷贝运行的,但是注意包结构导入,需要更改。


3.队列

特性: 队列最大的特点就是 先进先出;

  • 队列是一个有序列表,可以用数组或者链表来实现。
  • 遵循先入先出的原则。即先存入的数据,先取出,后存入的数据,后取出。

3.1 队列的代码实现

package com.data_structure.queue;

/**
 * 队列,且是用数组模拟队列
 */
public class ArrayQueueDemo {

    public static void main(String[] args) {
        ArrayQueue arrayQueue = new ArrayQueue(5);
        arrayQueue.addQueue(5);
        arrayQueue.addQueue(1);
        arrayQueue.addQueue(7);
        arrayQueue.addQueue(9);
        arrayQueue.addQueue(4);
        arrayQueue.addQueue(4);
        System.out.println();
        arrayQueue.show();
        System.out.println();
        System.out.println(arrayQueue.getQueue());
        System.out.println(arrayQueue.getQueue());
        System.out.println(arrayQueue.getQueue());
        System.out.println(arrayQueue.getQueue());
        arrayQueue.show();
    }

}

// 新建一个数组模拟队列,编写一个ArrayQueue
class ArrayQueue {

    // 表示数组的最大容量
    private final int maxSize;

    // 指向队列头
    private int front;

    // 指向队列尾
    private int rear;

    // 队列数组
    private final int[] array;

    /**
     * 构造器,初始化数据
     * @param arrSize 队列的大小
     */
    public ArrayQueue(int arrSize) {
        this.maxSize = arrSize;   // 获取到队列的最大容量
        this.array = new int[maxSize];   // 根据最大容量创建数组队列
        this.front = -1;   // 指针头,指向队列头部的前一个位置
        this.rear = -1;  // 指针尾,指向队列尾部的最后一个位置
    }

    /**
     * 判断队列是否为空,只需要检查 指针头和指针尾是否相撞就可以了
     * @return 空为true  反之false
     */
    public boolean queueIsEmpty() {
        return this.front == this.rear;
    }

    /**
     * 判断队列是否已满,只需要检查队列尾部指针是否和最大容量 -1(下标) 相撞
     * @return 满了返回true ,反之反之
     */
    public boolean queueIsFull() {
        return this.rear == this.maxSize - 1;
    }

    /**
     * 添加队列
     * @param num 向队列里添加的值
     */
    public void addQueue(int num) {

        // 判断队列是否已满
        if (!queueIsFull()) {

            this.rear ++;

            array[rear] = num;

        } else {

            System.out.println("队列已满,无法添加数值。");

        }

    }

    /**
     * 获取队列元素
     * @return 队列元素
     */
    public int getQueue() {

        // 判断队列是否为空
        if (!queueIsEmpty()) {

            front++;

            return array[front];

        }

        throw new RuntimeException("队列为空。");

    }

    public void show() {
        for (int i = this.front + 1; i <= this.rear; i++) {
            System.out.println("数据: " + this.array[i]);
        }
    }

}


3.2 环形队列

如果按照上面的方式来模拟队列,就会出现用过一次的位置不能第二次去使用。没法达到复用效果。

我们希望取出的空间还可以重复的去使用

对比上面的基本队列,环形队列实现的基本思路做出了调整

  1. front变量的含义做出调整,front调整为指向队列的第一个元素,也就是说,array[front]时,获取到的是队列的第一个元素。front的初始值为 0;不再是 -1。
  2. rear变量的含义做出调整,rear调整为指向队列最后一个元素 + 1 的位置。因为希望空留出一个空间作为约定。rear的初始值也 = 0。
  3. 当队列满的情况下,原先的条件是, rear == maxSize - 1; 但是现在 front和 rear的条件做了调整,变为 (rear + 1) % maxSize == front; 这就是满的条件。
  4. 当队列为空的条件没有发生变化,如果 rear == front 就是空。
  5. 当按照上面走的时候,队列中有效的数据个数,永远是(rear + maxSize - front) % maxSize
    在这里插入图片描述

3.3 环形队列代码的实现

package com.data_structure.queue;

public class CircleArrayQueue {

    public static void main(String[] args) {

        CircleQueue circleQueue = new CircleQueue(5);

        circleQueue.addQueue(5);
        circleQueue.addQueue(8);
        circleQueue.addQueue(1);
        circleQueue.addQueue(2);
        circleQueue.addQueue(9);
        circleQueue.addQueue(4);

        System.out.print("\n展示此时的队列内容: ");

        circleQueue.show();

        System.out.println();

        System.out.println("取出的内容: ");

        System.out.println(circleQueue.getQueue());
        System.out.println(circleQueue.getQueue());
        System.out.println(circleQueue.getQueue());
        System.out.println(circleQueue.getQueue());

        System.out.println("添加三个队列: ");

        circleQueue.addQueue(9);
        circleQueue.addQueue(1);
        circleQueue.addQueue(5);

        circleQueue.show();

    }

}

class CircleQueue {

    private final int maxSize;

    private final int[] array;

    private int front;

    private int rear;

    public CircleQueue(int maxSize) {
        this.maxSize = maxSize;
        this.array = new int[maxSize];
        this.front = 0;
        this.rear = 0;
    }

    public boolean isEmpty() {
        return this.front == this.rear;
    }

    public boolean isFull() {
        // 队列已满情况下。 最大值 % (尾指针 + 1) 如果 == front 就说明元素已经还差一个就满了 ,这一个是约定需要留下来的
        return ((this.rear + 1) % this.maxSize) == this.front;
    }

    public void addQueue(int number) {

        if (!isFull()) {
            // 因为它本身就是在元素的后一个位置,所以我们不需要对他先进行指针后移操作,在添加完成数据后,进行指针后移,保证空留一个,
            // 这里必须考虑取模,如果rear到顶层,切前面有空间,就需要让rear到前面去
            this.array[this.rear] = number;
            // 假设最大容量为5。此时rear为4.我们必须约定空留一个元素空间,
            this.rear = (this.rear + 1) % maxSize;
        } else {
            System.out.println("队列已满");
        }

    }

    public int getQueue() {

        // 不为空的情况下才能取数据
        if (!isEmpty()) {

            // front是指向队列的第一个元素,我们需要先把front的值存储到一个临时变量中,把front后移,考虑取模,然后将临时的变量返回
            int value = this.array[this.front];

            this.front = (front + 1) % maxSize;

            return value;

        }

        throw new RuntimeException("已经空掉了");

    }

    public void show() {

        if (!isEmpty()) {

            // 从front开始遍历, 遍历多少个元素。
            for (int i = this.front; i < front + (rear + maxSize - front) % maxSize; i++) {

                System.out.printf("数据下标arr[%d] = %d", i % maxSize, this.array[i % maxSize]);

            }

        }

    }

}


4. 链表 (Linked List)

在这里插入图片描述
head 为头指针,就是第一个数据,然后data域为数据,next域,就是指向下一个节点的地方。

  • 链表是以节点的方式来存储数据的。
  • 每个节点包含data域,next域
  • 链表的各个节点并不一定是连续的。
  • 链表分带头节点的链表和没有头节点的链表,根据实际需求来确定
    在这里插入图片描述

4.1 单链表的代码实现(不带排序)

package com.data_structure.linkedList;

public class SingleLinkedListDemo {

    public static void main(String[] args) {

        HeroNode heroNode1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode heroNode2 = new HeroNode(2, "李逵", "黑旋风");
        HeroNode heroNode3 = new HeroNode(3, "林冲", "及时雨");
        HeroNode heroNode4 = new HeroNode(4, "鲁智深", "及时雨");

        SingleLinkedList singleLinkedList = new SingleLinkedList();

        singleLinkedList.add(heroNode1);
        singleLinkedList.add(heroNode2);
        singleLinkedList.add(heroNode3);

        singleLinkedList.list();

    }

}

class SingleLinkedList {

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

    // 添加节点
    public void add(HeroNode heroNode) {
        // 添加的原理就是,当有新节点时,找到原有链表的最后一个节点,让最后一个节点指向该新节点。
        // 但是头节点不动,就可以使用一个引用来代替头节点
        HeroNode tempHead = this.herd;
        // 遍历链表,找到最后一个元素
        while (tempHead.next != null) {
            // 如果没有找到最后一个元素,就将temp后移
            tempHead = tempHead.next;
        }
        // 当退出while循环时,tempHead就指向了链表的最后。
        tempHead.next = heroNode;
    }

    // 显示链表
    public void list() {
        // 先判断链表是否为空。
        if (herd.next == null) {
            System.out.println("链表为空: []");
            return;
        }
        // 因为头节点不能动,因此我们需要一个辅助变量来遍历
        // 因为他不为空,所以说明他至少有一个数据
        HeroNode temp = this.herd.next;
        while (true) {
            // 判断是否到链表的最后
            if (temp.next == null) {
                // 最后一个元素也是有数据的
                System.out.println(temp);
                break;
            }
            // 输出
            System.out.println(temp);
            // 将temp后移
            temp = temp.next;
        }
    }

}

/**
 * 创建英雄节点
 * 每一个HeroNode就是一个节点
 */
class HeroNode {

    public int no;  // 英雄编号

    public String name;  // 英雄名称

    public String nickname;  // 英雄绰号

    public HeroNode next;  // 指向下一个节点

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

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


4.1.1 – 4.1的代码拆解

首先我们需要创建英雄节点,每一个英雄除去他们基本的属性以外,还需要有一个节点属性,来对应下一个元素所处于的位置,如果next节点为null,则说明他这个英雄是最后一个节点。

所以我们只需要先实现这个英雄节点功能即可。

// 英雄节点类
class HeroNode {

	// 直接使用public 方便访问
	public int noId;  // 英雄的编号

	public String name; // 英雄名称
	
	public String nickname; // 英雄绰号

	// 存放下一个英雄的位置
	public HeroNode next;	// next域

	// 使用构造器来构造Hero英雄
	public HeroNode(int noId, String name, String nickname) {
		this.noId = noId;
		this.name = name;
		this.nickname = nickname;
	}

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

}

到这一步为止。我们的英雄节点就已经创造完成,接下来,就可以去实现我们的链表逻辑。

class SingleLinkedList {
	// 因为头部节点不动, 所以我们先把头部节点创建好,以后使用一个节点引用到头部节点即可。
	private final HeroNode herd = new HeroNod(0, "", "");

	// 用于添加节点元素, 需要传入一个节点
	public void add(HeroNode heroNode) {
		// 创建一个引用指向头部节点, 因为头节点的信息是不变的
		HeroNode temp = this.herd;
		// 遍历循环,判断是否指向的是最后一个节点
		// 判断最后一个节点的逻辑是 节点的 next域 == null
		while(temp.next != null) {
			// 如果不是最后一个元素,就让节点后移
			temp = temp.next;
		}
		// 此时跳出循环的时候,temp指向的就是最后一个节点元素
		temp.next = heroNode;	
	}

	// 查询链表内容
	public void list() {
		// 查询前先判断链表是否为空
		// 只需要判断头节点的next是否为null就可以
		if (this.herd.next == null) {
			System.out.println("链表为空");
			return;
		}
		
		// 头节点是不允许动的。
		HeroNode temp = this.herd;

		while(true) {
			if(temp.next == null){
				// 虽然为null,但是他也是最后一个元素
				System.out.println(temp);
				// 跳出循环
				break;
			}
			System.out.println(temp);
			// 输出当前节点元素后,还需要把这个节点指向下一个内容。
			temp = temp.next;
		}
	}

}

至此完成。测试内容自己写


4.2 单链表的代码实现(附带排序,按照顺序插入节点)

思路实现:

  • 首先找到新添加的节点位置。是通过辅助指针来找的, 通过遍历
  • 新的节点的.next域 = temp的.next域
  • 然后将temp.next域指向新的节点

在这里插入图片描述

4.2.1 代码的实现

题目,在添加英雄时,根据排名将英雄插入到指定位置,如果有这个排名,则添加失败,并给出提示。

代码在原有基础上进行添加,也就是在上述代码中,在SingleLinkedList.class类中,新增加一个方法,叫addByOrder(HeroNode heroNode); 里面要传递的参数是新增的英雄节点。


package com.data_structure.linkedList;

public class SingleLinkedListDemo {

    public static void main(String[] args) {

        HeroNode heroNode1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode heroNode2 = new HeroNode(2, "李逵", "黑旋风");
        HeroNode heroNode3 = new HeroNode(3, "林冲", "及时雨");
        HeroNode heroNode4 = new HeroNode(4, "鲁智深", "及时雨");

        SingleLinkedList singleLinkedList = new SingleLinkedList();


        singleLinkedList.addByOrder(heroNode3);
        singleLinkedList.addByOrder(heroNode1);
        singleLinkedList.addByOrder(heroNode2);
        singleLinkedList.addByOrder(heroNode4);

        singleLinkedList.list();


    }

}

class SingleLinkedList {

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

    // 添加节点
    public void add(HeroNode heroNode) {
        // 添加的原理就是,当有新节点时,找到原有链表的最后一个节点,让最后一个节点指向该新节点。
        // 但是头节点不动,就可以使用一个引用来代替头节点
        HeroNode tempHead = this.head;
        // 遍历链表,找到最后一个元素
        while (tempHead.next != null) {
            // 如果没有找到最后一个元素,就将temp后移
            tempHead = tempHead.next;
        }
        // 当退出while循环时,tempHead就指向了链表的最后。
        tempHead.next = heroNode;
    }

	// 根据编号按升序排序来排序链表节点。 如: 1, 2, 3, 4, 5, 6, 7
    public void addByOrder(HeroNode heroNode) {
		// 因为头节点是固定的,不可以改动的,所以我们需要创建一个引用对象来引用头节点
		HeroNode temp = this.head;
		// 定义一个boolean的变量,用来检查元素是否重复。默认为false
		boolean flag = false;
		// 开始遍历节点
		while(true) {
			// 判断链表是否为空,如果为空的话,可以直接的去做一个添加操作
			if(temp.next == null) {
				break;
			}
			// 如果不为空的情况下,判断节点应该添加在那个位置
			// 逻辑就是,如果temp的下一个节点的编号,大于当前要添加的编号,说明找到位置了
			// 我们就是应该添加到 比他大的前面, 比他小的后面位置。
			else if(temp.next.no > heroNode.no) {
				break;
			} else if (temp.next.no == heroNode.no) {
				// 说明当前被添加的节点的编号已经存在于链表中设置为false
				flag = true;
				break;
			} 
			// 如果都不成立的话,仍然需要让节点元素后移。
			temp = temp.next;
		}
		// 在跳出循环后,首先判断 flag是否为true
		if (flag) {
			// 如果为true的情况下,则说明已经有重复的编号。
			System.out.println("该节点编号重复: " + heroNode.to);
		} else {
			// 则说明编号不重复,具体的添加逻辑是
			// 让当前要添加的英雄的节点位置,指向比他编号大的节点位置,也就是原先 temp 所
			// 指向的节点
			heroNode.next = temp.next;
			// 然后,让原先的temp的下一个节点指向当前的新添加的节点。
			temp.next = heroNode;
		}
    }

    // 显示链表
    public void list() {
        // 先判断链表是否为空。
        if (head.next == null) {
            System.out.println("链表为空: []");
            return;
        }
        // 因为头节点不能动,因此我们需要一个辅助变量来遍历
        // 因为他不为空,所以说明他至少有一个数据
        HeroNode temp = this.head.next;
        while (true) {
            // 判断是否到链表的最后
            if (temp.next == null) {
                // 最后一个元素也是有数据的
                System.out.println(temp);
                break;
            }
            // 输出
            System.out.println(temp);
            // 将temp后移
            temp = temp.next;
        }
    }

}

/**
 * 创建英雄节点
 * 每一个HeroNode就是一个节点
 */
class HeroNode {

    public int no;  // 英雄编号

    public String name;  // 英雄名称

    public String nickname;  // 英雄绰号

    public HeroNode next;  // 指向下一个节点

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

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

他的核心内容依然是那个方法(addByOrder),其他的只是借用一下,不需要重复的去书写。
具体的解释,代码里的注释已经全部帮我做了。好吧虽然也是我自己写的。但是一边看注释一边看代码,要胜过看完代码之后看解析。(个人感觉)。


4.3 单链表节点的修改

链表内节点的修改,还是依赖在上述代码内,定义一个方法 update(HeroNode newHeroNode); 其中参数就是要修改的内容,节点的id是不变的,也就是说,是通过id来修改节点。

4.3.1 代码的实现
	// 用于修改节点内容
	public void update(HeroNode newHeroNode) {
		// 首先判断头节点内是否有下一个节点
		if (this.herd.next == null) {
			// 链表内没有数据,没有办法更改,直接返回即可。
			System.out.println("链表内没有数据,没有办法更改内容。");
			return;
		}
		// 在链表不为空的情况下,定义一个辅助节点来操作,然后定义一个boolean变量来代表是否找到
		// true为找到了no 相同的节点,false为没有找到no相同的节点
		HeroNode temp = this.herd.next;
		boolean flag = false;
		// 开始遍历每一个节点
		while(true) {
			// 判断temp域是否为空,如果为空的话则说明遍历到了最后一个元素.
			// 并且已经遍历完毕,因为指针永远指向下一个next temp = herd.next
			if (temp == null) {
				// 直接退出即可
				break;
			}
			// 还有一种情况就是找到了no编号相对等的元素,仍然直接退出即可
			// 因为此时 temp 指向的就是 那个对象
			if (temp.no == newHeroNode.no) {
				// 并且flag设为true
				flag = true;
				break;
			}
			// 最后如果两种条件都不成立,别忘记进行下一个节点的遍历
			temp = temp.next;
		}
		// 跳出循环以后,无非就是两种可能性,一种flag为 true, 一种为false
		// 在flag为true的情况下,说明找到了对应的节点元素。反之没有
		if (flag) {
			// 因为编号是不允许改变的,我们只需要改变这个节点的其他内容即可。指向仍然不变
			temp.name = newHeroNode.name;
			temp.nickname = newHeroNode.nickname;
		} else {
			System.out.println("没有找到对应匹配的节点元素, 无法更改");
		}

	}

4.4 单向链表的删除

这一块是我自己的思路,因为前面添加的时候规定过,首先,头节点不做添加的内容,也就是说 0 号no 也是一样可以进行添加的,第二,编号 no 是唯一的!, 那么现在就可以跟操作数据库一样,思路逐渐清晰,通过 no 编号来删除,就是因为他是唯一的。


4.4.1 代码的实现

依然是在以上的代码上做添加方法内容。 方法名称叫做 HeroNode deleteByNo(Integer ino); 参数就是通过ino来删除节点内容。 返回值是HeroNode,来返回一个被删除的元素内容。

// 通过该方法来进行节点的删除。
public HeroNode deleteByNo(Integer ino) {
	// 首先必要的就是一个辅助指针。该指针指向头元素,并且不做一下一个指向
	// 就仅仅是指向头元素本身
	HeroNode temp = this.herd;
	// 创建一个boolean类型的变量,后续判断是否找到对应匹配ino的节点,默认为fales;
	boolean flag = fales;
	// 创建一个对象,用于返回被删除的内容
	HeroNode deleteNode = null;
	// 遍历循环节点。因为while循环总是在为true的时候去执行,所以可以直接判断
	// temp.next != null
	while (temp.next != null) {
		// 此时只需要在循环内判断 ino是否和 temp.next.no 匹配,如果匹配,直接跳出循环
		if (temp.next.no == ino) {
			// 设置flag为true
			flag = true;
			break;
		}
		// 如果没有,继续向下一个节点走去
		temp = temp.next;
	}
	// 在跳出循环之后,只会存在两种结果,flag = true/false
	if (flag) {
		// 我们需要在修改节点内容以前,把被删除的节点内容赋值
		deleteNode = temp.next;
		// 说明找到了该节点内容,在这里可以做分析,因为此时的temp指针,指向的是被删除的节点的
		// 前一个节点内容。也就是说,temp.next 指向的是被删除的节点元素,而
		// temp.next.next 指向的是被删除节点指向的下一个节点。我们需要做的就是把当前
		// temp.next 的节点,指向被删除的下一个节点
		temp.next = temp.next.next;
	} else {
		System.out.println("没有找到对应编号的节点,无法删除");
	}
	return deleteNode;
}

4.5 链表面试题(新浪,腾讯,百度)

已有代码结构为

package com.data_structure.linkedList;

public class SingleLinkedListDemo {

    public static void main(String[] args) {

        HeroNode heroNode1 = new HeroNode(0, "宋江", "及时雨");
        HeroNode heroNode2 = new HeroNode(2, "李逵", "黑旋风");
        HeroNode heroNode3 = new HeroNode(3, "林冲", "及时雨");
        HeroNode heroNode4 = new HeroNode(4, "鲁智深", "及时雨");

        SingleLinkedList singleLinkedList = new SingleLinkedList();


        singleLinkedList.addByOrder(heroNode3);
        singleLinkedList.addByOrder(heroNode1);
        singleLinkedList.addByOrder(heroNode2);
        singleLinkedList.addByOrder(heroNode4);

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

        System.out.println(singleLinkedList.deleteByNo(3));

        singleLinkedList.list();


    }

}

class SingleLinkedList {

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

    public HeroNode getHead() {
        return this.head;
    }

    // 添加节点
    public void add(HeroNode heroNode) {
        // 添加的原理就是,当有新节点时,找到原有链表的最后一个节点,让最后一个节点指向该新节点。
        // 但是头节点不动,就可以使用一个引用来代替头节点
        HeroNode tempHead = this.head;
        // 遍历链表,找到最后一个元素
        while (tempHead.next != null) {
            // 如果没有找到最后一个元素,就将temp后移
            tempHead = tempHead.next;
        }
        // 当退出while循环时,tempHead就指向了链表的最后。
        tempHead.next = heroNode;
    }

    public void addByOrder(HeroNode heroNode) {

        // 因为头节点不能动,因此我们仍然需要一个辅助指针来帮助我们添加位置。
        // 因为是单链表,因此我们找的temp是处于新增节点的前一个节点。否则添加不了。
        HeroNode temp = this.head;

        boolean flag = false; // 表示添加的编号是否存在,默认为不存在。

        while (true) {
            // 如果temp.next == null 则说明 此时处于链表的末尾。说明链表为空
            if (temp.next == null) {
                break; // 不管找不找得到 都需要break
            }
            // 如果是这种情况,则说明,位置找到了,我们本来需要添加的就是比他大的前一位。temp的后一位
            if (temp.next.no > heroNode.no) {
                break;
            }  else if (temp.next.no == heroNode.no) {
                // 说明希望添加的heroNode的编号已经存在了
                flag = true;  // 说明编号存在
                break;
            }
            temp = temp.next;
        }
        // 退出循环后,我们首先需要判断flag的值
        if (flag) {
            // 不能添加,说明编号存在
            System.out.println("准备插入的这个英雄的编号: " + heroNode.no + " 已经存在。");
        } else  {
            // 插入到链表中,temp的后面
            heroNode.next = temp.next;
            temp.next = heroNode;
        }

    }

    // 修改节点的信息, 根据编号来修改,即编号无法做出更改。
    public void update(HeroNode newHeroNode) {
        // 判断链表是否为空
        if (this.head.next == null) {
            System.out.println("链表为空");
            return;
        }

        // 如果不为空,就找到需要修改的节点
        // 定义一个辅助节点
        HeroNode temp = this.head.next;
        // 表示是否找到该节点
        boolean flag = false;
        while (true) {
            if (temp == null) {
                // 就说明,已经遍历完链表
                break;
            }

            if (temp.no == newHeroNode.no) {
                // 则说明找到了,直接设置好flag 然后返回
                flag = true;
                break;
            }

            temp = temp.next;
        }

        // 一共就两种可能,第一种是找放到了,退出了循环,此时flag是true,第二种是找不到,flag是false。
        if (flag) {
            // 这种情况说明找到了
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        } else {
            // 说明没找到节点
            System.out.println("没有找到该节点: " + newHeroNode.no);
        }

    }

    // 删除节点, 通过编号 no 来删除, 因为no 是唯一的。
    public HeroNode deleteByNo(Integer ino) {
        // 创建一个辅助指针,来辅助循环
        HeroNode temp = this.head;
        // 创建一个boolean 来记录是否找到
        boolean flag = false;
        // 遍历循环temp.next 来寻找与之对应的节点
        while (temp.next != null) {
            //  这种情况下,则说明,下一个节点就是要被删除掉的节点。直接返回
            if (temp.next.no == ino) {
                flag = true;
                break;
            }
            // 对指针进行后移
            temp = temp.next;
        }

        // 创建一个节点元素,表示被删除的节点
        HeroNode deleteNode = null;

        // 判断flag是否为true,如果为true,说明找到了,此时temp的位置,处于被删除元素的上一个,那么被删除元素的下一个就是 temp.next.next 只
        // 需要让 temp.next = temp.next.next
        if (flag) {
            deleteNode = temp.next;
            temp.next = temp.next.next;
        } else {
            System.out.println("找不到对应的ino编号。无法删除。");
        }

        return deleteNode;

    }


    // 显示链表
    public void list() {
        // 先判断链表是否为空。
        if (head.next == null) {
            System.out.println("链表为空: []");
            return;
        }
        // 因为头节点不能动,因此我们需要一个辅助变量来遍历
        // 因为他不为空,所以说明他至少有一个数据
        HeroNode temp = this.head.next;
        while (true) {
            // 判断是否到链表的最后
            if (temp.next == null) {
                // 最后一个元素也是有数据的
                System.out.println(temp);
                break;
            }
            // 输出
            System.out.println(temp);
            // 将temp后移
            temp = temp.next;
        }
    }
}

/**
 * 创建英雄节点
 * 每一个HeroNode就是一个节点
 */
class HeroNode {

    public int no;  // 英雄编号

    public String name;  // 英雄名称

    public String nickname;  // 英雄绰号

    public HeroNode next;  // 指向下一个节点

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

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



4.5.1 求单链表中节点的个数

这个很简单,直接上代码

 /**
  * 获取单链表节点的个数(如果是带头节点的链表,需求不统计。
  * @param heroNode 链表的头节点
  * @return 返回有效的节点个数。
  */
public int getLength(HerdNode herd) {
	// 判断头节点内是否有下一个节点数据
	if (herd.next == null) {
		System.out.println("没有有效的数据个数。");
		return 0;
	}
	// 创建记录节点
	int count = 0;
	// 开始遍历,设置代替变量。有效数据不包含头节点
	HerdNode temp = herd.next;
	while(temp.next != null) {
		count ++;
		temp = temp.next;
	}
	return count;
}

4.5.2 查找单链表中倒数第k个节点(新浪面试题)

思路:

  1. 编写一个方法,,接受head节点,同时接受一个index
  2. index表示的是倒数第index个节点
  3. 就可以得到如下算法
  4. 有效的节点数 - index = 正数的第几个节点
  5. 然后我们从第一个开始遍历,到正数的第几个节点即可。
/**
 *  查找单链表中倒数第k个节点(新浪面试题)
 *  1. 编写一个方法,,接受head节点,同时接受一个index
 *  2. index表示的是倒数第index个节点
 *  3. 就可以得到如下算法
 *  4. 有效的节点数 - index = 正数的第几个节点
 *  5. 然后我们从第一个开始遍历,到正数的第几个节点即可。
 * @param head 需要遍历的节点
 * @param index 倒数第几个
 * @return 如果找到返回节点,找不到返回null
 */
public static HeroNode findLastNode(HeroNode head, int index){
	 // 在已经得到index的情况下,先不要急着去取值,先判断节点是否为空
        if (head.next == null || index < 0) {
            return null;
        }

        // 开始获取总有效的元素 - index 得到需要遍历到第几个元素
        int length = getLength(head) - index;
        // 创建一个辅助节点。
        HeroNode temp = head.next;

        for (int i = 0; i < length; i++) {
            temp = temp.next;
        }
        return temp;
}

4.5.3 单链表的反转(腾讯面试题)

实现思路如下

  1. 先定义一个节点,resverseHead = new HeroNode();
  2. 从头到尾遍历,每遍历一个节点,就将其取出,并且放在新的链表的最前端。
  3. 原来链表的 head.next = resverseHead.next;

其实说白了就是头插法,下面上代码,注释一一标出。

/**
 * 链表的逆转
 * @param head 传入的头链表
 */
public static void singleLinkedListT(HeroNode head) {
	// 首先判断链表是否为空,或者是否只存在一个节点
	if(head.next == null || head.next.next == null) {
		// 直接返回就可以了
		return;
	}
	// 现在定义一个辅助指针,让他指向头部的下一个节点。
	HeroNode temp = head.next;
	// 定义一个next链表,用来暂时存储数据,防止链表丢失
	HeroNode next = null;
	// 定义一个新的链表,用来逆序节点, 也就是这里采用头插法, 带有一个默认的头指针
	HeroNode reverseHead = new HeroNode(0, "", "");
	// 开始遍历,只要辅助节点本身不等于null,就可以确定没有到链表尾部。
	// 因为temp开始就是指向了 head.next
	while(temp != null) {
		// 首先,让next链表,指向temp的下一个节点, 需要把数据暂时保留
		next = temp.next;
		// 然后让temp的下一个节点,指向新链表的头节点的下一个,后续可以拼接到
		// 新链表的头节点后面去(头插法)
		temp.next = reverseHead.next;
		// 然后让新链表的头节点位置的下一个节点等于这个temp本身
		reverseHead.next = temp;
		// 让temp进行下一次遍历, 因为实现存储了next中。
		temp = next;
	}
	// 然后让头节点引用指向新的链表
	head.next = reverseHead.next;
}

4.5.4 从尾到头打印单链表(百度面试题,要求方式1,反向遍历。方式2,Stack栈)

栈方式,可以利用栈的先入后出原则,完成逆序打印而不改变链表原有的结构。这里我们要借助一个类,Stack。也就是栈的意思。先上代码演示如何使用

package stack;

import java.util.Stack;

public class StackTest {

    public static void main(String[] args) {

        Stack<String > stack = new Stack<>();

        // 入栈
        stack.add("1");
        stack.add("2");
        stack.add("3");
        stack.add("4");

        // 出栈
        while (stack.size() > 0) {
            // pop() 就是将栈顶的数据取出
            System.out.println(stack.pop());
        }

    }

}

实际逻辑的实现


 /**
  * 使用栈来逆序打印链表
  */
 public static void reversePrint(HeroNode head) {
 	// 判断头节点内是否有数据
 	if (head.next == null) {
		// 直接退出
		return;
	}
	// 创建Stack栈
	Stack<HeroNode> stackHero = new Stack<>();
	// 创建辅助指针
	HeroNode temp = head.next;
	// 遍历到最后一个
	while(temp != null) {
		stackHero.push(temp);
		// 添加完成后后移
		temp = temp.next;
	}
	// 遍历取出栈内元素
	while(staciHero.size() != 0) {
		System.out.println(stackHero.pop());
	}
 }

4.5.5 合并两个有序的单链表,合并之后的链表依然有序(课后练习)

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

示例1:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
限制:

0 <= 链表长度 <= 1000

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // 建立虚拟头节点
        ListNode dummy = new ListNode(0);
        // 把cur指针先放到dummy上
        ListNode cur = dummy;
        // 基于比较遍历两个链表
        while(l1 != null && l2 != null) {
            // 对l1和l2的值做一个比较,看看哪一个的值最小。
            // 如果l1比l2小。走if,如果l2比l1小。走else
            if(l1.val < l2.val) {
                // 让cur的下一个节点指向l1,并且让cur移动一下
                cur.next = l1;  // 指向更小的
                cur = cur.next;  // cur起到串联作用
                l1 = l1.next;   // l1 是比较的基准
            } else {
                cur.next = l2;
                cur = cur.next;
                l2 = l2.next;
            }
        }
        if(l1 != null) cur.next = l1;
        else cur.next = l2;
        return dummy.next;
    }
}

4.6 双向链表

目标为 使用带head头的双向链表实现水浒传排行。
缺优点分析。

  1. 单向链表,查找的方向只能是一个方向,而双向链表可以从前向后找也可以从后向前找,因为他多了一个尾指针 pre。
  2. 单链表不可以自我删除,需要依靠辅助节点,而双向链表,则可以自我删除,所以前面我们单链表删除节点时,总是需要找到temp,temp是待删除节点的前一个节点。

在这里我们需要用到的最基础的代码为

package com.data_structure.linkedList;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 双向链表
 */
public class DoubleLinkedListDemo {
    public static void main(String[] args) {

    }
}



@Data
@NoArgsConstructor
@AllArgsConstructor
class ListNode {
    public int no;
    public String name;
    public String nickname;
    public HeroNode next;    // 指向下一个节点  默认为null
    public HeroNode pre;     // 指向前一个节点  默认为null

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

完结代码,所有的解释都存在于注释中。认真阅读注释

package com.data_structure.linkedList;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;


/**
 * 双向链表
 */
public class DoubleLinkedListDemo {
    public static void main(String[] args) {
        ListNode listNode1 = new ListNode(1, "宋江", "及时雨");
        ListNode listNode2 = new ListNode(2, "李逵", "黑旋风");
        ListNode listNode3 = new ListNode(3, "林冲", "豹子头");
        ListNode listNode4 = new ListNode(4, "鲁智深", "垂杨柳");
        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
        System.out.println("------------打印空数据-------------");
        doubleLinkedList.list();
        System.out.println("---------------------------------");
        System.out.println();
        doubleLinkedList.add(listNode1);
        System.out.println("------------添加一个节点-------------");
        doubleLinkedList.list();
        System.out.println("---------------------------------");
        doubleLinkedList.add(listNode2);
        doubleLinkedList.add(listNode3);
        doubleLinkedList.add(listNode4);
        System.out.println();
        System.out.println("------------添加四个节点-------------");
        doubleLinkedList.list();
        System.out.println("---------------------------------");
        ListNode updateList = new ListNode(4, "盖伦", "德玛西亚");
        doubleLinkedList.update(updateList);
        System.out.println("------------修改编号四-------------");
        doubleLinkedList.list();
        System.out.println("---------------------------------");
        System.out.println();
        System.out.println("------------删除一个不存在的节点-------------");
        doubleLinkedList.delete(9);
        System.out.println("---------------------------------");
        System.out.println();
        System.out.println("------------删除一个存在的节点-------------");
        doubleLinkedList.delete(4);
        doubleLinkedList.list();
        System.out.println("---------------------------------");
    }
}

// 链表
class DoubleLinkedList {
    // 初始化头
    private final ListNode head = new ListNode();

    /**
     * 返回头节点
     * @return 头节点
     */
    public ListNode getHead() {
        return head;
    }

    /**
     * 遍历双向链表,和单向链表无异,直接可以用上面的,但是还是建议手写一遍,加深记忆
     */
    public void list() {
        // 判断节点内是否有元素。
        if (this.head.next == null) {
            System.out.println("节点内没有数据");
            return;
        }
        // 判断节点是否只存在于一个数据
        if (this.head.next.next == null) {
            System.out.println(this.head.next);
            return;
        }
        // 创建辅助节点开始辅助遍历, 直接从具体的元素数据开始遍历
        ListNode temp = this.head.next;
        while (temp != null) {
            System.out.println(temp);
            temp = temp.next;
        }
    }

    /**
     * 添加链表,因为是一个双向链表,所以需要在原有的单向链表的基础上,进行整改。
     * 在next指向下一个对象后,需要让该对象的pre指向该对象的前一个temp;
     */
    public void add(ListNode listNode) {
        // 判断链表是否为空。
        if (this.head.next == null) {
            // 可以直接做添加
            this.head.next = listNode;
            listNode.pre = this.head;
            return;
        }
        // 创建辅助节点,遍历到最后一个
        ListNode temp = this.head;
        while (temp.next != null) {
            temp = temp.next;
        }
        // 遍历出来后temp一定是指向最后一个节点元素的
        temp.next = listNode;
        listNode.pre = temp;
    }

    /**
     * 修改一个节点的内容。和原先地修改是一样的
     */
    public void update(ListNode listNode) {
        // 先判断节点是否为空,不包含头节点
        if (this.head.next == null) {
            System.out.println("链表内没有数,没有办法修改对应的节点。");
            return;
        }
        // 创建辅助节点
        ListNode temp = this.head.next;
        while (temp != null) {
            // 逐步判断是否有编号配对的,如果有多个编号,则总是修改第一个遇到的
            if (temp.no == listNode.no) {
                temp.name = listNode.name;
                temp.nickname = listNode.nickname;
                return;
            }
            temp = temp.next;
        }
        System.out.println("没有找到对应编号的节点");
    }

    /**
     * 删除一个节点来使用, 因为他是双向链表,我们可以直接找到待删除节点的本身。然后进行自我删除。
     * 不管有几个重复的节点no,我们只删除第一个出现的no
     */
    public void delete(int no) {
        // 判断节点内是否有元素。
        if (this.head.next == null) {
            // 说明没有元素。
            System.out.println("节点内没有数据无法删除.");
            return;
        }
        // 创建复制节点直接遍历
        ListNode temp = this.head.next;
        while (temp != null) {
            // 寻找节点是否匹配
            if (temp.no == no) {
                if (temp.next == null) {
                    // 说明在最后一个 ,直接删除即可。
                    temp.pre.next = null;
                    return;
                }
                // 说明找到该被删除的元素
                // temp本身的上一个节点的下一个节点要指向temp本身的下一个节点。
                temp.pre.next = temp.next;
                // temp下一个节点的上一个节点需要指向temp的上一个节点
                temp.next.pre = temp.pre;
                return;
            }
            temp = temp.next;
        }
        System.out.println("找不到编号为: " + no + " 的节点。");
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class ListNode {
    public int no;
    public String name;
    public String nickname;
    public ListNode next;    // 指向下一个节点  默认为null
    public ListNode pre;     // 指向前一个节点  默认为null

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

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


4.7 单向环形链表

4.7.1 单向环形链表的介绍

在这里插入图片描述

构建一个单向环形链表的基本思路

  • 先创建第一个节点,让first指向该节点,形成环形。
  • 后面我们每创建一个新的节点,就把该节点添加到环形链表中即可。

遍历环形链表

  • 先让辅助指针 cur,指向first节点。
  • 然后通过一个while循环遍历该环形链表即可。
  • 在cur.next == first时,遍历一整个结束。
    在这里插入图片描述

代码的实现

package com.data_structure.linkedList;

import lombok.Data;
import lombok.ToString;

/**
 * 约瑟夫问题
 */
public class Josepfu {

    public static void main(String[] args) {

        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();

        circleSingleLinkedList.addBoy(10);

        circleSingleLinkedList.showBoy();

    }

}

// 创建一个环形的单向链表
class CircleSingleLinkedList {
    // 创建第一个first节点。当前没有编号
    private Boy first = null;

    // 添加小孩节点。构成一个环形链表。传入你要添加几个小孩
    public void addBoy(int nums) {
        // 判断这个数是否具有效果,比如最少需要添加一个小孩
        if (nums < 1) {
            System.out.println("输入的数字不对, 没办法创建小于 1 的孩子数量!");
            return;
        }
        // 创建辅助指针
        Boy cur = null;
        // 通过for循环来添加节点
        for (int i = 1; i <= nums; i++) {
            Boy boy = new Boy(i);
            // 在添加第一个的时候,直接的让第一个的next指向它本身
            if (i == 1) {
                // 总是需要添加第一个小孩的,直接让first等于第一个创建的小孩。
                this.first = boy;
                // 然后把添加的第一个小孩的下一个指向它自己,形成一个人的环状
                this.first.setNext(first);
                // 然后让cur指向第一个小孩
                cur = this.first;
            } else {
                // 如果是第二个小孩开始
                // 让辅助指针先指向新的boy,也就是做后移操作
                cur.setNext(boy);
                // 然后把当前boy的下一个节点指向first,连接到头部
                boy.setNext(this.first);
                // 然后cur后移到boy本身的节点上
                cur = boy;
            }
        }
    }

    // 遍历当前的环形链表
    public void showBoy() {
        // 判断当前链表内是否有内容。
        if (this.first == null) {
            System.out.println("链表为空。无法打印内容。");
            return;
        }
        // 创建一个辅助节点。开始辅助遍历
        Boy temp = this.first;
        while (true) {
            System.out.println("小孩的编号为: " +temp.getNo());
            if (temp.getNext() == this.first) {
                return;
            }
            temp = temp.getNext();
        }
    }

}

// 创建一个Boy类。表示一个节点。
@Data
class Boy {
    private int no; // 编号
    private Boy next; // 指向下一个节点。默认为空
    public Boy(int no) {
        this.no = no;
    }

    @Override
    public String toString() {
        return "Boy{" +
                "no=" + no +
                '}';
    }
}


4.7.2 实现约瑟夫问题

思路

  1. 需要创建一个辅助指针helper,指向first节点的上一个节点。也就是整个链表的最后一个节点。
    在这里插入图片描述
  2. 当开始报数的时候,让first指针开始移动,helper指针需要一直跟在first后面(也就是同时移动),移动m-1次。因为当前节点自己也需要报数一次。
  3. 这时就可以将first指向的节点,开始出圈。也就是,先让first向前移动一次。helper.next = first; 那么原来要被出圈的first就没有了任何引用,就会被垃圾回收机制给回收。

所有的注释注意看


public void main(String[] args) {
	CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();

    circleSingleLinkedList.addBoy(10);

    circleSingleLinkedList.showBoy();

    System.out.println(circleSingleLinkedList.getCountBoy());

    System.out.println("约瑟夫取出问题: " + circleSingleLinkedList.countBoy(1, 3, 10));
}

/**
 * 经典约瑟夫问题,根据用户的输入,计算出出圈的一个顺序。
 * @param startNo 表示从第几个小孩开始数数
 * @param countNumber 表示数几下
 * @param nums 表示最初有多少小孩在圈内
 */
public List<Integer > countBoy(int startNo, int countNumber, int nums) {
    // 首先判断first是否为空,然后判断开始人是否小于1 然后判断是否超出最大数
    if (this.first == null || startNo < 1 || startNo > nums || nums != getCountBoy()) {
        System.out.println("参数有误,请重新输入。");
        return null;
    }
    // 首先创建一个辅助指针,然后让其遍历至最后一个。达到最后一个,也就是指向链表的尾部(虽然环形链表不存在尾部,但是也是意义上的尾部)
    Boy helper = this.first;
    while (helper.getNext() != this.first) {
        helper = helper.getNext();
    }
    // 此时出来以后,helper所处的位置已经到了first的前一个位置。现在开始确定从哪一个小孩开始数数,for循环解决
    for (int i = 1; i < startNo; i++) {
        // 如果此时从1号开始数,则根本进不来该循环。无需担心会额外跳出的问题。
        // 现在解决的就是指向问题。
        this.first = this.first.getNext();
        helper = helper.getNext();
    }
    // 跳出循环后,无论怎么样,都会解决掉从那个位置开始。两个指针都会放置到他们应该出现的位置。且 helper永远在first前一个。
    // 当他指向这里之后,我们就开始看,每几下。出一个圈。然后仍然使用for 解决。
    // 假设这里需要每五人出一下,但是,第一个数的人也包含在内。所以需要循环四次。因为指向的是next
    //我们要做的是把他取空,所以使用while
    // 创建集合,依次保存取出的值。ArrayList插入快。
    List<Integer > josepfu = new ArrayList<>();
    while (this.first != null) {
        // 说明到了最后一个,直接添加并且返回
        if (this.first.getNext() == this.first) {
            System.out.println("被移除了节点元素: " + first.getNo());
            josepfu.add(first.getNo());
            break;
        }
        for (int i = 1; i < countNumber; i++) {
            this.first = first.getNext();
            helper = helper.getNext();
        }
        // 当出来for循环之后,他们会分别停在对应的位置上,first会停在被删除的节点的位置上,helper会停留在first的上一个节点上。
        // 此时first后移,然后helper指向fist
        josepfu.add(first.getNo());
        System.out.println("被移除了节点元素: " + first.getNo());
        first = first.getNext();
        helper.setNext(first);
    }
    return josepfu;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

王子良.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值