数据结构中的稀疏数组、队列和链表详解(java语言)

基本上是尚硅谷数据结构的学习笔记

一.稀疏数组

1.1 稀疏数组sparsearray

1.1.1一个实际需求

编写的五子棋程序中,有存盘退出和续上盘的功能。

在这里插入图片描述

➢分析问题:因为该二维数组的很多值是默认值0,因此记录了很多没有意义的数据->所以可以采用稀疏数组来记录。

1.1.2基本介绍

​ 当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
稀疏数组的处理方法是:记录数组一共有几行几列,有多少个不同的值把具有不同值的元素的行列及值记录在-一个小规模的数组中,从而缩小程序的规模

在这里插入图片描述

1.1.2思路分析

二维数组转稀疏数组的思路
1.遍历原始的二_维数组,得到有效数据的个数sum .
2.根据sum就可以创建稀疏数组sparseArr int[sum+1] [3]
3.将二维数组的有效数据数据存入到稀疏数组

package sparsearray;

import java.io.*;

public class Sparsearray {
    //二维数组转稀疏数组
    //1代表黑子  2代表蓝子   0代表没有棋子
    public static void main(String[] args) throws IOException {
        int[][] ints = new int[11][11];
        ints[1][2] =1;
        ints[2][3] =2;
        System.out.println("**********原数组***********");
        for (int i = 0; i < 11; i++) {
            for (int j = 0; j < 11; j++) {
                System.out.print(ints[i][j]+"\t");
            }
            System.out.println();
        }
        System.out.println("************稀疏数组*************");
        //1.先遍历二维数组  得到非零数据的个数
        int sum = 0;
        for (int i = 0; i < 11; i++) {
            for (int j = 0; j < 11; j++) {
                if(ints[i][j] != 0){
                    sum++;
                }
            }
        }
        //2.创建对应的稀疏数组
        int[][] sparsearray = new int[sum + 1][3];
        sparsearray[0][0] = 11;
        sparsearray[0][1] = 11;
        sparsearray[0][2] = sum;
        int count = 1;
        for (int i = 0; i < 11; i++) {
            for (int j = 0; j < 11; j++) {
                if(ints[i][j] != 0){
                    sparsearray[count][0] = i;
                    sparsearray[count][1] = j;
                    sparsearray[count][2] = ints[i][j];
                    count++;
                }
            }
        }
        for (int i = 0; i < sum+1; i++) {
            for (int j = 0; j < 3; j++) {
                System.out.print(sparsearray[i][j]+"\t");
            }
            System.out.println();
        }
        //将稀疏数组中的数据写到文件夹中(其实可以在上面的循环中就可以实现数据存储的,单独拿出来是为了理解!!!)
        FileWriter fw = new FileWriter("data.txt");//字符流
        for (int[] ints2 : sparsearray) {
            for (int i = 0; i < 3; i++) {
                String srr =String.valueOf(ints2[i]);
                //笔记,将"123134"字符串变成整型   int num = Integer.valueOf("123135");
                fw.write(srr+"\t");
            }
            fw.write("\n");
        }
        fw.close();
    }
}

稀疏数组转原始的二维数组的思路
1.先读取稀疏数组的第-行,根据第- -行的数据,创建原始的二维数组,比如上面的chessArr2= int[11][11]
2.在读取稀疏数组后几行的数据,并赋给原始的二维数组即可

public class IOSparsearray {
    public static void main(String[] args) throws IOException {
        ArrayList<Integer> sparsearray = new ArrayList<>();
        FileReader fr = new FileReader("data.txt");
        BufferedReader bfr = new BufferedReader(fr);
        String line = bfr.readLine();
        while(line != null){
            String[] strings = line.split("\t");
            for (int j = 0; j < strings.length; j++) {
                sparsearray.add(Integer.valueOf(strings[j]));//比较垃圾qvq,按理来说我应该让他变成二维数组,但我搞了很久
            }  //以后会了之后会回来修改(如果大佬你写出来了几个call我)
            line = bfr.readLine();
        }
        System.out.println(sparsearray);
        //还原数组
        System.out.println("*********原数组********");
        int[][] ints = new int[sparsearray.get(0)][sparsearray.get(1)];
        for (int i = 1; i <= sparsearray.get(2) ; i++) {
            ints[sparsearray.get(i*3)][sparsearray.get(i*3+1)] = sparsearray.get(3*i+2);
        }
        for (int i = 0; i < 11; i++) {
            for (int j = 0; j < 11; j++) {
                System.out.print(ints[i][j]+"\t");
            }
            System.out.println();
        }
        bfr.close();
    }
}

.

二.队列

2.2.1队列的介绍

➢队列是一个有序列表,可以用数组(顺序存储)或是链表(链式存储)来实现。
➢遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出
➢示意图: (使用数组模拟队列示意图)

在这里插入图片描述

2.2.2数组模拟队列思路

➢队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图,其中maxSize 是该队列的最大容量。
➢因为队列的输出、输入是分别从前后端来处理,因此需要两个变量front及rear分别记录队列前后端的下标,front会随着数据输出而改变,而rear则是随着数据输入而改变,如上图所示.

➢当我们将数据 存入队列时称为”addQueue",addQueue 的处理需要有两个步骤:思路分析
1)将尾指针往后移: rear+1, 当front== rear [空]
2)若尾指针rear 小于队列的最大下标maxSize-1, 则将数据存入rear 所指的数组元素中,否则无法存入数据。rear == maxSize - 1[队列满]

2.2.3代码实现

package Demo02queue;

import java.util.Scanner;

public class ArrayQueueDemo {
    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 'a':
                    System.out.println("输入一个数");
                    int value = scanner.nextInt();
                    queue.addQueue(value);
                    break;
                case 's':
                    queue.showQueue();
                    break;
                case 'g'://取出数据
                    try {
                        int res = queue.getQueue();
                        System.out.println("取出的数据是"+res);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try {
                        int res = queue.headQueue();
                        System.out.println("队列的头序列是"+res);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                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;//该数组用来存放数据,模拟队列

    public ArrayQueue(int maxSize) {
        this.maxSize = maxSize;
        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++;
        return arr[front];
    }

    //显示队列的所有数据
    public void showQueue(){
        if(isEmpty()){
            System.out.println("队列为空无法遍历");
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.println("arr["+i+"]="+arr[i]);
        }
    }

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

问题分析并优化

1)目前的数组使用一次就不能用了,没有实现服用效果.

2)将这个数组使用算法,改进成一个环形队列,用取模实现

2.3环形队列

环形队列详解连接

2.3.1思路

1)front变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素。(front的初始值为0)

2)rear变量的含义做一个调整:rear指向队列的最后一个元素的后一个位置,因为希望空出一个空间作为约定。(由于出现了预留空间,那么队列的长度最长也是规定的数组的长度减一)(rear的初始值为0)

3)当队列满时,条件是(rear + 1) % maxSize= front [满]

4)对队列为空的条件,rear == front空

5)当我们这样分析,队列中有效的数据的个数(rear + maxSize- front) % maxSize (rear= 1 front=0)

在这里插入图片描述

import java.util.Scanner;

public class CricleArrayQueue {
    public static void main(String[] args) {
        CircleArray queue = new CircleArray(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 'a':
                    System.out.println("输入一个数");
                    int value = scanner.nextInt();
                    queue.addCircleQueue(value);
                    break;
                case 's':
                    queue.showCircleQueue();
                    break;
                case 'g'://取出数据
                    try {
                        int res = queue.getCircleQueue();
                        System.out.println("取出的数据是"+res);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try {
                        int res = queue.headCircleQueue();
                        System.out.println("队列的头序列是"+res);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'e':
                    scanner.close();
                    loop = false;
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序退出!");

    }
}
class CircleArray{
    private int maxSize;//表示数组的长度
    //front指向队列的第一个元素
    //front的初始值为front=0
    private int front;
    private int rear;
    //rear变量的含义是:指向队列的最后一个元素的后一个位置
    //!!!是指向队列的最后一个元素,rear<front的情况是成立的!!
    //
    private int[] arr;//用于存放数据,模拟队列

    public CircleArray(int arrMaxSize) {
        front = 0;
        rear = 0;
        maxSize = arrMaxSize;
        arr = new int[arrMaxSize];
    }
    //判断环形队列是否已满
    public boolean isFull(){
        return (rear+1) % maxSize == front;
    }
    //判断环形队列是否为空
    public boolean isEmpty(){
        return rear == front;
    }
    //添加数据到环形队列
    public void addCircleQueue(int n){
        if(isFull()){
            System.out.println("环形队列已满,不能加入数据");
            return;
        }
        arr[rear] = n;
        rear = (rear+1)%maxSize;
    }
    //取出队列中的元素
    public int getCircleQueue(){
        if(isEmpty()){
            //通过抛出异常
            throw new RuntimeException("队列为空无法取出!");
        }
        //front是指向队列的第一个元素
        int temp = front;
        front = (front+ 1)%maxSize;
        return arr[temp];
    }
    //显示队列中的所有数据
    public void showCircleQueue(){
        if(isEmpty()){
            System.out.println("队列为空,无法遍历");
        }
        for (int i = front; i < front +size() ; i++) {
            System.out.println("arr["+i+"]="+arr[i]);
        }
    }
    //返回队列的长度
    public int size(){
        return (rear - front + maxSize)%maxSize;
        //队列的长度实际上就是rear-front的绝对值,这样做是为了防止出现负数
    }
    //队列的头数据(不是取出数据)
    public int headCircleQueue(){
        if(isEmpty()){
            throw new RuntimeException("队列为空,没有数据");
        }
        return arr[front];
    }
}

三.链表(Linked List)介绍

3.1单链表

3.1.1单链表介绍

链表是有序列表,但是它在内存中是储存如下

(有的链表带头节点,有的不带)

(内存中的实际结构)
在这里插入图片描述

在这里插入图片描述

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

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

3)如图:发现链表的各个节点不一定是连续存储

4)链表分带头节点的链表和没有头节点的链表,根据实际需求来确定

3.1.2单链表的实例应用

使用带head头的单向链表实现-水浒英雄排行榜管理
1)完成对英雄 人物的增删改查操作
2)第一种方法在添加英雄时,直接添加到链表的尾部
3)第二种方式在添加英雄时,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示)
在这里插入图片描述

第一种方式

package Demo03LinkedList;

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 sigleLinkedListDemo = new SingleLinkedList();
        //加入
        sigleLinkedListDemo.add(hero1);
        sigleLinkedListDemo.add(hero2);
        sigleLinkedListDemo.add(hero3);
        sigleLinkedListDemo.add(hero4);

        //显示
        sigleLinkedListDemo.list();
    }
}
class SingleLinkedList{
    //初始化一个头节点,头节点不懂,不存放具体数据
    private HeroNode head = new HeroNode(0,"","");
    //添加节点到单项列表,当不考虑编号顺序时
    //1.找到当前链表的最后节点
    //2.将最后这个节点的next指向 新的节点

    public void add(HeroNode heroNode){
        //因为head节点不能动,因此我们需要一个辅助遍历temp
        HeroNode temp = head;
        //遍历链表到最后
        while(true){
            //找到链表的最后一个(next域为null)的
            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;
        }
        HeroNode temp = head.next;
        while(true){
            //判断是否到链表最后
            if(temp == null){
                break;
            }
            //输出节点信息
            System.out.println(temp);
            //将temp后移
            temp = temp.next;
        }
    }
}
//定义SigleLinkedListDemo来管理hero
//定义HeroNode,每个HeroNode 对象就是一个节点
class HeroNode{
    public int no;//指的是编号
    public String name;//名字
    public String nickName;//昵称
    public HeroNode next;//next域,指向下一个节点
    //构造器
    public HeroNode(int hNo,String hName,String hNickName){
        this.no = hNo;
        this.name = hName;
        this.nickName = hNickName;
    }
    //为了显示方便,重写toString
    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickName='" + nickName+
                '}';
    }
}

第二种方式

(以下方法都是都是添加到SingleLinkedList类中的)
在这里插入图片描述

//第二种方法在添加英雄时,根据排名将英雄插入指定位置
//如果有这个排名,添加失败,并给出提示
public void addByOrder(HeroNode heroNode){
    //因为头节点不能动,因此仍热得通过一个辅助指针(变量)来帮助找到添加位置
    //因为是单链表,我们 找的temp是添加位置的前一个节点
    HeroNode temp = head;
    while(true){
        if(temp.next==null){
            temp.next =heroNode;
            break;
        }
        if(temp.no== heroNode.no){
            System.out.println(heroNode.no+"已存在无法添加");
            break;
        }
        if((temp.next.no>heroNode.no)&&(temp.no<heroNode.no)){
            //实际上直接if(tem.next.no>heroNode.no)即可,我这么写好理解写==
            heroNode.next = temp.next;
            temp.next = heroNode;
            break;
        }
        temp = temp.next;//遍历

    }
}

根据编号修改节点的信息

//根据编号no来修改(no是不能改的)
public void update(HeroNode heroNode){
    HeroNode temp = head;
    while(true){
        if(temp.next ==null){
            System.out.println("该英雄节点不在链表中,无法修改");
            break;
        }
        if(temp.no==heroNode.no){
            temp.nickName =heroNode.nickName;
            temp.name = heroNode.name;
            break;
        }
        temp = temp.next;
    }
}

在这里插入图片描述

//根据编号删除节点
public void del(int hNo){
    HeroNode temp = head;
    while(true){
        if (temp.next == null){
            System.out.println("无该编号的的HeroNode");
            break;
        }
        if(temp.next.no == hNo){
            temp.next = temp.next.next;
            if(temp.next==null){
                break;
            }
        }
        temp =temp.next;
    }

3.1.3单链表的面试题

##其实把3.1.2看懂了下面的都很简单的

1)新浪面试题

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

(如果是带头结点的链表,头节点不需要计入)

(我依靠上面的代码简写了)就是一个方法 在SingleLinkedList这个类中添加方法

public int getLength(){
	Node temp = head.next;  //Node 链表中的节点 就是上面的HeroNode 
    int a=o;
    while(temp != null){
        a++;
        temp = temp.next;
    }
    return a;
}

查找单链表中倒数第K个节点

思路

1.编写一个方法,接收一个index

2.index 表示的是倒数的index的节点

3.先把链表从头到尾遍历,的到链表的总长度(即上面的方法getLength)

4.得到size后,从链表的第一个开始遍历(size-index)个,就可以得到

5.如果找到了则返回该节点,否则返回null

public int index(int index){
    Node temp = head;//这也是上面的,这里我没有定义,定义在SingleLinkedList中
 	if(temp.next == null){
        return null;//链表为空返回null
    }
    in size = getLength(head);//这是什么不用我说说了吧
    //遍历   size = getLength(g)
    if(index<=0||index>=size){
        return null;
    }
    for(int i=0;i<(size-index);i++){
        temp =temp.next;
    }
    return temp;
}
2)腾讯面试题

单链表的反转(这个难)

在这里插入图片描述

思路:

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

public class ReverseSingleLinkedListDemo {
    public static void main(String[] args) {
        Node node1 =new Node(1,"胡成小弟");
        Node node2 =new Node(2,"腾达小弟");
        Node node3 =new Node(3,"志文小弟");
        Node node4 =new Node(4,"浩爸爸");

        ReverseSingleLinkedList reverseSingleLinkedList = new ReverseSingleLinkedList();

        reverseSingleLinkedList.add(node1);
        reverseSingleLinkedList.add(node4);
        reverseSingleLinkedList.add(node2);
        reverseSingleLinkedList.add(node3);
        System.out.println("*******遍历加入的元素");
        reverseSingleLinkedList.list();
        System.out.println("*******遍历逆转后的元素");
        reverseSingleLinkedList.reverse();

    }


}
class ReverseSingleLinkedList{
    private Node head = new Node(0,"");//头节点,不存放数据

    //向链表中有序添加元素(按照输入的编号添加)
    public void add(Node node){
        //定义一个中间变量
        Node temp = head;
        while(true){
            if(temp.next==null){
                temp.next = node;
                break;
            }
            if(temp.next.no>node.no){
                node.next = temp.next;
                temp.next = node;
                break;
            }else if(temp.next.no==node.no){
                System.out.println(node.no+"已存在");
                break;
            }
            temp = temp.next;
        }
    }

    //逆转这个单链表(不包括头节点)
    public void reverse(){
        if(head.next == null|| head.next.next ==null){
            System.out.println("链表为长度达不到逆转要求");
        }
        Node reverseHead = new Node(0,"");
        Node temp=head.next;
        Node temp1;
        while(temp != null){
            temp1 = temp.next;
            temp.next =reverseHead.next;//指向新链表的最前端
            reverseHead.next = temp;
            temp = temp1;
        }
        head.next = reverseHead.next;
        list();
    }

    //遍历链表
    public void list(){
        Node temp =head.next;
        if(temp == null){
            System.out.println("链表为空,无法遍历");
            return;
        }
        while(temp != null){
            System.out.println(temp);
            temp = temp.next;
        }
    }
}
//链表中的节点类
class Node{
    public int no;
    public String name;//data域,节点中的数据,自己根据情况自己定义
    public Node next;//next域,用于连接下一个节点

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

    @Override
    public String toString() {
        return "Node{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }
}
3)百度面试题

从尾到头打印单链表(要求方式:反向遍历,Stack栈)

思路:

​ 1.要求的是逆序打印单链表

​ 2.方式一:先将单链表进行反转操作,再打印出来即可(就是上面腾讯的),但这样操作实际上会破坏原来的单链表的结构(他要求的只是是逆向打印,不是将链表的结构改变在打印==),so不建议

​ 3.方式二:可以利用栈这个数据结构,将各个节点压入栈中,然后利用栈先进后出的特点,就实现了逆序打印的效果

因此我选择用方法二,但是现阶段是没有学栈的

//这是一些栈的基本方法,后面会有栈的详细讲解
import java.util.Stack;

//stack的基本演示
public class StackTest {
    public static void main(String[] args) {
        Stack<String> stack = new Stack<>();
        //向栈中压入元素(入栈)
        stack.add("hc");
        stack.add("ttd");
        stack.add("zzw");
        //将栈中的元素弹出(出栈)
        while(stack.size()>0){
            System.out.println(stack.pop());
        }

    }
}

具体实现不改变原有结构实现逆序输出(方法就写这了啊,这我不用多解释了吧)

//逆序遍历单链表
public void reverseListStack(){
    Stack<Node> nodes = new Stack<>();
    Node temp = head.next;
    if(temp==null){
        System.out.println("没有节点你叫我怎么遍历啊!!");
    }
    while(temp!=null){
        nodes.add(temp);//push和add一样的作用
        temp = temp.next;
    }
    while(nodes.size()>0){
        System.out.println(nodes.pop());
    }
}
4)练习

看完前面这么多,链表应该能理解的差不多了吧。这题自己去写写 ==

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

(如果链表理解的差不多了,是可以直接看SingleLInked类中的merge方法

package Demo03LinkedList;

public class LianXi {
    public static void main(String[] args) {
        NodeBiu one = new NodeBiu(1, "浩爸爸");
        NodeBiu two = new NodeBiu(2, "胡成");
        NodeBiu three = new NodeBiu(3, "谭腾达");
        NodeBiu four = new NodeBiu(4, "詹志文");
        NodeBiu five = new NodeBiu(5, "陈泽宇");
        NodeBiu six = new NodeBiu(6, "卢诗航");

        SingleLinked one1 = new SingleLinked();
        SingleLinked two2 = new SingleLinked();

        one1.add(two);
        one1.add(five);
        one1.add(four);
        System.out.println("one1链表~~~");
        one1.list();
        two2.add(six);
        two2.add(three);
        two2.add(one);
        System.out.println("two2链表~~");
        two2.list();
        System.out.println("合并");
        SingleLinked three3 = one1.merge(two2);
        three3.list();


    }
}
class SingleLinked{
    public void list(){
        NodeBiu temp = head.next;
        if(temp == null){
            System.out.println("链表为空,无法遍历");
            return;
        }
        while(temp != null){
            System.out.println(temp);
            temp = temp.next;
        }
    }
    private NodeBiu head =new NodeBiu(0, "");//头节点,不存放数据

    public NodeBiu getHead(){
        return head;
    }

    public void add(NodeBiu nodeBiu){
        NodeBiu temp = head;
        while(true){
            if(temp.next==null){
                temp.next = nodeBiu;
                break;
            }
            if(temp.next.no>nodeBiu.no){
                nodeBiu.next = temp.next;
                temp.next = nodeBiu;
                break;
            }
            if(temp.next.no == nodeBiu.no){
                break;
            }
            temp = temp.next;
        }
    }
    //与另一个单链表合并,返回一个新的单链表,(默认的合并的节点中no没有相等的,如果有no相等的节点,那别的data数据也必须想的,不然这就不叫合并了)
    public SingleLinked merge(SingleLinked one) {
        SingleLinked singleLinked = new SingleLinked();
        NodeBiu temp1 = head.next;
        NodeBiu temp2 = one.getHead().next;
        NodeBiu temp3 = singleLinked.getHead();
        while (true) {
            if (temp1 == null && temp2 == null) {
                return singleLinked;
            } else if (temp1 == null && temp2 != null) {
                temp3.next = temp2;
                temp3 = temp3.next;
                temp2 = temp2.next;
            } else if (temp1 != null && temp2 == null) {
                temp3.next = temp1;
                temp3 = temp3.next;
                temp1 = temp1.next;
            } else if(temp1 != null && temp2 != null){
                if (temp1.no > temp2.no) {
                    temp3.next = temp2;
                    temp3 = temp3.next;
                    temp2 = temp2.next;
                } else if (temp1.no < temp2.no) {
                    temp3.next = temp1;
                    temp3  = temp3.next;
                    temp1 = temp1.next;
//                }else{
//                    temp3.next = temp1.next;
//                    temp3 = temp3.next;
//                    temp1 = temp1.next;
//                    temp2 = temp2.next;
//                }
                }
            }

        }
    }
}
//链表中的节点类
class NodeBiu{
    public int no;
    public String name;//data域,节点中的数据,自己根据情况自己定义
    public NodeBiu next;//next域,用于连接下一个节点

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

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

3.2双向链表

3.2.1双向链表的应用

使用带head头的双向链表实现- -水浒英雄排行榜管理单向链表的缺点分析:
1)单向链表, 查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
2)单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除,所以前面我们单链表删除时节点,总是找到temp的下一个节点来副除的(认真体会).
3)示意图帮助理解删除

分析 双向链表的遍历,添加,修改,删除的操作思路
1)遍历方和单链表一样,只是可以向前,也可以向后查找
2)添加(默认添加到双向链表的最后)
(1)先找到双向链表的最后这个节点
(2) temp.next = newHeroNode
(3) newHeroNodepre = temp;
3)修改思路和原理的单向链表一样.
4)删除
(1)因为是双向链表,因此,我们可以实现自我删除某个节点
(2)直接找到要删除的这个节点,比如temp
(3) temp.pre.next =temp.next
(4) temp.next.pre=temp.pre;

package Demo03LinkedList;

public class DoubleLinkedListDemo {
    public static void main(String[] args) {
        System.out.println("双向链表的测试");
        HeroNode2 node1 =new HeroNode2(1,"胡成小弟");
        HeroNode2 node2 =new HeroNode2(2,"腾达小弟");
        HeroNode2 node3 =new HeroNode2(3,"志文小弟");
        HeroNode2 node4 =new HeroNode2(4,"浩爸爸");
        //创建一个双向链表对象
        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
        doubleLinkedList.add(node1);
        doubleLinkedList.add(node2);
        doubleLinkedList.add(node3);
        doubleLinkedList.add(node4);

        doubleLinkedList.list();
        //修改
        HeroNode2 node5 = new HeroNode2(3, "狗蛋");
        System.out.println("修改后~~~");
        doubleLinkedList.update(node5);
        doubleLinkedList.list();
        //删除
        System.out.println("删除后~~");
        doubleLinkedList.del(3);
        doubleLinkedList.list();
    }
}
class DoubleLinkedList{
    private HeroNode2 head = new HeroNode2(0,"");

    //只能在链表的末尾添加
    public void add(HeroNode2 heroNode){
        HeroNode2 temp = head;
        while(temp.next != null){
            temp = temp.next;
        }
        temp.next = heroNode;
        heroNode.pre = temp;
    }
    //遍历双向链表
    public void list(){
        HeroNode2 temp = head.next;
        if(temp == null){
            System.out.println("链表为空无法遍历");
        }
        while(temp != null){
            System.out.println(temp);
            temp = temp.next;
        }
    }
    //从双向链表中删除一个节点
    public void del(int no){
        //判断当前链表是否为空
        if(head.next ==null){
            System.out.println("当前链表为空链表");
            return;
        }
        HeroNode2 temp = head.next;
        while(true){
            if(temp == null){
                System.out.println("链表中的节点无该编号");
                break;
            }
            if(temp.no == no){
                temp.pre.next = temp.next;//
                if(temp.next != null) {
                    //如果要删除的节点是最后一个,这句话则不要,不然会出现空指针异常
                    temp.next.pre = temp.pre;
                }
                break;
            }
            temp = temp.next;
        }
    }
    public void update(HeroNode2 heroNode){
        HeroNode2 temp = head;
        while(true){
            if(temp ==null){
                System.out.println("该英雄节点不在链表中,无法修改");
                break;
            }
            if(temp.no==heroNode.no){
                temp.name = heroNode.name;
                break;
            }
            temp = temp.next;
        }
    }
}
//定义HeroNode,每个HeroNode 对象就是一个节点
class HeroNode2{
    public int no;//指的是编号
    public String name;//名字
    public HeroNode2 next;
    public HeroNode2 pre;
    //构造器
    public HeroNode2(int hNo,String hName){
        this.no = hNo;
        this.name = hName;
    }
    //为了显示方便,重写toString
    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +

                '}';
    }
}

3.3双向环形链表

3.3.1应用

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

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

n=5,即有5个人
k=1,从第一个人开始报数
m=2,数2下
在这里插入图片描述

3.3.2环形链表的构建思路

在这里插入图片描述

public class yosephu {
    public static void main(String[] args) {
        CircleSingleLinked circleSingleLinked = new CircleSingleLinked();
        circleSingleLinked.addBoy(5);
        circleSingleLinked.list();
    }
}
class CircleSingleLinked{
    //先创建一个first节点,当前没有编号
    private Boy first;

    public void addBoy(int nums){
//先做一个数据校验
        if(nums<1){
            System.out.println("nums的值不正确");
            return;
        }
        Boy temp =null;
        //用for循环构建环形链表
        for (int i = 1; i < nums+1; i++) {
            //根据编号创建小孩节点
            Boy boy = new Boy(i);
            if(i==1){
                first =boy;//此处的first有点绕,其实你就可以把他也当作和temp一样的中间变量,它的值就是第一个节点
                            //本CircleLinkedlist的类是直接产生一个环形链表(通过addBoy来确认环形链表的数量)
                boy.next = boy;
                temp  = boy;
            }else{
                temp.next = boy;
                boy.next =first;
                temp = boy;
            }
        }
    }
    //遍历当前的环形链表
    public void list(){
        //判断链表是否为空
        if(first == null){
            System.out.println("链表为空,无法遍历");
            return;
        }
        Boy temp = first;
        while(true){
            System.out.println("编号为"+temp.no);
            if(temp.next == first){
                break;
            }
            temp = temp.next;//temp后移
        }
    }
}
//先创建一个boy类,表示节点
class Boy{
    public int no ;//编号
    public Boy next;

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

3.3.3josephu环的出圈思路

根据用户的输入,生成一个小孩出圈的顺序
n=5,即有5个人
k=1,从第一个人开始报数
m=2,数2下

1.需求创建-一个辅助指针(变量) helper,事先应该指向环形链表的最后这个节点

(补充:报数之前先让)

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

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

first = first .next
helper.next = first
原来first指向的节点就没有任何引用,就会被回收

3.3.4代码实现

public class yosephu {
    public static void main(String[] args) {
        CircleSingleLinked circleSingleLinked = new CircleSingleLinked();
        circleSingleLinked.addBoy(5);
        circleSingleLinked.list();
        circleSingleLinked.countBoy(1,2,5);
    }
}
class CircleSingleLinked{
    //先创建一个first节点,当前没有编号
    private Boy first;

    public void addBoy(int nums){
//先做一个数据校验
        if(nums<1){
            System.out.println("nums的值不正确");
            return;
        }
        Boy temp =null;
        //用for循环构建环形链表
        for (int i = 1; i < nums+1; i++) {
            //根据编号创建小孩节点
            Boy boy = new Boy(i);
            if(i==1){
                first =boy;//此处的first有点绕,其实你就可以把他也当作和temp一样的中间变量,它的值就是第一个节点
                            //本CircleLinkedlist的类是直接产生一个环形链表(通过addBoy来确认环形链表的数量)
                boy.next = boy;
                temp  = boy;
            }else{
                temp.next = boy;
                boy.next =first;
                temp = boy;
            }
        }
    }
    //遍历当前的环形链表
    public void list(){
        //判断链表是否为空
        if(first == null){
            System.out.println("链表为空,无法遍历");
            return;
        }
        Boy temp = first;
        while(true){
            System.out.println("编号为"+temp.no);
            if(temp.next == first){
                break;
            }
            temp = temp.next;//temp后移
        }
    }
    //根据用户的输入,计算出小孩的出圈顺序

    /**
     *
     * @param startNo 表示从第几个小孩开始数数
     * @param countNum 表示数几下
     * @param nums 表示最初有多少个小孩在圈中
     */
    public void countBoy(int startNo,int countNum,int nums){
        if(first == null ||startNo<1 ||startNo>nums){
            System.out.println("参数输入错误,请重新输入");
            return;
        }
        //创建辅助指针,帮助小孩出圈
        Boy helper = first;
        //需要创建一个辅助指针(变量)helper,事先应该指向环形链表的最后一个节点,(就是指向first的后面)
        while(true){
            if(helper.next== first){
                break;
            }
            helper = helper.next;
        }
        //小孩报数前,先让first和helper移动k-1次
        for (int i = 1; i < startNo; i++) {
            first = first.next;
            helper = helper.next;
        }
        while(helper != first){//说明圈中只有一个节点
            for (int i = 1; i <countNum ; i++) {
                first = first.next;
                helper = helper.next;
            }
            System.out.println("第"+first.no+"小孩出圈");
            //这时first是在出圈小孩的节点处
            first =first.next;
            helper.next =first;
        }
        System.out.println("最后在圈内的小孩是"+first.no);
    }
}
//先创建一个boy类,表示节点
class Boy{
    public int no ;//编号
    public Boy next;

    public Boy(int no) {
        this.no = no;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值