算法和数据结构(Java语言)

算法和数据结构(Java语言)

持续更新中…

线性结构和非线性结构

线性结构
  1. 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系
  2. 线性结构有两种不同的存储结构,即顺序存储结构和链式结构。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的
  3. 链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息
  4. 线性结构常用的有:数据、队列、链表和栈,后面我们会详细讲解
非线性结构

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

稀疏数组

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

稀疏数组的处理方法是:

1)记录数组一共有几行几列,有多少个不同的值

2)把具有不同值的元素的行列及记录在一个小规模的数组中,从而缩小程序的规模

image-20230107231446182

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

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

稀疏数组转原始的二维数组的思路

  1. 先读取稀疏数组的第一行,根据第一行数据,创建原始的二维数组,比如 chessArr2 = int【11】【11】
  2. 在读取稀疏数组后几行的数据,并赋给原始的二维数组即可
代码实现
package com.atguigu.sparsearray;

public class Aparsearray {
    public static void main(String[] args)
    {
        //创建一个原始的二维数组 11*11
        //0代表没有棋子,1代表黑子,2代表蓝子
        int chessArr1[][] = new int [11][11];
        chessArr1[1][2] = 1;
        chessArr1[2][3] = 2;
        chessArr1[4][5] = 2;
        //输出原始的二维数组
        System.out.println("原始的二维数组~~");
        for(int [] row:chessArr1) {
            for (int data : row) {
                System.out.printf("%d\t", data);
            }
            System.out.println();//每打印一行换行
        }

        //将二维数组转稀疏数组的思路
        //1.先遍历二维数组 得到非0数据的个数
        int sum = 0;
        for(int i = 0;i<11 ;i++)
        {
            for(int j = 0;j<11;j++)
            {
                if(chessArr1[i][j]!=0){
                    sum++;
                }

            }
        }
        System.out.println("sum="+sum);

        //2.创建对应的稀疏数组
        int sparseArr[][] = new int [sum+1][3];

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

        //遍历二维数组,将二维数组非0值存到稀疏数组中
        int count = 0;//count用于记录是第几个非0数据
        for(int i = 0;i<11 ;i++)
        {
            for(int j = 0;j<11;j++)
            {

                if(chessArr1[i][j]!=0){
                    count++;
                    sparseArr[count][0] = i;
                    sparseArr[count][1] = j;
                    sparseArr[count][2] = chessArr1[i][j];
                }

            }
        }


        //输出稀疏数组的形式
        System.out.println();//换行
        System.out.println("得到的稀疏数组为~~~~~");
        for (int i = 0; i <sparseArr.length ; i++) {
            System.out.printf("%d\t%d\t%d\t\n",sparseArr[i][0],sparseArr[i][1],sparseArr[i][2]);
        }
        System.out.println();


        //将稀疏数组恢复成原始的二维数组
        //1. 先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组
        int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];
        //2.在读取稀疏数组后几行的数据,并赋值给原始的二维数组即可
        for(int i = 1;i<sparseArr.length;i++)
        {
            chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
        }
        //输出恢复后的二维数组
        System.out.println();
        System.out.println("恢复后的二维数组");

        for(int [] row:chessArr2) {
            for (int data : row) {
                System.out.printf("%d\t", data);
            }
            System.out.println();//每打印一行换行
        }




    }
}
队列

队列是一个有序列表,可以用数组或是链表来实现

遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出

image-20230108005855750

队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图,其中maxSize是该队列的最大容量

因为队列的输出、输入是分别从前后端来处理,因此需要 两个变量front及rear分别记录队列前后端的下标,front会随数据输出而改变,而rear则是随着数据输入而变化

1)将尾指针往后移:rear+1,当front == rear 【空】

2)若尾指针rear小于队列的最大下标maxSize-1,则将数据存入rear存入rear所指的数组元素中,否则无法存入数据。rear == maxSize-1[队列满]

代码实现
package com.atguigu.queue;

import java.util.Scanner;

public class ArrayQueueDemo {
    public static void main(String [] args){
        //测试一把
        //创建一个队列
        ArrayQueue queue = new ArrayQueue(3);
        char key = ' ';//接收用户输入
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;
        //输出一个菜单
        while (loop){
            System.out.println("s(show):显示队列");
            System.out.println("e(exit):退出程序");
            System.out.println("a(add):添加数据到队列");
            System.out.println("g(get):从队列取出数据");
            System.out.println("h(head):查看队列头的数据");
            key = scanner.next().charAt(0);//接收一个字符
            switch(key){
                case 's':
                    queue.showQueue();
                    break;
                case 'a':
                    System.out.println("输入一个数");
                    int value = scanner.nextInt();
                    queue.addQueue(value);
                    break;
                case 'g':
                   try{
                         int res = queue.getQueue();
                         System.out.printf("取出的数据是%d\n",res);
                   }catch (Exception e){
                        System.out.println(e.getMessage());
                   }

                    break;
                case 'h'://查看队列头
                    try{
                        int res = queue.headQueue();
                        System.out.printf("队列头的数据是%d\n",res);
                    }catch (Exception e){
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'e':
                        scanner.close();
                        loop = false;
                    break;

            }
        }

        System.out.println("程序退出!");



    }
}


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

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

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

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

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

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

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


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

问题分析并优化

1)目前数组使用一次就不能用,没有达到复用的效果

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

数组模拟环形队列

使用数组模拟环形队列的分析:

  1. front变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front]就是队列第一个元素,front的初始值=0
  2. rear变量的含义做一个调整:rear指向队列的最后一个元素的后一个位置,因为希望空出一个空间作为约定rear的初始值=0
  3. 当队列满时,条件是rear + 1 %maxSize = front【满】
  4. 当队列为空的条件,rear==front【空】
  5. 当我们这样分析,队列有效的数据个数rear + maxSize-frint)%maxsize // rear = 1 front = 0
  6. 这样在原来的队列上修改得到一个环形队列

对前面的数组模拟队列的优化,充分利用数组

代码实现
package com.atguigu.queue;

import java.util.Scanner;

public class CircleArrayQueueDemo {
    public static void main(String [] args){
        //测试数组模拟环形
        System.out.println("测试数组模拟环形队列的案例");
        //创建一个环形队列
        CircleArray queue = new CircleArray(4);//实际有效为3,//预留一个位置做约定
        char key = ' ';//接收用户输入
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;
        //输出一个菜单
        while (loop){
            System.out.println("s(show):显示队列");
            System.out.println("e(exit):退出程序");
            System.out.println("a(add):添加数据到队列");
            System.out.println("g(get):从队列取出数据");
            System.out.println("h(head):查看队列头的数据");
            key = scanner.next().charAt(0);//接收一个字符
            switch(key){
                case 's':
                    queue.showQueue();
                    break;
                case 'a':
                    System.out.println("输入一个数");
                    int value = scanner.nextInt();
                    queue.addQueue(value);
                    break;
                case 'g':
                    try{
                        int res = queue.getQueue();
                        System.out.printf("取出的数据是%d\n",res);
                    }catch (Exception e){
                        System.out.println(e.getMessage());
                    }

                    break;
                case 'h'://查看队列头
                    try{
                        int res = queue.headQueue();
                        System.out.printf("队列头的数据是%d\n",res);
                    }catch (Exception e){
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'e':
                    scanner.close();
                    loop = false;
                    break;

            }

    }  }




}


class CircleArray {
        private int maxSize;//数组的最大容量
        //front变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front]就是队列第一个元素,
        // front的初始值=0
        private int front;//队列头
        //rear变量的含义做一个调整:rear指向队列的最后一个元素的后一个位置,因为希望空出一个空间作为约定
        //  rear的初始值=0
        private int rear;//队列尾
        private int[] arr;//该数组用于存放数据,模拟队列


        public CircleArray(int arrMaxSize)
        {
            maxSize = arrMaxSize;
            arr = new int[maxSize];
        }

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

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

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

        //获取队列的数据,出队列
        public int getQueue(){
            //判断队列是否空
            if(isEmpty()){
                //通过抛出异常
                throw new RuntimeException("队列空,不能取数据");
            }
            //这里需要分析出front是指向队列的第一个元素
            //1.先把front的值保留到一个临时的变量
            //2.将front后移
            //3.将临时保存的变量返回
            int value = arr[front];
            front = (front+1)%maxSize;
            return value;
        }


        //显示队列的所有数据
        public void showQueue() {
            //遍历
            if (isEmpty()) {
                System.out.println("队列空的,没有数据~~");
                return;
            }
            //思路:front开始遍历,遍历多少个元素
            //
            for (int i = front; i < front + size(); i++) {
                System.out.printf("arr[%d]=%d\n", i % maxSize, arr[i % maxSize]);
            }
        }

        //求出当前队列有效数据的个数
        public int size(){
            //rear = 2
            //front = 1
            //maxSize = 3
            return (rear + maxSize - front) % maxSize;
        }

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

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

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

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

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

内存图:

image-20230108031225006

逻辑结构

image-20230108031654129

image-20230108032158426

单链表的创建:

添加

  1. 先创建一个head头结点,作用就是表示单链表的头
  2. 后面每添加一个结点,就直接加入到链表的最后

遍历

  1. 通过一个辅助遍历,帮助遍历整个链表

需要按照编号的顺序添加

  1. 首先找到新添加的结点的位置,通过辅助指针,通过遍历
  2. 让新的结点.next = temp.next
  3. 让temp.next = 新的结点

从单链表不中删除结点

  1. 先找到需要删除的结点的前一个结点temp
  2. temp.next = temp.temp.temp
  3. 被删除的节点,将不会有其他的引用指向,会被垃圾回收机制回收
代码实现
package com.atguigu.linkedlist;

public class SingleLinkedListDemo {
    public static void main(String [] args){
        //进行一个测试
        //先创建结点
        HeroNode hero1 = new HeroNode(1,"宋江","及时雨");
        HeroNode hero2 = new HeroNode(2,"卢俊义","玉麒麟");
        HeroNode hero3 = new HeroNode(3,"吴用","智多星");
        HeroNode hero4 = new HeroNode(4,"林冲","豹子头");



        //创建一个链表
        SingleLinkList singleLinkList = new SingleLinkList();
        //加入
//        singleLinkList.add(hero1);
//        singleLinkList.add(hero2);
//        singleLinkList.add(hero3);
//        singleLinkList.add(hero4);

        //加入按照编号的顺序
        singleLinkList.addByOrder(hero1);
        singleLinkList.addByOrder(hero4);
        singleLinkList.addByOrder(hero2);
        singleLinkList.addByOrder(hero3);
        singleLinkList.list();

        //测试修改结点的代码
         HeroNode newHeroNode = new HeroNode(2,"小卢","玉麒麟~~");
         singleLinkList.update(newHeroNode);

        // 显示一把
        System.out.println("修改后的链表情况~~");
        singleLinkList.list();

        //删除一个结点
        singleLinkList.del(1);
        singleLinkList.del(4);
        singleLinkList.del(2);
        singleLinkList.del(3);
        System.out.println("删除后的链表情况~~");
        singleLinkList.list();
    }
}

//定义SingleLinedList 管理英雄
class SingleLinkList{
    //先初始化一个头结点,头结点不能动
    private HeroNode head = new HeroNode(0,"","");
    //添加结点的方法
    //当不考虑编号的顺序时
    //1.找到链表的最后结点
    //2.将最后这个结点的next指向新的结点
    public void add(HeroNode heroNode){
        //因为head结点不能动,因此需要一个辅助变量
        HeroNode temp = head;
        //遍历链表,找到最后
        while(true){
            //找到链表的最后
            if(temp.next==null){
                break;
            }
            //如果没有找到最后
            temp = temp.next;
        }
        //当退出链表的循环说明temp指向的是链表的最后
        temp.next = heroNode;


    }

    //第二种添加英雄的方法,根据排名将英雄插入对应位置
    public void addByOrder(HeroNode heroNode){
        //因为头结点不能动,因此我们仍然通过一个辅助指针来帮助找到添加的位置
        //因为单链表,因此我们找的Temp是位于添加位置的前一个位置,否则加入不了
        HeroNode temp = head;
        boolean flag = false;//flag标志添加的编号是否存在,默认为false
        while (true){
            if(temp.next==null){//说明temp已经在链表的最后
                break;
            }
            if(temp.next.no>heroNode.no)//位置找到,就在temp的后面
            {
               break;
            }
            else if(temp.next.no == heroNode.no){//说明希望添加的heroNode的编号已经存在
                flag = true;//说明编号存在
                break;
            }
            temp = temp.next;//后移,遍历当前的链表
        }
        //判断flag的值
        if(flag){//不能添加,说明编号cunz
            System.out.printf("准备插入的英雄的编号%d 已经存在了,不能加入",heroNode.no);
        }
        else {
                //插入到链表temp的后面
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }

    //修改结点的信息,根据编号no来修改,即no编号不能改
    //1.根据newHeroNode的no来修改
    public void update(HeroNode newHeroNode){
        if(head.next == null){
            System.out.println("链表为空!");
            return;
        }
        //找到需要修改的结点
        //定义一个辅助变量
        HeroNode temp = head.next;
        boolean flag = false;//表示是否找到该结点
        while(true){
            if(temp == null){
                break;//到链表的最后
            }
             if(temp.no == newHeroNode.no){
                 //找到
                 flag = true;
                 break;
             }
             temp = temp.next;
        }

        //根据flag判断是否找到要修改的结点
        if (flag){
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        }
        else {
            System.out.printf("没有找到 编号%d 的结点,不能修改\n",newHeroNode.no);
        }
    }


    //删除结点
    //1.head 不能动 因此需要temp辅助结点找到待删除的结点的前一个结点
    //2.比较的是temp.next.no 和 需要删除的结点no比较
    public void del(int no){
        HeroNode temp = head;
        boolean flag = false;//标志是否找到待删除结点
        while (true){
            if(temp.next==null)
            {
                break;
            }
            if(temp.next.no == no)
            {
                flag = true;
                break;
            }
            temp = temp.next;//Temp后移,遍历
        }

        if(flag){
            //可以删除
            temp.next = temp.next.next;
        }
        else {
            System.out.printf("要删除的%d 结点不存在",no);
        }
    }

    //显示链表[遍历]
    public void list()
    {
        //判断链表是否为空
        if(head.next == null)
        {
            System.out.println("链表为空~~");
            return;
        }
        //因头结点不能动,因此需要一个辅助变量来遍历
        HeroNode temp = head.next;
        while (true){
            //判断是否到链表的最后
            if(temp == null)
            {
                break;
            }
            //输出阶段的信息
            System.out.println(temp);
            //将temp后移
            temp = temp.next;
        }
    }








}

//定义HeroNode,每隔HeroNode对象就是一个结点
class HeroNode{
    public int no;
    public String name;
    public String nickname;
    public HeroNode next;

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

    //为了显示方便,重写toString
    public String toString(){
        return "HeroNode [no=" + no + ",name=" + name + ",nickname=" + nickname+ "]";
    }
}

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

package com.atguigu.linkedlist;

public class SingleLinkedListDemo {
    public static void main(String [] args){
        //进行一个测试
        //先创建结点
        HeroNode hero1 = new HeroNode(1,"宋江","及时雨");
        HeroNode hero2 = new HeroNode(2,"卢俊义","玉麒麟");
        HeroNode hero3 = new HeroNode(3,"吴用","智多星");
        HeroNode hero4 = new HeroNode(4,"林冲","豹子头");



        //创建一个链表
        SingleLinkList singleLinkList = new SingleLinkList();
        //加入
        singleLinkList.add(hero1);
        singleLinkList.add(hero2);
        singleLinkList.add(hero3);
        singleLinkList.add(hero4);
        //测试单链表的反转
        System.out.println("原来链表的情况~~");
        singleLinkList.list();
        System.out.println("反转单链表~~~~");
        reverseList( singleLinkList.getHead());
        singleLinkList.list();
/*
        //加入按照编号的顺序
        singleLinkList.addByOrder(hero1);
        singleLinkList.addByOrder(hero4);
        singleLinkList.addByOrder(hero2);
        singleLinkList.addByOrder(hero3);
        singleLinkList.list();

        //测试修改结点的代码
         HeroNode newHeroNode = new HeroNode(2,"小卢","玉麒麟~~");
         singleLinkList.update(newHeroNode);

        // 显示一把
        System.out.println("修改后的链表情况~~");
        singleLinkList.list();

        //删除一个结点
        singleLinkList.del(1);
        singleLinkList.del(4);
//        singleLinkList.del(2);
//        singleLinkList.del(3);
        System.out.println("删除后的链表情况~~");
        singleLinkList.list();

        //测试 求单链表中有效节点的个数
        System.out.println("有效的结点个数为:" + getLength(singleLinkList.getHead()));

        //测试看看是否到了倒数第K个结点
        HeroNode res = findLastIndexMode(singleLinkList.getHead(),1);
        System.out.println("res=" + res);
*/
    }
    //方法:获取单链表的结点的个数(如果是带头结点的个数,需求不统计头结点)
    /**
     * @param head 链表的头结点
     * @return 返回的就是有效结点的个数
     */
    public static int getLength(HeroNode head){
        if(head.next == null){//空链表
            return 0;
        }
        int length = 0;
        //定义一个辅助的变量,没有统计头结点
        HeroNode cur = head.next;
        while(cur != null){
            length++;
            cur = cur.next;
        }
        return length;
    }

    //方法:查找单链表的倒数第k个结点
    //思路
    //1.编写一个方法,接收head结点,同时接收一个index
    //2.index 表示是倒数index个结点
    //3.先把链表从头到尾遍历,得到链表的总的长度
    //4.得到size 后,从链表的第一个开始遍历(size-index)个,就可以得到
    //5.如果找到则,返回该结点,否则返回null


    public static HeroNode findLastIndexMode(HeroNode head,int index){
        //如果链表为空,返回null
        if(head.next == null)//没有找到
        {
            return null;
        }
        //第一个遍历得到链表长度
        int size = getLength(head);
        //第二遍历 size - index 位置,就是倒数第k个结点
        //先做一个index的校验
        if(index <= 0 || index > size){
            return null;
        }
        //定义一个辅助变量,for循环定位到倒数的index
        HeroNode cur = head.next;
        for(int i = 0;i<size - index;i++){
            cur = cur.next;
        }
        return cur;
    }


    //将单链表的反转
    //1.先定义一个结点,reverseHead = new HeroNode();
    //2.从头到尾遍历原来的链表,每遍历一个结点,就将其取出,并放在新的链表的最前端
    //3.原来的链表的head.next = reverseHead.next;
    public static void reverseList(HeroNode head){
        //如果当前链表为空,或者只有一个结点,无需反转,直接返回
        if(head.next == null||head.next.next == null){//第一个代表链表为空,第二个代表只有一个结点
            return;
        }
        //定义一个辅助指针
        HeroNode cur = head.next;
        HeroNode next = null;//指向当前节点[cur]的下一个节点
        HeroNode reverseHead = new HeroNode(0,"","");
        //遍历原来的链表,每遍历一个结点,求将其取出放在新的链表中
        while (cur != null){
            next = cur.next;//暂时保存当前结点的下一个结点
            cur.next = reverseHead.next;//将cur的下一个结点指向新的链表的最前端
            reverseHead.next = cur;//将cur 连接到新的链表上
            cur = next;//让cur后移
        }
        //将head.next指向reverseHead.next
        head.next = reverseHead.next;
    }


}

//定义SingleLinedList 管理英雄
class SingleLinkList{
    //先初始化一个头结点,头结点不能动
    private HeroNode head = new HeroNode(0,"","");


    public HeroNode getHead(){
        return head;
    }
    //添加结点的方法
    //当不考虑编号的顺序时
    //1.找到链表的最后结点
    //2.将最后这个结点的next指向新的结点
    public void add(HeroNode heroNode){
        //因为head结点不能动,因此需要一个辅助变量
        HeroNode temp = head;
        //遍历链表,找到最后
        while(true){
            //找到链表的最后
            if(temp.next==null){
                break;
            }
            //如果没有找到最后
            temp = temp.next;
        }
        //当退出链表的循环说明temp指向的是链表的最后
        temp.next = heroNode;


    }

    //第二种添加英雄的方法,根据排名将英雄插入对应位置
    public void addByOrder(HeroNode heroNode){
        //因为头结点不能动,因此我们仍然通过一个辅助指针来帮助找到添加的位置
        //因为单链表,因此我们找的Temp是位于添加位置的前一个位置,否则加入不了
        HeroNode temp = head;
        boolean flag = false;//flag标志添加的编号是否存在,默认为false
        while (true){
            if(temp.next==null){//说明temp已经在链表的最后
                break;
            }
            if(temp.next.no>heroNode.no)//位置找到,就在temp的后面
            {
               break;
            }
            else if(temp.next.no == heroNode.no){//说明希望添加的heroNode的编号已经存在
                flag = true;//说明编号存在
                break;
            }
            temp = temp.next;//后移,遍历当前的链表
        }
        //判断flag的值
        if(flag){//不能添加,说明编号cunz
            System.out.printf("准备插入的英雄的编号%d 已经存在了,不能加入",heroNode.no);
        }
        else {
                //插入到链表temp的后面
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }

    //修改结点的信息,根据编号no来修改,即no编号不能改
    //1.根据newHeroNode的no来修改
    public void update(HeroNode newHeroNode){
        if(head.next == null){
            System.out.println("链表为空!");
            return;
        }
        //找到需要修改的结点
        //定义一个辅助变量
        HeroNode temp = head.next;
        boolean flag = false;//表示是否找到该结点
        while(true){
            if(temp == null){
                break;//到链表的最后
            }
             if(temp.no == newHeroNode.no){
                 //找到
                 flag = true;
                 break;
             }
             temp = temp.next;
        }

        //根据flag判断是否找到要修改的结点
        if (flag){
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        }
        else {
            System.out.printf("没有找到 编号%d 的结点,不能修改\n",newHeroNode.no);
        }
    }


    //删除结点
    //1.head 不能动 因此需要temp辅助结点找到待删除的结点的前一个结点
    //2.比较的是temp.next.no 和 需要删除的结点no比较
    public void del(int no){
        HeroNode temp = head;
        boolean flag = false;//标志是否找到待删除结点
        while (true){
            if(temp.next==null)
            {
                break;
            }
            if(temp.next.no == no)
            {
                flag = true;
                break;
            }
            temp = temp.next;//Temp后移,遍历
        }

        if(flag){
            //可以删除
            temp.next = temp.next.next;
        }
        else {
            System.out.printf("要删除的%d 结点不存在",no);
        }
    }




    //显示链表[遍历]
    public void list()
    {
        //判断链表是否为空
        if(head.next == null)
        {
            System.out.println("链表为空~~");
            return;
        }
        //因头结点不能动,因此需要一个辅助变量来遍历
        HeroNode temp = head.next;
        while (true){
            //判断是否到链表的最后
            if(temp == null)
            {
                break;
            }
            //输出阶段的信息
            System.out.println(temp);
            //将temp后移
            temp = temp.next;
        }
    }







}

//定义HeroNode,每隔HeroNode对象就是一个结点
class HeroNode{
    public int no;
    public String name;
    public String nickname;
    public HeroNode next;

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

    //为了显示方便,重写toString
    public String toString(){
        return "HeroNode [no=" + no + ",name=" + name + ",nickname=" + nickname+ "]";
    }
}
双向链表

使用带head的双向链表实现管理单向链表的缺点分析

1)单向链表,查找的方向只能第一个方向,而双向链表可以向前或者向后查找

2)单向链表不能自我删除,需要辅助结点,而双向链表,则可以自我删除,所以前面我们单链表删除时结点,总是找到temp的下一个结点来删除

image-20230108211534794

双向链表的遍历,添加,修改,删除的操作思路

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

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

(1)先找到双向链表的最后一个结点

(2)temp.next = newHeroNode;

(3)newHeroNode.pre = temp;

3)修改思路和单向链表一样

4)删除

(1)因为是双向链表,因此,可以实现自我删除某个结点

(2)直接找到要删除的结点,比如temp

(3)让temp.pre.next = temp.next;

(4)temp.next.pre = temp.pre;

代码实现
package com.atguigu.linkedlist;

import java.util.Date;

public class DoubleLinkedListDemo {
    public static void main(String [] args){
        System.out.println("双向链表的测试~~");
        //先创建结点
        HeroNode2 hero1 = new HeroNode2(1,"宋江","及时雨");
        HeroNode2 hero2 = new HeroNode2(2,"卢俊义","玉麒麟");
        HeroNode2 hero3 = new HeroNode2(3,"吴用","智多星");
        HeroNode2 hero4 = new HeroNode2(4,"林冲","豹子头");

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

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

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

//创建一个双向链表的类
class DoubleLinkedList{
    private HeroNode2 head = new HeroNode2(0,"","");



    public HeroNode2 getHead(){
        return head;
    }
    //遍历双向链表




    //显示链表[遍历]
    public void list()
    {
        //判断链表是否为空
        if(head.next == null)
        {
            System.out.println("链表为空~~");
            return;
        }
        //因头结点不能动,因此需要一个辅助变量来遍历
        HeroNode2 temp = head.next;
        while (true){
            //判断是否到链表的最后
            if(temp == null)
            {
                break;
            }
            //输出阶段的信息
            System.out.println(temp);
            //将temp后移
            temp = temp.next;
        }
    }

    //添加一个结点到双向链表的最后
    //当不考虑编号的顺序时
    //1.找到链表的最后结点
    //2.将最后这个结点的next指向新的结点
    public void add(HeroNode2 heroNode){
        //因为head结点不能动,因此需要一个辅助变量
        HeroNode2 temp = head;
        //遍历链表,找到最后
        while(true){
            //找到链表的最后
            if(temp.next==null){
                break;
            }
            //如果没有找到最后
            temp = temp.next;
        }
        //当退出链表的循环说明temp指向的是链表的最后
        temp.next = heroNode;
        heroNode.pre = temp;


    }



    //修改结点的信息,根据编号no来修改,即no编号不能改,和单向链表一样
    //1.根据newHeroNode的no来修改
    public void update(HeroNode2 newHeroNode){
        if(head.next == null){
            System.out.println("链表为空!");
            return;
        }
        //找到需要修改的结点
        //定义一个辅助变量
        HeroNode2 temp = head.next;
        boolean flag = false;//表示是否找到该结点
        while(true){
            if(temp == null){
                break;//到链表的最后
            }
            if(temp.no == newHeroNode.no){
                //找到
                flag = true;
                break;
            }
            temp = temp.next;
        }

        //根据flag判断是否找到要修改的结点
        if (flag){
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        }
        else {
            System.out.printf("没有找到 编号%d 的结点,不能修改\n",newHeroNode.no);
        }
    }



    //删除结点
    //1.对于双向链表可以直接找到要删除的结点
    //2.找到后自我删除
    public void del(int no){
        //判断当前链表是否为空
        if(head.next == null){//空链表
            System.out.println("链表为空,无法删除");
        }
        HeroNode2 temp = head;//辅助指针
        boolean flag = false;//标志是否找到待删除结点
        while (true){
            if(temp==null)//已经到链表的最后结点的next
            {
                break;
            }
            if(temp.no == no)
            {
                flag = true;
                break;
            }
            temp = temp.next;//Temp后移,遍历
        }

        if(flag){
            //可以删除
            temp.pre.next = temp.next;
            //代码有问题,如果删除的是最后一个结点
            //如果是最后一个结点就不需要执行下面的这句话,否则会出现空指针异常
            if(temp.next != null){
                temp.next.pre = temp.pre;
            }

        }
        else {
            System.out.printf("要删除的%d 结点不存在",no);
        }
    }

}


//定义HeroNode2,每隔HeroNode2对象就是一个结点
class HeroNode2{
    public int no;
    public String name;
    public String nickname;
    public HeroNode2 next;//指向后一个结点,默认为null
    public HeroNode2 pre;//指向前一个结点,默认为null
    //构造器
    public HeroNode2(int no,String name,String nickname)
    {
        this.no = no;
        this.name = name;
        this.nickname = nickname;
    }

    //为了显示方便,重写toString
    public String toString(){
        return "HeroNode [no=" + no + ",name=" + name + ",nickname=" + nickname+ "]";
    }
}
单向环形链表

截屏2023-01-08 21.56.13

约瑟夫问题

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

n=5,即又5个人

k=1,从第一个人开始报数

m = 2,数两下

image-20230108220603740

2号出队列

image-20230108220632301

4号出队列

image-20230108220702715

1号出队列

image-20230108220738931

5号出队列

image-20230108220823262

3号出队列,没了

2->4->1->5->3

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

1.先创建第一个结点,让first指向该结点,并形成环形

2.后面每创建一个新的结点,就把该结点,加入到已有的环形链表中

遍历环形链表

1.先让一个辅助变量,指向first结点

2.然后通过一个while循环遍历该环形链表即可curBoy.next == first

根据用户的输入,生成一个小孩出圈的顺序

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

补充:小孩报数前,先让first和helper移动k-1次

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

3.这时可以将first指向的小孩结点出圈,first = first.next;helper.next = first;原来first指向的结点就没有任何引用,就会被回收。

image-20230108230943627

代码实现
package com.atguigu.linkedlist;

public class Josepfu {
    public static void main(String [] args){
        //测试构建环形链表
        CircleSinleLinkedList circleSinleLinkedList = new CircleSinleLinkedList();
        circleSinleLinkedList.addBoy(25);

        //测试小孩出圈
        circleSinleLinkedList.countBoy(1,2,25);

    }

}

//创建一个环形的单向链表
class CircleSinleLinkedList{
    //创建一个first结点,当前没有编号
    private Boy first  = new Boy(-1);

    public void addBoy(int nums){
        //nums 做一个数据校验
        if(nums<1){
            System.out.println("nums的值不正确!");
            return;
        }

        //根据编号,创建小孩结点
        Boy curBoy = null;//辅助指针,帮助构建环形链表
        for (int i = 1; i <=nums ; i++) {
            Boy boy = new Boy(i);
            //如果是第一个小号
            if(i==1){
                first = boy;
                first.setNext(first);//构成环状
                curBoy = first;//让curBoy指向第一个小孩
            }else {
                curBoy.setNext(boy);
                boy.setNext(first);
                curBoy = boy;
            }


        }


    }

    //遍历当前环形链表
    public void showBoy(){
        //判断链表是否为空
        if(first == null){
            System.out.println("链表为空!");
            return;
        }
        //因为first不能动,使用辅助指针
        Boy curBoy = first;
        while (true){
            System.out.printf("小孩的编号%d \n",curBoy.getNo());
            if(curBoy.getNext() == first){//说明遍历完毕
                break;
            }
            curBoy = curBoy.getNext();//curBoy后移
        }
    }

    /*
     * @param startNo 表示从第几个小孩开始数数
     * @param countNum 表示数几下
     * @param nums 表示最初有多少小孩在圈
     */

    //根据用户的输入,计算出小孩出圈的顺序
    public void countBoy(int startNo,int countNum,int nums){
        //先对数据进行校验
        if(first == null||startNo <1 ||startNo > nums){
            System.out.println("参数输入有误,请重新输入");
            return;
        }
        //创建一个辅助指针帮助完成小孩出圈
        Boy helper = first;
        //需1.求创建一个辅助指针,helper,事先应该指向环形链表的最后这个结点
        while (true){
            if(helper.getNext()==first){//说明helper指向最后小孩结点
                break;

            }
            helper = helper.getNext();
        }
        //小孩报数前,先让first和helper移动k-1次
        for (int j = 0; j <startNo-1 ; j++) {
            first = first.getNext();
            helper = helper.getNext();
        }
        //当小孩报数时时,让first和helper指针同时移动m-1次,然后出圈
        //这里是一个循环操作,直到圈中只有一个结点
        while (true){
            if(helper == first){//说明圈中只有一个结点
                break;
            }
            //让first和helper指针同时移动countNum-1次
            for(int j = 0;j<countNum - 1;j++){
                first = first.getNext();
                helper = helper.getNext();
            }

            //这时first指针指向的结点,就是要出圈的小孩结点
            System.out.printf("小孩%d出圈\n",first.getNo());
            //这时将first指向的小孩结点出圈
            first = first.getNext();
            helper.setNext(first);
        }

        System.out.printf("最后留在圈中的小孩编号%d \n",first.getNo());

    }
}



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

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public void setNext(Boy next) {
        this.next = next;
    }

    public Boy getNext() {
        return next;
    }
}

栈是一个先入后出的有序列表

栈(Stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶,另一端为固定的一端,称为栈底

image-20230109011336260

子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的子程序中。

处理递归调用:和子程序的调用类似,只是除了存储下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。

表达式的转换和求值

二叉树的遍历

图的深度优先搜索法

数组模拟栈:

  1. 使用数组模拟栈
  2. 定义一个top来表示为栈顶,初始化为-1
  3. 入栈的操作:当有数据加入到栈时,top++;stack[top]=data;
  4. 出栈的操作,int value = stack[top],top–
代码实现
package com.atguigu.stack;

import java.util.Scanner;

public class ArrayStackDemo {
    public static void main(String[] args){
        //测试栈
        //吸纳创建一个栈
        ArrayStack stack = new ArrayStack(4);
        String key = "";
        boolean loop = true;//控制是否退出菜单
        Scanner scanner = new Scanner(System.in);
        while (loop){


            System.out.println("show:表示显示栈");
            System.out.println("exit:表示退出程序");
            System.out.println("push:表示添加数据到栈入栈()");
            System.out.println("pop:表示从栈中取出数据(出栈)");
            System.out.println("请输入你的选择:");
            key = scanner.next();
            switch (key){
                case "show":
                    stack.list();
                    break;
                case "push":
                    System.out.println("请输入一个数:");
                    int value = scanner.nextInt();
                    stack.push(value);
                    break;
                case "pop":
                    try{
                        int res = stack.pop();
                        System.out.printf("出栈的数据时%d\n",res);
                    }catch (Exception e){
                        System.out.println(e.getMessage());
                    }
                    break;
                case "exit":
                    scanner.close();
                    loop = false;
                    break;
            }


        }
        System.out.println("程序退出");

    }
}

//定义一个ArrayStack 表示栈
class ArrayStack{
     private int maxSize;//栈的大小
     private int[] stack;//数组,模拟栈,数据放在该数组中
     private int top = -1;//top表示栈顶,初始化为-1
    //构造器
    public ArrayStack(int maxSize){
        this.maxSize = maxSize;
        stack = new int [this.maxSize];
    }

    //判断栈满
    public boolean isFull(){
        return top == maxSize -1;
    }

    //判断栈空
    public boolean isEmpty(){
        return top == -1;
    }

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

    //出栈pop
    public int pop(){
        if(isEmpty()){
            System.out.println("栈空!");
            //抛出异常
            throw new RuntimeException("栈空,没有数据");
        }
        int value = stack[top];
        top--;
        return value;
    }

    //遍历栈,遍历时需要从栈顶开始显示数据
    public void list(){
        if(isEmpty()){
            System.out.println("栈空,没有数据");
            return;
        }
        for(int i = top;i>=0;i--){
            System.out.printf("stack[%d]=%d\n",i,stack[i]);
        }
    }
}

使用栈计算一个表达式的结果

image-20230109020715632

使用栈完成表达式的计算思路

  1. 通过一个index值(索引),来遍历表达式

  2. 如果发现是一个数字,就直接入数栈,

  3. 如果发现扫描到的是一个符号,分如下情况

    3.1 如果发现当前的符号栈为空就直接入栈

    3.2 如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop两个数,在从符号栈中pop出一个符号,进行运算,将得到的结果入数栈,然后将当前的操作符入符号栈,如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈

  4. 当表达式扫描完毕, 就顺序的从数栈和符号栈中pop出相应的数和符号,并运行

  5. 最后在数栈只有一个数字,就是表达式的结果

package com.atguigu.stack;

public class Calculator {
    public static void main(String[] args){
        //根据思路完成表达式的运算
        String expression = "70+20*6-4";
        //创建两个栈,数栈和符号栈
       ArrayStack2 numStack =  new ArrayStack2(10);
       ArrayStack2 operStack = new ArrayStack2(10);
       //定义需要的相关变量
        int index = 0;//用于扫描
        int num1 = 0;
        int num2 = 0;
        int oper = 0;
        int res = 0;
        char ch = ' ';//将每次扫描得到的char保存到ch
        String keepNum = "";//用于拼接多位数
        //开始用while循环的扫描expression
        while (true)
        {
            //先一次扫描experssion的每一个字符
            ch = expression.substring(index,index+1).charAt(0);
            //判断ch是什么,然后做相应的处理
            if(operStack.isOper(ch)){//如果是运算符
                //判断当前的符号栈是否为空
                if(!operStack.isEmpty()){
                    //如果符号栈有操作符,不为空,就进行比较
                    if(operStack.priority(ch)<=operStack.priority(operStack.peek())){
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        oper = operStack.pop();
                        res = numStack.cal(num1,num2,oper);
                        //把运算的结果入数栈
                        numStack.push(res);
                        //然后把当前的操作符入符号栈
                        operStack.push(ch);
                    }
                    else {
                        //如果当前的操作符优先级大于栈中的操作符,就直接入符号栈
                        operStack.push(ch);
                    }
                }else {
                    //如果为空直接入符号栈
                    operStack.push(ch);
                }
            }else {
                //如果是数,则直接入数栈
                //1.当处理多位数时,不能发现是一个数就立即入栈,因为可能是多位数
                //2.在处理数,需要向expreesion的表达式的index后再看一位,如果是数就继续扫描,如果是符号才入栈
                //3.因此需要定义一个字符串变量,用于拼接
                //处理多位数
                keepNum += ch;

                //如果ch已经是ecpresion的最后一位,就直接入栈
                if(index == expression.length()-1){
                    numStack.push(Integer.parseInt(keepNum));
                }else {
                    //判断下一个字符是不是数字,如果是数字就继续扫描,如果是运算符,则入栈
                    //注意看后一位,不是index++

                    if(operStack.isOper(expression.substring(index+1,index+2).charAt(0))){
                        //如果后一位是运算符,就入栈keepNun = "1"或者"123"
                        numStack.push(Integer.parseInt(keepNum));
                        //重要的!!!!!
                        keepNum = "";
                }

                }

            }
            //让index+1,并判断是否扫描到expression最后
            index++;
            if(index>=expression.length())
            {
                break;
            }


        }
        //扫描结束
        while (true){
            //如果符号栈为空则计算到最后结果,数栈只有一个数字,就是最终结果
            if(operStack.isEmpty()){
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = operStack.pop();
            res = numStack.cal(num1,num2,oper);
            numStack.push(res);
        }
        //将数栈的最后数,pop出就是最终结果
        int res2 = numStack.pop();
        System.out.printf("表达式%s = %d",expression,res2);
    }
}

//先创建一个栈
//定义一个ArrayStack 表示栈
class ArrayStack2 {
    private int maxSize;//栈的大小
    private int[] stack;//数组,模拟栈,数据放在该数组中
    private int top = -1;//top表示栈顶,初始化为-1

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

    //返回当前栈顶的值,不是真正的出栈
    public int peek(){
        return stack[top];
    }
    //判断栈满
    public boolean isFull() {
        return top == maxSize - 1;
    }

    //判断栈空
    public boolean isEmpty() {
        return top == -1;
    }

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

    //出栈pop
    public int pop() {
        if (isEmpty()) {
            System.out.println("栈空!");
            //抛出异常
            throw new RuntimeException("栈空,没有数据");
        }
        int value = stack[top];
        top--;
        return value;
    }

    //遍历栈,遍历时需要从栈顶开始显示数据
    public void list() {
        if (isEmpty()) {
            System.out.println("栈空,没有数据");
            return;
        }
        for (int i = top; i >= 0; i--) {
            System.out.printf("stack[%d]=%d\n", i, stack[i]);
        }
    }


    //返回运算符的优先级,优先级是程序员来确定,优先级使用数字表示
    //数字越大,则优先级越高
    public int priority(int oper){
        if(oper == '*'||oper == '/'){
            return 1;
        }
        else if(oper == '+'||oper == '-'){
            return 0;
        }
        else {
            return -1;
        }
    }

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

    //计算方法
    public  int cal(int num1,int num2,int oper){
        int res = 0;//用于存放计算的结果
        switch (oper){
            case '+':
                res = num1 + num2;
                break;
            case '-':
                res = num2 - num1;
                break;
            case '*':
                res = num1 * num2;
                break;
            case '/':
                res = num2 / num1;
                break;
        }
        return res;
    }

}
前缀表达式

(1) 前缀表达式又称波兰式,前缀表达式的运算是运算符位于操作符之前

(2)举例说明:(3+4)× 5-6对应的前缀表达式就是**- × + 3 4 5 6**

前缀表达式的计算机求值

从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素和次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果

例如(3+4)× 5-6对应的前缀表达式就是**- × + 3 4 5 6**

1)从右至左扫描,将6、5、4、3压入堆栈

2)遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算3+4的值,得7,再将7入栈

3)接下来是X运算符,因此弹出7和5,计算出7 × 5 = 35,将35入栈

4)最后是-运算符,计算出35 - 6的值,即29,由此得出最终结果

中缀表达式

1)中缀表达式就是常见的运算表达式,如(3+4)× 5 - 6

2)中缀表达式的求值是人熟悉的,但是对计算机来说不好操作,在计算时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式)

后缀表达式

1)后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后

2)举例:(3+4)× 5-6 对应的后缀表达式就是3 4 + 5×6-

image-20230120203816362

后缀表达式的计算机求值

从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素和栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后把运算得出的值即为表达式的结果

例如:(3+4)× 5 - 6对应的后缀表达式是3 4 + 4 × 6-

1)从左至右扫描,将3和4压入堆栈;

2)遇到+运算符因此弹出4和3(4为栈顶元素,3为次顶元素),计算3+4的值,得7,再将7入栈;

3)将5入栈;

4)接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈

5)将6入栈

6)最后是-运算符,计算出35-6的值,即29,由此得出最终结果

package com.atguigu.stack;

import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class PolandNotation {
    public static void main(String[] args) {
        //定义一个逆波兰表达式
        //(30+4)×5-6 =>30 4 + 5 × 6 -
        //为了说明方便,逆波兰表达式的数字和符号使用空格隔开
        String suffixExpression = "30 4 + 5 * 6 -";
        //思路
        //1.先将"3 4 + 5 × 6 - "放入ArrayList中
        //2.将ArrayList 传递给一个方法,遍历 配合栈完成计算

        List<String> list = getListString(suffixExpression);
        System.out.println("rpnList:" + list);

        int res = calculate(list);
        System.out.println("计算的结果是=" + res);
    }
    //将一个逆波兰表达式,依次将数据和运算符放入到ArrayList中
    public static List<String> getListString(String suffixExpression){
        //将suffixExpression 分割
        String[] split = suffixExpression.split(" ");
        List<String> list = new ArrayList<String>();
        for(String ele:split){
            list.add(ele);
        }
        return list;
    }

    //完成对逆波兰表达式的运算
    public static int calculate(List<String> ls){
        //创建栈,只需一个即可
        Stack<String> stack = new Stack<String>();
        //遍历ls
        for(String item:ls){
            //使用正则表达式来取出数
            if(item.matches("\\d+")){//匹配的是多位数
                //入栈
                stack.push(item);


            }else {//是运算符
                //pop出两个数,再入栈
                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("运算符有误");
                }
                //把res入栈
                stack.push(res + "");//整数转换成字符串
            }

        }

        //最后留在stack中的数据是运算结果
        return Integer.parseInt(stack.pop());
    }

}
中缀表达式转后缀表达式

1)初始化两个栈:运算符栈s1和储存中间结果的栈s2;

2)从左到右扫描中缀表达式

3)遇到操作数,将其压入s2

4)遇到运算符时,比较其与s1栈顶运算符的优先级

​ (1) 如果s1为空,或栈顶运算符为左括号“( ”,则直接将此运算符入栈;

​ (2) 否则,若优先级比栈顶运算符的高 ,也将运算符压入s1;

​ (3) 否则,将s1栈顶的运算弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较

5)遇到括号时:

​ (1) 如果是左括号“(”,则直接压入s1

​ (2)如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃

6)重复步骤2至5,直到表达式的最右边

7)将s1中剩余的运算符依次弹出并压入s2

8)依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

递归

迷宫回溯问题

递归就是方法自己调用自己,每次调用时传入不同的变量,递归有助于编程者解决复杂的问题,同时可以让代码变的简洁

递归调用规则:当程序执行到一个方法时,就会开辟一个独立的空间(栈)

递归需要遵守的重要规则

  1. 执行一个方法时,就创建一个新的受保护的独立空间栈空间
  2. 方法的局部变量是独立的,不会受影响
  3. 如果方法中使用的是引用类型的变量,就会共享该应用类型的数据
    1. 递归必须向退出递归的条件逼近,否则就是无限递归,StackOverflow死龟了
  4. 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时方法执行完毕或者返回时,该方法也就执行完毕。
迷宫问题

image-20230125194452130

package com.atguigu.recursion;

public class MiGong {
    public static void main(String[] args) {
         //先创建一个二维数组模拟迷宫
        //地图
        int [][] map = new int [8][7];
        //使用1表示墙
        //上下全部置为1
         for(int i = 0;i<7;i++){
             map[0][i] = 1;
             map[7][i] = 1;
         }
         //左右全部置1
        for (int i = 0; i < 8; i++) {
            map[i][0] = 1;
            map[i][6] = 1;
        }

        //设置挡板每,1表示
        map[3][1] = 1;
        map[3][2] = 1;
        //输出地图情况
        System.out.println("地图的情况");
        for (int i = 0; i <8 ; i++) {
            for (int j = 0; j <7 ; j++) {
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }

        //使用递归回溯给小球找路
        setWay(map,1,1);

        //输出新的地图,小球走过,并表示过的地图
        //输出地图情况
        System.out.println("小球走过,并标识过的 地图的情况");
        for (int i = 0; i <8 ; i++) {
            for (int j = 0; j <7 ; j++) {
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }
    }
    //使用递归回溯来给小球找路
    //说明
    //1.马屁表示地图
    //2.i,j表示从地图的哪个位置开始触发(1,1)
    //3.如果小球能到map[6][5]位置,则说明通路找到
    //4.约定:当map[i][j]为 0 表示该点没有走过 当为1是为墙;2表示通路可以走;3表示该位置已经走过,但是走不通
    //5.在走迷宫时需要确定一个策略(方法)下->右->上->左,如果该点走不通再回溯
    /**
     *
     * @param map 表示地图
     * @param i  从哪个位置开始找
     * @param j
     * @return 如果找到通路,就返回true,否则返回false
     */
    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 {//如果map[i][j] != 0,可能是1,2,3
                return false;
            }


        }

    }


}
迷宫问题最短路径

把所有策略用数组方式表示出来,统计2结点的个数,最少的是最短路径

八皇后问题

image-20230125204816800

八皇后问题算法思路分析(92种)

  1. 第一个皇后放在第一行第一列
  2. 第二个皇后放在第二行第一列,判断是否OK,如果不OK,继续放在第二列、第三列、依次把所有列都放完,找到一个合适的位置
  3. 继续第三个皇后,还是第一列、第二列…直到第8个皇后也能放在一个不冲突的位置,算是找到了一个正确解
  4. 当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,即将第一个皇后,放到第一列的所有正确解,全部得到
  5. 然后回头继续第一个皇后放第二列,后面继续循环执行1、2、3、4的步骤

说明:理论上应该创建一个二维数组来表示棋盘,但是实际上可以通过算法,用一个一维数组即可解决问题arr[8] = {0,4,7,5,2,6,1,3}对应arr下标表示第几行,即第几个皇后,具体,arr[i] = val,val表示第i+1个皇后,放在第i+1行的第val+1列

package com.atguigu.recursion;

public class Queue8 {
    //定义一个max表示共有多少个皇后
    int max = 8;
    //定义数 组array,保存皇后放置位置的结果,比如arr[8] = {0,4,7,5,2,6,1,3}
    static int count = 0;
    int [] array = new int [max];
    public static void main(String[] args) {
        //测试一把,8皇后是否zhengque
        Queue8 queue8 = new Queue8();
        queue8.check(0);
        System.out.printf("一共有%d种解法",count);

    }

    //编写一个方法放置第n个皇后
    //特别注意:check每一次递归,进入到check中都有一次for (int i = 0; i < max ; i++),因此会有回溯
    private void check(int n){
        if(n == max){//n = 8,8个皇后已经放好了
            print();
            return;
        }

        //依次放皇后,并判断是否冲突
        for (int i = 0; i < max ; i++) {
            //先把当前皇后 n ,放到该行第1列
            array[n] = i;
            //当放置第n个皇后到i列时,是否冲突
            if(jude(n)){//不冲突
                //接着放n+1个皇后
                check(n+1);//如果有8个皇后
            } //如果冲突继续执行array[n] = i;即将第n个皇后放置在本行的后移的一个位置



        }
    }

    //查看当我们放置第n个皇后时,就去检测该皇后是否和前面已经摆放的皇后冲突
    private boolean jude(int n){//n表示第n个皇后
        for (int i = 0; i <n ; i++) {
            //说明
            //1.判断 array[i] == array[n] 表示第n个皇后是否和前面的n-1个皇后在同一列
            //2.Math.abs(n-i) == Math.abs(array[n] - array[i]) 第n个皇后是否在和第i个皇后是否在同一斜线
            // n = 1放置在第二列 1 n = 1 array[1] = 1
            //Math.abs(1-0) == 1 Math.abs(array[n] - array[i]) == Math.abs(1-0) = 1
            //3.没有必要判断同一行,n每次都在递增
            if(array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n] - array[i])){
                return false;
            }
        }
        return true;
    }

    //写一个方法,将皇后摆放的位置输出
    private void print(){
        count++;
        for (int i = 0; i <array.length ; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.println();
    }

}
排序

排序是将一组数据,依指定的顺序进行排列

排序的分类

(1) 内部排序:

指将需要处理的所有数据都加载到内部存储器中进行排序

(2)外部排序:

数据量过大,无法全部加载到内存中,需要借助外部存储进行排序

常见的排序算法:

截屏2023-01-25 21.57.45

算法的时间复杂度

度量一个程序(算法)执行时间的两种方法

1)事后统计法

这种方法可行,但是有两个问题:一是想要对设计的算法的运行性能进行评测,需要实际运行该程序;二是所得时间的统计量依赖于计算机的硬件、软件等环境因素,这种方式,要在同一台计算机的相同状态下运行,才能比较哪个算法更快

2)事前估算的方法

通过分析某个算法的时间复杂度来判断哪个算法更优

时间频度:一个算法中渔具包执行次数称为语句频度或时间频度

冒泡排序

冒泡排序的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就像水底的气泡一样逐渐向上冒

冒泡排序的优化:因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。(可以在冒泡排序完成后进行)

例如:3,0,-1,10,20使用冒泡排序

image-20230126012935632

第一趟排序

(1)

image-20230126013015520

如果相邻的元素逆序就交换

(2)

image-20230126013136349

(3)

image-20230126013249427

(4)

image-20230126013338573

第一趟确定下来最大的数

第二趟排序

image-20230126013431299

(1)

image-20230126013457098

(2)

image-20230126013549319

(3)

image-20230126013619204

第三趟排序

image-20230126013619204

(1)

image-20230126013715751

(2)

image-20230126013744737

第四趟排序

(1)

image-20230126013816261

小结冒泡排序

(1)一共进行数组的大小减一次排序大的循环

(2)每一趟排序的次数在逐渐的减少

(3)如果在某趟排序中,没有发生一次交换,可以提前结束冒泡排序(优化)

package com.atguigu.sort;

import java.lang.reflect.Array;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

public class BubbleSort {
    public static void main(String[] args) {
        //int arr[] = {3, 9, -1, 10, 20};
        //int arr[] = {1, 2, 3, 4, 5};
        //测试冒泡排序的速度O(n^2),给80000个数据
        //创建80000个随机的数组
        int [] arr = new int [80000];
        for (int i = 0; i < 80000; i++) {
             arr[i] = (int)(Math.random() * 8000000); //生成一个[0,8000000)数
        }

       //测试冒泡排序
//        System.out.println("排序前");
//        System.out.println(Arrays.toString(arr));
        Date date1 = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date1Str = simpleDateFormat.format(date1);
        System.out.println("排序前的时间是=" + date1Str);
        bubbleSort(arr);
        //System.out.println("排序后");
        //System.out.println(Arrays.toString(arr));
        Date date2 = new Date();
        String date2Str = simpleDateFormat.format(date2);
        System.out.println("排序后的时间是=" + date2Str);
        /*
        //第二趟排序,把第二大的数排在倒数第二位
        for (int j = 0; j < arr.length - 1 - 1; j++) {
            //如果前面的数比后面的数大,则交换
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
        System.out.println("第2趟排序后的数组");
        System.out.println(Arrays.toString(arr));
        //第三趟排序
        for (int j = 0; j < arr.length - 1 - 1-1; j++) {
            //如果前面的数比后面的数大,则交换
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
        System.out.println("第3趟排序后的数组");
        System.out.println(Arrays.toString(arr));

        //第四趟排序
        for (int j = 0; j < arr.length - 1 - 1-1-1; j++) {
            //如果前面的数比后面的数大,则交换
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
        System.out.println("第4趟排序后的数组");
        System.out.println(Arrays.toString(arr));
        */
    }
    //将冒泡排序封装成方法
    public static void bubbleSort(int [] arr){
        //把冒泡排序的演变过程展示出来
        //冒泡排序时间复杂度(n^2)
        int temp = 0;//临时变量
        boolean flag = false;//标志变量,表示是否进行过交换
        //第一趟排序,就是将最大的数排在最后
        for (int i = 0; i < arr.length - 1; i++) {

            for (int j = 0; j < arr.length - 1 - i; j++) {
                //如果前面的数比后面的数大,则交换
                if (arr[j] > arr[j + 1]) {
                    flag = true;
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
            if (flag == false) {//在一趟排序中,一次交换都没有发生过
                break;
            } else {
                flag = false;//重置flag,进行下次判断
            }
        }
    }
}
选择排序

选择排序的基本思想是:第一次从arr[0]arr[n-1]中选取最小值,与arr[0]交换,第二次从arr[1]arr[n-1]中选取最小值,与arr[1]交换,第三次从arr[2]arr[n-1]中选取最小值,与arr[2]交换,…,第i次从arr[i-1]arr[n-1]中选取最小值,与arr[i-1]交换,…,第n-1次从arr[n-2]~arr[n-1]中选取最小值,与arr[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排序的有序序列

原始数组:101,34,119,1

image-20230126143127696

image-20230126143213727

image-20230126143233445

说明:

1.选择排序一共有数组大小减一轮排序

2.每1轮排序,有事一个循环,循环的规则(代码)

2.1.先假定当前的数是最小数

2.2然后和后面的每个数进行比较,如果发现有比当前数更小的数,就重新确定最小数,并得到下标

2.3档遍历到数组的最后时,就得到本轮最小数和下标

2.4 交换

package com.atguigu.sort;

import java.util.Arrays;

public class SelectSort {
    public static void main(String[] args) {
        int []arr = {101,34,119,1,-1,90,123};
        System.out.println("排序前");
        System.out.println(Arrays.toString(arr));
        selectSort(arr);
        System.out.println("排序后");
        System.out.println(Arrays.toString(arr));
    }
    //选择排序
    public static void selectSort(int[] arr){
        //在推导的过程中发现了规律,因此可以使用一个循环来剞劂

        for(int i = 0;i< arr.length-1;i++){
            int minIndex = i;
            int min = arr[i];
            for (int j = i + 1; j < arr.length; j++) {
                if (min > arr[j]) {//说明假定的最小值,并不是最小
                    min = arr[j];//重置min
                    minIndex = j;//重置minIndex
                }
            }
            //将最小值放在arr[0],即交换
            if (minIndex != i) {
                arr[minIndex] = arr[i];
                arr[i] = min;
            }
            //System.out.println("第"+(i+1)+"轮~~");
            //System.out.println(Arrays.toString(arr));
        }
       /* //逐步推导第二步方式来,讲解选择排序
        //第一轮
        //原始数组:101,34,119,1
        //第一轮排序:1,34,119,101
        //算法 先简单-->再复杂,就是可以把一个复杂的算法,拆分成简单的问题->逐步解决
        int minIndex = 0;
        int min = arr[0];

        for (int j = 0+1; j < arr.length; j++) {
            if(min>arr[j]){//说明假定的最小值,并不是最小
                min = arr[j];//重置min
                minIndex = j;//重置minIndex
            }
        }
        //将最小值放在arr[0],即交换
        if(minIndex!=0) {
            arr[minIndex] = arr[0];
            arr[0] = min;
        }
        System.out.println("第1轮~~");
        System.out.println(Arrays.toString(arr));

        //第二轮
        minIndex = 1;
        min = arr[1];

        for (int j = 1+1; j < arr.length; j++) {
            if(min>arr[j]){//说明假定的最小值,并不是最小
                min = arr[j];
                minIndex = j;
            }
        }
        //将最小值放在arr[0],即交换
        if(minIndex!=1) {
            arr[minIndex] = arr[1];
            arr[1] = min;
        }
        System.out.println("第3轮~~");
        System.out.println(Arrays.toString(arr));

        //第三轮
        minIndex = 2;
        min = arr[2];

        for (int j = 1+1+1; j < arr.length; j++) {
            if(min>arr[j]){//说明假定的最小值,并不是最小
                min = arr[j];
                minIndex = j;
            }
        }
        //将最小值放在arr[0],即交换
        if(minIndex!=2) {
            arr[minIndex] = arr[2];
            arr[2] = min;
        }
        System.out.println("第3轮~~");
        System.out.println(Arrays.toString(arr));*/
    }

}

选择排序的时间相对于冒泡排序会缩短,因为选择排序每次只交换一次

插入排序

插入排序是内部排序法,是对于欲排序的元素以插入的方式找寻该严肃的适当位置,以达到排序的目的

插入排序的基本思想是:把n个待排序的元素看成一个有序表的无序表,开始时有序表中只包含一个元素,无序表中包含n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表

package com.atguigu.sort;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

public class InsertSort {
    public static void main(String[] args) {
        //int[] arr = {101,34,119,1,-1,89};
        int [] arr = new int [80000];
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int)(Math.random() * 8000000); //生成一个[0,8000000)数
        }
        Date date1 = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date1Str = simpleDateFormat.format(date1);
        System.out.println("排序前的时间是=" + date1Str);
        insertSort(arr);
        Date date2 = new Date();
        String date2Str = simpleDateFormat.format(date2);
        System.out.println("排序后的时间是=" + date2Str);
    }

    //插入排序
    public static void insertSort(int[] arr){
        //使用for循环
        for (int i = 1; i < arr.length; i++) {
            int insertVal = arr[i];
            int insertIndex = i - 1;//即arr[1]的前面的数的下标
            //给insertVal 插入的位置
            //1.保证insertIndex >= 0 保证在给insertVal 找插入位置,不越界
            //2.待插入的数还没有找到适当的插入位置
            //3.就需要将arr[indexIndex]后移
            while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
                arr[insertIndex + 1] = arr[insertIndex];//将arr[indexIndex]后移
                insertIndex--;
            }
            //当退出whlie循环时,说明插入的位置找到,insertIndex + 1
            //判断是否需要赋值
            if(insertIndex+1 != i ) {//不用插入
                arr[insertIndex + 1] = insertVal;
            }
            //System.out.println("第"+i+"轮插入后");
            //System.out.println(Arrays.toString(arr));
        }
        //使用逐步推导的方式
        //第1轮 {101,34,119,1}=>{34,101,119,1}

        //定义待插入的数
        /*int insertVal = arr[1];
        int insertIndex = 1-1;//即arr[1]的前面的数的下标
        //给insertVal 插入的位置
        //1.保证insertIndex >= 0 保证在给insertVal 找插入位置,不越界
        //2.待插入的数还没有找到适当的插入位置
        //3.就需要将arr[indexIndex]后移
        while (insertIndex >= 0 && insertVal < arr[insertIndex]){
            arr[insertIndex+1] = arr[insertIndex];//将arr[indexIndex]后移
            insertIndex--;
        }
        //当退出whlie循环时,说明插入的位置找到,insertIndex + 1
        arr[insertIndex + 1] = insertVal;

        System.out.println("第1轮插入后");
        System.out.println(Arrays.toString(arr));

        //第2轮
        insertVal = arr[2];
        insertIndex = 2-1;//即arr[1]的前面的数的下标
        while (insertIndex >= 0 && insertVal < arr[insertIndex]){
            arr[insertIndex+1] = arr[insertIndex];//将arr[indexIndex]后移
            insertIndex--;
        }
        //当退出whlie循环时,说明插入的位置找到,insertIndex + 1
        arr[insertIndex + 1] = insertVal;

        System.out.println("第2轮插入后");
        System.out.println(Arrays.toString(arr));

        //第3轮
        insertVal = arr[3];
        insertIndex = 3-1;//即arr[1]的前面的数的下标
        while (insertIndex >= 0 && insertVal < arr[insertIndex]){
            arr[insertIndex+1] = arr[insertIndex];//将arr[indexIndex]后移
            insertIndex--;
        }
        //当退出whlie循环时,说明插入的位置找到,insertIndex + 1
        arr[insertIndex + 1] = insertVal;

        System.out.println("第3轮插入后");
        System.out.println(Arrays.toString(arr));*/
    }

}

缺点:当插入的数是比较小的数时,后移的次数明显增多,对效率有影响

希尔排序

也称缩小增量排序

希尔排序的基本思想:希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序,随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件被分成一组,算法便终止

image-20230126173530627

package com.atguigu.sort;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

public class ShellSort {
    public static void main(String[] args) {
        int[] arr = {8,9,1,7,2,3,5,4,6,0};
//        int [] arr = new int [80000];
//        for (int i = 0; i < 80000; i++) {
//            arr[i] = (int)(Math.random() * 8000000); //生成一个[0,8000000)数
//        }
        Date date1 = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date1Str = simpleDateFormat.format(date1);
        System.out.println("排序前的时间是=" + date1Str);
        shellSort2(arr);
        Date date2 = new Date();
        String date2Str = simpleDateFormat.format(date2);
        System.out.println("排序后的时间是=" + date2Str);
    }

    //使用逐步推导的方式来编写希尔排序
    public static void shellSort(int[] arr){
        //希尔排序的交换法
        int temp = 0;
        int count = 0;
        //使用循环处理
        for (int gap = arr.length/2; gap>0 ; gap/=2) {//间隔
            for (int i = gap; i< arr.length ; i++) {
                //遍历各组中所有的元素(共gap组,每组两个元素),步长gap
                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;
                    }

                }
            }
            //System.out.println("希尔排序第"+(++count)+"轮后=" + Arrays.toString(arr));
        }



        //希尔排序的第1轮排序
        //因为第1轮排序,是将10个数据分成5组

//        System.out.println("希尔排序1轮后="+ Arrays.toString(arr));
        /*int temp = 0;
        //希尔排序的第1轮排序
        //因为第1轮排序,是将10个数据分成5组
        for (int i = 5; i< arr.length ; i++) {
            //遍历各组中所有的元素(共5组,每组两个元素)
            for (int j = i - 5; j >=0 ; j-=5) {
                //如果当前元素大于加上步长后的那个元素,说明交换
                if(arr[j] > arr[j+5]){
                    temp = arr[j];
                    arr[j] = arr[j+5];
                    arr[j+5] = temp;
                }

            }
        }
        System.out.println("希尔排序1轮后="+ Arrays.toString(arr));

        //第2轮 5/2两组
        for (int i = 2; i< arr.length ; i++) {
            //遍历各组中所有的元素(共5组,每组两个元素)
            for (int j = i - 2; j >=0 ; j-=2) {
                //如果当前元素大于加上步长后的那个元素,说明交换
                if(arr[j] > arr[j+2]){
                    temp = arr[j];
                    arr[j] = arr[j+2];
                    arr[j+2] = temp;
                }

            }
        }
        System.out.println("希尔排序2轮后="+ Arrays.toString(arr));

        //第3轮 5/2/2 = 1组
        for (int i = 1; i< arr.length ; i++) {
            //遍历各组中所有的元素(共5组,每组两个元素)
            for (int j = i - 1; j >=0 ; j-=1) {
                //如果当前元素大于加上步长后的那个元素,说明交换
                if(arr[j] > arr[j+1]){
                    temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }

            }
        }
        System.out.println("希尔排序3轮后="+ Arrays.toString(arr));
        */

    }
    //希尔排序插入法
        public static void shellSort2(int[] arr) {
            for (int gap = arr.length / 2; gap > 0; gap /= 2) {//间隔
                //从gap个元素开始,逐个对其所在的组进行直接插入
                for (int i = gap; i <arr.length ; i++) {
                    int j = i;
                    int temp = arr[j];
                    if(arr[j]<arr[j-gap]){
                        while (j-gap>=0 && temp < arr[j-gap])
                        {
                            //移动
                            arr[j] = arr[j-gap];
                            j -= gap;
                        }
                        //退出while循环,找到插入的位置
                        arr[j] = temp;
                    }
                }
                System.out.println( Arrays.toString(arr));
            }

        }

}
快速排序

快速排序是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据小,然后再按照此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

image-20230126184724484

image-20230126184846525

image-20230126185057248

package com.atguigu.sort;

import java.util.Arrays;

public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {-9,78,0,23,-567,70};
        quickSort(arr,0,arr.length-1);
        System.out.println("arr="+ Arrays.toString(arr));
    }

    //
    public static void quickSort(int[] arr,int left,int right)
    {
        int l = left;//左下标
        int r = right;//右下标
        int pivot = arr[(left + right) / 2];
        int temp = 0;//临时变量,作为交换时使用
        while (l<r){//while循环的目的是让比pivot小的值放到左边,比pivot大的放到右边
            //在pivot的左边一直找,找到大于等于pivot的值,才退出
            while (arr[l]<pivot){
                l+=1;
            }
            //在pivot的右边找小于等于pivot的值,才退出
            while (arr[r]>pivot){
                r-=1;
            }
            //l>=r说明pivot左右两边的值已经按照左边全部是小于pivot的值,右边全部>=pivot的值
            if(l>=r){
                break;
            }//交换
            temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;
            //如果交换完后,发这个arr[l]==pivot的值相等,r--,前移
            if(arr[l]==pivot){
                r-=1;
            }
            //如果交换完后,发这个arr[r]==pivot的值相等,l++,后移
            if(arr[r]==pivot){
                l+=1;
            }
        }
        //如果l==r,必须l++,r--,否则出现栈溢出
        if(l==r){
            l+=1;
            r-=1;
        }
        //向左递归
        if(left<r){
            quickSort(arr,left,r);
        }
        //向右递归
        if(right > l){
            quickSort(arr,l,right);
        }

    }

}
归并排序

归并排序是利用归并的思想实现的排序方法,该算法采用经典的分治策略(分治法将问题分成一些小的问题然后递归求解,而治的阶段则将分的阶段得到的答案”修补“在一起,即分而治之)

image-20230126204724382

重点是治

需要将已经有序的子序列合并成一个子序列,比如上图中的最后一次合并

image-20230126205520681

截屏2023-01-26 20.56.29

package com.atguigu.sort;

import java.util.Arrays;

public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {-9,78,0,23,-567,70};
        quickSort(arr,0,arr.length-1);
        System.out.println("arr="+ Arrays.toString(arr));
    }

    //
    public static void quickSort(int[] arr,int left,int right)
    {
        int l = left;//左下标
        int r = right;//右下标
        int pivot = arr[(left + right) / 2];
        int temp = 0;//临时变量,作为交换时使用
        while (l<r){//while循环的目的是让比pivot小的值放到左边,比pivot大的放到右边
            //在pivot的左边一直找,找到大于等于pivot的值,才退出
            while (arr[l]<pivot){
                l+=1;
            }
            //在pivot的右边找小于等于pivot的值,才退出
            while (arr[r]>pivot){
                r-=1;
            }
            //l>=r说明pivot左右两边的值已经按照左边全部是小于pivot的值,右边全部>=pivot的值
            if(l>=r){
                break;
            }//交换
            temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;
            //如果交换完后,发这个arr[l]==pivot的值相等,r--,前移
            if(arr[l]==pivot){
                r-=1;
            }
            //如果交换完后,发这个arr[r]==pivot的值相等,l++,后移
            if(arr[r]==pivot){
                l+=1;
            }
        }
        //如果l==r,必须l++,r--,否则出现栈溢出
        if(l==r){
            l+=1;
            r-=1;
        }
        //向左递归
        if(left<r){
            quickSort(arr,left,r);
        }
        //向右递归
        if(right > l){
            quickSort(arr,l,right);
        }

    }

}
基数排序

基数排序又称桶排序,属于”分配式排序“,又称”桶子法“,他是通过键值的个位的值将要排序的元素分配至某些”桶“中,达到排序的作用

基数排序基本思想

1)将所有待比较值统一为同样的数位长度,数位较短的数,前面补0.然后,从最低位开始,依次进行一次排序,这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列

image-20230126220541319

image-20230126220925826

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java算法数据结构指的是在使用Java编程语言时,用于解决问题和组织数据的方法和技术。 数据结构是计算机存储和组织数据的方式,它由相互之间存在特定关系的数据元素的集合组成。精心选择的数据结构可以提高程序的运行效率和存储效率。常用的数据结构包括数组、链表、栈、队列、树和图等。Java提供了一些内置的数据结构类和接口,例如ArrayList、LinkedList和HashMap等,可以方便地应用于各种问题的解决。 算法则是为了更有效地处理数据,提高数据运算效率而研究的方法和技术。算法是在数据的逻辑结构上定义的操作序列,用于解决特定的问题。在Java中,可以使用各种算法来实现排序、搜索、图算法等。Java提供了一些内置的算法类和接口,例如Collections类中的sort方法可以用于对集合进行排序,以及java.util包中的各种排序和搜索算法。 因此,Java算法数据结构是指在使用Java编程语言时,利用适当的数据结构以及选择和实现高效的算法来解决问题和处理数据的方法和技术。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Java 数据结构与算法](https://blog.csdn.net/yuan_dai_ma/article/details/113996341)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

计算机小混子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值