数据结构和算法学习笔记一_数组_队列_链表(Java)

本文介绍了稀疏数组、队列及链表等基本数据结构的概念、应用场景及实现方式,包括数组模拟队列、环形队列、单链表、双向链表、环形链表等,提供了丰富的代码实例。

数据结构和算法学习笔记一_数组_队列_链表

(学自尚硅谷数据结构和算法前30节(java版))

一、稀疏数组(压缩存储数组)

1.1、介绍

当一个数组种大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。

1.2、稀疏数组的处理办法是:
  1. 记录数组一共有几行几列,有多少个不同的值
  2. 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
1.3、举例:五子棋程序,存盘退出和恢复上盘

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ec9X7eui-1605108410981)(D:\TyporaPic\image-.png)]

因为该二维数组的很多值是默认值0,因此记录了很多没有意义的数据->转换成稀疏数组
在这里插入图片描述

二维数组转成稀疏数组的思路:

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

稀疏数组转成二维数组的思路:

  • 读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组
  • 读取稀疏数组除第一行的数组并赋值给原始的二维数组
1.4、代码实现
package com.lxf.sparsearray;

public class SparseArray {
    public static void main(String[] args) {
        //创建一个原始的二维数组11*11,假设有两个棋子:黑子在第二行第三列,蓝子在第三行第四列。
        //0:表示没有棋子,1表示黑子 ,2表篮子
        int charArr1[][] = new int[11][11];
        charArr1[1][2]=1;
        charArr1[2][3]=2;
        charArr1[1][3]=1;
        charArr1[1][4]=2;
        charArr1[3][2]=1;
        charArr1[2][2]=2;
        //row column 长度
        int rowL=charArr1.length;
        int columnL=charArr1[0].length;
        //输出原始的二维数组
        System.out.println("原始的二维数组:");
        for (int[] row : charArr1) {
            for (int data : row) {
                System.out.printf("%d\t",data);
            }
            System.out.println();
        }
        //原始二维数组转成稀疏数组
        //1.
        // 得到非0数据的个数
        //记录数组中非零数的个数
        int sum=0;
        for (int[] row : charArr1) {
            for (int data : row) {
                if(data!=0){
                    sum++;
                }
            }
        }
        //2.创建对应的稀疏数组
        int[][] sparseArr = new int[sum + 1][3];

        //3.将二维数组的数赋值给稀疏数组
        sparseArr[0][0]=rowL;
        sparseArr[0][1]=columnL;
        sparseArr[0][2]=2;
        //记录稀疏数组行
        int count=0;
        for (int i = 0; i < rowL; i++) {
            for (int j = 0; j < columnL; j++) {
                if(charArr1[i][j]!=0){
                    count++;
                    sparseArr[count][0]=i;
                    sparseArr[count][1]=j;
                    sparseArr[count][2]=charArr1[i][j];
                }
            }
        }
        //4.打印稀疏数组
        System.out.println("得到的稀疏数组为:");
        for (int[] row : sparseArr) {
            System.out.printf("%d\t%d\t%d\t",row[0],row[1],row[2]);
            System.out.println();
        }
        //5.将稀疏数组恢复成二维数组
        int[][] charArr2=new int[sum+1][3];
        for (int i = 1; i < sparseArr.length; i++) {
            charArr2[sparseArr[i][0]][sparseArr[i][1]]=sparseArr[i][2];
        }
        //输出恢复后的二维数组
        System.out.println("恢复后的二维数组:");
        for (int[] row : charArr2) {
            for (int data : row) {
                System.out.printf("%d\t",data);
            }
            System.out.println();
        }
    }
}

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tiimeHw8-1605108411030)(D:\TyporaPic\image-.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7LEFGRlX-1605108411047)(D:\TyporaPic\image-.png)]

稀疏数组存入文件,从文件取出操作代码:

package com.lxf.sparsearray;

import java.io.*;
import java.util.Arrays;

public class SparseArray {
    public static void main(String[] args) throws IOException {
        //创建一个原始的二维数组11*11,假设有两个棋子:黑子在第二行第三列,蓝子在第三行第四列。
        //0:表示没有棋子,1表示黑子 ,2表篮子
        int charArr1[][] = new int[11][11];
        charArr1[1][2]=1;
        charArr1[2][3]=2;
        charArr1[1][3]=1;
        charArr1[1][4]=2;
        charArr1[3][2]=1;
        charArr1[2][2]=2;
        //row column 长度
        int rowL=charArr1.length;
        int columnL=charArr1[0].length;
        //原始二维数组转成稀疏数组
        //1.
        // 得到非0数据的个数
        //记录数组中非零数的个数
        int sum=0;
        for (int[] row : charArr1) {
            for (int data : row) {
                if(data!=0){
                    sum++;
                }
            }
        }
        //2.创建对应的稀疏数组
        int[][] sparseArr = new int[sum + 1][3];

        //3.将二维数组的数赋值给稀疏数组
        sparseArr[0][0]=rowL;
        sparseArr[0][1]=columnL;
        sparseArr[0][2]=2;
        //记录稀疏数组行
        int count=0;
        for (int i = 0; i < rowL; i++) {
            for (int j = 0; j < columnL; j++) {
                if(charArr1[i][j]!=0){
                    count++;
                    sparseArr[count][0]=i;
                    sparseArr[count][1]=j;
                    sparseArr[count][2]=charArr1[i][j];
                }
            }
        }
        //将稀疏数组存储到文件中
        File file=new File("C:\\Users\\Administrator\\Desktop\\test.txt");
        if(file!=null&&!file.exists()){
            file.createNewFile();
        }
        //输出流
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
        //先输出第一行
        //写出到文件中
        bos.write((rowL+","+columnL+","+2+"\r\n").getBytes());
        bos.flush();
        for (int i = 1; i < sparseArr.length; i++) {
            //将每一行拼接成字符串
            String str=sparseArr[i][0]+","+sparseArr[i][1]+","+sparseArr[i][2]+"\r\n";
            //写出到文件中
            bos.write(str.getBytes());

            bos.flush();
        }

        //输入流,读取文件
        BufferedReader br = new BufferedReader(new FileReader(file));
        int[][] charArr2=new int[sum+1][3];
        String s=null;
        int count2=0;
        while((s=br.readLine())!=null){
            int[] split = Arrays.stream(s.split(",")).mapToInt(Integer::valueOf).toArray();
            charArr2[count2][0]=split[0];
            charArr2[count2][1]=split[1];
            charArr2[count2][2]=split[2];
            count2++;
        }

        for (int i = 0; i < charArr2.length; i++) {
            System.out.printf("%d\t%d\t%d",charArr2[i][0],charArr2[i][1],charArr2[i][2]);
            System.out.println();
        }
    }
}

二、队列

2.1、数组模拟队列

在这里插入图片描述

MaxSize:队列的最大容量,rear:尾端下标,front:前端下标

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

代码实现:

package com.lxf.queue;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 *
 * @author lxf
 */
public class ArrayQueueDemo {
    public static void main(String[] args) throws IOException {
        //测试
        ArrayQueue arrayQueue = new ArrayQueue(3);
        //接收输入的命令
        char key = ' ';
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        boolean flag = true;
        while (flag) {
            System.out.println("输入e(esc)表示退出程序");
            System.out.println("输入a(addQueue)表示往队列中加入一个数据");
            System.out.println("输入r(removeQueue)表示从队列中取出一个数据");
            System.out.println("输入s(showQueue)表示展示队列的所有数据");
            System.out.println("输入h(headQueue)表示展示队列的头部第一个数据");
            System.out.println("输入t(tailQueue)表示展示队列的尾部最后一个数据");
            //获取输入的命令
            key = br.readLine().charAt(0);
            switch (key){
                case 'e':
                    br.close();
                    flag=false;  
                    break;
                case 'a':
                    System.out.println("请输入您要添加的数据:");
                    int n = Integer.valueOf(br.readLine());
                    arrayQueue.addQueue(n);
                    break;
                case 'r':
                    try {
                        int r = arrayQueue.removeQueue();
                        System.out.printf("取出的数据为:%d",r);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 's':
                    arrayQueue.showQueue();
                    break;
                case 'h':
                    int h = 0;
                    try {
                        h = arrayQueue.headQueue();
                        System.out.printf("队列头部第一个数据为:%d",h);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 't':
                    int t = 0;
                    try {
                        t = arrayQueue.tailQueue();
                        System.out.printf("队列头部第一个数据为:%d",t);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                default:
                    System.out.println("命令错误,请参考提示重新输入!");
                    break;
            }
        }
        System.out.println("成功退出程序!");
    }
}
/**
 * 使用数组模拟队列-编写一个ArrayQueue类
 */
class ArrayQueue{
    /**
     * 表示数组的最大容量
     */
    private int maxSize;
    /**
     * 队列头
     */
    private int front;
    /**
     * 队列尾
     */
    private int rear;
    /**
     * 该数组用于存放数据,模拟队列
     */
    private int[] arr;

    /**
     * 创建队列的构造器
     * @param maxSize 数组大小
     */
    public ArrayQueue(int maxSize) {
        this.maxSize = maxSize;
        arr=new int[maxSize];
        //指向队列头部,分析出front是指向队列头的前一个位置
        front=-1;
        //指向队列尾,指向队列尾的数据(即就是队列的最后一个数据)
        rear=-1;
    }

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

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

    /**
     * 添加数据到队列
     * @param n 添加的数据
     */
    public void addQueue(int n){
        if(isFull()){
            System.out.println("队列已满,无法加入数据!");
            return;
        }
        //rear后移添加数据
        arr[++rear]=n;
        System.out.println("添加数据成功!");
    }

    /**
     * 从队列中取出数据
     * @return 取出的数据
     */
    public int removeQueue(){
        //判断队列是否为空
        if(isEmpty()){
            //抛出异常
            throw new RuntimeException("队列空,不能取数据");
        }
        return arr[++front];
    }

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

    /**
     * 显示头部第一个数
     * @return
     */
    public int headQueue(){
        //判断
        if(isEmpty()){
            throw new RuntimeException("队列为空,没有数据!");
        }
        return arr[front+1];
    }
    /**
     * 显示尾部最后一个数
     * @return
     */
    public int tailQueue(){
        //判断
        if(isEmpty()){
            throw new RuntimeException("队列为空,没有数据!");
        }
        return arr[rear];
    }
}
2.2、数组模拟环形队列

1.1出现问题出现并优化:

  • 目前数组使用一次就不能再用,没有达到复用效果
  • 将这个数组使用算法,改进成一个环形的队列 取模:%

思路如下:

  • front变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素front的初始值=0
  • rear变量的含义做一个调整:rear指向队列的最后一个元素的后一个位置,因为希望空出一个空间做为约定,rear的初始值=0
  • 当队列满时,条件时(rear+1)%maxSize=front【满】
  • 当队列为空的条件,rear==front【空】
  • 当我们这样分析,队列中有效的数据的个数(rear+maxSize-front)%maxSize

代码如下:

package com.lxf.queue;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 *
 * @author lxf
 */
public class CircleArrayQueueDemo {
    public static void main(String[] args) throws IOException {
        //测试
        System.out.println("测试数组模拟环形队列的案例:");
        //说明设置4:其队列的有效数为3
        CircleArray circleArray = new CircleArray(4);
        //接收输入的命令
        char key = ' ';
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        boolean flag = true;
        while (flag) {
            System.out.println("输入e(esc)表示退出程序");
            System.out.println("输入a(addQueue)表示往队列中加入一个数据");
            System.out.println("输入r(removeQueue)表示从队列中取出一个数据");
            System.out.println("输入s(showQueue)表示展示队列的所有数据");
            System.out.println("输入h(headQueue)表示展示队列的头部第一个数据");
            System.out.println("输入t(tailQueue)表示展示队列的尾部最后一个数据");
            //获取输入的命令
            key = br.readLine().charAt(0);
            switch (key){
                case 'e':
                    br.close();
                    flag=false;
                    break;
                case 'a':
                    System.out.println("请输入您要添加的数据:");
                    int n = Integer.valueOf(br.readLine());
                    circleArray.addQueue(n);
                    break;
                case 'r':
                    try {
                        int r = circleArray.removeQueue();
                        System.out.printf("取出的数据为:%d",r);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 's':
                    circleArray.showQueue();
                    break;
                case 'h':
                    int h = 0;
                    try {
                        h = circleArray.headQueue();
                        System.out.printf("队列头部第一个数据为:%d",h);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 't':
                    int t = 0;
                    try {
                        t = circleArray.tailQueue();
                        System.out.printf("队列头部第一个数据为:%d",t);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                default:
                    System.out.println("命令错误,请参考提示重新输入!");
                    break;
            }
        }
        System.out.println("成功退出程序!");
    }

}
class CircleArray{
    /**
     * 表示数组的最大容量
     */
    private  int maxSize;
    /**
     * front变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front]
     * front的初始值=0
     */
    private int front;
    /**
     * rear变量的含义做一个调整:rear指向队列的最后一个位置,因为希望空出一个空间做为约定
     * rear的初始值=0
     */
    private int rear;
    /**
     * 该数据用于存放数据,模拟队列
     */
    private int[] arr;
    /**
     * 创建队列的构造器
     * @param maxSize 数组大小
     */
    public CircleArray(int maxSize) {
        this.maxSize = maxSize;
        arr=new int[maxSize];
    }
    /**
     * 判断队列是否满
     * @return
     */
    public boolean isFull(){
        return (rear+1)%maxSize==front;
    }
    /**
     * 判断队列是否为空
     * @return
     */
    public boolean isEmpty(){
        return rear==front;
    }
    /**
     * 添加数据到队列
     * @param n 添加的数据
     */
    public void addQueue(int n){
        if(isFull()){
            System.out.println("队列已满,无法加入数据!");
            return;
        }
        //rear添加数据再后移
        arr[rear]=n;
        rear=(rear+1)%maxSize;
        System.out.println("添加数据成功!");
    }
    /**
     * 从队列中取出数据
     * @return 取出的数据
     */
    public int removeQueue(){
        //判断队列是否为空
        if(isEmpty()){
            //抛出异常
            throw new RuntimeException("队列空,不能取数据");
        }
        //1.先把front对应的值保留到一个临时变量
        //2.将front后移
        //3.将临时保存的变量返回
        int value=arr[front];
        front=(front+1)%maxSize;
        return value;
    }
    /**
     * 显示队列的所有数据
     */
    public void showQueue(){
        if(isEmpty()){
            System.out.println("队列为空,没有数据!");
            return;
        }
        //从front开始遍历,遍历多少个元素
        for (int i = front; i < front+size(); i++) {
            System.out.printf("arr[%d]=%d\n",i%maxSize,arr[i%maxSize]);
        }
    }

    /**
     * 求出当前队列有效数据的个数
     * @return
     */
    private int size() {
        return (rear+maxSize-front)%maxSize;
    }
    /**
     * 显示头部第一个数
     * @return
     */
    public int headQueue(){
        //判断
        if(isEmpty()){
            throw new RuntimeException("队列为空,没有数据!");
        }
        return arr[front];
    }
    /**
     * 显示尾部最后一个数
     * @return
     */
    public int tailQueue(){
        //判断
        if(isEmpty()){
            throw new RuntimeException("队列为空,没有数据!");
        }
        return arr[rear];
    }
}

三、链表

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

在这里插入图片描述

3.1、单链表的应用

使用带head头的单向链表实现-水浒英雄排行榜管理完成对英雄人物的增删改查操作

  • 第一种方法在添加英雄时,直接添加到链表的尾部

在这里插入图片描述

  • 第二种方法在添加英雄时,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示),思路分析图:

在这里插入图片描述

  • 修改节点功能

修改节点的信息,根据英雄的no编号来找到对应节点后修改英雄信息

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

代码如下:

package com.lxf.linklist;

/**
 *单链表
 * @author Administrator
 */
public class SingleLinkedListDemo {
    public static void main(String[] args) {
        //测试
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");

        //创建一个链表
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        singleLinkedList.addByOrder(hero2);
        singleLinkedList.addByOrder(hero3);
        singleLinkedList.addByOrder(hero1);
        singleLinkedList.addByOrder(hero4);

        //显示数据
        singleLinkedList.list();

        //修改成功一次
        //singleLinkedList.update(new HeroNode(1, "宋江~", "及时雨~"));
        //显示数据
        //singleLinkedList.list();
        //修改失败一次
        //singleLinkedList.update(new HeroNode(11, "宋江~", "及时雨~"));

        singleLinkedList.deleteNode(hero2);
        singleLinkedList.deleteNode(hero1);
        singleLinkedList.deleteNode(hero3);
        singleLinkedList.deleteNode(hero4);

        singleLinkedList.deleteNode(hero3);
    }
}
/**
 * 定义一个SingleLinkedList 管理我们的英雄
 */
class SingleLinkedList{

    /**
     * 先初始化一个头节点,头节点不要动,不存放具体的数据
     */
    private HeroNode head=new HeroNode(0, "","");

    /**
     * 第一种方式添加节点到单向链表
     * 思路(当不考虑编号顺序时):
     * 1.找到当前链表的最后节点
     * 2.将最后这个节点的next指向新的节点
     * @param heroNode
     */
    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;
    }
    /**
     * 第二种方式在添加英雄时,根据排名插入到指定位置
     * 思路(虑编号顺序时):
     * 1.找到当前链表的最后节点
     * 2.将最后这个节点的next指向新的节点
     * @param heroNode
     */
    public void addByOrder(HeroNode heroNode){
        //因为head节点不能动,因此我们需要一个辅助节点temp遍历
        HeroNode temp=head;
        boolean flag=false;
        while (true){
            if(temp.next==null){
                break;
            }
            //位置找到,就在temp的后面插入
            if(temp.next.no>heroNode.no){
                break;
            }else if(temp.next.no==heroNode.no){
                //说明编号存在
                flag=true;
                break;
            }
            //后移,遍历当前链表
            temp=temp.next;
        }
        //判断flag的值
        if(flag){
            System.out.printf("准备插入的英雄的编号 %d 已经存在了,不能加入了\n",heroNode.no);
        }else{
            //插入到链表中,temp的后面
            heroNode.next=temp.next;
            temp.next=heroNode;
        }
        //当退出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;
        }
    }

    /**
     * 修改节点的信息,根据no编号来修改,即no编号不能改
     * 说明:
     * 1.根据newHeroNode的no来修改即可
     * @param newHeroNode
     */
    public void update(HeroNode newHeroNode){
        //判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空!");
            return;
        }
        //找到需要修改的节点,根据no编号
        //定义一个辅助变量
        HeroNode temp=head.next;
        while (temp!=null){
            if(temp.no==newHeroNode.no){
                //找到该节点,修改
                temp.name=newHeroNode.name;
                temp.nickName=newHeroNode.nickName;
                System.out.println("修改节点成功!");
                return;
            }
            temp=temp.next;
        }
        System.out.printf("没有找到编号为%d的英雄!",newHeroNode.no);
    }

    /**
     * 根据英雄节点的no删除一个节点
     * @param heroNode
     */
    public void deleteNode(HeroNode heroNode){
        //判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空!");
            return;
        }
        HeroNode temp=head;
        while (temp!=null){
            if (temp.next.no==heroNode.no) {
                //找到要删除的节点,直接将该节点从引用链上剔除,它最终会GC回收
                temp.next=temp.next.next;
                System.out.println("删除{"+heroNode+"}节点成功");
                return;
            }
            temp=temp.next;
        }
        System.out.println("未找到{"+heroNode+"}节点");
    }
}


/**
 * 定义HeroNode,每个HeroNode对象就是一个节点
 */
class HeroNode{
    /**
     * 英雄编号
     */
    public int no;
    /**
     * 英雄名字
     */
    public String name;
    /**
     * 英雄昵称
     */
    public String nickName;
    /**
     * 指向下一个节点
     */
    public HeroNode next;

    /**
     * 构造器
     * @param no 英雄编号
     * @param name 英雄名字
     * @param nickName 英雄昵称
     */
    public HeroNode(int no, String name, String nickName) {
        this.no = no;
        this.name = name;
        this.nickName = nickName;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickName='" + nickName + '\'' +
                '}';
    }
}
3.2、单链表面试题(新浪、百度、腾讯)

单链表的常见面试题有如下:

  • 求单链表中有效节点的个数
/**
 * 方法:获取到单节点的节点的个数(如果是带头节点的链表,需不统计头节点)
 * @param head 链表额头节点
 * @return 返回的就是有效节点的个数
 */
public static int getLength(HeroNode head){
    if(head.next==null){
        //如果头节点的指向为null,说明该链表是空链表
        return 0;
    }
    int length=0;
    //定义一个辅助的变量
    HeroNode cur=head.next;
    while (cur!=null){
        length++;
        cur=cur.next;
    }
    return length;
}
  • 查找单链表中的倒数第k个结点【新浪面试题】

    • 思路:
    • 1.编写一个方法,接收head节点,同时接收一个index
    • 2.index表示是倒数第index个节点
    • 3.先把链表从头到尾遍历的,得到链表的总的长度getLength
    • 4.得到size后,我们从链表的第一个开始遍历(size-index)个,就可以得到
    • 5.如果找到了,则返回该节点,否则返回null
     /**查找单链表中的倒数第index个节点
         * @param head  头节点
         * @param index 倒数第几个的下标
         * @return
         */
        public static HeroNode findLastIndexNode(HeroNode head,int index){
            //判断链表是否为空
            if(head.next==null){
                return null;
            }
            //循环整个链表的长度
            int length = getLength(head);
            if(index>length||index<0){
                //倒数第index比length还大,说明没有这个节点
                return null;
            }
            //要获取的节点是第length-index个
            int i = length - index+1;
            //记录当前的下标
            int count=1;
            //因为头节点是固定的,所以取中间值来操作
            HeroNode temp=head.next;
            while (count<i){
                temp=temp.next;
                count++;
            }
            return temp;
        }
    
  • 单链表的反转【腾讯面试题】

  /**
     * 思路:顺序遍历链表,到达一个节点先取出它的下一个节点,然后再把它指向反向的那个节点
     * 反转链表
     * @param head 头节点
     */
    public  static void reverseLinkedList(HeroNode head){
        //判断是否需要反向链表
        if(head.next==null||head.next.next==null){
            return;
        }
        //存储当前节点值
        HeroNode cur=head.next;
        //存储下一节点值
        HeroNode nexNode=null;
        //韩老师讲的太复杂了,这个就是为了存储上一个节点值的对象
        //有了三个节点值:当前、下一节点、上一节点,就可以实现反转了
        HeroNode reverseHead=new HeroNode(0, "", "");
        while (cur!=null){
            //获取下一节点值
            nexNode=cur.next;
            //将当前节点的引用指向上一节点,reverseHead.next刚开始为null
            cur.next=reverseHead.next;
            //将当前节点赋值给reverseHead对象,"存储上一节点值"
            reverseHead.next=cur;
            //当前节点等于下一节点,此时三个节点又构造好了
            cur=nexNode;
        }
        //最后就是原链表头节点重新指向头节点就可以了
        head.next=reverseHead.next;
    }
  • 从尾到头打印单链表【百度,要求方式1:反序遍历。方式2:Stack栈】

    • 解决方法一:单链表的反转方法反转后打印,缺点:破坏链表原有结构(参考上题)
    • 解决方法二:将链表按顺序入栈再出栈
    /**
         * 反转打印单向链表
         * @param head  头节点
         */
        public  static void reversePrintLinkedList(HeroNode head){
            //如果链表为空或者链表长度为1,无需操作直接返回
            if(head.next==null||head.next.next==null){
                return;
            }
            //新建一个栈存储链表
            Stack<HeroNode> heroNodeStacks=new Stack<>();
            HeroNode temp=head.next;
            while(temp!=null){
                //顺序入栈
                heroNodeStacks.push(temp);
                temp=temp.next;
            }
            while (!heroNodeStacks.empty()){
                //出栈打印
                System.out.println(heroNodeStacks.pop());
            }
        }
    
  • 合并两个有序的单链表,合并之后的链表依然有序【课后练习】

/**
	 * 合并两个有序列表,合并后也有序
	 * @param head1
	 * @param head2
	 */
	public void mergeLinkedList(HeroNode head1,HeroNode head2) {
		//定义两个中间节点
		HeroNode temp1=head1.next;
		HeroNode temp2=head2.next;
		//记录temp1上一个节点
		HeroNode preNode1=head1;
		//记录temp2的下一节点
		HeroNode nextNode2=null;
		//判断两个链表是否为空
		if(temp1==null||temp2==null) {
			return;
		}
		//循环链表2的每一个数据
		while(temp2!=null) {
			//循环比较链表1的数据,找到对应的位置加入
			while(temp1!=null) {
				//一个一个比较,找到比找到它大的数,这个数的前一个位置就是要插入的位置
				if(temp2.no<temp1.no) {
					//链表2指向下一个数据
					nextNode2=temp2.next;
					//将链表2的数据加入链表1中
					preNode1.next=temp2;
					temp2.next=temp1;
					//赋值链表1的开始位置,进入下一个循环
					temp1=head1.next;
					preNode1=head1;
					
					break;
				}
				if(temp1.next==null) {
					//如果这个数比链表的所有数据都大则加入链表1的最后位置
					nextNode2=temp2.next;
					//将链表2的数据加入链表1中
					temp1.next=temp2;
					temp2.next=null;
					//赋值链表1的开始位置,进入下一个循环
					temp1=head1.next;
					preNode1=head1;
					
					break;
				}
				//指向链表的下一个位置,直到找到对应位置
				preNode1=temp1;
				temp1=temp1.next;
			}
			temp2=nextNode2;
		}
	}
3.3、双向链表的增删改查

在这里插入图片描述

  1. 遍历和单链表一样,只是可以向前,也可以向后查找

  2. 添加(默认加到双向链表的最后)

    • 先找到双向链表的最后这个节点
    • temp.next=newHeroNode
    • newHeroNode.pre=temp;
  3. 修改思路和原理同单向链表一样。

  4. 删除

    • 因为是双向链表,因此,我们可以实现自我删除某个节点
    • 直接找到要删除的这个节点,比如temp
    • temp.pre.next=temp.next
    • temp.next.pre=temp.pre

    代码如下:

    package com.lxf.linklist;
    
    /**
     *
     * @author lxf
     */
    public class DoubleLinkedListDemo {
        public static void main(String[] args) {
            //测试
            DoubleLinkedList  doubleLinkedList=new DoubleLinkedList();
    
            HeroNodeD hero1 = new HeroNodeD(1, "宋江", "及时雨");
            HeroNodeD hero2 = new HeroNodeD(2, "卢俊义", "玉麒麟");
            HeroNodeD hero3 = new HeroNodeD(3, "吴用", "智多星");
            HeroNodeD hero4 = new HeroNodeD(4, "林冲", "豹子头");
    
            doubleLinkedList.addNode(hero1);
            doubleLinkedList.addNode(hero2);
            doubleLinkedList.addNode(hero3);
            doubleLinkedList.addNode(hero4);
    
            doubleLinkedList.list();
            System.out.println();
    
            doubleLinkedList.deleteNode(hero3);
            doubleLinkedList.list();
            System.out.println();
    
            doubleLinkedList.update(new HeroNodeD(2, "有用", "有才"));
            doubleLinkedList.list();
            System.out.println();
        }
    }
    
    
    
    class DoubleLinkedList{
        /**
         * 初始化头节点
         */
        private HeroNodeD headD=new HeroNodeD(0, "","");
    
        public HeroNodeD getHeadD() {
            return headD;
        }
    
        /**
         * 显示链表
         */
        public void list(){
            //判断链表是否为空
            if (headD.next == null) {
                System.out.println("链表为空");
                return;
            }
            //因为头节点不能动,因此我们需要一个辅助变量来遍历
            HeroNodeD temp=headD.next;
            while (true){
                //判断是否到链表的最后
                if (temp == null) {
                    break;
                }
                //输出节点的信息
                System.out.println(temp);
                //将temp后移
                temp=temp.next;
            }
        }
    
        /**
         *
         */
        public void addNode(HeroNodeD newHeroNode){
            //因为head节点不能动,因此我们需要一个辅助节点temp遍历
            HeroNodeD temp=headD;
            while (true){
                //找到链表的最后
                if(temp.next==null){
                    break;
                }
                //如果没有找到最后,就将temp后移
                temp=temp.next;
            }
            //当退出while循环时,temp就指向了链表的最后
            //将这个节点加在最后即可
            //并将新加的节点指向先前的尾节点
            temp.next=newHeroNode;
            newHeroNode.pre=temp;
        }
    
        /**
         * 修改节点的信息,根据no编号来修改,即no编号不能改
         * 说明:
         * 1.根据newHeroNode的no来修改即可
         * @param newHeroNodeD
         */
        public void update(HeroNodeD newHeroNodeD){
            //判断链表是否为空
            if (headD.next == null) {
                System.out.println("链表为空!");
                return;
            }
            //找到需要修改的节点,根据no编号
            //定义一个辅助变量
            HeroNodeD temp=headD.next;
            while (temp!=null){
                if(temp.no==newHeroNodeD.no){
                    //找到该节点,修改
                    temp.name=newHeroNodeD.name;
                    temp.nickName=newHeroNodeD.nickName;
                    System.out.println("修改节点成功!");
                    return;
                }
                temp=temp.next;
            }
            System.out.printf("没有找到编号为%d的英雄!",newHeroNodeD.no);
            System.out.println();
        }
    
        /**
         * 根据英雄节点的no删除一个节点
         * 1.对于双向链表,我们可以直接找到这个要被删除的节点,找到后,自我删除即可
         * @param heroNodeD
         */
        public void deleteNode(HeroNodeD heroNodeD){
            //判断链表是否为空
            if (headD.next == null) {
                System.out.println("链表为空!");
                return;
            }
            HeroNodeD temp=headD.next;
            //标志是否找到待删除的节点
            boolean flag=false;
            while (true){
                if(temp==null){
                    break;
                }
                //找到该节点
                if(temp.no==heroNodeD.no){
                    flag=true;
                    break;
                }
                temp=temp.next;
            }
            //未找到,打印信息并返回
            if(!flag){
                System.out.println("未找到{"+heroNodeD+"}节点");
                return;
            }
            //找到直接将该节点删除(如果删除的节点是最后一个节点特殊处理)
            temp.pre.next=temp.next==null?null:temp.next;
            temp.next.pre=temp.pre;
    
        }
    }
    
    
    /**
     * 定义HeroNode,每个HeroNode对象就是一个节点
     */
    class HeroNodeD{
        /**
         * 英雄编号
         */
        public int no;
        /**
         * 英雄名字
         */
        public String name;
        /**
         * 英雄昵称
         */
        public String nickName;
        /**
         * 指向上一个节点
         */
        public HeroNodeD pre;
        /**
         * 指向下一个节点
         */
        public HeroNodeD next;
    
        /**
         * 构造器
         * @param no 英雄编号
         * @param name 英雄名字
         * @param nickName 英雄昵称
         */
        public HeroNodeD(int no, String name, String nickName) {
            this.no = no;
            this.name = name;
            this.nickName = nickName;
        }
    
        @Override
        public String toString() {
            return "HeroNode{" +
                    "no=" + no +
                    ", name='" + name + '\'' +
                    ", nickName='" + nickName + '\'' +
                    '}';
        }
    }
    

    家庭作业:双向链表的第二种添加方式,按照编号顺添加

/**
     * 根据编号顺序添加节点
     * @param heroNewNodeD
     */
    public void addNodeByOrder(HeroNodeD heroNewNodeD){
        //判断双向链表是否为空
        if(headD.next==null){
            return;
        }
        HeroNodeD temp=headD.next;
        while(temp!=null){
            //找到比新加节点大的节点然后插入这个节点之前
            if(temp.no>heroNewNodeD.no){
                //temp的前一个节点next指向新节点
                temp.pre.next=heroNewNodeD;
                //新节点pre指向前一个节点
                heroNewNodeD.pre=temp.pre;
				//新节点next指向temp
                heroNewNodeD.next=temp;
                //temp的前一节点指向新节点
                temp.pre=heroNewNodeD;
                break;
            }
            //直到查找到链表尾部仍未找到
            if(temp.next==null){
                //temp的next指向新节点
                temp.next=heroNewNodeD;
                //新节点的pre指向temp
                heroNewNodeD.pre=temp;
				//新节点next指向null(可以不写,默认为null)
                heroNewNodeD.next=null;
                break;
            }

            temp=temp.next;
        }
    }
3.4、环形列表
3.4.1、单向环形链表

在这里插入图片描述

构建一单向的环形链表思路:

  1. 先创建一个节点,让first指向该节点,并形成环形
  2. 后面当我们每创建一个新的节点,就把该节点加入到已有的环形链表中即可

遍历环形链表

  1. 先让一个辅助指针(变量),指向firs节点
  2. 然后通过一个while循环遍历该环形链表即可,当curBoy.next==first结束

代码演示:

package com.lxf.linklist;

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

        circleSingleLinkedList.addBoy(5);

        circleSingleLinkedList.list();
    }
}

/**
 * 创建一个环形的单向链表
 */
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 (int i = 1; i <= nums; i++) {
            Boy boy=new Boy(i);
            if(i==1){
                //第一步比较特殊
                //首先头节点要等于这个boy,first的下一个节点等于自己,形成环
                //再记录现在的节点为first
                first=boy;
                first.setNext(first);
                curBoy=first;

                continue;
            }
            //第二步及之后的只需要切入就行
            boy.setNext(curBoy.getNext());
            curBoy.setNext(boy);

            curBoy=boy;
        }
    }
    /**
     * 遍历当前的环形链表
     */
    public void list(){
        //判断链表是否为空
        if (first == null) {
            System.out.println("没有任何小孩");
            return;
        }
        //因为first不能动,因此我们仍然使用一个指针完成遍历
        Boy curBoy=first;
        while(true){
            System.out.printf("小孩的编号%d \n",curBoy.getNo());
            if(curBoy.getNext()==first){
                break;
            }
            curBoy=curBoy.getNext();
        }
        }


}

/**
 * 创建一个Boy类,表示一个节点
 * @author  lxf
 */
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;
    }
}

在这里插入图片描述

Josephu问题代码解决:

/**
     * 根据用户输入,计算出小孩出圈的顺序
     * @param startNo  表示从第几个小孩开始数数 k
     * @param countNum 表示数几下 m
     * @param nums 表示最初有多少小孩在圈中 n
     */
        public void countBoy(int startNo,int countNum,int nums){
            //对数据进行校验
            if(first.getNo()<0||startNo<1||startNo>nums||countNum<1){
                System.out.println("参数输入有误,请重新输入");
                return;
            }

            //1.从第几个数字开始数,first就移动到哪个位置
            for (int i = 1; i < startNo; i++) {
                first=first.getNext();
            }
            //创建要给辅助指针,帮助完成小孩出道
            Boy helper=first;
            //2.需求创建一个辅助指针(变量)helper,事先应该指向环形链表的最后这个节点
            while(true){
                if (helper.getNext()==first) {
                    //说明helper指向最后小孩节点
                    break;
                }
                helper=helper.getNext();
            }
            //当小孩报数时,让first和helper指针同时的移动m-1次,然后出圈
            //这里是一个循环操作,直到圈中只有一个节点
            while(true){
                if(helper==first){//说明圈中只有一个节点
                    break;
                }
                //让first和helper指针同时的移动m-1次
                for (int i = 0; i < countNum - 1; i++) {
                    first=first.getNext();
                    helper=helper.getNext();
                }
                //这时first指向的节点,就是要出圈的小孩节点
                System.out.printf("小孩%d出圈\n",first.getNo());
                //这时将first指向的小孩节点出圈
                helper.setNext(first.getNext());
                first=helper.getNext();
            }
            System.out.printf("小孩%d出圈\n",first.getNo());
        }
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值