数据结构基础和排序算法

数据结构和算法

1. 数据结构

1.1 稀疏数组

  • 这个简单
  • 稀疏数组即二维数组中有大量为0或同一个无效值的时候,将其压缩为只有有效数据的稀疏数组,需要使用时将其读写出来转为二维数组。
public class dom1 {

    public static void main(String[] args) {
        //创建一个原始二维数组  11*11
        int ints[][] = new int[11][11];

        ints[1][2] = 1 ;
        ints[2][3] = 2 ;

        System.out.println("原始的二位数组");
        for (int[] anInt : ints) {
            for (int i : anInt) {
                System.out.printf("%d\t",i);
            }
            System.out.println("");
        }

        //将二维数组转稀疏数组
        //遍历二位数组,得到有效数据的个数
        int sum = 0;
        for (int[] anInt : ints) {
            for (int i : anInt) {
                if(i!=0){
                    sum++;
                }
            }
        }
        System.out.println("有效数据个数"+sum);
        System.out.println("遍历完毕");
        //创建稀疏数组
        int arr[][] = new int[sum+1][3];
        //给稀疏数组赋值
        arr[0][2] = sum;

        int count = 1;   //记录第几个非零数
        int col = 0;
        for (int[] anInt : ints) {
            for (int i : anInt) {
                if(i!=0){
                    arr[count][0]   = count;   // 第几行
                    arr[count][1]   = col;     // 第几列
                    arr[count][2]   = i;
                    count++;
                    continue;
                }
                col++;
            }
            col = 0;
        }

        //懒得加参数算行列个数了,就按照标准稀疏数组读取
        arr[0][0] = 11;
        arr[0][1] = 11;

        //输出稀疏数组
        /**
        for (int[] ints1 : arr) {
            for (int i : ints1) {
                System.out.printf("%d\t",i);
            }
            System.out.println("");
        }
         */
        //稀疏数组只有三列,对以上打印方法优化
        for(int i = 0;i < arr.length;i++){
            System.out.printf("%d%d%d\n",arr[i][0],arr[i][1],arr[i][2]);
        }

        //将稀疏数组恢复成原始二位数组
        //读取第一行,创建新二维数组
        int[][] brr = new int[arr[0][0]][arr[0][1]];

        //数据转移
        int num2 = 0;
        for (int i = 1;i<arr.length;i++){
            brr[arr[i][0]][arr[i][1]] = arr[i][2];
        }

        //打印新二维数组
        for (int[] ints1 : brr) {
            for (int i : ints1) {
                System.out.printf("%d\t",i);
            }
            System.out.println("");
        }
    }
}

1.2 队列

1.2.1 数组模拟单向队列
  • 先来先出原则
  • 代码实现:
import java.util.Scanner;

public class dom2 {
    //队列本身是有序列表
    public static void main(String[] args) {
        ArrayQueue arrayQueue = new ArrayQueue(3);
        //不添加先测展示数据
//        arrayQueue.showQueue();
//        System.out.println(arrayQueue.headQueue());

        //搞一个菜单玩
        char key = ' ';  //接收用户输入
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;
        //输出一个菜单;
        while (loop){
            //死循环持续输出菜单
            //这里直接写比枚举更方便
            System.out.println("s(show): 显示队列");
            System.out.println("e(exit): 退出");
            System.out.println("a(add): 添加");
            System.out.println("g(get): 提取");
            System.out.println("h(head):查看列头");

            key = scanner.next().charAt(0);

            //
            switch (key){
                case 's':
                    arrayQueue.showQueue();
                    break;
                case 'e':
                    //断开
                    scanner.close();
                    loop = false;
                    break;
                case 'a':
                    System.out.println("客官想添点啥呢");
                    int value = scanner.nextInt();
                    arrayQueue.addQueue(value);
                    break;
                case 'g':
                    //方法有异常处理,用try/catch写法
                    try {
                        System.out.println("提取出来的是"+arrayQueue.getQueue());
                    }catch (Exception e){
                        //固打印提升信息就行,别断进程
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try {
                        System.out.println("提取出来的队列头数据是"+arrayQueue.headQueue());
                    }catch (Exception e){
                        System.out.println(e.getMessage());
                    }
                default:
                    //用户不按照菜单提示输入时...
                    System.out.println("给朕好好输!");
                    break;
            }
        }
        System.out.println("欢迎下次光临~~~");
    }
}

//数组模拟单向队列类
class ArrayQueue{
    //最大容量
    private int maxSize;
    //队列头
    private int front;
    //队列尾
    private int rear;
    //模拟队列的数组
    private int arr [];

    //创建队列构造器
    public ArrayQueue(int MaxSize){
        maxSize = MaxSize;
        arr = new int[maxSize];
        //初始化指向队列头尾部前一个位置
        front = -1;
        rear = -1;
    }

    //判断队列满不满
    public boolean isFull(){
        return rear==maxSize-1?true:false;
    }

    //判断队列是不是空
    public boolean isEmpty(){
        return rear == front?true:false;
    }

    //添加数据到队列
    public void addQueue(int n){
        //判断是否满
        if (isFull()){
            System.out.println("满了满了满了满了!!!!!");
            return;
        }
        rear++;
        //添加是往尾部加
        arr[rear] = n;
        //简写:arr[++rear] = n;
    }

    //获取队列数据---出去
    public int getQueue(){
        //判断是否空
        if (isEmpty()){
            //通过异常处理
            throw new RuntimeException("没了没了没了没了!!!!!");
            //return;   这个不用写!!!
        }
        //出队列
        front++;
        return arr[front];
        //return arr[++front];
    }

    //显示队列所有数据
    public void showQueue(){
        if (isEmpty()){
            System.out.println("这啥也没有,拜拜了您呢");
            return;
        }
        for (int i : arr) {
            System.out.println(i);
        }
    }

    //显示头数据
    public int headQueue(){
        if (isEmpty()){
            throw new RuntimeException("想不到吧,队列是空的~~~~~~");
        }
        return arr[front+1];
        //这不能写arr[0],即要返回的是当前还在排队的第一个
    }
}

  • 问题提出:数组只能单次使用,不能复用。
1.2.2 数组模拟环形队列

分析

  • 解决单向队列不能复用的问题
  • 满了的情况尾跑到头:rear代表指向元素最后一个元素,不是数组空间的固定某个位置
  • 满了的条件:(rear+1)%maxSize = front; ---- 这个算法牛逼!
  • 空的条件不变:rear = front
  • 初始值:rear,front都是0;原先是站在队列外等待进去,现在是站在环形跑道内。
  • 有效数据个数:(rear+maxSize-front)%maxSize

代码实现

  • 在单项队列上进行改进
import java.util.Scanner;

/**
 * 里面需要好好思考的算法:
 * 判满:(rear+1)%maxSize==front?true:false;
 * 
 * 有效值个数:(rear + maxSize -front)%maxSize;
 * 
 * 巧用临时变量保存数据: int value = arr[front];
 *                    front = (front+1)%maxSize;
 *                    return value;
 *                    
 *打印全部有效数据:     for (int i = front;i < front+size();i++){
 *                        System.out.println(arr[i%maxSize]);
 *                   }
 */

//环形队列
public class dom3 {
    public static void main(String[] args) {

        //测试环形队列
        CircleQueue circleQueue = new CircleQueue(4);//队列有效数据是三个
        //搞一个菜单玩
        char key = ' ';  //接收用户输入
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;
        //输出一个菜单;
        while (loop){
            //死循环持续输出菜单
            System.out.println("s(show): 显示队列");
            System.out.println("e(exit): 退出");
            System.out.println("a(add): 添加");
            System.out.println("g(get): 提取");
            System.out.println("h(head):查看列头");

            key = scanner.next().charAt(0);

            //
            switch (key){
                case 's':
                    circleQueue.showQueue();
                    break;
                case 'e':
                    //断开
                    scanner.close();
                    loop = false;
                    break;
                case 'a':
                    System.out.println("客官想添点啥呢");
                    int value = scanner.nextInt();
                    circleQueue.addQueue(value);
                    break;
                case 'g':
                    //方法有异常处理,用try/catch写法
                    try {
                        System.out.println("提取出来的是"+circleQueue.getQueue());
                    }catch (Exception e){
                        //固打印提升信息就行,别断进程
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try {
                        System.out.println("提取出来的队列头数据是"+circleQueue.headQueue());
                    }catch (Exception e){
                        System.out.println(e.getMessage());
                    }
                    break;
                default:
                    //用户不按照菜单提示输入时...
                    System.out.println("给朕好好输!");
                    break;
            }
        }
        System.out.println("欢迎下次光临~~~");
    }
}


//数组模拟环形队列类
class CircleQueue{
    //最大容量
    private int maxSize;
    //队列头
    private int front;
    //队列尾
    private int rear;
    //模拟队列的数组
    private int arr [];

    //创建队列构造器
    public CircleQueue(int MaxSize){
        maxSize = MaxSize;
        arr = new int[maxSize];
        //初始化指向队列头尾部前一个位置
        //rear,front默认为0,这里可以不用赋值
    }

    //判断队列满不满
    public boolean isFull(){
        return (rear+1)%maxSize==front?true:false;
    }

    //判断队列是不是空
    public boolean isEmpty(){
        return rear == front?true:false;
    }

    //添加数据到队列
    public void addQueue(int n){
        //判断是否满
        if (isFull()){
            System.out.println("满了满了满了满了!!!!!");
            return;
        }
        arr[rear] = n;
        //尾后移,防止越界
        rear = (rear+1)%maxSize;
    }

    //获取队列数据---出去
    public int getQueue(){
        //判断是否空
        if (isEmpty()){
            //通过异常处理
            throw new RuntimeException("没了没了没了没了!!!!!");
            //return;   这个不用写!!!
        }
        //出队列
        //return arr[front];
        //不能直接返回,return后front就没有后移的机会了
        //用临时变量保存起来,先把指针后移,再提取
        //防止越界一定要取模
        int value = arr[front];
        front = (front+1)%maxSize;
        return value;
    }

    //获取队列有效值个数
    public int size(){
        return (rear + maxSize -front)%maxSize;
    }

    //显示队列所有数据
    public void showQueue(){
        if (isEmpty()){
            System.out.println("这啥也没有,拜拜了您呢");
            return;
        }
        //从front遍历,遍历多少个元素。
        for (int i = front;i < front+size();i++){
            System.out.println(arr[i%maxSize]);
        }
    }

    //显示头数据
    public int headQueue(){
        if (isEmpty()){
            throw new RuntimeException("想不到吧,队列是空的~~~~~~");
        }
        return arr[front];
        //这不能写arr[0],即要返回的是当前还在排队的第一个
    }
}

1.3 链表

1.3.1 单链表的创建和遍历

基本介绍

  • 链表是有序列表

  • 链表以节点的方式存储

  • 每个节点包含data域存储数据,next域指向下一个节点

  • 链表的各个节点不一定是连续存储

  • 链表分为带头结点的跟不带头节点的,头节点根据需求确定有无

  • 头节点不存放具体数据,只存放指向下一个节点地址

  • 最后一个节点next域为null

  • 创建:

    • 创建一个head头节点
    • 每添加一个节点,直接加入到最后
  • 遍历:

    • 通过一个辅助变量,帮助遍历整个链表

代码实现----添加水浒人物

public class dom4 {

    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, "林冲", "猫猫头");
        HeroNode heroNode5 = new HeroNode(5, "李逵", "李白的黑妹");
        HeroNode heroNode6 = new HeroNode(6, "张顺", "浪里白条鸡");
        HeroNode heroNode7 = new HeroNode(7, "孙二娘", "母老虎");

        //new 一个管理器
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        //加入
        singleLinkedList.addSingleLinkedList(heroNode1);
        singleLinkedList.addSingleLinkedList(heroNode2);
        singleLinkedList.addSingleLinkedList(heroNode3);
        singleLinkedList.addSingleLinkedList(heroNode4);
        singleLinkedList.addSingleLinkedList(heroNode5);
        singleLinkedList.addSingleLinkedList(heroNode6);
        singleLinkedList.addSingleLinkedList(heroNode7);
        //显示
        singleLinkedList.list();
    }
}

//定义SingleLinkedList管理hero
class SingleLinkedList{
    //初始化头节点,头节点不动,不放具体数据
    private HeroNode head = new HeroNode(0,"","");

    //添加节点向单链表
    //不考虑编号顺序时,找到当前链表最后节点,后节点next指向新节点
    public void addSingleLinkedList(HeroNode heroNode){

        //head节点不能动,需要辅助变量,注意辅助变量temp是HeroNode类
        HeroNode temp = head;
        //遍历链表找到最后
        while (true){
            if(temp.next == null){
                break;
            }
            //没有找到null,temp后移
            temp = temp.next;
        }
        //退出while循环时,temp指向了链表最后
        //将最后节点指向新节点
        temp.next = heroNode;   // 所有数据都在next里面!!!!!!!!!!!!!!!!!!!将next想成层层嵌套的宝塔肉
    }

    //显示链表,通过辅助变量
    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);
            //一定要将next后移
            temp = temp.next;
        }
    }
}

//定义一个HeroNode,每个对象是一个节点
class HeroNode{
    private int no;
    private String name;
    private String nikeNode;

    //数据都放在next里面
    public HeroNode next;

    //这样构造是为了插入数据
    public HeroNode(int hno,String hName,String hNikeName){
        this.no = hno;
        this.name = hName;
        this.nikeNode = hNikeName;
    }

    //显示方便,重写toString
    //注意不要打印next,每一层next都包含着本层后面所有数据
    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nikeNode='" + nikeNode +
                '}';
    }
}
  • 当前实现的问题是将插入的数据连接起来,跟插入的前后有关,与插入的对象自身属性无关

  • 与数组形式的列表不同,链表可以没有上限,真实物理内存动态变化。

  • 这里较为难理解的next,注意!!!

  • 只能实现尾插

1.3.2 单链表按顺序插入节点
  • 修改addSingleLinkedList方法变成按顺序插入
public class dom4 {

    public static void main(String[] args) {
        //先创建节点
        HeroNode heroNode1 = new HeroNode(1, "宋江", "马后炮");
        HeroNode heroNode2 = new HeroNode(2, "卢俊义", "黑麒麟");
        HeroNode heroNode5 = new HeroNode(5, "李逵", "李白的黑妹");
        HeroNode heroNode6 = new HeroNode(6, "张顺", "浪里白条鸡");
        HeroNode heroNode3 = new HeroNode(3, "吴用", "无用");
        HeroNode heroNode4 = new HeroNode(4, "林冲", "猫猫头");
        HeroNode heroNode7 = new HeroNode(7, "孙二娘", "母老虎");

        //new 一个管理器
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        //加入
        singleLinkedList.addSingleLinkedList(heroNode1);
        singleLinkedList.addSingleLinkedList(heroNode2);
        singleLinkedList.addSingleLinkedList(heroNode3);
        singleLinkedList.addSingleLinkedList(heroNode4);
        singleLinkedList.addSingleLinkedList(heroNode5);
        singleLinkedList.addSingleLinkedList(heroNode6);
        singleLinkedList.addSingleLinkedList(heroNode7);
        //显示
        singleLinkedList.list();
    }
}

//定义SingleLinkedList管理hero
class SingleLinkedList{
    //初始化头节点,头节点不动,不放具体数据
    private HeroNode head = new HeroNode(0,"","");

    //添加节点向单链表
    //不考虑编号顺序时,找到当前链表最后节点,后节点next指向新节点
    public void addSingleLinkedList(HeroNode heroNode){

        //head节点不能动,需要辅助变量
        HeroNode temp = head;

        //判断是不是已经有这个编号
        boolean flag = false;
        //遍历链表找到要插入的位置
        while (true){
            //temp在链表最后面
            if(temp.next == null){
                break;
            }
            //直接从头节点开始判断下一个与当前heroNode.no,这个真没想到
            //排除法原则
            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.println(heroNode.no+"  这个编号被占了");
        }else {
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }

    //显示链表,通过辅助变量
    public void list(){
        //判断链表为空
        if(head.next==null){
            System.out.println("链表为空");
            return;
        }
        //辅助变量遍历打印
        HeroNode temp = head.next;
        while (true){
            //判断是否结束
            if (temp == null){
                break;
            }
            //输出节点信息
            System.out.println(temp);
            //一定要将next后移
            temp = temp.next;
        }
    }
}

//定义一个HeroNode,每个对象是一个节点
class HeroNode{
    public int no;

    //这俩参数暂时用不到
    private String name;
    private String nikeNode;

    //数据都放在next里面
    public HeroNode next;

    //这样构造是为了插入数据
    public HeroNode(int hno,String hName,String hNikeName){
        this.no = hno;
        this.name = hName;
        this.nikeNode = hNikeName;
    }

    //显示方便,重写toString
    //注意不要打印next,每一层next都包含着本层后面所有数据
    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nikeNode='" + nikeNode +
                '}';
    }
}
  • 算法亮点:用链表的机制做插入判断,遍历过程中直接判断下一个。
1.3.3 单链表节点的修改
public class dom4 {

    public static void main(String[] args) {
        //先创建节点
        HeroNode heroNode1 = new HeroNode(1, "宋江", "马后炮");
        HeroNode heroNode2 = new HeroNode(2, "卢俊义", "黑麒麟");
        HeroNode heroNode5 = new HeroNode(5, "李逵", "李白的黑妹");
        HeroNode heroNode6 = new HeroNode(6, "张顺", "浪里白条鸡");
        HeroNode heroNode3 = new HeroNode(3, "吴用", "无用");
        HeroNode heroNode4 = new HeroNode(4, "林冲", "猫猫头");
        HeroNode heroNode7 = new HeroNode(7, "孙二娘", "母老虎");

        //测试修改
        HeroNode heroNode8 = new HeroNode(1, "鲁智深", "阿弥陀佛么么哒");
        HeroNode heroNode9 = new HeroNode(3, "武松", "动物保护协会会长");
        HeroNode heroNode10 = new HeroNode(7, "史进", "纹身的不良少年");
        HeroNode heroNode11 = new HeroNode(8, "关胜", "关二爷的小迷弟");

        //new 一个管理器
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        //加入
        singleLinkedList.addSingleLinkedList(heroNode1);
        singleLinkedList.addSingleLinkedList(heroNode2);
        singleLinkedList.addSingleLinkedList(heroNode3);
        singleLinkedList.addSingleLinkedList(heroNode4);
        singleLinkedList.addSingleLinkedList(heroNode5);
        singleLinkedList.addSingleLinkedList(heroNode6);
        singleLinkedList.addSingleLinkedList(heroNode7);
        //修改
        singleLinkedList.updateSingleLinkedList(heroNode8);
        singleLinkedList.updateSingleLinkedList(heroNode9);
        singleLinkedList.updateSingleLinkedList(heroNode10);
		//测试不存在的对象修改
        singleLinkedList.updateSingleLinkedList(heroNode11);
        //显示
        singleLinkedList.list();
    }
}

//定义SingleLinkedList管理hero
class SingleLinkedList{
    //初始化头节点,头节点不动,不放具体数据
    private HeroNode head = new HeroNode(0,"","");

    //添加节点向单链表
    //不考虑编号顺序时,找到当前链表最后节点,后节点next指向新节点
    public void addSingleLinkedList(HeroNode heroNode){

        //head节点不能动,需要辅助变量
        HeroNode temp = head;

        //判断是不是已经有这个编号
        boolean flag = false;
        //遍历链表找到要插入的位置
        while (true){
            //temp在链表最后面
            if(temp.next == null){
                break;
            }
            //直接从头节点开始判断下一个与当前heroNode.no,这个真没想到
            //排除法原则
            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.println(heroNode.no+"  这个编号被占了");
        }else {
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }

    //显示链表,通过辅助变量
    public void list(){
        //判断链表为空
        if(head.next==null){
            System.out.println("链表为空");
            return;
        }
        //辅助变量遍历打印
        HeroNode temp = head.next;
        while (true){
            //判断是否结束
            if (temp == null){
                break;
            }
            //输出节点信息
            System.out.println(temp);
            //一定要将next后移
            temp = temp.next;
        }
    }

    //根据newHeroNode的 no 修改就行
    public void updateSingleLinkedList(HeroNode newHeroNode){
        if (head.next == null){
            System.out.println("链表是空哒小老弟");
            return;
        }
        HeroNode temp = head;
        while (true){
            if(temp == null){
                System.out.println("没有 "+newHeroNode.no+" 号这个小朋友");
                break;
            }
            //找到了
            if(temp.no == newHeroNode.no){
                temp.name = newHeroNode.name;
                temp.nikeNode = newHeroNode.nikeNode;
                break;
            }
            temp = temp.next;
        }
    }
}

//定义一个HeroNode,每个对象是一个节点
class HeroNode{
    public int no;

    //能修改了那就改成public
    public String name;
    public String nikeNode;

    //数据都放在next里面
    public HeroNode next;

    //这样构造是为了插入数据
    public HeroNode(int hno,String hName,String hNikeName){
        this.no = hno;
        this.name = hName;
        this.nikeNode = hNikeName;
    }

    //显示方便,重写toString
    //注意不要打印next,每一层next都包含着本层后面所有数据
    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nikeNode='" + nikeNode +
                '}';
    }
}



  • 加一个update类方法就行,这个简单
1.3.4 单链表节点的删除
public class dom4 {

    public static void main(String[] args) {
        //先创建节点
        HeroNode heroNode1 = new HeroNode(1, "宋江", "马后炮");
        HeroNode heroNode2 = new HeroNode(2, "卢俊义", "黑麒麟");
        HeroNode heroNode5 = new HeroNode(5, "李逵", "李白的黑妹");
        HeroNode heroNode6 = new HeroNode(6, "张顺", "浪里白条鸡");
        HeroNode heroNode3 = new HeroNode(3, "吴用", "无用");
        HeroNode heroNode4 = new HeroNode(4, "林冲", "猫猫头");
        HeroNode heroNode7 = new HeroNode(7, "孙二娘", "母老虎");

        //测试修改
        HeroNode heroNode8 = new HeroNode(1, "鲁智深", "阿弥陀佛么么哒");
        HeroNode heroNode9 = new HeroNode(3, "武松", "动物保护协会会长");
        HeroNode heroNode10 = new HeroNode(7, "史进", "纹身的不良少年");
        HeroNode heroNode11 = new HeroNode(8, "关胜", "关二爷的小迷弟");

        //new 一个管理器
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        //增加
        singleLinkedList.addSingleLinkedList(heroNode1);
        singleLinkedList.addSingleLinkedList(heroNode2);
        singleLinkedList.addSingleLinkedList(heroNode3);
        singleLinkedList.addSingleLinkedList(heroNode4);
        singleLinkedList.addSingleLinkedList(heroNode5);
        singleLinkedList.addSingleLinkedList(heroNode6);
        singleLinkedList.addSingleLinkedList(heroNode7);
        //修改
        singleLinkedList.updateSingleLinkedList(heroNode8);
        singleLinkedList.updateSingleLinkedList(heroNode9);
        singleLinkedList.updateSingleLinkedList(heroNode10);

        //测试不存在的对象修改
        singleLinkedList.updateSingleLinkedList(heroNode11);

        //删除
        singleLinkedList.deleteSingleLinkedList(3);
        singleLinkedList.deleteSingleLinkedList(7);
        singleLinkedList.deleteSingleLinkedList(9);
        //显示
        singleLinkedList.list();
    }
}

//定义SingleLinkedList管理hero
class SingleLinkedList{
    //初始化头节点,头节点不动,不放具体数据
    private HeroNode head = new HeroNode(0,"","");

    //添加节点向单链表
    //不考虑编号顺序时,找到当前链表最后节点,后节点next指向新节点
    public void addSingleLinkedList(HeroNode heroNode){

        //head节点不能动,需要辅助变量
        HeroNode temp = head;

        //判断是不是已经有这个编号
        boolean flag = false;
        //遍历链表找到要插入的位置
        while (true){
            //temp在链表最后面
            if(temp.next == null){
                break;
            }
            //直接从头节点开始判断下一个与当前heroNode.no,这个真没想到
            //排除法原则
            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.println(heroNode.no+"  这个编号被占了");
        }else {
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }

    //显示链表,通过辅助变量
    public void list(){
        //判断链表为空
        if(head.next==null){
            System.out.println("链表为空");
            return;
        }
        //辅助变量遍历打印
        HeroNode temp = head.next;
        while (true){
            //判断是否结束
            if (temp == null){
                break;
            }
            //输出节点信息
            System.out.println(temp);
            //一定要将next后移
            temp = temp.next;
        }
    }

    //根据newHeroNode的 no 修改就行
    public void updateSingleLinkedList(HeroNode newHeroNode){
        if (head.next == null){
            System.out.println("链表是空哒小老弟");
            return;
        }
        HeroNode temp = head;
        while (true){
            if(temp == null){
                System.out.println("没有 "+newHeroNode.no+" 号这个小朋友");
                break;
            }
            //找到了
            if(temp.no == newHeroNode.no){
                temp.name = newHeroNode.name;
                temp.nikeNode = newHeroNode.nikeNode;
                break;
            }
            temp = temp.next;
        }
    }

    //删除,想删几号嘉宾?
    public void deleteSingleLinkedList(int no){
        if (head.next == null){
            System.out.println("链表是空哒小老弟");
            return;
        }
        HeroNode temp = head;
        //自己写的,绕了点
//        while (true){
//            //尾删,不管咋删注意遍历的temp指向的是欲删节点的前一个节点
//            if(temp.next.no == no && temp.next.next==null){
//                temp.next = null;
//                System.out.println("删除了"+ no +"号嘉宾");
//                break;
//            }
//            //尾之前的删
//            if(temp.next.no == no){
//                temp.next = temp.next.next;
//                System.out.println("删除了"+ no +"号嘉宾");
//                break;
//            }
//            temp = temp.next;
//            //注意判断遍历完的位置
//            if(temp.next == null){
//                System.out.println("没有 "+ no +" 号这个小朋友");
//                break;
//            }
//        }

        //大佬的思维
        //先判断能不能删!默认不能
        boolean flag = false;
        while (true){
            if(temp.next == null){
                System.out.println("没有 "+no+" 号这个小朋友");
                break;
            }
            if(temp.next.no == no){
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag){
            temp.next = temp.next.next;
        }
    }
}

//定义一个HeroNode,每个对象是一个节点
class HeroNode{
    public int no;

    //能修改了那就改成public
    public String name;
    public String nikeNode;

    //数据都放在next里面
    public HeroNode next;

    //这样构造是为了插入数据
    public HeroNode(int hno,String hName,String hNikeName){
        this.no = hno;
        this.name = hName;
        this.nikeNode = hNikeName;
    }

    //显示方便,重写toString
    //注意不要打印next,每一层next都包含着本层后面所有数据
    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nikeNode='" + nikeNode +
                '}';
    }
}
  • 被删除的节点不会有任何引用指向它,会被jvm垃圾回收机制清理。

  • 回顾单链表:

    • 链表存储的形式
    • 逻辑示意图-像羊肉串
    • 链表的增删改查
    • 大佬的思维:修改和删除–标志位法,先找到该节点(删除-节点前一项)
1.3.5 单链表经典面试题
  1. 求单链表中节点个数
  1. 查找单链表倒数第k个节点 – 新浪
  1. 单链表反转 – 腾讯
  1. 从尾到头打印单链表 --要求:方法1.反向遍历 方法2.Stack栈 – 百度
  1. 合并两个有序链表,合并之后的链表依然有序
  • 尝试实现-

  • 方便显示五道题互不干涉,实现的方法写在一起

    • 实现成功:
      • 计算节点个数: T(n) = n
      • 查找单链表倒数第k个节点: T(n) = n
      • 从尾到头打印单链表: T(n) = n2
    • 未实现成功
      • 反转 — 不了解栈的具体操作实现,暴力遍历…
    • 欠缺思路
      • 合并

public class dom5 {
    public static void main(String[] args) {

        //创建对象
        User u1 = new User(1, "赵信");
        User u2 = new User(2, "盖伦");
        User u3 = new User(3, "嘉文");
        User u4 = new User(4, "铁苍蝇");
        User u5 = new User(5, "狗头");


        //创建链表管理器
        SingleLinkedList2 singleLinkedList2 = new SingleLinkedList2();
        //测试添加
        singleLinkedList2.addUser(u1);
        singleLinkedList2.addUser(u4);
        singleLinkedList2.addUser(u2);
        singleLinkedList2.addUser(u3);

        //测试显示
        singleLinkedList2.showUser();

        //测试计数器
        System.out.println(singleLinkedList2.UserNum());

        //测试倒数第 k 个数据打印
        singleLinkedList2.reciprocalByK(1);
        singleLinkedList2.reciprocalByK(2);
        singleLinkedList2.reciprocalByK(3);
        singleLinkedList2.reciprocalByK(4);
        singleLinkedList2.reciprocalByK(5);
        singleLinkedList2.reciprocalByK(-1);

        //先测尾插---添加时候只能添加新的节点对象,不能重复添加,重复的链表对象只要不是尾插next里面不是null,容易造成死循环
        singleLinkedList2.add(u5);

        //测试反转---测试失败!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        //singleLinkedList2.reversal();
        //System.out.println("----------------");
        //singleLinkedList2.showUser();

        //测试反向打印
        singleLinkedList2.reciprocalPrintln();
    }
}


class SingleLinkedList2{

    //头节点
    User head = new User(0,"");

    //给尾插用(受反转影响)
    User str = head;

    //有序添加,自动排序
    public void addUser(User user){
        User temp = head;

        //能不能插入,默认不能
        boolean flag = false;

        while (true) {
            if(temp.next == null){
                flag = true;
                break;
            }
            if(temp.next.id>user.id){
                flag = true;
                break;
            }else if(temp.next.id==user.id){
                break;
            }
            temp = temp.next;
        }
        if(flag){
           user.next = temp.next;
           temp.next = user;
        }else {
            System.out.println(user.id+"数据重复,插入失败");
        }
    }

    //尾插--反转用得到
    public void add(User user){
        while (true){
            if(str.next == null){
                break;
            }
            str = str.next;
        }
        str.next = user;
    }

    //显示所有数据
    public void showUser(){
        if(head.next==null){
            System.out.println("链表为空");
            return;
        }

        User temp = head.next;

        while (true) {
            if(temp == null){
                break;
            }
            System.out.println(temp);
            temp = temp.next;
        }
    }

    //计算节点数
    public int UserNum(){
        if(head.next == null){
            return 0;
        }

        User temp = head.next;
        int con = 0;
        while (true){
            if(temp == null){
                return con;
            }
            con++;
            temp = temp.next;
        }
    }

    //打印倒数第k个对象
    public void reciprocalByK(int k){
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        if(k < 1){
            System.out.println("无效参数");
            return;
        }
        int num = UserNum();
        if(k>num){
            System.out.println("链表当前仅有 " + num +"条数据");
            return;
        }
        User temp = head.next;
        while (num-k > 0){
            num--;
            temp = temp.next;
        }
        System.out.println("倒数第 "+ k + " 条数据为" + temp);
    }

    //链表反转--先写的反向打印--反向都是一个思路  失败!!!!!!!!!!!!!
    public void reversal(){
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }

        int num = UserNum();

        //这里得用一个新临时头节点代表之前的链表头,真正的head得变了
        User newHead = head.next;

        User temp = head.next;

        while (num>0){
            int i = num;
            while (true){
                if(i == 1){
                    //调用尾插-- 置空,只插一条数据
                    User user = temp;
                    user.next = null;
                    add(user);
                    break;
                }
                i -- ;
                temp = temp.next;
            }
            num --;
            //头节点后移
            head = head.next;
            temp = newHead.next;
        }
    }

    //链表反向打印---有UserNum干啥都方便
    public void reciprocalPrintln(){
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        int num = UserNum();
        User temp = head.next;

        while (num>0){
            int i = num;
            while (true){
                if(i == 1){
                    System.out.println(temp);
                    break;
                }
                i -- ;
                temp = temp.next;
            }
            num --;
            temp = head.next;
        }
        System.out.println("反向打印完毕");
    }
}

//链表对象
class User{
    public int id;
    public String name;
    public User next;

    public User(int Uid , String Uname){
        this.id = Uid;
        this.name = Uname;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name +
                '}';
    }
}
  • 来看大佬的思维


//单链表面试题
//        > 1. 求单链表中节点个数
//        > 2. 查找单链表倒数第k个节点  -- 新浪
//        > 3. 单链表反转  -- 腾讯
//        > 4. 从尾到头打印单链表 --要求:方法1.反向遍历  方法2.Stack栈   -- 百度
//        > 5. 合并两个有序链表,合并之后的链表依然有序

import java.util.Stack;

public class dom5 {
    public static void main(String[] args) {

        //创建对象
        User u1 = new User(1, "赵信");
        User u2 = new User(2, "盖伦");
        User u3 = new User(3, "嘉文");
        User u4 = new User(4, "铁苍蝇");
        User u5 = new User(5, "狗头");

        //头节点
        User head = new User(0,"");

        //创建链表管理器
        SingleLinkedList2 singleLinkedList2 = new SingleLinkedList2();
        //测试添加
        singleLinkedList2.addUser(head,u1);
        singleLinkedList2.addUser(head,u4);
        singleLinkedList2.addUser(head,u2);
        singleLinkedList2.addUser(head,u3);

        //测试显示
//        singleLinkedList2.showUser(head);

        //测试计数器
//        System.out.println(singleLinkedList2.UserNum(head));

        //测试倒数第 k 个数据打印---我的写法
//        singleLinkedList2.reciprocalByK(head,1);
//        singleLinkedList2.reciprocalByK(head,2);
//        singleLinkedList2.reciprocalByK(head,3);
//        singleLinkedList2.reciprocalByK(head,4);
//        singleLinkedList2.reciprocalByK(head,5);
//        singleLinkedList2.reciprocalByK(head,-1);

//        System.out.println("倒数第k个节点,老师写法");
//        User test1 = singleLinkedList2.find(head,1);
//        User test2 = singleLinkedList2.find(head,2);
//        User test3 = singleLinkedList2.find(head,3);
//        User test4 = singleLinkedList2.find(head,4);
//        User test5 = singleLinkedList2.find(head,5);
//        System.out.println(test1);
//        System.out.println(test2);
//        System.out.println(test3);
//        System.out.println(test4);
//        System.out.println(test5);

        //测试反转---测试失败!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        //singleLinkedList2.reversal();
        //System.out.println("----------------");
        //singleLinkedList2.showUser();

        //测试反转,新思路:从倒数第二个开始依次向前遍历添加到最后面
        //测试反转---测试失败!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        //System.out.println("------------------------");
        //singleLinkedList2.reversal2(head);
        //singleLinkedList2.showUser(head);

        //失败总结:::永远不要在同一条链中有复制粘贴类的想法!!!!,它会出现同手同脚,删除时两个位置全部删,赋值死循环的问题。

        //单链表反转---测试老师的代码
//        System.out.println("原版:");
//        singleLinkedList2.showUser(head);
//        System.out.println("反转后");
//        SingleLinkedList2.reversalList(head);
//        singleLinkedList2.showUser(head);

        //测试反向打印
        //singleLinkedList2.reciprocalPrintln();

        //测试stack栈的逆向打印方式,不会改变链表结构
        System.out.println("原版:");
        singleLinkedList2.showUser(head);
        System.out.println("逆向打印");
        SingleLinkedList2.reversePrint(head);

    }
}

class SingleLinkedList2{

    //有序添加,自动排序
    public void addUser(User head,User user){
        User temp = head;

        //能不能插入,默认不能
        boolean flag = false;

        while (true) {
            if(temp.next == null){
                flag = true;
                break;
            }
            if(temp.next.id>user.id){
                flag = true;
                break;
            }else if(temp.next.id==user.id){
                break;
            }
            temp = temp.next;
        }
        if(flag){
           user.next = temp.next;
           temp.next = user;
        }else {
            System.out.println(user.id+"数据重复,插入失败");
        }
    }
    //尾插--反转用得到--实际上尾插可以,用尾插的反转没成功
    public static void add(User head,User user){
        User str = head;
        while (true){
            if(str.next == null){
                break;
            }
            str = str.next;
        }
        str.next = user;
    }
    //显示所有数据
    public void showUser(User head){
        if(head.next==null){
            System.out.println("链表为空");
            return;
        }

        User temp = head.next;

        while (true) {
            if(temp == null){
                break;
            }
            System.out.println(temp);
            temp = temp.next;
        }
    }
    //计算节点数
    public static int UserNum(User head){
        if(head.next == null){
            return 0;
        }

        User temp = head.next;
        int length = 0;

        while (temp != null){
            length ++;
            temp = temp.next;
        }
        return length;
    }
    //打印倒数第k个对象
    public static void reciprocalByK(User head,int k){
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        if(k < 1){
            System.out.println("无效参数");
            return;
        }
        int num = UserNum(head);
        if(k>num){
            System.out.println("链表当前仅有 " + num +"条数据");
            return;
        }
        User temp = head.next;
        while (num-k > 0){
            num--;
            temp = temp.next;
        }
        System.out.println("倒数第 "+ k + " 条数据为" + temp);
    }
    //打印第k个节点,老师写法
    public static User find(User head,int index){
        if(head.next == null){
            return null;
        }
        //第一次遍历得到链表长度
        int num = UserNum(head);

        User temp = head.next;

        if(index < 1 || index - num > 0){
            System.out.println("无效参数");
            return null;
        }
        while (num-index > 0){
            num--;
            temp = temp.next;
        }
        return temp;
    }

    //链表反转--先写的反向打印--反向都是一个思路
    public static void reversal(User head) {
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }

        int num = UserNum(head);

        //这里得用一个新临时头节点代表之前的链表头,真正的head得变了
        User newHead = head.next;

        User temp = head.next;

        while (num>0){
            int i = num;
            while (true){
                if(i == 1){
                    //调用尾插-- 置空,只插一条数据
                    User user = temp;
                    user.next = null;
                    add(head,user);
                    break;
                }
                i -- ;
                temp = temp.next;
            }
            num --;
            //头节点后移
            head = head.next;
            temp = newHead.next;
        }
    }
    //链表反转--新思路--从倒数第二个开始依次向前遍历添加到最后面--遍历length-1次
    public  void reversal2(User head){
        if(head.next == null){
            System.out.println("链表为空");
            return;
        }
        int length = UserNum(head)-1;
        User temp = head;

        while(length>0){
            //临时存储数据
            User sum;
            //找到原链表倒数第二个数据
            while (true){
                if(temp.next.next.next == null){
                    sum = temp;
                    while (true){
                        if(temp.next == null){
                            temp.next = sum.next;
                            temp.next.next = null;
                            break;
                        }
                        temp = temp.next;
                    }
                    break;
                }
                temp = temp.next;
            }
            //删除原位置节点
            sum.next = sum.next.next;
            temp = head.next;
            length--;
        }
    }

    //关于反转看看老师的思路
    public static void reversalList(User head){
        //只有一个就没必要了
        if(head.next == null || head.next.next == null){
            return;
        }
        //定义辅助指针,帮助遍历原来链表
        User cur = head.next;
        //指向当前节点的下一个节点
        User next = null;
        //新的表头
        User reverseHead = new User(0,"");

        //遍历原来链表,每遍历一个节点将其取出,放在node前面
        while (cur != null){
            //暂时保存下一个节点
            next = cur.next;
            //将cur下一个节点指向新链表最前端--不要原链表后面的了!!
            cur.next = reverseHead.next;
            //将原链表连接到新链表
            reverseHead.next = cur;
            cur = next;
        }
        //连接
        head.next = reverseHead.next;

    }

    //链表反向打印---有UserNum干啥都方便
    public void reciprocalPrintln(User head){
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        int num = UserNum(head);
        User temp = head.next;

        while (num>0){
            int i = num;
            while (true){
                if(i == 1){
                    System.out.println(temp);
                    break;
                }
                i -- ;
                temp = temp.next;
            }
            num --;
            temp = head.next;
        }
        System.out.println("反向打印完毕");
    }

    //反向打印:老师思维路线---1.先反转,再打印(时间复杂度为n,无需嵌套,完美方法),但是会破坏原链表结构,这个可以实现就不用了。
    //2.利用栈数据结构,将各个节点压入到栈中,利用栈的先进后出特点。
    public static void reversePrint(User head){
        if(head.next == null || head.next.next == null){
            return;
        }

        Stack<User> stack = new Stack<>();
        User temp = head.next;
        while (temp != null){
            //入栈,先进
             stack.push(temp);
             temp = temp.next;
        }
        //出栈,后出
        while (stack.size()>0){
            System.out.println(stack.pop());
        }
    }

}

//链表对象
class User{
    public int id;
    public String name;
    public User next;

    public User(int Uid , String Uname){
        this.id = Uid;
        this.name = Uname;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name +
                '}';
    }
}
  • 算法亮点及收获:
    • 静态方法
    • 代码更加简洁
    • 反转:对同一链表不能做copy类行为,用一条新链表做临时变量,原表顺序遍历再到新表从前插队,时间复杂度 T(n) = n; 高效简单易读的算法!
    • 反向打印:之前的算法反向遍历类似暴力排序,T(n) = n2; 不推荐使用,stack栈的操作真的是方便快捷。
1.3.6 双向链表
  • 单链表体现出来的不足:

    • 每次只能从头并且只有一个方向遍历,双向链表有两个方向
    • 单链表删除只能依赖前一个节点,不能自我删除,双向链表可以实现自我删除
  • 双向链表创建与基本增删改查功能实现

public class dom6 {

    public static void main(String[] args) {
        Student st1 = new Student(1, "张三");
        Student st2 = new Student(2, "李四");
        Student st3 = new Student(3, "王二");
        Student st4 = new Student(4, "马六");
        Student st5 = new Student(4, "老八");
        Student st6 = new Student(1, "老八");
        Student st7 = new Student(0, "老八");
        Student st8 = new Student(5, "老八");

        //插入
        SingleLinkedList3 singleLinkedList3 = new SingleLinkedList3();
        singleLinkedList3.addById(singleLinkedList3.getHead(), st1);
        singleLinkedList3.addById(singleLinkedList3.getHead(), st3);
        singleLinkedList3.addById(singleLinkedList3.getHead(), st4);
        singleLinkedList3.addById(singleLinkedList3.getHead(), st2);


        singleLinkedList3.list(singleLinkedList3.getHead());

        //修改
//        singleLinkedList3.update(singleLinkedList3.getHead(), st5);
//        singleLinkedList3.update(singleLinkedList3.getHead(), st6);
//        singleLinkedList3.update(singleLinkedList3.getHead(), st7);
//        singleLinkedList3.update(singleLinkedList3.getHead(), st8);
//        singleLinkedList3.list(singleLinkedList3.getHead());

        //删除
//        singleLinkedList3.delete(singleLinkedList3.getHead(),1);
//        singleLinkedList3.delete(singleLinkedList3.getHead(),2);
//        singleLinkedList3.delete(singleLinkedList3.getHead(),4);
//        singleLinkedList3.delete(singleLinkedList3.getHead(),5);
//        singleLinkedList3.list(singleLinkedList3.getHead());
    }
}

//双向链表管理器类
class SingleLinkedList3{

    Student head = new Student(0,"");

    //获取头节点
    public Student getHead(){
        return head;
    }

    //遍历双向链表
    public void list(Student head){
        if(head.next == null){
            System.out.println("链表为空");
        }
        Student temp = head.next;
        while (temp != null){
            System.out.println(temp);
            temp = temp.next;
        }
    }

    //尾插
    public void add(Student head,Student student){
        Student temp = head;
        while (true){
            if(temp.next == null){
                temp.next = student;
                student.pre = temp;
                break;
            }
            temp = temp.next;
        }
    }

    //添加--按顺序
    public void addById(Student head , Student student){
        Student temp = head;
        //判断能不能插入,默认为true
        boolean flag = true;
        //找插入位置
        while (true){
            if(temp.next == null){
                break;
            }
            if(temp.next.id > student.id){
                break;
            }else if(temp.next.id == student.id){
                flag = false;
            }
            temp = temp.next;
        }
        if(flag){
            student.next = temp.next;
            temp.next = student;
            student.pre = temp;
            if(student.next != null){
                student.next.pre = student;
            }

        }else {
            System.out.println(student.id + ":该编号已存在");
        }
    }

    //修改
    public void update(Student head , Student student){
        if(head.next == null){
            System.out.println("链表为空");
            return;
        }
        boolean flag = false;
        Student temp = head.next;
        while (true){
            if(temp.id == student.id){
                flag = true;
                break;
            }
            if(temp.next == null){
                break;
            }
            temp = temp.next;
        }
        if(flag){
            temp.name = student.name;
        }else {
            System.out.println("查无此人");
        }
    }

    //删除
    public void delete(Student head , int index){
        if(head.next == null){
            System.out.println("链表为空");
            return;
        }
        Student temp = head.next;
        boolean flag = false;
        while (true){
            if(temp == null){
                break;
            }
            if(temp.id == index){
                if(temp.next == null){
                    temp.pre.next = null;
                    return;
                }
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if(flag){
            temp.pre.next = temp.next;
            temp.next.pre = temp.pre;
        }else {
            System.out.println("无效参数");
        }
    }
}

//双向链表类
class Student{
    int id;
    String name;

    Student next;
    Student pre;

    public Student(int id,String name){
        this.id = id;
        this.name = name;
    }

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

1.3.7 单向环形链表与约瑟夫问题
  • Josephu 约瑟夫环

约瑟夫问题详情

  • 编号为1,2,3…的n个人围坐在一圈,约定编号为k的人从1报数,数到m的人出列,以此类推,直到所有人出列,求产生的出队编号。

  • 分析:单项环形链表!

  • 先自己写一个,不难

public class dom7 {

    public static void main(String[] args) {
        SingleLinkedList4 singleLinkedList4 = new SingleLinkedList4();

        //边界测试,边界值为  n>0,m>0
        singleLinkedList4.GetJosepho(0,1);
        singleLinkedList4.GetJosepho(1,0);
        singleLinkedList4.GetJosepho(0,0);
        singleLinkedList4.GetJosepho(1,1);
        //测试约瑟夫问题
        singleLinkedList4.GetJosepho(5,9);
        singleLinkedList4.GetJosepho(10,9);
        singleLinkedList4.GetJosepho(10,1);

    }
}

class SingleLinkedList4{

    //创建环形链表,要几个节点?返回的节点为环尾,即josephu.next = 环首
    public Josephu getJosephu(int n){
        //初始化头
        Josephu josephu = new Josephu(0);
        //记录环首
        Josephu Next = null;

        int length = n;
        int i = 1;
        while (length>0){
            josephu.next = new Josephu(i);
            if(i == 1){
                Next = josephu.next;
            }
            i++;
            length --;
            josephu = josephu.next;
        }
        josephu.next = Next;
        return josephu;
    }

    //打印约瑟夫环算法  n: 多少人    m: 第几个出列
    public void GetJosepho(int n , int m){
        //完成合理性校验,即参数正确,环不为空
        if(n<1 || m<1){
            System.out.println("无效参数");
            return;
        }

        //得到约瑟夫环及所有数据
        Josephu josephu = getJosephu(n);

        int num = 1;
        while (n - num > -1){
            int sum = m;
            while (true){
                if(sum==1){
                    System.out.println("第" + num +"个出列的为:"+josephu.next);
                    josephu.next = josephu.next.next;
                    break;
                }
                sum--;
                josephu = josephu.next;
            }
            num++;
        }
    }
}

//节点
class Josephu{
    public int id;
    public Josephu next;

    public Josephu(int id){
        this.id = id;
    }

    @Override
    public String toString() {
        return "Josephu{" +
                "id=" + id +
                '}';
    }
}
  • 老师的方式:创建思路一致,出圈方式都是数到一个m删一个

  • 原链表管理器上加一个遍历链表的方法

    //遍历环
    public void showJosepho(int n){
        if(n<1){
            System.out.println("无效参数");
            return;
        }

        //得到约瑟夫环及所有数据
        Josephu josephu = getJosephu(n);

        //尽量不要改变公用参数   n
        int i = n;
        while (i-->0){
            System.out.println(josephu.next);
            josephu = josephu.next;
        }
    }

1.4 栈

1.4.1 数组模拟栈
  • stack – 先入后出的有序列表
  • 限制只能在线性表的同一端进行的特殊线性表,任何操作只能在栈顶操作,栈底固定
  • 子程序调用:跳往子程序时将主程序的下一步的地址存到栈中,子程序执行完毕,从栈中取出地址,继续主程序
  • push:入 pop:出

数组模拟栈

import java.util.Scanner;

public class domTow01 {

    //测试数组模拟的栈
    public static void main(String[] args) {

        Stack stack = new Stack(2);

        //用循环菜单
        String key = "";
        boolean loop = true;//控制退出菜单
        Scanner scanner = new Scanner(System.in);
        while (loop){
            System.out.println("show:显示栈");
            System.out.println("push:添加数据");
            System.out.println("pop:取出数据");
            System.out.println("exit:退出程序");
            System.out.println("num:栈内数据个数");

            //接收数据
            key = scanner.next();
            switch (key){
                case "show":
                    stack.list();
                    break;
                case "push":
                    System.out.println("请输入:");
                    int value = scanner.nextInt();
                    try {
                        stack.push(value);
                    } catch (Exception e) {
                        //提示错误信息就行,不关闭进程
                        System.out.println(e.getMessage());
                    }
                    break;
                case "pop":
                    stack.pop();
                    break;
                case "exit":
                    //退出一定要释放资源!
                    scanner.close();
                    loop = false;
                    break;
                case "num":
                    System.out.println(stack.num());
                    break;
                default:
                    System.out.println("what?????????");
                    break;
            }
        }
    }
}

//定义栈结构类
class Stack{

    private int maxSize;
    private int arr[];
    //栈顶
    private int top = -1;

    //构造器
    public Stack(int maxSize){
        this.maxSize = maxSize;
        arr = new int[maxSize];
    }

    //判断栈满
    public boolean isFull(){
        return top != maxSize - 1 ?false:true;
    }

    //判断栈空
    public boolean isNoone(){
        return top == -1?true:false;
    }

    //入栈
    public void push(int value){
        if(isFull()){
            System.out.println("栈满");
        }
        top ++;
        arr[top] = value;
    }

    //出栈
    public int pop(){
        if(isNoone()){
            //这里必须有返回值,不能return 0,用异常处理的方式
            throw new RuntimeException("栈空");
        }
        top -- ;
        return arr[top+1];
    }

    //遍历栈--从栈顶显示数据
    public void list(){
        if (isNoone()){
            System.out.println("栈空");
        }
        //遍历不能改变原始结构-即top位置
        for (int i = top;i>-1;i--){
            System.out.println(arr[i]);
        }
    }

    //栈内数据个数
    public int num(){
        return top+1;
    }
}
  • 基础巩固:一定要注意数组对象的创建,异常处理的使用方式,中断进程or不中断进程打印异常信息。
1.4.2 链表模拟栈
  • 这个比数组好写,只用链表最基础简单的结构就能实现
public class domTow02 {

    public static void main(String[] args) {
        Stack2 stack1 = new Stack2(1);
        Stack2 stack2 = new Stack2(2);
        Stack2 stack3 = new Stack2(3);
        Stack2 stack4 = new Stack2(4);

        //测试,出入遍历随便写
//        SingleLinkedList5 singleLinkedList5 = new SingleLinkedList5(2);
//        singleLinkedList5.push(singleLinkedList5.getHead(), stack1);
//        singleLinkedList5.list(singleLinkedList5.getHead());
//        System.out.println("成功取出:"+singleLinkedList5.pop(singleLinkedList5.getHead()));
//        singleLinkedList5.push(singleLinkedList5.getHead(), stack2);
//        singleLinkedList5.push(singleLinkedList5.getHead(), stack3);
//        singleLinkedList5.push(singleLinkedList5.getHead(), stack4);
//        singleLinkedList5.list(singleLinkedList5.getHead());
//        System.out.println("成功取出:"+singleLinkedList5.pop(singleLinkedList5.getHead()));
//        System.out.println("成功取出:"+singleLinkedList5.pop(singleLinkedList5.getHead()));
//        System.out.println("成功取出:"+singleLinkedList5.pop(singleLinkedList5.getHead()));
    }
}


//链表对象类
class Stack2{
    int id;

    Stack2 next;

    public Stack2(int id){
        this.id = id;
    }

    @Override
    public String toString() {
        return "Stack2{" +
                "id=" + id +
                '}';
    }
}

//链表管理器类
class SingleLinkedList5{

    Stack2 head = new Stack2(0);
    int maxSize;
    int nowSize=0;

    //栈最大容量
    public SingleLinkedList5(int maxSize){
        this.maxSize = maxSize;
    }

    //获取头节点
    public Stack2 getHead(){
        return head;
    }

    //添加---模拟栈只能是尾插
    public void push(Stack2 head,Stack2 stack){
        if(nowSize == maxSize){
            System.out.println("栈满");
            return;
        }
        Stack2 temp = head;

        while (true){
            if (temp.next == null){
                temp.next = stack;
                break;
            }
            temp = temp.next;
        }
        nowSize ++;
    }

    //取出---同样只能尾插
    public Stack2 pop(Stack2 head){
        if(head.next == null && nowSize == 0){
            System.out.println("栈空");
            return null;
        }
        Stack2 temp = head;
        while (true){
            if(temp.next.next==null){
                Stack2 cur = temp.next;
                temp.next = null;
                nowSize --;
                return cur;
            }
            temp = temp.next;
        }
    }

    //遍历
    public void list(Stack2 head){
        if(head.next == null && nowSize == 0){
            System.out.println("栈空");
            return;
        }
        Stack2 temp = head.next;
        while (true){
            if (temp == null){
                break;
            }
            System.out.println(temp);
            temp = temp.next;
        }
    }
}
1.4.3 栈实现综合计算器
  • 树栈:存放表达式

  • 符号栈:存放运算符

  • 整数型跟char型可以混用!!!符号底层也是一个数字来表示

算法思路

  • 通过index(索引),遍历表达式
  • 数字—直接入树栈
  • 符号—入符号栈
    • 当前符号栈为空,直接入栈
    • 符号栈有操作符,进行比较
      • 当前操作符优先级小于等于栈中操作符,需要从树栈中pop出两个数,从符号栈中pop一个符号,进行运算,计算结果入树栈,当前符号入符号栈
      • 当前操作符的优先级大于栈中的操作符,直接入符号栈
  • 表达式扫描完毕,顺序从树栈和符号栈中pop出相应的数字和符号,运算
  • 最后树栈中只有一个数字,符号栈为空,就是表达式的结果
  • 代码实现

//栈模拟简单计算器代码实现
public class domTow04 {

    public static void main(String[] args) {

        //创建树栈和符号栈
        Stack3 numStack = new Stack3(10);
        Stack3 operStack = new Stack3(10);

        //测试的表达式
        String str = "1+2*3-1";

        //需要的变量
        int num1 = 0;
        int num2 = 0;
        int oper = 0; //符号  : char型和整型可以混用
        int ver = 0; //计算结果
        int index = 0; //扫描的索引
        char ch = ' ';//当前扫描到的数据

        //扫描遍历
        while (true) {
            //扫描依次得到表达式每一个字符
            ch = str.substring(index , index+1).charAt(0);//记住字符串的substring方法

            //如果是符号
            if (numStack.isOper(ch)) {
                //判断符号栈是否为空
                if (operStack.isNoone()) {
                    operStack.push(ch);
                } else {
                    //判断符号优先级
                    //当前优先级小于等于栈中符号的优先级
                    if (operStack.priority(ch) <= operStack.priority(operStack.watch())) {
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        oper = operStack.pop();
                        ver = numStack.cal(num1, num2, oper);
                        numStack.push(ver);

                        //算完一定要将当前的运算符放到符号栈
                        operStack.push(ch);
                    } else {
                        //当前优先级大于栈中符号的优先级
                        operStack.push(ch);
                    }
                }

            } else {
                //不是符号则直接入数栈
                //第一次入栈时一定要注意! 遍历出来的是字符,表达的数字与真实有差异,想要字符转数字,ch - 48 或 ch - '0'
                numStack.push(ch-'0');
            }

            index++;
            //遍历结束
            if (index == str.length()) {
                break;
            }
        }

        //对扫描后的数栈跟符号栈顺序遍历计算
        while (true) {
            //若符号栈为空,则不需要计算
            if (operStack.isNoone()) {
                ver = numStack.pop();
                break;
            }
            //顺序计算
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = operStack.pop();
            ver = numStack.cal(num1, num2, oper);
            numStack.push(ver);
        }

        //输出计算结果
        System.out.println("计算:" + str + " = " + ver);
    }
}

//先创建一个栈
class Stack3{

    private int maxSize;
    private int arr[];
    //栈顶
    private int top = -1;

    //构造器
    public Stack3(int maxSize){
        this.maxSize = maxSize;
        arr = new int[maxSize];
    }

    //判断栈满
    public boolean isFull(){
        return top != maxSize - 1 ?false:true;
    }

    //判断栈空
    public boolean isNoone(){
        return top == -1?true:false;
    }

    //入栈
    public void push(int value){
        if(isFull()){
            System.out.println("栈满");
        }
        top ++;
        arr[top] = value;
    }

    //出栈
    public int pop(){
        if(isNoone()){
            //这里必须有返回值,不能return 0,用异常处理的方式
            throw new RuntimeException("栈空");
        }
        top -- ;
        return arr[top+1];
    }

    //遍历栈--从栈顶显示数据
    public void list(){
        if (isNoone()){
            System.out.println("栈空");
        }
        //遍历不能改变原始结构-即top位置
        for (int i = top;i>-1;i--){
            System.out.println(arr[i]);
        }
    }

    //栈内数据个数
    public int num(){
        return top+1;
    }

    //在原来的数组栈中扩展功能

    //返回运算符优先级---这是程序员定的
    //运算符优先级由数字表示
    public int priority(int oper){
        if(oper == '+' || oper == '-'){
            return 0;
        }else if(oper == '*' || oper == '/'){
            return 1;
        }else {
            //假定只有加减乘除功能
            return -1;
        }
    }

    //判断是不是运算符
    public boolean isOper(char val){
        return val == '+' || val == '-' || val == '*' || val == '/' ;
    }

    //计算方法---注意计算顺序
    public int cal(int num1 , int num2 , int oper){
        int ver = 0;
        switch (oper){
            case '+':
                ver = num1 + num2;
                break;
            case '-':
                ver = num2 - num1;
                break;
            case '*':
                ver = num1 * num2;
                break;
            case '/':
                ver = num2 / num1;
                break;
            default:
                break;
        }
        return ver;
    }

    //查看栈顶数据,不是pop
    public int watch(){
        return arr[top];
    }
}
  • 以上代码只能实现一位数的运算,不能实现多位数运算
  • 改进后的代码如下—只改扫描遍历那块即可
        String keepNum = "";  //定义字符串变量,拼接字符串

        //扫描遍历
        while (true) {
            //扫描依次得到表达式每一个字符
            ch = str.substring(index , index+1).charAt(0);//记住字符串的substring方法

            //如果是符号
            if (numStack.isOper(ch)) {
                //判断符号栈是否为空
                if (operStack.isNoone()) {
                    operStack.push(ch);
                } else {
                    //判断符号优先级
                    //当前优先级小于等于栈中符号的优先级
                    if (operStack.priority(ch) <= operStack.priority(operStack.watch())) {
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        oper = operStack.pop();
                        ver = numStack.cal(num1, num2, oper);
                        numStack.push(ver);

                        //算完一定要将当前的运算符放到符号栈
                        operStack.push(ch);
                    } else {
                        //当前优先级大于栈中符号的优先级
                        operStack.push(ch);
                    }
                }

            } else {
                //不是符号则直接入数栈
                //第一次入栈时一定要注意! 遍历出来的是字符,表达的数字与真实有差异,想要字符转数字,ch - 48 或 ch - '0'
                //numStack.push(ch-'0');

                //不能发现是一个数字直接入数栈,可能是多位数
                //定义字符串变量keepNum,拼接字符串
                //遍历当前数字的后一位,是数字则继续遍历,符号入栈
                keepNum += ch;

                if (index == str.length() - 1) {
                    //如果遍历到最后一位,直接入栈,避免index+2的指针溢出问题
                    numStack.push(Integer.parseInt(keepNum));
                } else {
                    if (numStack.isOper(str.substring(index + 1, index + 2).charAt(0))) {
                        //下一位是字符,将字符串直接入栈---强转
                        numStack.push(Integer.parseInt(keepNum));
                        //入栈后keepNum置空
                        keepNum = "";
                    } else {
                    }
                }
            }
            index++;
            //遍历结束
            if (index == str.length()) {
                break;
            }
        }

1.5 前缀,中缀,后缀表达式

1.5.1 简介
  • 前缀表达式(波兰表达式): 运算符位于数字之前
    • 从右向左扫描表达式 (3+4) * 5 -6 ---- - * + 3 4 5 6
  • 中缀表达式: 常见的运算表达式: (3+4) * 5 -6
    • 对于人简洁明了:对于计算机较为困难
  • 后缀表达式(逆波兰表达式): 运算符位于数字之后
    • 对计算机来说最方便的表达式
    • 从左到右依次扫描,遇到符号就计算,不用考虑优先级
    • (3+4) * 5 -6 ---- 3 4 + 5 * 6 -
    • a + b ---- a b +
    • a + (b - c) — a b c - +
    • a + (b - c) * d — a b c - d * +
    • a + d * (b - c) ---- a d b c - * +
    • a = b + c — a b c + =
1.5.2 逆波兰计算器
  • 输入后缀表达式,使用栈实现
  • 支持小括号及多位整数
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class domTow05 {

    public static void main(String[] args) {
        //定义逆波兰表达式
        //为方便数字和符号中使用空格隔开
        String expression = "3 4 + 5 * 6 -";
        List<String> list = getListString(expression);
        System.out.println("list = " + list);

        System.out.println(cal(list));
    }
    //将表达式依次放入ArrayList
    public static List<String> getListString(String expression){
        //分割表达式,返回一个String数组
        String[] split = expression.split(" ");
        List<String> list = new ArrayList<String>();
        for (String s : split) {
            //将split数组里面的数据依次放入list
            list.add(s);
        }
        return list;
    }

    //运算式
    public static int cal(List<String> list){
        //只需要一个栈就行
        Stack<String> stack = new Stack<String>();
        //遍历list
        for(String item : list){
            //使用正则表达式取出数
            if(item.matches("\\d+")){//匹配多位数
                stack.push(item);
            }else {
                //遇到符号就计算
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                if(item.equals("+")){
                    res = num1 + num2;
                }else if(item.equals("-")){
                    res = num1 - num2;
                }else if(item.equals("*")){
                    res = num1 * num2;
                }else if(item.equals("/")){
                    res = num1 / num2;
                }else {
                    throw new RuntimeException("错误符号");
                }
                //数字转字符串放到栈中
                stack.push(res + "");
            }
        }
        //时刻要注意数据形式转换
        return Integer.parseInt(stack.pop());
    }
}
  • 涉及到的基础回顾加强:
    • ArrayList
    • split 分割字符串: 将字符串变为字符串数组
    • 正则表达式
    • 格式转换: 数字转字符串 num + “” , 字符串转数字 Integer.parseInt()
1.5.3 中缀转后缀
  • 后缀表达式计算机理解方便,人不容易写出来,需要在开发中将中缀表达式自动转换为后缀表达式

步骤分析:

  1. 初始化两个栈,中间栈(中间结果),符号栈

  2. 顺序扫描表达式

  3. 遇到操作数,入中间栈

  4. 遇到运算符,比较优先级

    4.1 符号栈为空或左括号,直接入符号栈

    4.2 当前的优先级比栈顶高也入符号栈

    4.3 否则将符号栈栈顶运算符弹出并压入中间栈,当前运算符与符号栈栈顶运算符继续比较

  5. 遇到括号时:

    5.1 左括号直接入符号栈

    5.2 右括号:依次弹出栈顶运算符,压入中间栈,直到遇到右括号为止,将右括号丢弃

  6. 遍历到表达式完

  7. 将符号栈运算符全部依次放入中间栈

  8. 依次弹出中间栈元素,转为字符串并反转

//将中缀表达式转为对应的list
    public static List<String> toInfix(String s){
        //定义list,存放中缀表达式
        List<String> ls =  new ArrayList<String>();
        int i = 0; // 指针:遍历字符串s
        String str ; //拼接多位数
        char c ; //每遍历一个字符,放入list

        do {
            //遍历到的非数字,直接加入到ls
            if((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57){
                ls.add("" + c);
                i ++ ;
            }else {
                //考虑多位数的问题
                str = "";  //先置空!
                while (i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57){
                    str += c;  // 拼接字符串
                    i ++ ;
                }
                ls.add("" + str);
            }
        }while (i < s.length());
        return ls;
    }

    //中缀转后缀表达式
    public static String parseSuffix(List<String> ls){
        //初始化两个栈
        Stack<String> milStack = new Stack<String>();
        Stack<String> operStack = new Stack<String>();

        //由于中间栈全程只是添加,用list更方便
        List<String> mil = new ArrayList<String>();

        //利用for取出list里面的数据
        for (String item : ls) {
            if(item.matches("\\d+")){//正则表达式匹配多位数
                //是数字直接入栈
                milStack.push(item);
            }else if(item.equals("(")){
                operStack.push(item);
            }else if(item.equals(")")){
                // 右括号:依次弹出栈顶运算符,压入中间栈,直到遇到右括号为止,将右括号丢弃

                String ch;

                while (!(ch = operStack.pop()).equals("(")){
                    milStack.push(ch);
                }
            }else {
                //最后就是运算符了,这里需要比较优先级
                //如果为空,直接入栈
                if(operStack.size() == 0){
                    operStack.push(item);
                }else if(priority(item) > priority(watch(operStack))){//当前的优先级比栈顶高也入符号栈
                    operStack.push(item);
                }else {
                    //否则将符号栈栈顶运算符弹出并压入中间栈,当前运算符与符号栈栈顶运算符继续比较
                    while (true){
                        milStack.push(operStack.pop());
                        if(operStack.size() == 0){
                            operStack.push(item);
                            break;
                        }
                        if(priority(item) > priority(watch(operStack))){
                            operStack.push(item);
                            break;
                        }
                    }
                }
            }
        }

        //将符号栈运算符全部依次放入中间栈
        while (operStack.size() != 0){
            milStack.push(operStack.pop());
        }

        //要返回的字符串
        String str = "";
        //将中间栈的数据以字符串形式返回
        while (milStack.size() != 0){
            //拼接时候注意一下,省的再反转
                str = milStack.pop() + " " + str;
        }

        //删除最后一个空格
        str = str.substring(0 , str.length() - 1);

        return str;
    }
    //判断优先级
    public static int priority(String oper){
        if(oper.equals("+")|| oper.equals("-")){
            return 0;
        }else if(oper.equals("*") || oper.equals("/")){
            return 1;
        }else {
            //假定只有加减乘除功能
            return -1;
        }
    }

    //查看栈顶数据
    public static String watch(Stack<String> st){
        String str = st.pop();
        st.push(str);
        return str;
    }
  • main方法测试
//测试将中缀表达式转为list
        String str = "1+((2+3)*4)-5";
        List<String> infix = toInfix(str);
        System.out.println(infix);

        //测试转换后的表达式
        System.out.println(parseSuffix(infix));
  • 需要注意到的点:
    • 注意两个栈同时使用时进出栈别出错
    • 栈判空用它自己的size()方法
    • 在之后会有反转字符串加空格等操作时,在拼接的时候就可以拼接为想要的
1.5.4 综合逆波兰计算器
  • 将1.5.2 与1.5.3 里面的算法合并,就是完整的逆波兰计算器
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class domTow05 {

    public static void main(String[] args) {

        //测试将中缀表达式转为list
        String str = "1+((2+3)*4)-5";
        List<String> infix = toInfix(str);
        System.out.println(infix);

        //测试转换后的表达式
        System.out.println(parseSuffix(infix));

        //综合测试
        System.out.println(cal(getListString(parseSuffix(infix))));

    }
    //将表达式依次放入ArrayList
    public static List<String> getListString(String expression){
        //分割表达式,返回一个String数组
        String[] split = expression.split(" ");
        List<String> list = new ArrayList<String>();
        for (String s : split) {
            //将split数组里面的数据依次放入list
            list.add(s);
        }
        return list;
    }

    //运算式
    public static int cal(List<String> list){
        //只需要一个栈就行
        Stack<String> stack = new Stack<String>();
        //遍历list
        for(String item : list){
            //使用正则表达式取出数
            if(item.matches("\\d+")){//匹配多位数
                stack.push(item);
            }else {
                //遇到符号就计算
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                if(item.equals("+")){
                    res = num1 + num2;
                }else if(item.equals("-")){
                    res = num1 - num2;
                }else if(item.equals("*")){
                    res = num1 * num2;
                }else if(item.equals("/")){
                    res = num1 / num2;
                }else {
                    throw new RuntimeException("错误符号");
                }
                //数字转字符串放到栈中
                stack.push(res + "");
            }
        }
        //时刻要注意数据形式转换
        return Integer.parseInt(stack.pop());
    }

    //将中缀表达式转为对应的list
    public static List<String> toInfix(String s){
        //定义list,存放中缀表达式
        List<String> ls =  new ArrayList<String>();
        int i = 0; // 指针:遍历字符串s
        String str ; //拼接多位数
        char c ; //每遍历一个字符,放入list

        do {
            //遍历到的非数字,直接加入到ls
            if((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57){
                ls.add("" + c);
                i ++ ;
            }else {
                //考虑多位数的问题
                str = "";  //先置空!
                while (i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57){
                    str += c;  // 拼接字符串
                    i ++ ;
                }
                ls.add("" + str);
            }
        }while (i < s.length());
        return ls;
    }

    //中缀转后缀表达式
    public static String parseSuffix(List<String> ls){
        //初始化两个栈
        Stack<String> milStack = new Stack<String>();
        Stack<String> operStack = new Stack<String>();

        //由于中间栈全程只是添加,用list更方便
        List<String> mil = new ArrayList<String>();

        //利用for取出list里面的数据
        for (String item : ls) {
            if(item.matches("\\d+")){//正则表达式匹配多位数
                //是数字直接入栈
                milStack.push(item);
            }else if(item.equals("(")){
                operStack.push(item);
            }else if(item.equals(")")){
                // 右括号:依次弹出栈顶运算符,压入中间栈,直到遇到右括号为止,将右括号丢弃

                String ch;

                while (!(ch = operStack.pop()).equals("(")){
                    milStack.push(ch);
                }
            }else {
                //最后就是运算符了,这里需要比较优先级
                //如果为空,直接入栈
                if(operStack.size() == 0){
                    operStack.push(item);
                }else if(priority(item) > priority(watch(operStack))){//当前的优先级比栈顶高也入符号栈
                    operStack.push(item);
                }else {
                    //否则将符号栈栈顶运算符弹出并压入中间栈,当前运算符与符号栈栈顶运算符继续比较
                    while (true){
                        milStack.push(operStack.pop());
                        if(operStack.size() == 0){
                            operStack.push(item);
                            break;
                        }
                        if(priority(item) > priority(watch(operStack))){
                            operStack.push(item);
                            break;
                        }
                    }
                }
            }
        }

        //将符号栈运算符全部依次放入中间栈
        while (operStack.size() != 0){
            milStack.push(operStack.pop());
        }

        //要返回的字符串
        String str = "";
        //将中间栈的数据以字符串形式返回
        while (milStack.size() != 0){
            //拼接时候注意一下,省的再反转
                str = milStack.pop() + " " + str;
        }

        //删除最后一个空格
        str = str.substring(0 , str.length() - 1);

        return str;
    }
    //判断优先级
    public static int priority(String oper){
        if(oper.equals("+")|| oper.equals("-")){
            return 0;
        }else if(oper.equals("*") || oper.equals("/")){
            return 1;
        }else {
            //假定只有加减乘除功能
            return -1;
        }
    }

    //查看栈顶数据
    public static String watch(Stack<String> st){
        String str = st.pop();
        st.push(str);
        return str;
    }
}

1.6 递归

1.6.1 简介
  • 递归调用机制:自己调用自己

  • 调用一次开一个栈空间,每个空间独立

  • 空间复杂度!!!

  • 递归问题列举

public class domTow06 {

    public static void main(String[] args) {
        System.out.println(factorial(4));
    }

    //打印问题
    public static void test(int n){
        if(n >2){
            test(n - 1);
        }
        System.out.println(n);
    }

    //阶乘问题
    public static int factorial(int n){
        if(n == 1){
            return 1;
        }else {
            return factorial(n-1) * n;
        }
    }
}
1.6.2 递归能解决的问题和规则
  • 谷歌算法大赛 *
  • 算法中:快排,归并等算法问题
  • 迷宫问题,汉诺塔

规则

  • 执行一个方法,创建一个新的受保护的独立空间(栈空间)
  • 方法调用即在栈顶再压入一个栈空间,执行时候遵循栈的原则
  • 方法的局部变量不能互相影响,如果方法中使用的是引用类型变量(数组),则共享该引用类型的数据
  • 递归必须向退出递归的方向逼近,否则会无限循环,到栈满至栈溢出
  • 一个方法执行完毕,遇到return,谁调用返回给谁,同时当方法执行完毕或return时,该方法也执行完毕
1.6.3 迷宫问题
  • 实际可选路程为六行五列的迷宫,中间设置障碍
public class domTow07 {

    public static void main(String[] args) {

        //创建二维数组,模拟迷宫
        int[][] map = new int[8][7];

        //迷宫周边设置为墙
        //上下置为一
        for(int i = 0; i < 7 ; i ++){
            map[0][i] = 1;
            map[7][i] = 1;
        }
        //左右置为一
        for(int i = 0; i < 8 ; i ++){
            map[i][0] = 1;
            map[i][6] = 1;
        }

        //设置迷宫障碍
        map[3][1] = 1;
        map[3][2] = 1;

        //将路堵死测试
//        map[1][3] = 1;
//        map[2][3] = 1;
//        map[3][3] = 1;

        setWay(map , 1 , 1);
        showMap(map);
    }
    //地图情况
    public static void showMap(int[][] map){
        for (int[] ints : map) {
            for (int anInt : ints) {
                System.out.print(anInt + " ");
            }
            System.out.println();
        }
    }

    /**
     * map:地图
     * i:横轴
     * j:纵轴
     * boolean:判断找没找到
     * 出口  map[6][5]
     * 当地图上map[i][j] = 0:此路没有经过
     * 当地图上map[i][j] = 1:墙
     * 当地图上map[i][j] = 2:通路,可以走
     * 当地图上map[i][j] = 3:该位置已经走过,不通
     * 确定探路策略:下->右->上->左
     * 如果走不通:回溯!
     * */
    //使用递归找路  放入地图跟起始位置
    public static boolean setWay(int[][] map , int i , int j){
        if(map[6][5] == 2){
            return true;
        }else {
            if(map[i][j] == 0){ //如果这个点没有走过
                //按照策略走,假定可以走通
                map[i][j] = 2;

                if(setWay(map , i+1 , j)){   //向下走
                    return true;
                }else if(setWay(map , i ,j+1)){  //向右走
                    return true;
                }else if(setWay(map , i-1 , j)){  //向上走
                    return true;
                }else if(setWay(map , i , j-1)){  //向左走
                    return true;
                }else {
                    //该点为死路,
                    map[i][j] = 3;
                    return false;
                }
            }else { //如果该点不等于0 , map可能为1,2,3
                //代表起点位置开始就走不通,返回false,该迷宫无解
                return false;
            }
        }
    }
}

1.6.4 迷宫最短路径问题
  • 小球的路径与程序员设计的路径有关
  • 当前算法调优只能限制找路方法
  • 上下左右的排序依次测试打印数组中2的个数最少的为最短路径
    //修改找路方法 上右下左
    public static boolean setWay2(int[][] map , int i , int j){
        if(map[6][5] == 2){
            return true;
        }else {
            if(map[i][j] == 0){ //如果这个点没有走过
                //按照策略走,假定可以走通
                map[i][j] = 2;

                if(setWay(map , i-1 , j)){   //向上走
                    return true;
                }else if(setWay(map , i ,j+1)){  //向右走
                    return true;
                }else if(setWay(map , i+1 , j)){  //向下走
                    return true;
                }else if(setWay(map , i , j-1)){  //向左走
                    return true;
                }else {
                    //该点为死路,
                    map[i][j] = 3;
                    return false;
                }
            }else { //如果该点不等于0 , map可能为1,2,3
                //代表起点位置开始就走不通,返回false,该迷宫无解
                return false;
            }
        }
    }
1.6.5 八皇后问题
  • 问题描述:8*8的棋盘,放置八枚皇后棋子,要求每一行,每一列,每一条斜线只能有一枚棋子
  • 用一维数组就能实现----不画棋盘!!!
public class domTow08 {

    //定义共有多少个皇后
    int max = 8;
    //定义数组保存皇后被放置的位置--一维数组就可以
    int[] array = new int[max];
    //统计共有多少种方法
    int num = 0;

    public static void main(String[] args) {

        //测试八皇后算法
        new domTow08().check(0);
    }

    //放置第n个皇后
    private void check(int n){
        //当需要放置第九个皇后时,意味着八个皇后已经放置妥当
        if(n == max){
            num ++;
            System.out.print("第 " + num +" 种方法:");
            print();
            return;
        }

        /**
         * 核心的for循环!!!!!!!!!!!
         * 每次递归都有一个for循环,一定会执行完
         * 因为是for循环一定会执行完的原因,完成回溯的算法思想,能将所有可能性执行一遍
         * */
        //依次放入皇后,判断冲突
        for (int i = 0 ; i < max ; i ++){
            //先把当前皇后放到该行的第1列
            array[n] = i;
            //判断第n个皇后放到第i列是否冲突
            if(judge(n)){ //不冲突
                //接着放
                check(n+1);
            }
        }
    }

    //放置第n个皇后时,检测该皇后是否与之前的冲突,若冲突则为false
    private boolean judge(int n) {
        for(int i = 0 ; i < n ; i ++){
            //是否在同一列,或同一斜线(即二维棋盘上横轴差等于纵轴差)
            //判断同一行没必要
            if(array[i] == array[n] || Math.abs(n - i) == Math.abs(array[n] - array[i])){
                return false;
            }
        }
        return true;
    }

    //打印皇后位置--输出最后的结果
    private void print () {
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.println();
    }
}

1.7 哈希表

1.7.1 简介
  • key–value 结构 :根据关键字码值直接进行访问的数据结构
  • 通过关键码值映射到表中一个位置来访问记录,该映射关系函数为散列函数,存放记录的数组称为散列表(哈希表)
  • 缓存层
  • 数组+链表 — 链表数组
  • 数组+二叉树 — 二叉树数组
1.7.2 hash经典笔试题
  • 某公司,有新员工报道时,要求该员工的信息加入(id,姓名,性别,年龄,住址…)输入该员工id时,查到该员工所有信息。

    要求:不用数据库,速度快 => hash(散列)

import java.util.Scanner;

public class domThree03 {

    public static void main(String[] args) {

        //创建Hash表
        HashTable hashTable = new HashTable(7);

        //写个菜单
        String key = "";
        Scanner scanner = new Scanner(System.in);
        while (true){
            System.out.println("add: 添加");
            System.out.println("list: 显示");
            System.out.println("exit: 退出");
            System.out.println("find: 查找");

            key = scanner.next();
            switch (key){
                case "add":
                    System.out.println("输入id");
                    int id = scanner.nextInt();
                    System.out.println("输入姓名");
                    String name = scanner.next();
                    hashTable.add(new Emp(id,name));
                    break;
                case "list":
                    hashTable.list();
                    break;
                case "exit":
                    scanner.close();
                    System.exit(0);
                case "find":
                    System.out.println("输入id");
                    int id2 = scanner.nextInt();
                    hashTable.listByid(id2);
                    break;
                default:
                    System.out.println("好好输");
                    break;
            }
        }

    }
}

//链表对象类--员工信息类
class Emp{
    public int id;
    public String name;
    public Emp next;

    public Emp(int id , String name){
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "id=" + id +
                ", name='" + name  +
                '}';
    }
}

//创建链表管理器
class EmpLinkedList{
    //链表的head,直接指向Emp
    public Emp head;

    //添加--默认最后
    //id自分配,自增
    public void add(Emp emp){
        //如果是添加第一个雇员
        if (head == null){
            head = emp;
            return;
        }

        Emp cur = head;
        while (true){
            if (cur.next == null){
                break;
            }
            cur = cur.next;
        }
        cur.next = emp;
    }

    //遍历链表
    public void list(int no){
        if(head == null){
            System.out.println("第 "+ no +"条链表为空");
            return;
        }
        Emp cur = head;
        while (true){
            System.out.println("第"+ no +"条链表" + cur);
            if(cur.next == null){
                break;
            }
            cur = cur.next;
        }
    }

    //根据id查找雇员,找到返回Emp,没找到返回null
    public Emp listById(int id){
        if(head == null){
            System.out.println("链表为空");
            return null;
        }
        Emp cur = head;
        while (true){
            if (cur.id == id){
                return cur;
            }
            if(cur.next == null){
                return null;
            }
            cur = cur.next;
        }
    }
}

//hash表
class HashTable{
    //用管理器类创建叔祖
    private EmpLinkedList[] empLinkedListsArray;
    public int size;

    //构造器
    public HashTable(int size){
        this.size = size;
        //初始化
        empLinkedListsArray = new EmpLinkedList[size];

        //不能直接向空数组添加
        for (int i = 0 ; i < size ; i ++){
            empLinkedListsArray[i] = new EmpLinkedList();
        }
    }

    //添加雇员
    public void add(Emp emp){
        //根据id,得到该员工数据应该添加到那条链表
        int empLinkedListsNo = hashFun(emp.id);
        //将emp添加到对应的链表
        empLinkedListsArray[empLinkedListsNo].add(emp);
    }

    //编写散列函数
    public int hashFun(int id){
        return id % size;
    }

    //遍历哈希表
    public void list(){
        for (int i = 0; i < size ; i ++){
            empLinkedListsArray[i].list(i);
        }
    }

    //根据id查找
    public void listByid(int id){
        int empLinkedListsNo = hashFun(id);
        Emp emp = empLinkedListsArray[empLinkedListsNo].listById(id);
        if(emp != null){
            System.out.println(emp);
        }else {
            System.out.println("没找着");
        }
    }
}

  • 总的来说,就是在原来链表基础上,给链表管理器再套一层hashTable类—链表数组

1.8 树

1.8.1 存储结构

数组存储

  • 根据下表访问,访问速度快

  • 缺点:插入值需要整体移动,效率低

  • 数组扩容时需要创建新的数组,将原来数据拷贝到新数组

  • arrList集合(object类型数组)底层自适应扩容同样也是这个原理,但他是按照比例扩容

链表存储

  • 添加非常方便,避免了数组的扩容问题

  • 缺点:每次遍历都需要从头节点开始

树存储

  • 优化存储,读取的速度,保证插入,修改,删除的速度
  • 如果使用二叉排序树存储数据,对数据的增删改查都将提高
1.8.2 二叉树遍历

前序,中序,后续—父节点输出顺序来区分

  • 前序,根节点—左节点—右节点
  • 中序,左节点—头节点—右节点
  • 后续,左节点—右节点—头节点
//二叉树
public class domThree04 {
    public static void main(String[] args) {
        BinaryTree binaryTree = new BinaryTree();
        //创建节点
        Node n1 = new Node(1, "老王");
        Node n2 = new Node(2, "老张");
        Node n3 = new Node(3, "老刘");
        Node n4 = new Node(4, "老吴");
        Node n5 = new Node(5, "老孙");

        //暂且手动创建  n1为根节点
        n1.setLeft(n2);
        n1.setRight(n3);
        n2.setLeft(n4);
        n2.setRight(n5);

        //把根节点给到位
        binaryTree.setRoot(n1);
        System.out.println("前序");
        binaryTree.preOrder();
        System.out.println("中序");
        binaryTree.infixOrder();
        System.out.println("后序");
        binaryTree.postOrder();
    }
}

//创建树
class BinaryTree {
    private Node root;

    public void setRoot(Node root) {
        this.root = root;
    }

    //前序遍历
    public void preOrder() {
        if(this.root != null){
            this.root.preOrder();
        }else {
            System.out.println("二叉树为空");
        }
    }

    //中序
    public void infixOrder() {
        if (this.root != null) {
            this.root.infixOrder();
        } else {
            System.out.println("二叉树为空");
        }
    }

    //后续
    public void postOrder(){
        if (this.root != null) {
            this.root.postOrder();
        } else {
            System.out.println("二叉树为空");
        }
    }
}

//创建节点
class Node {
    private int id;
    private String name;
    private Node left;
    private Node right;

    //构造器
    public Node(int id , String name){
        this.id = id;
        this.name = name;
    }

    //前序遍历
    public void preOrder(){
        System.out.println(this);//先输出父节点
        //递归左子树
        if(this.left != null){
            this.left.preOrder();
        }
        //递归向右
        if(this.right != null){
            this.right.preOrder();
        }
    }
    //中序
    public void infixOrder(){
        //递归向左
        if(this.left != null){
            this.left.infixOrder();
        }
        //输出父节点
        System.out.println(this);
        if(this.right != null){
            this.right.infixOrder();
        }
    }
    //后续
    public void postOrder(){
        if(this.left != null) {
            this.left.postOrder();
        }
        if(this.right != null){
            this.right.postOrder();
        }
        System.out.println(this);
    }


    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Node getLeft() {
        return left;
    }

    public void setLeft(Node left) {
        this.left = left;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Node getRight() {
        return right;
    }

    public void setRight(Node right) {
        this.right = right;
    }

    @Override
    public String toString() {
        return "Node{" +
                "id=" + id +
                ", name='" + name +
                '}';
    }
}
1.8.3 二叉树查找
  • 前序中序后序查找

  • main函数

        //测试查找
        System.out.println("前序");
        binaryTree.preSeek(3);
        System.out.println("中序");
        binaryTree.infixSeek(3);
        System.out.println("后序");
        binaryTree.postSeek(3);
  • 节点类
    //查找方法
    //前序
    public Node preseek(int id) {
        System.out.println("进入前序遍历");//统计查询递归查找次数。下同
        if(this.id == id){
            return this;
        }
        Node falg = null;
        if(this.left != null){
            falg = this.left.preseek(id);
        }
        if(falg != null){
            return falg;
        }
        if(this.right != null){
            falg = this.right.preseek(id);
        }
        return falg;
    }
    //中序
    public Node infixSeek(int id) {
        Node falg = null;
        if(this.left != null){
            falg = this.left.infixSeek(id);
        }
        if(falg != null){
            return falg;
        }
        System.out.println("进入中序遍历");
        if(this.id == id){
            return this;
        }
        if(this.right != null){
            falg = this.right.infixSeek(id);
        }
        return falg;
    }
    //后序
    public Node postSeek(int id) {
        Node falg = null;
        if(this.left != null) {
            falg = this.left.postSeek(id);
        }
        if(falg != null){
            return falg;
        }
        if(this.right != null){
            falg = this.right.postSeek(id);
        }
        if(falg != null){
            return falg;
        }
        System.out.println("进入后续遍历");
        if (this.id == id){
            return this;
        }
        return falg;
    }
  • 树类
    //查找方法
    //前序
    public void preSeek(int id) {
        if (this.root != null) {
            System.out.println(this.root.preseek(id));
        } else {
            System.out.println("二叉树为空");
        }
    }
    //中序
    public void infixSeek(int id) {
        if (this.root != null) {
            System.out.println(this.root.infixSeek(id));
        } else {
            System.out.println("二叉树为空");
        }
    }
    //后序
    public void postSeek(int id) {
        if (this.root != null) {
            System.out.println(this.root.postSeek(id));
        } else {
            System.out.println("二叉树为空");
        }
    
  • 一定要搞明白递归思想,否则这里会绕
1.8.4 删除节点
  • 这里规定:

  • 如果是叶子节点,直接删除

  • 如果不是叶子节点,删除子树

  • 二叉树是单向的,参考链表删除节点思想

  • 树类

    //删除
    public void delet(int id) {
        if (this.root != null) {
            if(root.getId() == id){
                root = null ;
                System.out.println("删除成功");
            }else {
                root.delet(id);
            }
        } else {
            System.out.println("二叉树为空");
        }
    }
  • 节点类
    //删除节点(前序)
    public void delet(int id){

        if(this.left != null && this.left.id == id){
            this.left = null;
            return;
        }
        if (this.right != null && this.right.id == id){
            this.right = null;
            return;
        }
        if(this.left != null){
            this.left.delet(id);
        }
        if(this.right != null){
            this.right.delet(id);
        }
    }
1.8.5 顺序存储二叉树
  • 数组和树能互相转换
  • 必须满足是满二叉树 (完全二叉树) 的情况
    public static void main(String[] args) {
        int[] arr = {1 , 2 , 3 ,4 , 6 , 7};

        ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
        arrBinaryTree.preOrder();
    }
}

class ArrBinaryTree {
    private int[] arr;

    public ArrBinaryTree(int arr[]){
        this.arr = arr;
    }

    //重载遍历方法,使在主函数中更简洁
    public void preOrder(){
        preOrder(0);
    }

    //实现对数组以前序二叉树方式遍历
    public void preOrder(int index) {
         if(this.arr == null && arr.length < index){
             System.out.println("无法遍历");
         }
         //打印当前遍历位置的数据
        //前序,中序,后序只需要改变这句话与左右递归位置即可
        System.out.println(arr[index]);
         
         //主要核心在于左子节点下标为2n+1,右子节点为2n+2
         //左递归
         if(2*index + 1 < arr.length) {
             preOrder(2*index + 1);
         }
         //右递归
         if(2*index +2 < arr.length) {
             preOrder(2*index + 2);
         }
    }
}
1.8.6 线索化二叉树
  • 充分利用叶子节点的空指针
  • 叶子节点的左右指针分别指向前驱与后继节点
public class domThree08 {

    public static void main(String[] args) {
        Hn h1 = new Hn(1, "li");
        Hn h2 = new Hn(2, "wu");
        Hn h3 = new Hn(3, "su");
        Hn h4 = new Hn(4, "lu");
        Hn h5 = new Hn(5, "gu");

        BinaryTree2 binaryTree2 = new BinaryTree2();
        binaryTree2.setRoot(h1);
        h1.setLeft(h2);
        h1.setRight(h3);
        h2.setLeft(h4);
        h2.setRight(h5);

        binaryTree2.threadHd();

        //测试线索化后叶子节点的左右子树是否指向前驱/后继节点
        System.out.println(h4.getRight());

    }
}

//节点
class Hn {
    public int id ;
    public String name;

    private Hn left;
    private Hn right;

    /**
     * 标记该节点是否为叶子节点,在遍历时需要该参数
     * leftType:  0 ->左子树   1 -> 前驱节点
     * rightType:  0 ->右子树   1 -> 后继节点
     * */
    private int leftType;
    private int rightType;

    public Hn(int id , String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Hn{" +
                "id=" + id +
                ", name='" + name +
                '}';
    }

    public Hn getLeft() {
        return left;
    }

    public void setRight(Hn right) {
        this.right = right;
    }

    public Hn getRight() {
        return right;
    }

    public void setLeft(Hn left) {
        this.left = left;
    }

    public int getLeftType() {
        return leftType;
    }

    public void setLeftType(int leftType) {
        this.leftType = leftType;
    }

    public int getRightType() {
        return rightType;
    }

    public void setRightType(int rightType) {
        this.rightType = rightType;
    }
}

//树
class BinaryTree2 {
    private Hn root;
    private Hn pre = null; // 指向当前节点的前驱节点

    public void setRoot(Hn root) {
        this.root = root;
    }

    //重载线索化方法
    public void threadHd(){
        threadHn(root);
    }

    //线索化二叉树(中序)
    public void threadHn(Hn node) {
        if(node == null) {
            return;
        }

        //线索化左子树
        threadHn(node.getLeft());

        //当前节点
        //处理当前节点的前驱节点
        if(node.getLeft() == null){ //只处理叶子节点
            node.setLeft(pre); //当前左指针指向前驱节点
            node.setLeftType(1); //修改当前左指针类型
        }
        if(pre != null && pre.getRight() == null) {
            pre.setRight(node); // 后继节点的前驱节点是当前节点
            pre.setRightType(1);
        }
        //!!!!!!!!!每处理完一个节点,当前节点变为前驱节点
        pre = node;

        //右子树
        threadHn(node.getRight());
    }
}
1.8.7 遍历线索化二叉树
  • 遍历顺序应当和线索化的顺序保持一致
  • 线索化后不需要递归就能实现
    //线索化中序遍历
    public void preOrder() {
        Hn node = root;

        while (node != null) {
            //先找到头节点
            while(node.getLeftType() == 0) {
                node = node.getLeft();
            }
            System.out.println(node);
            while(node.getRightType() == 1) {
                node = node.getRight();
                System.out.println(node);
            }
            node = node.getRight();
        }
    }
1.8.8 赫夫曼树
  • 最优二叉树
  • WPL : 树的带权路径长度
  • 最优二叉树:WPL最小的二叉树
import java.util.ArrayList;
import java.util.Collections;

public class domThree09 {

    public static void main(String[] args) {
        int arr[] = {1 , 4 , 6 , 2 , 3} ;

        preOrder(HuffmanTree(arr));
    }

    //创建赫夫曼树的方法
    public static Node3 HuffmanTree(int[] arr) {
        //为了操作方便,遍历arr数组
        //将每个元素构成一个Node
        //将Node全部放入Arrlist
        ArrayList<Node3> node3s = new ArrayList<>();
        for (int i : arr) {
            node3s.add(new Node3(i));
        }

        while (node3s.size() > 1) {
            //排序--从小到大
            Collections.sort(node3s);

            //取出权值最小的两个二叉树
            Node3 left = node3s.get(0);
            Node3 right = node3s.get(1);

            //构建新二叉树
            Node3 parent = new Node3(left.val + right.val);
            parent.left = left;
            parent.right = right;

            //挂在树上后删除List里的left与right
            node3s.remove(left);
            node3s.remove(right);

            //将新建树根节点放入List
            node3s.add(parent);
        }
        //返回赫夫曼树root节点
        return node3s.get(0);
    }

    //遍历方法
    public static void preOrder(Node3 root) {
        if(root != null) {
            root.preOrder();
        }else {
            System.out.println("空树");
        }
    }
}

//创建节点
class Node3 implements Comparable<Node3> {  //让node实现Comparable接口
    int val;

    Node3 left;
    Node3 right;

    public Node3(int val) {
        this.val = val;
    }

    //前序遍历
    public void preOrder() {
        System.out.println(this);
        if(this.left != null){
            this.left.preOrder();
        }
        if(this.right != null) {
            this.right.preOrder();
        }
    }

    @Override
    public String toString() {
        return "Node3{" +
                "val=" + val +
                '}';
    }

    @Override
    public int compareTo(Node3 o) {
        //表示从小到大排序
        return this.val - o.val;
    }
}
1.8.9 数据压缩
1.8.9.1 创建赫夫曼树
1.8.9.2 生成赫夫曼编码表
1.8.9.3 赫夫曼编码字节数组
1.8.9.4 字节转二进制字符串
1.8.9.5 赫夫曼解码
1.8.9.6 赫夫曼压缩文件
1.8.9.7 赫夫曼解压文件
1.8.10 二叉排序树

2.算法

2.1 排序算法

2.1.1 简介
  • 排序算法的介绍
  • 将一组数据,依照指定的顺序排序

分类:

  • 内部排序:所有数据加载到内部存储器中进行排序(重点)
    • 插入排序
      • 直接插入
      • 希尔排序
    • 选择排序
      • 简单选择排序
      • 堆排序
    • 交换排序
      • 冒泡
      • 快排
    • 归并排序
    • 基数排序(桶排序)
  • 外部排序:数据量过大,需要加载外部存储进行排序
2.1.2 时间复杂度

度量程序优越与否

  • 事后统计:运行一下看效果(涉及因素过多,不推荐)
  • 事前估算:时间复杂度评估

时间频度

  • T(n) :算法中语句的执行次数

  • 忽略常数项

  • 忽略低次项

  • 忽略系数

  • T(n)简写为:O(f(n)) —时间复杂度

  • 对数阶:时间复杂度O(log2 n)比线性n还小,非常优秀的算法,仅次于常熟阶O(1)

while (i < n){
            i = i * 2;
        }

平均时间复杂度与最坏时间复杂度

  • 平均:所有可能性等概率出现运行时间
  • 最坏:最坏情况运行的时间,算法时间上限!
  • 稳定性:
    • 冒泡,交换,选择,插入:n值小时较好
    • 基数,希尔
    • 快排,归并,堆:n值大时较好

空间复杂度

  • 算法中所耗费的存储空间
  • 更看重时间(用户希望越快越好)—缓存:空间换时间
2.1.3 冒泡排序
  • 理解为从右往左排
public class domThree01 {

    public static void main(String[] args) {

        int arr[] = {3 , 4 , 1 , 2 , 6};

        effervescent(arr);
        
        System.out.println(Arrays.toString(arr));
    }

    //冒泡排序方法
    public static void effervescent(int[] arr){
        int temp;//用来交换
        for(int i = 0 ; i < arr.length ; i ++){
            for(int j = 1 ; j < arr.length - i ; j ++){
                if(arr[j] < arr[j - 1]){
                    temp = arr[j];
                    arr[j] = arr[j - 1];
                    arr[j - 1] = temp;
                }
            }
        }
    }
}

  • 算法调优:如果某一趟第二次的遍历没有发生交换,说明此时的数组已经有序
    //冒泡排序方法
    public static void effervescent(int[] arr){
        int temp;//用来交换
        for(int i = 0 ; i < arr.length ; i ++){
            boolean flag = true;
            for(int j = 1 ; j < arr.length - i ; j ++){
                if(arr[j] < arr[j - 1]){
                    temp = arr[j];
                    arr[j] = arr[j - 1];
                    arr[j - 1] = temp;
                    flag = false;
                }
            }
            System.out.println("遍历次数:" + (i + 1));
            if(flag){
                break;
            }
        }
    }
2.1.4 选择排序
  • 理解为从左往右排
  • 最多只交换 n-1 次,相较于冒泡排序,交换的次数有质的飞跃,在大量随机数的考验下,比冒泡排序快好多倍
    //选择排序
    public static void choose(int[] arr){

        for (int i = 0 ; i < arr.length-1 ; i ++){
            int temp = arr[i];
            int index = i; //记录下标
            for (int j = i + 1 ; j < arr.length ; j ++){
              if(arr[i] > arr[j]){
                  temp = arr[j];
                  index = j;
              }
            }
            if(index != i){ //优化,最小值没有变化就不用交换
                arr[index] = arr[i];
                arr[i] = temp;
            }
        }
    }
2.1.5 插入排序
  • 从左往右排

  • 从第二个位置开始循环向前遍历找到插入位置

    //插入排序
    public static void insert(int[] arr){
        for(int i = 1 ; i < arr.length ; i ++){
            //待插入数
            int val = arr[i];
            //待插入数前面的下标
            int index = i - 1;
            // 待插入数比前一个数大或到了下标为0的位置,退出循环
            while (index >= 0 && val < arr[index]){
                arr[index + 1] = arr[index];
                index --;
            }
            //退出while循环即找到插入位置
            if(i != index + 1){ // 这里优化可有可无,性能不会发生明显变化
                arr[index + 1] = val;
            }
        }
    }
2.1.6 希尔排序
  • 插入排序缺点:如果最小的几个数都在数组的最后面,位移次数太多,影响效率

  • 改良版插入排序: 分组思想,逐步变为接近有序的数组

  • 缩小增量排序

//希尔排序
    public static void shier(int[] arr){
        //定义交换的第三方变量
        int temp = 0;

        //分组,定义步长
        for(int gap = arr.length / 2 ; gap > 0 ; gap /= 2){
            //按照分组遍历
            for (int i = gap ; i < arr.length ; i ++ ){
                //遍历组中所有元素
                for (int j = i - gap ; j >= 0 ; j -= gap){
                    if(arr[j] > arr[j + gap]){
                        temp = arr[j];
                        arr[j] = arr[j + gap];
                        arr[j + gap] = temp;
                    }
                }
            }
        }
    }
  • 以上遍历每组所有数据中采用交换法改变数据位置,算法稍复杂,性能跟冒泡差不多(慢!)
  • 以上并没有用到插入排序思想,而真正的希尔排序是在插入排序的思想基础上升级优化的
  • 用移动法优化—性能高于插入排序!!!百倍!!!
    //改良版希尔排序
    public static void shier2(int[] arr){
        //确定增量
        for(int gap = arr.length / 2 ; gap > 0 ; gap /= 2){
            //根据分组进行插入排序
            for(int i = gap ; i < arr.length ; i ++){
                int j = i;
                int temp = arr[j];
                while (j - gap >= 0 && temp < arr[j - gap]){
                    arr[j] = arr[j - gap];
                    j -= gap;
                }
                //退出while循环,找到插入位置
                arr[j] = temp;
            }
        }
    }
2.1.7 快速排序
  • 对冒泡排序进行改进

  • 随便找一个值开始就行,这里以中间值为例

  • 比希尔快一点: 空间换时间典型算法

    //快速排序
    public static void quick(int[] arr , int left , int right){
        int l = left;
        int r = right;
        int pivot = arr[(left + right) / 2];
        int temp; // 交换的临时变量

        while (l < r){
            //左遍历
            while (arr[l] < pivot){
                l ++;
            }
            //右变量
            while (arr[r] > pivot){
                r --;
            }

            //左右遍历完毕
            if(l >= r){
              break;
            }

            //交换
            temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;

            //交换完以后,若当前位置与中间量相等,会一直卡在这里,手动移动,避免死循环
            if(arr[l] == pivot){
                r --;
            }
            if(arr[r] == pivot){
                l ++;
            }
        }

        //从这里开始为递归做准备
        if(l == r){
            l += 1;
            r -= 1;
        }
        //向左递归
        if(left < r){ //只剩一个数据时,不会进递归
            quick(arr , left , r);
        }
        //向右递归
        if(right > r){
            quick(arr , l , right);
        }
    }
2.1.8 归并排序
  • 先分后和(重点在和!)

  • n个数据需要合并n-1次,即调用最复杂的合并方法只需要n-1次

  • 800 0000 条数据仅需要2s , 非常快!!!

  • 该方法需要开一个临时存储空间,即new一个等长数组

public class domThree01 {

    public static void main(String[] args) {

        int arr[] = {1 , 2 , 6 , 4 , -1};
        //临时等长空间
        int[] temp =  new int[arr.length];

        //测试合并方法
        merge(arr , 0 , 2 , 4 , temp);
        //测试完整归并方法
        mergeSore(arr , 0 , arr.length - 1 , temp);

        System.out.println(Arrays.toString(arr));

    }
    //归并排序
    //合并方法
    public static void merge(int arr[] , int left , int mid , int right , int temp[]){
        int i = left; //左边初始
        int j = mid + 1; //右边初始
        int t = 0;  //第三方数组temp初始

        //左右两边数据按照规则放到temp数组,直到一边全部放完
        while (i <= mid && j <= right){
            if(arr[i] > arr[j]){
                temp[t] = arr[j];
                t ++;
                j ++;
            }else {
                temp[t] = arr[i];
                t ++;
                i ++;
            }
        }
        //剩余部分按照顺序全部放到temp数组
        if(i == mid + 1){
            while (j <= right){
                temp[t] = arr[j];
                t ++;
                j ++;
            }
        }else {
            while (i <= mid){
                temp[t] = arr[i];
                t ++;
                i ++;
            }
        }
        //将temp数组拷贝到原始数组
        int tempLeft = left;
        for (int n = 0; n < t; n++) {
            arr[tempLeft] = temp[n];
            tempLeft ++ ;
        }
    }

    //分+和
    public static void mergeSore(int[] arr, int left , int right , int[] temp){
        if(left < right){
            int mid = (left + right) / 2 ;
            //向左分解
            mergeSore(arr , left , mid , temp);
            //向右分解
            mergeSore(arr , mid + 1 , right , temp);
            //合并
            merge(arr , left , mid , right , temp);
        }
    }
2.1.9 基数排序
  • 桶排序的扩展

  • 稳定性排序

  • 按照位数进行分类排序

  • 连递归都用不到!!!

  • 速度比归并还要快得多

  • 缺点就是占用内存:需要额外开辟十倍数据空间

    //基数排序
    public static void radix(int[] arr){
        //找到最大数
        int max = arr[0];
        for (int i : arr) {
            if(max < i){
                max = i;
            }
        }
        //计算数字位数巧妙方法,变成字符串,用它自带的length方法
        int maxLength = (max + "").length();
        //定义桶
        int[][] temp = new int[10][arr.length];

        //定义数组记录每个桶中数组元素个数
        int[] sum = new int[10];

        for (int cal = 0 , n = 1; cal < maxLength ; cal ++ , n *= 10){
            //便利原数组,按规则放到桶中
            for (int i : arr) {
                //取出元素各位
                int val = i / n % 10 ;
                //放入桶中
                temp[val][sum[val]] = i;
                sum[val]++;
            }

            //便利每一个桶,放到原数组
            int index = 0 ;
            for (int k = 0 ; k < sum.length ; k ++) {
                //桶里有数据才放入原数组
                if (sum[k] != 0){
                    //循环放入
                    for (int j = 0; j < sum[k] ; j ++){
                        arr[index] = temp[k][j];
                        index ++ ;
                    }
                    //放完置空
                    sum[k] = 0;
                }
            }
        }
    }
2.1.10 堆排序
  • 树结构的应用

  • O(nlogn)

  • 速度与归并相差不多,但不用开空间,整体性能较好

    //堆排序
    public static void heap(int[] arr) {
        int temp;
        for (int i = arr.length / 2 - 1 ; i >= 0 ; i --) {
            //将待排序序列构造成一个大顶堆,这时整个序列最大值为根节点
            adjust(arr , i , arr.length);
        }

        for (int j = arr.length - 1 ; j > 0 ; j --) {
            //根节点与末尾元素交换,末尾元素为最大值
            temp = arr[j];
            arr[j] = arr[0];
            arr[0] = temp;
            //循环遍历其余n-1个元素
            adjust(arr , 0 , j);
        }

    }

    //将i为非叶子节点的数组调整为大顶堆,---只处理该节点和该节点以下的数据
    public static void adjust(int arr[] , int i , int length) { //数组  非叶子节点的索引  对多少个元素进行调整
        int temp = arr[i];
        for (int k = 2*i + 1 ; k < length ; k = i *2 +1) {
            if(k + 1 < length && arr[k] < arr[k + 1]) {
                k ++; //左右节点取大值
            }
            if(arr[k] > temp) {
                arr[i] = arr[k];
                i = k; //循环遍历子节点
            }else {
                break;
            }
        } // for循环结束,该非叶子节点为该模块的最大值
        arr[i] = temp;
    }

2.2 查找算法

2.2.1 线性查找
  • 遍历数组,这个简单
    //线性查找,顺序比对
    public static int seq(int[] arr , int n){
        for (int i = 0 ; i < arr.length ; i ++){
            if(arr[i] == n){
               return i;
            }
        }
        throw new RuntimeException("未找到");
    }
2.1.2 二分查找
  • 也叫折半查找,要求数据是有序的
  • 这个简单
    //二分查找
    public static int binary(int arr[] , int n , int left , int right){

        if(left > right){
            throw new RuntimeException("未找到");
        }

        //中间值
        int mid = (left + right) / 2 ;

        if(arr[left] == n){
            return left ;
        }else if(arr[mid] == n){
            return mid;
        }else if(arr[right] == n){
            return right;
        }else {
            if(arr[mid] < n){
                //右边
                return binary(arr , n , mid + 1 , right - 1);
            }else {
                //左边
                return binary(arr , n , left + 1 , mid - 1);
            }
        }
    }
  • 主方法调用时注意异常处理
    int arr[] = {1, 3 , 4 , 5 , 6 , 6 , 8};
        try {
            System.out.println(binary(arr , 9 , 0 , arr.length - 1));
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
  • 当需要找到多个数据时
    //二分查找
    public static ArrayList binary(int arr[] , int n , int left , int right){

        if(left > right){
            throw new RuntimeException("未找到");
        }

        //中间值
        int mid = (left + right) / 2 ;

        if(arr[left] == n ){
            ArrayList<Integer> list = new ArrayList<Integer>();
            while (arr[left] == n && left <= right){
                list.add(left);
                left ++;
            }
            return list ;
        }else if(arr[mid] == n){
            ArrayList<Integer> list = new ArrayList<Integer>();
            int mid2 = mid;
            while (arr[mid2] == n && mid2 <= right){
                list.add(mid2);
                mid2 ++;
            }
            while (arr[mid - 1] == n && mid > left){
                list.add(mid - 1);
                mid --;
            }
            return list ;
        }else if(arr[right] == n){
            ArrayList<Integer> list = new ArrayList<Integer>();
            while (arr[right] == n && left < right){
                list.add(right);
                right--;
            }
            return list ;
        }else {
            if(arr[mid] < n){
                //右边
                return binary(arr , n , mid + 1 , right - 1);
            }else {
                //左边
                return binary(arr , n , left + 1 , mid - 1);
            }
        }
    }
2.1.3 插值查找
  • 在二分查找的基础上,将原来的mid中间值修改为按照比例定义中间值位置
  • 将mid取值改为:

​ int mid = left + (right - left) * (n - arr[left]) / (arr[right] - arr[left]);

//二分查找
public static ArrayList binary(int arr[] , int n , int left , int right){

    if(left > right){
        throw new RuntimeException("未找到");
    }

    //中间值
    int mid = left + (right - left) * (n - arr[left]) /  (arr[right] - arr[left]);

    if(arr[left] == n ){
        ArrayList<Integer> list = new ArrayList<Integer>();
        while (arr[left] == n && left <= right){
            list.add(left);
            left ++;
        }
        return list ;
    }else if(arr[mid] == n){
        ArrayList<Integer> list = new ArrayList<Integer>();
        int mid2 = mid;
        while (arr[mid2] == n && mid2 <= right){
            list.add(mid2);
            mid2 ++;
        }
        while (arr[mid - 1] == n && mid > left){
            list.add(mid - 1);
            mid --;
        }
        return list ;
    }else if(arr[right] == n){
        ArrayList<Integer> list = new ArrayList<Integer>();
        while (arr[right] == n && left < right){
            list.add(right);
            right--;
        }
        return list ;
    }else {
        if(arr[mid] < n){
            //右边
            return binary(arr , n , mid + 1 , right - 1);
        }else {
            //左边
            return binary(arr , n , left + 1 , mid - 1);
        }
    }
}
  • 插值查找,适合关键字分布均匀的数据,否则还是用二分查找稳定一点
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值