这里写目录标题

  • 数据结构
    • 线性结构和非线性结构
      • 线性结构
      • 非线性结构
  • 稀疏 sparsearray 数组 和队列
    • 稀疏 sparsearray 数组
      • 基本介绍
      • 应用实例
      • 代码的实现
    • 队列
      • 数组模拟队列思路
        • 实现代码
        • 问题分析并优化
      • 数组模拟环形队列
        • 实现代码
  • 链表
    • 链表介绍
    • 单链表的应用:
      • 第一步:在添加英雄时,直接添加到链表的尾部
      • 第二步:根据排名插入到指定位置
      • 第三步:修改节点
      • 第四步:删除节点
      • 面试题
      • 求单链表中有效节点的个数
      • 求单链表的倒数第K个节点
      • 反转链表
      • 打印逆序列表
      • 合并两个有序链表
      • 实现全部代码
    • 双向链表
      • 管理单向链表的缺点分析:
      • 双向链表的操作分析
    • 单向环形链表
      • Josephu(约瑟夫、约瑟夫环) 问题
      • 单向环形链表的介绍
      • Josephu(约瑟夫、约瑟夫环) 问题 利用单向环形链表解决
      • 实现代码
    • 栈的介绍
    • 栈的应用场景
    • 栈的快速入门
      • 利用数组模拟栈
        • 实现代码
      • 利用链表模拟栈
    • 栈实现综合计算器
      • 只能实现一位计算的
      • 可以进行多位数字的运算
    • 栈的三种表达式
      • 前缀表达式
      • 中缀表达式
      • 后缀表达式 逆波兰表达式
      • 逆波兰计算器
    • 中缀表达式转换为后缀表达式
      • 思路理解
      • 代码实现
      • 完整的逆序表达式计算器
  • 递归
    • 应用场景
    • 概念
    • 调用机制
    • 递归能解决什么样的问题
    • 递归需要遵守的规则
    • 迷宫问题
      • 代码实现
      • 最短路径的代码实现
      • 对迷宫问题的讨论
    • 八皇后
      • 解题思路
      • 说明:
      • 实现代码
  • 排序算法
    • 介绍
    • 排序的分类:
    • 算法的时间复杂度
      • 度量一个程序(算法)执行时间的两种方法
      • 时间频度
        • 基本介绍
      • 时间复杂度
      • 常见的时间复杂度
      • 平均时间复杂度和最坏时间复杂度
    • 算法的空间复杂度
      • 基本介绍
    • 冒泡排序
      • 基本介绍
      • 代码实现
    • 选择排序
      • 基本介绍
      • 基本思想
      • 实现代码
    • 插入排序
      • 插入排序的介绍
      • 插入排序的思想
      • 代码实验
      • 结论: 当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响.
    • 希尔排序
      • 介绍
      • 基本思想
      • 示意图
      • 代码实现
        • 交换法
        • 移动法
    • 快速排序
      • 介绍
      • 快速排序示意图
      • 快速排序法应用实例:
    • 归并排序
      • 介绍
      • 归并排序思想示意图2-合并相邻有序子序列
      • 代码实现
    • 基数排序
      • 基数排序(桶排序)介绍:
      • 基数排序的思想
      • 代码实现:
    • 排序算法对比
      • 相关术语解释:
  • 查找算法
    • 线性查找算法
    • 二分查找
      • 二分查找的思路分析
      • 代码实现
    • 插值查找

数据结构

线性结构和非线性结构

线性结构

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

非线性结构

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

稀疏 sparsearray 数组 和队列

稀疏 sparsearray 数组

基本介绍

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

稀疏数组的处理方法是:

  1. 第一行记录数组一共有几行几列,有多少个不同的值
  2. 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模

应用实例

  1. 使用稀疏数组,来保留类似前面的二维数组(棋盘、地图等等)
  2. 把稀疏数组存盘,并且可以从新恢复原来的二维数组数
  3. 整体思路分析

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

  1. 遍历原始的二维数组,得到有效数据的个数 sum

  2. 根据sum 就可以创建稀疏数组 sparseArr int[sum + 1] [3]

  3. 将二维数组的有效数据数据存入到稀疏数组

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

  1. 先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组,比如上面的 chessArr2 = int [11] [11]

  2. 在读取稀疏数组后几行的数据,并赋给 原始的二维数组 即可.

代码的实现

package com.atguigu.sparsearry;public class SparseArray {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;//输出int sum = 0;System.out.println("------原始的二维数组------");for (int[] row:chessArr1){for (int item:row){System.out.printf("%d\t",item);//转换为稀疏数组//先遍历二维数组,得到非零数据的个数if(item!= 0){sum++;}}System.out.println();}System.out.println("-------有效值-------");System.out.println(sum);//创建对应的稀疏数组int[][] sparseArray = new int[sum+1][3];//给稀疏数组赋值sparseArray[0][0] = 11;sparseArray[0][1] = 11;sparseArray[0][2] = sum;//遍历二维数组将非0的值存到稀疏数组里int count = 1;System.out.println("------得到的稀疏数组------");for(int i = 0;i<chessArr1.length;i++){for (int j = 0; j<chessArr1[0].length;j++){if (chessArr1[i][j]!=0){sparseArray[count][0] = i;sparseArray[count][1] = j;sparseArray[count][2] = chessArr1[i][j];count++;}}}for (int[] row:sparseArray){for (int item:row){System.out.printf("%d\t",item);}System.out.println();}//将稀疏数组恢复成二维数组/*1.首先读取稀疏数组的第一行,创建原始二维数组2.在读取稀疏数组后几行的数据,并赋给二维数组*/int r = sparseArray[0][0];int c = sparseArray[0][1];sum = sparseArray[0][2];int[][] chessArray2 = new int[r][c];/*for (int[] row:chessArray2){for (int item:row){System.out.printf("%d\t",item);}System.out.println();}*/for (int i = 1;i<sparseArray.length;i++){r = sparseArray[i][0];c = sparseArray[i][1];int data = sparseArray[i][2];chessArray2[r][c] = data;}System.out.println("-------得到的新二维数组--------");for (int[] row:chessArray2){for (int item:row){System.out.printf("%d\t",item);}System.out.println();}}
}
------原始的二维数组------
0   0   0   0   0   0   0   0   0   0   0
0   0   1   0   0   0   0   0   0   0   0
0   0   0   2   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   2   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0   0
-------有效值-------
3
------得到的稀疏数组------
11  11  3
1   2   1
2   3   2
4   5   2
-------得到的新二维数组--------
0   0   0   0   0   0   0   0   0   0   0
0   0   1   0   0   0   0   0   0   0   0
0   0   0   2   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   2   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0   0   0   
package com.atguigu.sparsearry;import sun.awt.image.BufferedImageGraphicsConfig;import java.io.*;public class SparseArray {public static void main(String[] args) throws IOException {//先创建一个原始的二维数组 11 * 11//0:表示没有棋子,1表示黑子,2表示蓝子int[][] chessArr1 = new int[11][11];chessArr1[1][2] = 1;chessArr1[2][3] = 2;chessArr1[4][5] = 2;//输出int sum = 0;System.out.println("------原始的二维数组------");for (int[] row:chessArr1){for (int item:row){System.out.printf("%d\t",item);//转换为稀疏数组//先遍历二维数组,得到非零数据的个数if(item!= 0){sum++;}}System.out.println();}System.out.println("-------有效值-------");System.out.println(sum);//创建对应的稀疏数组int[][] sparseArray = new int[sum+1][3];//给稀疏数组赋值sparseArray[0][0] = 11;sparseArray[0][1] = 11;sparseArray[0][2] = sum;//遍历二维数组将非0的值存到稀疏数组里int count = 1;System.out.println("------得到的稀疏数组------");for(int i = 0;i<chessArr1.length;i++){for (int j = 0; j<chessArr1[0].length;j++){if (chessArr1[i][j]!=0){sparseArray[count][0] = i;sparseArray[count][1] = j;sparseArray[count][2] = chessArr1[i][j];count++;}}}System.out.println("把稀疏数组保存到文件中.....");FileOutputStream fileOutputStream = new FileOutputStream(new File("chess.text"));for (int i = 0; i< sparseArray.length;i++){for (int j = 0;j< sparseArray[0].length;j++){int a = sparseArray[i][j];if(j == 2){fileOutputStream.write((String.valueOf(a)).getBytes());}else {fileOutputStream.write((String.valueOf(a)+",").getBytes());}}fileOutputStream.write("\n".getBytes());}System.out.println("-----读取文件中的稀疏数组并恢复为原来的数组-------");BufferedReader bufferedReader = new BufferedReader(new FileReader("chess.text"));String line = null;int c = 0;String row = null;String col = null;int[][] chessArray2 = null;while((line = bufferedReader.readLine())!= null){c++;if(c==1){//稀疏矩阵的第一行String[] array = line.split(",");//以,为分割读取文件row = array[0];col = array[1];chessArray2 = new int[Integer.parseInt(row)][Integer.parseInt(col)];}else {String[] array = line.split(",");String hang = array[0];String lie = array[1];String data = array[2];chessArray2[Integer.parseInt(hang)][Integer.parseInt(lie)] = Integer.parseInt(data);}}for (int[] r:chessArray2){for (int item:r){System.out.printf("%d\t",item);}System.out.println();}}}

队列

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0mVFykO4-1635986667264)(C:/Users/77/AppData/Roaming/Typora/typora-user-images/image-20210803095945202.png)]

数组模拟队列思路

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

当我们将数据存入队列时称为”addQueue”,addQueue 的处理需要有两个步骤:思路分析

  1. 将尾指针往后移:rear+1 , 当 front == rear 【空】
  2. 若尾指针 rear 小于队列的最大下标 maxSize-1,则将数据存入 rear 所指的数组元素中,否则无法存入数据。 rear == maxSize - 1[队列满]
实现代码
package com.atguigu.queue;import java.util.Scanner;public class ArrayQueueDemo {public static void main(String[] args){//创建一个队列ArrayQueue arrayQueue = new ArrayQueue(3);char Key = ' ';//接口用户输入Scanner s= new Scanner(System.in);boolean loop = true;//输出菜单while(loop){System.out.println("s:显示队列");System.out.println("e:退出程序");System.out.println("a:添加数据到队列");System.out.println("g:从队列取出数据");System.out.println("h:查看队列头部数据");Key = s.next().charAt(0);//接收一个字符switch (Key){case  's':arrayQueue.showQueue();break;case 'a':System.out.println("请输入一个数");int value= s.nextInt();arrayQueue.addQueue(value);break;case 'g':try{int res = arrayQueue.gerQueue();System.out.println("去除的数据为:"+res);}catch (Exception e){System.out.println(e.getMessage());//捕捉异常信息}break;case 'h':try{int res = arrayQueue.headQueue();System.out.println("去除的数据为:"+res);}catch (Exception e){System.out.println(e.getMessage());//捕捉异常信息}break;case 'e':s.close();loop = false;break;default:break;}}System.out.println("程序退出");}}//使用数组模拟队列
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;//头部,指向队列头的前一个位置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++;arr[rear] = n;}//出队列public int gerQueue() {//判断队列是否为空if (isEmpty()){//通过抛出异常来处理throw new RuntimeException("队列空,不能取出");}front++;return arr[front];}//显示队列public void showQueue(){//遍历if(isEmpty()){System.out.println("队列为空");}for (int i = 0; i<arr.length;i++){System.out.print(arr[i] + " ");}}//显示队列的头数据public int headQueue(){//判断if (isEmpty()){throw new RuntimeException("队列空");}else {return arr[++front];}}
}
问题分析并优化
  1. 目前数组使用一次就不能用, 没有达到复用的效果
  2. 将这个数组使用算法,改进成一个环形的队列 取模:%

数组模拟环形队列

实现代码
package com.atguigu.queue;import java.util.Scanner;public class ArrayQueueDemo2 {public static void main(String[] args){//创建一个队列ArrayQueue2 arrayQueue2 = new ArrayQueue2(3);char Key = ' ';//接口用户输入Scanner s= new Scanner(System.in);boolean loop = true;//输出菜单while(loop){System.out.println("s:显示队列");System.out.println("e:退出程序");System.out.println("a:添加数据到队列");System.out.println("g:从队列取出数据");System.out.println("h:查看队列头部数据");Key = s.next().charAt(0);//接收一个字符switch (Key){case  's':arrayQueue2.showQueue();break;case 'a':System.out.println("请输入一个数");int value= s.nextInt();arrayQueue2.addQueue(value);break;case 'g':try{int res = arrayQueue2.getQueue();System.out.println("去除的数据为:"+res);}catch (Exception e){System.out.println(e.getMessage());//捕捉异常信息}break;case 'h':try{int res = arrayQueue2.headQueue();System.out.println("头元素为"+res);}catch (Exception e){System.out.println(e.getMessage());//捕捉异常信息}break;case 'e':s.close();loop = false;break;default:break;}}System.out.println("程序退出");}
}
class ArrayQueue2{private int maxSize;private int front;private int rear;private int[] arr;public ArrayQueue2() {}public ArrayQueue2(int maxSize, int front, int rear, int[] arr) {this.maxSize = maxSize;this.front = front;this.rear = rear;this.arr = arr;}//船舰队列构造器public ArrayQueue2(int maxSize){this.maxSize = maxSize;arr = new int[this.maxSize];}//判断是否为空public boolean isEmpty(){return rear == front;}//判断是否满public boolean isFull(){return (rear+1) % maxSize == front;}public void addQueue(int n){if (isFull()){System.out.println("队列满不能加入数据");return;}else {arr[rear] = n;//将rear后移rear = (rear+1)%maxSize;}}public int getQueue(){if (isEmpty()){throw new RuntimeException("队列空,不能取出");}int value  = arr[front];front = (front+1)%maxSize;return value;}//显示队列public void showQueue(){if (isEmpty()){throw new RuntimeException("队列空");}for (int i = front; i <front + (rear + maxSize - front) %maxSize; i++){System.out.println(arr[i% maxSize]+ " " + i% maxSize);}}//显示队列的头数据public int headQueue(){if (isEmpty()){throw new RuntimeException("队列空");}else {return arr[front];}}
}

链表

链表介绍

链表是有序的列表,但是它在内存中是存储如下;实际结构

小结上图:

  1. 链表是以节点的方式来存储,是链式存储
  2. 每个节点包含 data 域, next 域:指向下一个节点.
  3. 如图:发现链表的各个节点不一定是连续存储.
  4. 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定

单链表(带头结点) 逻辑结构示意图如下

单链表的应用:

第一步:在添加英雄时,直接添加到链表的尾部

实现代码

package com.atguigu.linkedlist;public class SingleLikedListDemo {public static void main(String[] args){//先创建节点HeroNode heroNode1 = new HeroNode(1,"x", "x");HeroNode heroNode2 = new HeroNode(2,"xx","xx");HeroNode heroNode3 = new HeroNode(3,"xxx","xxx");//创建一个链表SingleLikedlist singleLikedlist = new SingleLikedlist();singleLikedlist.add(heroNode1);singleLikedlist.add(heroNode2);singleLikedlist.add(heroNode3);singleLikedlist.showList();}
}
//定义SingleLinkedlist 管理我们的英雄
class SingleLikedlist{//先初始化一个头节点private HeroNode head = new HeroNode(0,"","");//添加节点到单向节点//当不考虑编号的顺序时,找到当前链表的最后节点,将最后这个节点测next指向新节点public void add(HeroNode heroNode){//因为head节点不能动,因此我们需要一个辅助变量HeroNode temp = head;//遍历链表,找到最后while(true){if(temp.next == null){break;}//没有到最后temp = temp.next;}//当推出while循环时,temp指向链表最后//将最后这个节点的next指向新的节点temp.next = heroNode;//将最后一个节点的next指向新加入的点}//显示链表public void showList(){//先判断链表是否为空if(head.next == null){return;}HeroNode temp = head.next;while(true){//是否到链表最后if (temp == null){break;}//输出节点信息;System.out.println(temp);temp = temp.next;//后移节点}}}
//定义一个HeroNode,每一个HroeNode就是一个节点
class HeroNode{//定义节点的私有属性 编号,姓名 外号 和nextpublic int no;public String name;public String nickname;public HeroNode next;//构造器public HeroNode() {}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 SingleLikedListDemo {public static void main(String[] args){//先创建节点HeroNode heroNode1 = new HeroNode(1,"x", "x");HeroNode heroNode2 = new HeroNode(2,"xx","xx");HeroNode heroNode3 = new HeroNode(3,"xxx","xxx");HeroNode heroNode4 = new HeroNode(4,"xxxx","xxxx");HeroNode heroNode5 = new HeroNode(5,"xxxxx","xxxxx");//创建一个链表SingleLikedlist singleLikedlist = new SingleLikedlist();singleLikedlist.addOder(heroNode1);singleLikedlist.addOder(heroNode2);singleLikedlist.addOder(heroNode3);singleLikedlist.addOder(heroNode5);singleLikedlist.addOder(heroNode4);singleLikedlist.showList();}
}
//定义SingleLinkedlist 管理我们的英雄
class SingleLikedlist{//先初始化一个头节点private HeroNode head = new HeroNode(0,"","");//添加节点到单向节点//当不考虑编号的顺序时,找到当前链表的最后节点,将最后这个节点测next指向新节点public void add(HeroNode heroNode){//因为head节点不能动,因此我们需要一个辅助变量HeroNode temp = head;//遍历链表,找到最后while(true){if(temp.next == null){break;}//没有到最后temp = temp.next;}//当推出while循环时,temp指向链表最后//将最后这个节点的next指向新的节点temp.next = heroNode;//将最后一个节点的next指向新加入的点}//按顺序添加public void addOder(HeroNode heroNode){//因为head节点不能动,因此我们需要一个辅助变量,找到添加的位置//找的temp是添加位置的前一个节点HeroNode temp = head;boolean 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){//编号已经存在flag = true;break;}temp = temp.next;//后移继续寻找}//判断flag值if (flag){System.out.println("编号已经存在" + heroNode.no);}else {//可以插入到temp的后面heroNode.next = temp.next;temp.next = heroNode;}}//显示链表public void showList(){//先判断链表是否为空if(head.next == null){return;}HeroNode temp = head.next;while(true){//是否到链表最后if (temp == null){break;}//输出节点信息;System.out.println(temp);temp = temp.next;//后移节点}}}
//定义一个HeroNode,每一个HroeNode就是一个节点
class HeroNode{//定义节点的私有属性 编号,姓名 外号 和nextpublic int no;public String name;public String nickname;public HeroNode next;//构造器public HeroNode() {}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 SingleLikedListDemo {public static void main(String[] args){//先创建节点HeroNode heroNode1 = new HeroNode(1,"x", "x");HeroNode heroNode2 = new HeroNode(2,"xx","xx");HeroNode heroNode3 = new HeroNode(3,"xxx","xxx");HeroNode heroNode4 = new HeroNode(4,"xxxx","xxxx");HeroNode heroNode5 = new HeroNode(5,"xxxxx","xxxxx");HeroNode newheroNode = new HeroNode(2,"xx1","xx1");//创建一个链表SingleLikedlist singleLikedlist = new SingleLikedlist();singleLikedlist.addOder(heroNode1);singleLikedlist.addOder(heroNode2);singleLikedlist.addOder(heroNode3);singleLikedlist.addOder(heroNode5);singleLikedlist.addOder(heroNode4);singleLikedlist.showList();singleLikedlist.updata(newheroNode);singleLikedlist.showList();}
}
//定义SingleLinkedlist 管理我们的英雄
class SingleLikedlist{//先初始化一个头节点private HeroNode head = new HeroNode(0,"","");//添加节点到单向节点//当不考虑编号的顺序时,找到当前链表的最后节点,将最后这个节点测next指向新节点public void add(HeroNode heroNode){//因为head节点不能动,因此我们需要一个辅助变量HeroNode temp = head;//遍历链表,找到最后while(true){if(temp.next == null){break;}//没有到最后temp = temp.next;}//当推出while循环时,temp指向链表最后//将最后这个节点的next指向新的节点temp.next = heroNode;//将最后一个节点的next指向新加入的点}//按顺序添加public void addOder(HeroNode heroNode){//因为head节点不能动,因此我们需要一个辅助变量,找到添加的位置//找的temp是添加位置的前一个节点HeroNode temp = head;boolean 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){//编号已经存在flag = true;break;}temp = temp.next;//后移继续寻找}//判断flag值if (flag){System.out.println("编号已经存在" + heroNode.no);}else {//可以插入到temp的后面heroNode.next = temp.next;temp.next = heroNode;}}//修改//根据no信息来进行修改public void updata(HeroNode heroNode){if (head.next == null){System.out.println("链表为空");}//找到需要修改的节点HeroNode temp = head;boolean flag = false;while (true){if (temp == null){break;//链表已经遍历结束了}if (temp.no == heroNode.no){flag = true;break;}temp = temp.next;}if (flag){temp.name = heroNode.name;temp.nickname = heroNode.nickname;}else {System.out.println("没有找到这个编号" + heroNode.no);}}//显示链表public void showList(){//先判断链表是否为空if(head.next == null){return;}HeroNode temp = head.next;while(true){//是否到链表最后if (temp == null){break;}//输出节点信息;System.out.println(temp);temp = temp.next;//后移节点}}}
//定义一个HeroNode,每一个HroeNode就是一个节点
class HeroNode{//定义节点的私有属性 编号,姓名 外号 和nextpublic int no;public String name;public String nickname;public HeroNode next;//构造器public HeroNode() {}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 SingleLikedListDemo {public static void main(String[] args){//先创建节点HeroNode heroNode1 = new HeroNode(1,"x", "x");HeroNode heroNode2 = new HeroNode(2,"xx","xx");HeroNode heroNode3 = new HeroNode(3,"xxx","xxx");HeroNode heroNode4 = new HeroNode(4,"xxxx","xxxx");HeroNode heroNode5 = new HeroNode(5,"xxxxx","xxxxx");HeroNode newheroNode = new HeroNode(2,"xx1","xx1");//创建一个链表SingleLikedlist singleLikedlist = new SingleLikedlist();singleLikedlist.addOder(heroNode1);singleLikedlist.addOder(heroNode2);singleLikedlist.addOder(heroNode3);singleLikedlist.addOder(heroNode5);singleLikedlist.addOder(heroNode4);singleLikedlist.showList();System.out.println("修改后");singleLikedlist.updata(newheroNode);singleLikedlist.showList();singleLikedlist.deleteNode(1);singleLikedlist.deleteNode(5);System.out.println("删除后");singleLikedlist.showList();}
}
//定义SingleLinkedlist 管理我们的英雄
class SingleLikedlist{//先初始化一个头节点private HeroNode head = new HeroNode(0,"","");//添加节点到单向节点//当不考虑编号的顺序时,找到当前链表的最后节点,将最后这个节点测next指向新节点public void add(HeroNode heroNode){//因为head节点不能动,因此我们需要一个辅助变量HeroNode temp = head;//遍历链表,找到最后while(true){if(temp.next == null){break;}//没有到最后temp = temp.next;}//当推出while循环时,temp指向链表最后//将最后这个节点的next指向新的节点temp.next = heroNode;//将最后一个节点的next指向新加入的点}//按顺序添加public void addOder(HeroNode heroNode){//因为head节点不能动,因此我们需要一个辅助变量,找到添加的位置//找的temp是添加位置的前一个节点HeroNode temp = head;boolean 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){//编号已经存在flag = true;break;}temp = temp.next;//后移继续寻找}//判断flag值if (flag){System.out.println("编号已经存在" + heroNode.no);}else {//可以插入到temp的后面heroNode.next = temp.next;temp.next = heroNode;}}//修改//根据no信息来进行修改public void updata(HeroNode heroNode){if (head.next == null){System.out.println("链表为空");}//找到需要修改的节点HeroNode temp = head;boolean flag = false;while (true){if (temp == null){break;//链表已经遍历结束了}if (temp.no == heroNode.no){flag = true;break;}temp = temp.next;}if (flag){temp.name = heroNode.name;temp.nickname = heroNode.nickname;}else {System.out.println("没有找到这个编号" + heroNode.no);}}//删除public void deleteNode(int no){if (head.next == null){return;}boolean flag  = false;HeroNode temp = head;while (true){if (temp == null){break;}if (temp.next.no == no){flag = true;break;}temp = temp.next;}if (flag){temp.next = temp.next.next;}}//显示链表public void showList(){//先判断链表是否为空if(head.next == null){return;}HeroNode temp = head.next;while(true){//是否到链表最后if (temp == null){break;}//输出节点信息;System.out.println(temp);temp = temp.next;//后移节点}}}
//定义一个HeroNode,每一个HroeNode就是一个节点
class HeroNode{//定义节点的私有属性 编号,姓名 外号 和nextpublic int no;public String name;public String nickname;public HeroNode next;//构造器public HeroNode() {}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+"]";}
}

面试题

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

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

public static int Length(ListNode head){ListNode temp = head.next;int count = 0;if (head.next == null){return 0;}while (true){if (temp == null){break;}temp = temp.next;count++;}//System.out.println("链表的总长度:" + count);return count;}

求单链表的倒数第K个节点

public static ListNode findNode(ListNode head,int index){///得到总长度int l = Length(head);int goal = l - index;ListNode temp = head;if (index <= 0 || index > l){System.out.println("输入错误");}if (head.next == null){return null;}for (int i =0; i <= goal;i++){if (temp == null){System.out.println("已经遍历结束,没有想要的节点");break;}temp = temp.next;}//System.out.println("倒数第"+index+"个节点为" + temp.var);return temp;}

反转链表

public static void reverseList(ListNode head){ListNode reverseNode = new ListNode();//只有一个节点或者没有节点不需要处理if (head.next == null || head.next.next == null){return;}ListNode cur = head.next;ListNode next = null;//表示当前节点的下一个节点while(cur!=null){next = cur.next;//暂时保存当前节点的下一个节点cur.next = reverseNode.next;//将cur下一个节点,指向新的链表的头部reverseNode.next = cur;cur = next;}head.next = reverseNode.next;}

打印逆序列表

​ 上面的题的要求就是逆序打印单链表.

​ 方式1: 先将单链表进行反转操作,然后再遍历即可,这样的做的问题是会破坏原来的单链表的结构,不建议

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

  public static void reversePrint(ListNode head){if(head.next == null){return;}//创建一个栈,将各个节点压栈Stack<ListNode> stack = new Stack<>();ListNode cur = head.next;//将链表的所有节点压入栈中while(cur!=null){stack.push(cur);cur = cur.next;}//将栈中的节点进行打印while(stack.size()>0){System.out.println(stack.pop());}}

合并两个有序链表

 public static Node mergeList(Node head1,Node head2){if (head1 == null){return head2;}else if (head2 == null){return head1;} else if (head1 == null && head2 == null){return null;}Node temp = null;if (head1.var <= head2.var){temp = head1;temp.next = mergeList(head1.next,head2);}else if(head1.var > head2.var){temp = head2;temp.next = mergeList(head1.next,head2);}return temp;}

实现全部代码

package com.atguigu.NodeLikned;import javax.print.DocFlavor;
import java.util.List;
import java.util.Stack;public class linkedListTest {public static void main(String[] args) {LinkdeList list1 = new LinkdeList();LinkdeList list2 = new LinkdeList();list1.add(1);list1.add(2);list1.add(4);list1.orderadd(3);list1.orderadd(5);list1.orderadd(6);list2.add(7);list2.add(8);list2.add(9);list1.print();list2.print();Node head1= list1.getHead();Node head2= list2.getHead();/*int count = list1.Length();System.out.println("总长度为:" + count);int f1 = list1.find(2);System.out.println("寻找的倒数第2节点为:" + f1);list1.delNode(2);System.out.println("删除后的列表为:");//list.print();System.out.println(head1.var);reverselist(head1);list1.print();reversePrintList(head1);*/Node m1 = mergeList(head1.next,head2.next);while (m1!= null) {System.out.print(m1.var + " ");m1 = m1.next;}}//反转链表public static void reverselist(Node head) {if (head.next == null || head.next.next == null){return;}Node cur = head.next;Node reverse = new Node();Node next = null;while (cur != null) {next = cur.next;cur.next = reverse.next;//当前节点的下一节点,和新链表的下一个节点相连接reverse.next = cur;//新链表的下一个节点,指向当前节点cur = next;}head.next = reverse.next;}//逆序打印列表public static void reversePrintList(Node head){Stack<Node> stack = new Stack<>();if (head.next == null){return;}Node temp = head.next;while(temp != null){stack.push(temp);temp = temp.next;}while (stack.size()>0){System.out.print(stack.pop().var + " ");}}//合并两个有序的链表public static Node mergeList(Node head1,Node head2){if (head1 == null){return head2;}else if (head2 == null){return head1;} else if (head1 == null && head2 == null){return null;}Node temp = null;if (head1.var <= head2.var){temp = head1;temp.next = mergeList(head1.next,head2);}else if(head1.var > head2.var){temp = head2;temp.next = mergeList(head1.next,head2);}return temp;}
}class LinkdeList {public Node head = new Node(0); //头节点为空//得到头节点public Node getHead() {return head;}//末尾添加public void add(int var) {Node temp = head;while (temp.next != null) {temp = temp.next;}temp.next = new Node(var);}//指定位置添加public void orderadd(int var) {Node temp = head;Node cur = new Node(var);boolean flag = false;while (true) {if (temp.next == null) {flag = true;break;} else if (temp.next.var == var) {System.out.println("存在");break;} else if (temp.next.var > var) {flag = true;break;}temp = temp.next;}if (flag) {cur.next = temp.next;temp.next = cur;}}//求链表总数目public int Length() {if (head.next == null) {return 0;}Node temp = head.next;int count = 0;while (temp != null) {count++;temp = temp.next;}return count;}//查找倒数第K个节点public int find(int index) {if (head.next == null) {return -1;}Node temp = head.next;int size = Length();int curindex = size - index;for (int i = 0; i < curindex; i++) {temp = temp.next;}return temp.var;}//删除节点public void delNode(int var) {if (head.next == null) {return;}Node temp = head;Node cur = new Node(var);while (true) {if (temp.next == null) {System.out.println("不存在该节点");} else if (temp.next.var == var) {temp.next = temp.next.next;break;}temp = temp.next;}}//打印节点public void print() {if (head.next == null) {return;}Node temp = head.next;while (temp != null) {System.out.print(temp.var + " ");temp = temp.next;}System.out.println();}
}//链表class Node {int var;Node next;public Node() {}public Node(int var) {this.var = var;}
}

双向链表

管理单向链表的缺点分析:

  1. 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
  2. 单向链表不能自我删除,需要靠辅助节点 ,而双向链表,则可以自我删除,所以前面我们单链表删除时节点,总是找到 temp,temp 是待删除节点的前一个节点(认真体会)。
  3. 分析了双向链表如何完成遍历,添加,修改和删除的思路

双向链表的操作分析

package com.atguigu.linkedlist;import jdk.nashorn.internal.ir.SplitReturn;
import sun.security.provider.SHA;public class DoubleLinkedListDemo {public static void main(String[] args) {System.out.println("-----------双向链表的测试----------");HeroNode2 heroNode1 = new HeroNode2(1,"x", "x");HeroNode2 heroNode2 = new HeroNode2(2,"xx","xx");HeroNode2 heroNode3 = new HeroNode2(3,"xxx","xxx");HeroNode2 heroNode4 = new HeroNode2(4,"xxxx","xxxx");HeroNode2 heroNode5 = new HeroNode2(5,"xxxxx","xxxxx");HeroNode2 newheroNode = new HeroNode2(2,"xx1","xx1");DoubleLinkedList doubleLinkedList = new DoubleLinkedList();doubleLinkedList.add(heroNode1);doubleLinkedList.add(heroNode2);doubleLinkedList.add(heroNode3);doubleLinkedList.list();System.out.println("--------修改过后的节点----------");doubleLinkedList.updata(newheroNode);doubleLinkedList.list();doubleLinkedList.del(heroNode2.no);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){return;}//头节点不能动,所以要创建辅助节点HeroNode2 temp = head.next;while(true){//判断链表是否到最后if (temp == null){break;}System.out.println(temp);temp = temp.next;}}//默认添加到双向链表的结尾public void add(HeroNode2 heroNode2){HeroNode2 temp = head;while (true){if(temp.next == null){break;}temp = temp.next;}//形成一个双向链表temp.next = heroNode2;heroNode2.per = temp;}//修改一个结点的内容public void updata(HeroNode2 heroNode2) {if (head.next == null) {System.out.println("链表为空");}//找到需要修改的节点HeroNode2 temp = head;boolean flag = false;while (true) {if (temp == null) {break;//链表已经遍历结束了}if (temp.no == heroNode2.no) {flag = true;break;}temp = temp.next;}if (flag) {temp.name = heroNode2.name;temp.nickname = heroNode2.nickname;} else {System.out.println("没有找到这个编号" + heroNode2.no);}}//删除public void del(int no){//判断当前链表是否为空if (head.next == null){return;}HeroNode2 temp = head.next;boolean flag = false;//找到待删除的节点while(true){if (temp == null){break;//已经到了链表的最后}if (temp.no == no){//找到待删除节点flag = true;break;//说明找到了}temp = temp.next;}if (flag){temp.per.next = temp.next;//删除最后一个结点的时候 temp.pre.next == nullif (temp.next != null){temp.next.per = temp.per; //  防止出现空指针异常}}else {System.out.println("节点不存在");}}//显示链表public void showPrint(){if (head.next == null){return;}}
}
class HeroNode2{//定义节点的私有属性 编号,姓名 外号 和nextpublic int no;public String name;public String nickname;public HeroNode2 next;//默认为nullpublic HeroNode2 per;//指向前一个结点,默认为null//构造器public HeroNode2() {}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+"]";}
}

单向环形链表

Josephu(约瑟夫、约瑟夫环) 问题

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

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

单向环形链表的介绍

Josephu(约瑟夫、约瑟夫环) 问题 利用单向环形链表解决

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fzt0ELpj-1635986667272)(C:/Users/77/AppData/Roaming/Typora/typora-user-images/image-20210810085726585.png)]

实现代码

package com.atguigu.linkedlist;public class Joesphu {public static void main(String[] args) {CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();circleSingleLinkedList.add(5);circleSingleLinkedList.show();System.out.println();circleSingleLinkedList.count(1,2,5);}
}
//创建一个环形的单向链表
class CircleSingleLinkedList{//创建一个first节点,当前没有编号private Boy first = new Boy(-1);public void add(int no){//做一个数据校验if(no < 1){System.out.println("数据输入错误");return;}//创建环形列表Boy cur = new Boy();for (int i =1; i < no+1; i++){//根据编号创建节点Boy boy = new Boy(i);if (i == 1){first = boy;boy.setNext(first);  // 构成环形cur = first;}else {cur.setNext(boy);boy.setNext(first);cur = boy;}}}//遍历当前环形列表public void show(){if (first == null){System.out.println("链表为空");return;}Boy cur =new Boy();cur = first;while (true){System.out.print(cur.getNo()+ " ");if (cur.getNext() == first){break;}cur = cur.getNext();}}//根据用户的输入,计算小孩出圈的顺序/**** @param strNo 表示从第几个小孩开始数数* @param countNum 表示数几下* @param nums 表示最初有多少小孩在圈中*/public void count(int strNo , int countNum, int nums){//先对数据进行校验if (first == null || strNo < 1 || strNo > nums){System.out.println("参数输入有误");return;}//创建一个辅助指针,帮助完成小孩出圈Boy helper = first;while (true){//辅助指针指向最后if (helper.getNext() == first){break;}helper = helper.getNext();}//在移动之前,先让first和helper移动到起点kfor (int j = 0; j < strNo - 1 ;j++){first = first.getNext();helper = helper.getNext();}//进行循环操作,直到圈中只有一个节点while(true){if (helper == first){break;}//移动countNum-1次for (int i =0; i< countNum-1;i++){first = first.getNext();helper = helper.getNext();}//这是first这个节点为出圈的节点System.out.println(first.getNo());first = first.getNext();helper.setNext(first);}System.out.println("最后的一个节点" + first.getNo());}}
//创建一个Boy类表示一个节点
class Boy{private int no;private Boy next;public Boy() {}Boy(int no){this.no = no;}public int getNo() {return no;}public Boy getNext() {return next;}public void setNo(int no) {this.no = no;}public void setNext(Boy next) {this.next = next;}
}

栈的介绍

  1. 栈的英文为(stack)

  2. 栈是一个先入后出(FILO-First In Last Out)的有序列表。

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

  4. 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元 素最先删除,最先放入的元素最后删除

  5. 图解方式说明出栈(pop)和入栈(push)的概念

栈的应用场景

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

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

  3. 表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。

  4. 二叉树的遍历。

  5. 图形的深度优先(depth 一 first)搜索法。

栈的快速入门

利用数组模拟栈

实现代码
package com.atguigu.stack;import java.util.Scanner;public class ArrarystackDemo {public static void main(String[] args) {//创建对象ArrayStack arrarystack = new ArrayStack(5);String key = "";boolean lool = true;Scanner scanner = new Scanner(System.in);while (lool){System.out.println("1:show");System.out.println("2:push");System.out.println("3:pop");System.out.println("4:exit");key = scanner.next();switch (key){case "1":arrarystack.list();break;case "2":System.out.println("请输入一个数");int value = scanner.nextInt();arrarystack.push(value);break;case "3":try{int res  =arrarystack.pop();System.out.println(res);}catch (Exception e){System.out.println(e.getMessage());}break;case "4":scanner.close();lool = false;break;}}}
}
//定义一个栈
class ArrayStack{private int maxSize;//栈的大小private int[] stack; //模拟栈的数组private int top = -1;//初始化为栈顶public ArrayStack(int maxSize) {this.maxSize = maxSize;stack = new int[this.maxSize];}//栈满public boolean ifFull(){return top == maxSize -1;}//栈空public boolean isEmpty(){return top == -1;}public void push(int value){if (ifFull()){System.out.println("栈满");return;}top ++;stack[top] = value;}public int pop(){if (isEmpty()){throw new RuntimeException("栈空,没有数据");}int res = stack[top];top --;return res;}//遍历public void list(){if (isEmpty()){System.out.println("栈空,没有数据");}for (int i = top; i>= 0; i--){System.out.print(stack[i] + " ");}System.out.println();}}

利用链表模拟栈

package com.atguigu.stack;public class LinkedStackDemo {public static void main(String[] args) {LinkedStack linkedStack = new LinkedStack();linkedStack.push(1);linkedStack.push(2);linkedStack.show();Node head = linkedStack.getHead();reverseLinked(head);linkedStack.show();}public static  void reverseLinked(Node head){Node cur = head.getNext();Node next = null;Node reverse = new Node();while(cur != null){next = cur.getNext();cur.setNext(reverse.getNext());reverse.setNext(cur);cur = next;}head.setNext(reverse.getNext());}
}
//链表
class LinkedStack{//定义一个头节点Node head = new Node();public Node getHead(){return head;}//定义栈大小private int maxSize = 5;private int top = -1;public boolean isFull(){return top == maxSize-1;}public boolean isEmpty(){return top == -1;}public void push(int value){Node temp = head;if (isFull()){return;}while (temp.getNext()!=null){temp = temp.getNext();}Node res = new Node(value);temp.setNext(res);top ++;}public void show(){Node temp = head.getNext();if (isEmpty()){return;}while (temp != null){System.out.print(temp.getData() + " ");temp = temp.getNext();}System.out.println();}public int pop(){Node temp = head;if (isEmpty()){return -1;}top --;temp = temp.getNext();int res  = temp.getData();System.out.println("出栈的数为" + res);return res;}}
class Node{private int data;private Node next;public Node() {}public Node(int data) {this.data = data;}public Node(Node next) {this.next = next;}public int getData() {return data;}public Node getNext() {return next;}public void setData(int data) {this.data = data;}public void setNext(Node next) {this.next = next;}
}

栈实现综合计算器

只能实现一位计算的

package com.atguigu.stack;public class StackCounter {public static void main(String[] args) {String experssion = "7+2*6-2";//创建两个栈ArrayStack2 numberStack = new ArrayStack2(10);ArrayStack2 operStack = new ArrayStack2(10);int index = 0;//用于扫描int num1 = 0, num2 = 0;int oper = 0;int res = 0;char ch =' ' ;//将每次扫描得到的char放入//开始循环while(true){//依次得到每一个字符ch = experssion.substring(index,index+1).charAt(0);//判断ch是什么,然后做相应的处理if(operStack.isOper(ch)){//如果是符号if (!operStack.isEmpty()){//如果符号栈不为空//如果是符号,进行比较,如果当前操作符的优先级小于或等于栈中运算符if (operStack.priority(ch) <=operStack.priority(operStack.pick())) {num1 = numberStack.pop();num2 = numberStack.pop();oper = operStack.pop();res = numberStack.cal(num1,num2,oper);numberStack.push(res);operStack.push(ch);}else {operStack.push(ch);}}else{//如果为空直接入栈operStack.push(ch);//如果}}else{//如果是数,则直接入栈numberStack.push(ch - 48);//ASCII码转换为数字}//index+1.是否扫描到expersion最后index++;if (index >= experssion.length()){break;}}while(true){//如果符号栈为空,则结算结束,数栈中只有一个数字if (operStack.isEmpty()){break;}num1 = numberStack.pop();num2 = numberStack.pop();oper = operStack.pop();res = numberStack.cal(num1,num2,oper);numberStack.push(res);}//将数栈中最后一个打印出来System.out.println("表达式为:"+experssion+"结果为:"+numberStack.pop());}
}
//先创建一个栈,需要扩展功能
class ArrayStack2{private int maxSize;//栈的大小private int[] stack; //模拟栈的数组private int top = -1;//初始化为栈顶public ArrayStack2(int maxSize) {this.maxSize = maxSize;stack = new int[this.maxSize];}//栈满public boolean ifFull(){return top == maxSize -1;}//栈空public boolean isEmpty(){return top == -1;}public void push(int value){if (ifFull()){System.out.println("栈满");return;}top ++;stack[top] = value;}public int pop(){if (isEmpty()){throw new RuntimeException("栈空,没有数据");}int res = stack[top];top --;return res;}//遍历public void list(){if (isEmpty()){System.out.println("栈空,没有数据");}for (int i = top; i>= 0; i--){System.out.print(stack[i] + " ");}System.out.println();}//返回运算符的优先级,优先级使用数字表示,数字越大,优先级越高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;default:break;}return res;}//返回当前栈顶的方法public int pick(){return stack[top];}}

可以进行多位数字的运算

package com.atguigu.stack;public class StackCounter {public static void main(String[] args) {String experssion = "70+2*6-4+8";//创建两个栈ArrayStack2 numberStack = new ArrayStack2(10);ArrayStack2 operStack = new ArrayStack2(10);int index = 0;//用于扫描int num1 = 0, num2 = 0;int oper = 0;int res = 0;String s = "";//用于拼接多位数char ch = ' ';//将每次扫描得到的char放入//开始循环while (true) {//依次得到每一个字符ch = experssion.substring(index, index + 1).charAt(0);//判断ch是什么,然后做相应的处理if (operStack.isOper(ch)) {//如果是符号if (!operStack.isEmpty()) {//如果符号栈不为空//如果是符号,进行比较,如果当前操作符的优先级小于或等于栈中运算符if (operStack.priority(ch) <= operStack.priority(operStack.pick())) {num1 = numberStack.pop();num2 = numberStack.pop();oper = operStack.pop();res = numberStack.cal(num1, num2, oper);numberStack.push(res);operStack.push(ch);} else {//否则直接入栈operStack.push(ch);}}else{operStack.push(ch);}}else {//如果是数,则直接入栈//当处理多位数时,不能发现是一个数就立即入栈,在处理数时,需要向expression的表达式再看一位index,如果是符号就可以入栈//需要定义一个字符串变量用于拼接。s += ch;//是否是表达式的最后一位if (index == experssion.length() - 1) {numberStack.push(Integer.parseInt(s));} else {//判断下一个字符是不是数字,如果是数字就继续扫描if (operStack.isOper(experssion.substring(index + 1, index + 2).charAt(0))) {//如果是符号就可以入栈numberStack.push(Integer.parseInt(s));//字符串转为整数//清空ss = "";}}}//index+1.是否扫描到expersion最后index++;if (index >= experssion.length()) {break;}}while (true) {//如果符号栈为空,则结算结束,数栈中只有一个数字if (operStack.isEmpty()) {break;}num1 = numberStack.pop();num2 = numberStack.pop();oper = operStack.pop();res = numberStack.cal(num1, num2, oper);numberStack.push(res);}//将数栈中最后一个打印出来System.out.println("表达式为:" + experssion + "结果为:" + numberStack.pop());}}//先创建一个栈,需要扩展功能
class ArrayStack2{private int maxSize;//栈的大小private int[] stack; //模拟栈的数组private int top = -1;//初始化为栈顶public ArrayStack2(int maxSize) {this.maxSize = maxSize;stack = new int[this.maxSize];}//栈满public boolean ifFull(){return top == maxSize -1;}//栈空public boolean isEmpty(){return top == -1;}public void push(int value){if (ifFull()){System.out.println("栈满");return;}top ++;stack[top] = value;}public int pop(){if (isEmpty()){throw new RuntimeException("栈空,没有数据");}int res = stack[top];top --;return res;}//遍历public void list(){if (isEmpty()){System.out.println("栈空,没有数据");}for (int i = top; i>= 0; i--){System.out.print(stack[i] + " ");}System.out.println();}//返回运算符的优先级,优先级使用数字表示,数字越大,优先级越高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;default:break;}return res;}//返回当前栈顶的方法public int pick(){return stack[top];}}

栈的三种表达式

前缀表达式

中缀表达式

后缀表达式 逆波兰表达式

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

举例说明: (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=1+3 a 1 3 + =

逆波兰计算器

package com.atguigu.stack;import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;import java.sql.PreparedStatement;
import java.util.*;public class PolandNatation {public static void main(String[] args) {//定义一个逆波兰表达式String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";//为了方便,逆波兰表达式的数字和空格隔开//思路//先将"3 4 + 5 * 6 -" 放入ArrayList中//4*5-8+60+8/2//4 5 * 8 - 60 + 8 2 / +//将ArrayList传递给一个方法,这个方法使用配合栈完成计算List<String> rpnList = getListString(suffixExpression);System.out.println("rpnList " + rpnList);int res = calculate(rpnList);System.out.println("计算结果为:" + res);}//将一个逆波兰表达式,依次将数据和运算符放入ArrayList中public static List<String> getListString(String suffixExperssion){//将suffixExperssion分割String[] split = suffixExperssion.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>();//遍历listfor(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("运算符有误");}stack.push(String.valueOf(res));}}return Integer.parseInt(stack.pop());}
}

中缀表达式转换为后缀表达式

思路理解

大家看到,后缀表达式适合计算式进行运算,但是人却不太容易写出来,尤其是表达式很长的情况下,因此在开发中,我们需要将中缀表达式转成后缀表达式。

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

  2. 从左至右扫描中缀表达式;

  3. 遇到操作数时,将其压 s2;

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

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

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

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

  1. 遇到括号时:

    1. 如果是左括号“(”,则直接压入 s1
    2. 如果是右括号“)”,则依次弹出 s1 栈顶的运算符,并压入 s2,直到遇到左括号为止,此时将这一对括号丢弃
  1. 重复步骤 2 至 5,直到表达式的最右边

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

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

代码实现

package com.atguigu.stack;import java.util.*;public class PolandNatation {public static void main(String[] args) {//将中缀表达式转换为后缀表达式String experssion = "1+((2+3)*4)-5";List<String> infixExperssionList = toInfixExperssionList(experssion);System.out.println(infixExperssionList);List<String> pareSuffixExperssion = parseSuffixExpersion(infixExperssionList);//将中缀表达式转换为后缀表达式System.out.println(pareSuffixExperssion);System.out.println("结果为:" + calculate(pareSuffixExperssion));//定义一个逆波兰表达式/*String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";//为了方便,逆波兰表达式的数字和空格隔开//思路//先将"3 4 + 5 * 6 -" 放入ArrayList中//4*5-8+60+8/2//4 5 * 8 - 60 + 8 2 / +//将ArrayList传递给一个方法,这个方法使用配合栈完成计算List<String> rpnList = getListString(suffixExpression);System.out.println("rpnList " + rpnList);int res = calculate(rpnList);System.out.println("计算结果为:" + res);*/}//将中缀表达式转换成对应的Listpublic static List<String> toInfixExperssionList(String s){List<String> ls = new ArrayList<String>();int i = 0;//这是一个指针,用于遍历中缀表达式字符串String str;//多位数字的拼接char c;//每遍历到一个字符,就放入cdo{//如果c是一个非数字,就需要加入lsif ((c = s.charAt(i)) <48 || (c = s.charAt(i)) >57){//不是数ls.add(String.valueOf(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;}//将得到的中缀表达式对应的list 转换为后缀表达式对应的listpublic static List<String> parseSuffixExpersion(List<String> ls){//定义两个栈Stack<String> s1 = new Stack<String>();//因为s2在整个转换过程中没有pop操作,而且还要逆序输出,因此直接使用ListList<String> s2 = new ArrayList<String>();//遍历lsfor (String item:ls){if (item.matches("\\d+")){s2.add(item);}else if(item.equals("(")){s1.push(item);}else if(item.equals(")")){while(!s1.peek().equals("(")){s2.add(s1.pop());//s1一次弹出到s2}s1.pop();//将(弹出s1 消除(}else{//当item运算符的优先级小于s1栈顶运算符的优先级时,s1栈顶的运算符弹出栈,压入到s2while (s1.size()!= 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)){s2.add(s1.pop());}//把当前item运算符,压入栈中s1.push(item);}}while(s1.size()!= 0){s2.add(s1.pop());}return s2;//顺序输出即可}//优先级比较高低的方法//将一个逆波兰表达式,依次将数据和运算符放入ArrayList中public static List<String> getListString(String suffixExperssion){//将suffixExperssion分割String[] split = suffixExperssion.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>();//遍历listfor(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("运算符有误");}stack.push(String.valueOf(res));}}return Integer.parseInt(stack.pop());}
}
//编写一个类
class Operation{private  static  int add = 1;private  static  int sub = 1;private  static  int mul = 2;private  static  int div = 2;public static int  getValue(String operation){int result= 0;switch (operation){case "+":result = add;break;case "-":result = sub;break;case "*":result = mul;break;case "/":result = div;break;default://System.out.println("不存在该运算符");break;}return result;}
}

完整的逆序表达式计算器

使用了递归

package com.atguigu.stack;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;/*** @className: ReversePolishMultiCalc* @description: 逆波兰计算器完整版* @date: 2021/3/6* @author: cakin*/
public class CompersionPolandNatation {/*** 匹配 + - * / ( ) 运算符*/static final String SYMBOL = "\\+|-|\\*|/|\\(|\\)";static final String LEFT = "(";static final String RIGHT = ")";static final String ADD = "+";static final String MINUS = "-";static final String TIMES = "*";static final String DIVISION = "/";/*** 加減 + -*/static final int LEVEL_01 = 1;/*** 乘除 * /*/static final int LEVEL_02 = 2;/*** 括号*/static final int LEVEL_HIGH = Integer.MAX_VALUE;static Stack<String> stack = new Stack<>();static List<String> data = Collections.synchronizedList(new ArrayList<String>());/*** 去除所有空白符** @param s* @return*/public static String replaceAllBlank(String s) {// \\s+ 匹配任何空白字符,包括空格、制表符、换页符等等, 等价于[ \f\n\r\t\v]return s.replaceAll("\\s+", "");}/*** 判断是不是数字 int double long float** @param s* @return*/public static boolean isNumber(String s) {Pattern pattern = Pattern.compile("^[-\\+]?[.\\d]*$");return pattern.matcher(s).matches();}/*** 判断是不是运算符** @param s* @return*/public static boolean isSymbol(String s) {return s.matches(SYMBOL);}/*** 匹配运算等级** @param s* @return*/public static int calcLevel(String s) {if ("+".equals(s) || "-".equals(s)) {return LEVEL_01;} else if ("*".equals(s) || "/".equals(s)) {return LEVEL_02;}return LEVEL_HIGH;}/*** 匹配** @param s* @throws Exception*/public static List<String> doMatch(String s) throws Exception {if (s == null || "".equals(s.trim())) throw new RuntimeException("data is empty");if (!isNumber(s.charAt(0) + "")) throw new RuntimeException("data illeagle,start not with a number");s = replaceAllBlank(s);String each;int start = 0;for (int i = 0; i < s.length(); i++) {if (isSymbol(s.charAt(i) + "")) {each = s.charAt(i) + "";// 栈为空,(操作符,或者 操作符优先级大于栈顶优先级 && 操作符优先级不是( )的优先级 及是 ) 不能直接入栈if (stack.isEmpty() || LEFT.equals(each)|| ((calcLevel(each) > calcLevel(stack.peek())) && calcLevel(each) < LEVEL_HIGH)) {stack.push(each);} else if (!stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek())) {// 栈非空,操作符优先级小于等于栈顶优先级时出栈入列,直到栈为空,或者遇到了(,最后操作符入栈while (!stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek())) {if (calcLevel(stack.peek()) == LEVEL_HIGH) {break;}data.add(stack.pop());}stack.push(each);} else if (RIGHT.equals(each)) {// ) 操作符,依次出栈入列直到空栈或者遇到了第一个)操作符,此时)出栈while (!stack.isEmpty() && LEVEL_HIGH >= calcLevel(stack.peek())) {if (LEVEL_HIGH == calcLevel(stack.peek())) {stack.pop();break;}data.add(stack.pop());}}start = i; // 前一个运算符的位置} else if (i == s.length() - 1 || isSymbol(s.charAt(i + 1) + "")) {each = start == 0 ? s.substring(start, i + 1) : s.substring(start + 1, i + 1);if (isNumber(each)) {data.add(each);continue;}throw new RuntimeException("data not match number");}}// 如果栈里还有元素,此时元素需要依次出栈入列,可以想象栈里剩下栈顶为/,栈底为+,应该依次出栈入列,可以直接翻转整个stack 添加到队列Collections.reverse(stack);data.addAll(new ArrayList<>(stack));System.out.println(data);return data;}/*** 算出结果** @param list 逆波兰表达是* @return Double 计算结果*/public static Double doCalc(List<String> list) {Double d = 0d;if (list == null || list.isEmpty()) {return null;}if (list.size() == 1) {System.out.println(list);d = Double.valueOf(list.get(0));return d;}ArrayList<String> list1 = new ArrayList<>();for (int i = 0; i < list.size(); i++) {list1.add(list.get(i));if (isSymbol(list.get(i))) {Double d1 = doTheMath(list.get(i - 2), list.get(i - 1), list.get(i));list1.remove(i);list1.remove(i - 1);list1.set(i - 2, d1 + "");list1.addAll(list.subList(i + 1, list.size()));break;}}doCalc(list1);return d;}/*** 运算** @param s1* @param s2* @param symbol* @return*/public static Double doTheMath(String s1, String s2, String symbol) {Double result;switch (symbol) {case ADD:result = Double.valueOf(s1) + Double.valueOf(s2);break;case MINUS:result = Double.valueOf(s1) - Double.valueOf(s2);break;case TIMES:result = Double.valueOf(s1) * Double.valueOf(s2);break;case DIVISION:result = Double.valueOf(s1) / Double.valueOf(s2);break;default:result = null;}return result;}/*** 功能描述:完整版逆波兰计算器测试** @param args 命令行* @author cakin* @date 2021/3/6*/public static void main(String[] args) {// String math = "9+(3-1)*3+10/2";String math = "12.8 + (2 - 3.55)*4+10/5.0";try {doCalc(doMatch(math));} catch (Exception e) {e.printStackTrace();}}
}

递归

应用场景

概念

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

调用机制

若加入else,则输出一个2

package com.atguigu.recusion;public class RecusionTest01 {public static void main(String[] args) {//通过打印问题,回顾递归的调用机制test(4);}public static void test(int n){if(n > 2){test(n -1);}else {System.out.println("n = " + n);}}
}
package com.atguigu.recusion;public class RecusionTest01 {public static void main(String[] args) {//通过打印问题,回顾递归的调用机制test(4);System.out.println("res + " + factorial(2));}public static void test(int n){if(n > 2){test(n -1);}System.out.println("n = " + n);}//阶乘问题public static int factorial(int n) {if (n == 1) {return 1;} else {return factorial(n - 1) * n; // 1 * 2 * 3}}
}

递归能解决什么样的问题

  1. 各种数学问题如: 8 皇后问题 , 汉诺塔, 阶乘问题, 迷宫问题, 球和篮子的问题(google 编程大赛)

  2. 各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等.

  3. 将用栈解决的问题–>递归代码比较简洁

递归需要遵守的规则

  1. 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)

  2. 方法的局部变量是独立的,不会相互影响, 比如 n 变量

  3. 如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据.

  4. 递归必须向退出递归的条件逼近,否则就是无限递归,出现 StackOverflowError

  5. 当一个方法执行完毕,或者遇到 return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或 者返回时,该方法也就执行完毕

迷宫问题

代码实现

package com.atguigu.recusion;public class MiGongTest01 {public static void main(String[] args) {//先创建一个二维数组//地图int[][] map = new int[8][7];//使用1表示墙//先把上下置为1for(int i = 0;i<7;i++){map[0][i] = 1;map[7][i] = 1;}//左右全部置为1for (int i = 0;i<8;i++){map[i][0] = 1;map[i][6] = 1;}//设置障碍map[3][1] = 1;map[3][2] = 1;//输出地图System.out.println("------MAP-----");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("------NEWMAP-----");for(int i = 0; i <8 ;i++) {for (int j = 0; j < 7; j++) {System.out.print(map[i][j] + " ");}System.out.println();}}/*** 如果小球可以到达map[6][5],则说明通路找到* 约定:当map[i][j]为0表示该点没有走过,1的时候为墙;2的时候表示一个通路;3表示该点探测过了,但是不通* 走迷宫时候的策略:下 右 上 左,如果该点走不通在回溯* @param map 地图* @param i 起始位置* @param j* @return*/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 {return false;}}}
}

最短路径的代码实现

package ch4;import java.util.Scanner;public class MiGong {static int n,m,endx,endy,min = 99999;static int[][] a = new int[51][51];static int[][] book = new int[51][51];public static void main(String[] args) {//读入n和m,n为行,m为列Scanner scanner = new Scanner(System.in);System.out.println("输入行:");n = scanner.nextInt();System.out.println("输入列:");m = scanner.nextInt();System.out.println("输入迷宫:");for (int i =1; i<=n ;i++){for (int j =1;j<=m;j++){a[i][j] = scanner.nextInt();}}scanner.close();int stratx = 1,starty = 1;endx = n;endy = m;//从起点开始搜索book[stratx][starty] = 1;//标记起点已经在路径中dfs(stratx,starty,0);System.out.println("最短的路径 " + min);}public static void dfs(int x, int y, int step){//定义四个方向int[][] next= {{0,1},{1,0},{0,-1},{-1,0}};int tx,ty;//判断是否到达出口if(x ==endx  && y == endy){//更新最小值if(step<=min)min = step;return;}//四种走法for(int k =0; k<4;k++){//计算下一个坐标点tx = x + next[k][0];ty = y + next[k][1];//判断是否越界,是否为障碍物,是否在路径中if(tx < 1|| tx > n || ty <1|| ty > n){continue;}if(a[tx][ty] == 0 && book[tx][ty] == 0){book[tx][ty]  = 1;//标记这个点已经做过了dfs(tx,ty,step+1);//开始尝试下一个点book[tx][ty] = 0;//尝试结束,取消这个点的标记}}}
}

对迷宫问题的讨论

  1. 小球得到的路径,和程序员设置的找路策略有关即:找路的上下左右的顺序相关

  2. 再得到小球路径时,可以先使用(下右上左),再改成(上右下左),看看路径是不是有变化

  3. 测试回溯现象

  4. 思考: 如何求出最短路径? 思路代码实现.

八皇后

八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于 1848 年提出:在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意两个皇后都不能处于同一行、 同一列或同一斜线上,问有多少种摆法**(92)**。

解题思路

  1. 第一个皇后先放第一行第一列

  2. 第二个皇后放在第二行第一列,然后判断是否 OK, 如果不 OK,继续放在第二列、第三列、依次把所有列都 放完,找到一个合适

  3. 继续第三个皇后,还是第一列、第二列……直到第 8 个皇后也能放在一个不冲突的位置,算是找到了一个正确 解

  4. 当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,即将第一个皇后,放到第一列的所有正确解, 全部得到.

  5. 然后回头继续第一个皇后放第二列,后面继续循环执行 1,2,3,4 的步骤

  6. 示意图:

说明:

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

实现代码

package com.atguigu.recusion;public class Queue8 {//定义一个max表示共有多少个皇后int max = 8;//定义一个数组array,保存皇后放置位置的结果,比如arr = {0,4,7,5,2,6,1,3}int[] array = new int[max];static int count = 0;static int judgecount = 0;public static void main(String[] args) {Queue8 queue8 = new Queue8();queue8.check(0);System.out.println(count);System.out.println(judgecount);}//编写一个方法,放置第n个皇后//check 每一次递归时,都有一次for循环,因此会有回溯private void check(int n){if(n == max){//开始放第九个皇后,放置结束print();count++;return;}//没有放置结束,依次放入皇后for (int i =0; i < max ;i++){//先把当前皇后,放到该行的第1列array[n] = i;//判断是否可以放置if(judge(n)){check(n+1);}//如果冲突就继续执行for循环,直到全部列找完}}//查看当我们放置第n个皇后,就去检测该皇后是否和前面已经摆放完的皇后冲突/**** @param n 表示要放置的第n个皇后* @return*/public boolean judge(int n){judgecount ++;for (int i = 0 ;i <n ; i++){//第一个条件:判断是否在同一列//第二个条件:判断是否在同一斜线,如果在同一斜线当前行-行的绝对值 = 当前列- 列的绝对值//因为n是递增的所以一定不同一行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();}}

排序算法

介绍

排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程

排序的分类:

  1. 内部排序:

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

  1. 外部排序法:

数据量过大,无法全部加载到内存中,需要借助**外部存储(文件等)**进行排序。

  1. 排序算法的分类

算法的时间复杂度

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

  1. 事后统计的方法

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

  1. 事前估算的方法

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

时间频度

基本介绍

时间频度:一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为 T(n)。[举例说明]

-举例说明-基本案例

·比如计算 1-100 所有数字之和, 我们设计两种算法:

-举例说明-忽略常数项

结论:

  1. 2n+20 和 2n 随着 n 变大,执行曲线无限接近, 20 可以忽略

  2. 3n+10 和 3n 随着 n 变大,执行曲线无限接近, 10 可以忽略

-举例说明-忽略低次项

结论:

  1. 2n^2+3n+10 和 2n^2 随着 n 变大, 执行曲线无限接近, 可以忽略 3n+10

  2. n^2+5n+20 和 n^2 随着 n 变大,执行曲线无限接近, 可以忽略 5n+20

-举例说明-忽略系数

结论:

  1. 随着 n 值变大,5n^2+7n 和 3n^2 + 2n ,执行曲线重合, 说明 这种情况下, 5 和 3 可以忽略。

  2. 而 n^3+5n 和 6n^3+4n ,执行曲线分离,说明多少次方式关键

时间复杂度

  1. 一般情况下,算法中的基本操作语句的重复执行次数是问题规模 n 的某个函数,用 T(n)表示,若有某个辅助函数 f(n),使得当 n 趋近于无穷大时,T(n) / f(n) 的极限值为不等于零的常数,则称 f(n)是 T(n)的同数量级函数。 记作 T(n)=O( f(n) ),称O( f(n) ) 为算法的渐进时间复杂度,简称时间复杂度。

  2. T(n) 不同,但时间复杂度可能相同。 如:T(n)=n²+7n+6 与 T(n)=3n²+2n+2 它们的 T(n) 不同,但时间复杂 度相同,都为 O(n²)

  3. 计算时间复杂度的方法:

  • 用常数 1 代替运行时间中的所有加法常数 T(n)=n²+7n+6 => T(n)=n²+7n+1
  • 修改后的运行次数函数中,只保留最高阶项 T(n)=n²+7n+1 => T(n) = n²
  • 去除最高阶项的系数 T(n) = n² => T(n) = n² => O(n²)

常见的时间复杂度

  1. 常数阶 O(1)

  2. 对数阶 O(log2n)

  3. 线性阶 O(n)

  4. 线性对数阶 O(nlog2n)

  5. 平方阶 O(n^2)

  6. 立方阶 O(n^3)

  7. k 次方阶 O(n^k)

  8. 指数阶 O(2^n)

说明

  1. 常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n^3)< Ο(n^k) < Ο(2^n) ,随着问题规模 n 的不断增大,上述时间复杂度不断增大,算法的执行效率越低

  2. 从图中可见,我们应该尽可能避免使用指数阶的算法

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

  1. 平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,该算法的运行时间。

  2. 最坏情况下的时间复杂度称最坏时间复杂度。一般讨论的时间复杂度均是最坏情况下的时间复杂度。这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证了算法的运行时间不会 比最坏情况更长。

  3. 平均时间复杂度和最坏时间复杂度是否一致,和算法有关(如图:)

算法的空间复杂度

基本介绍

  1. 类似于时间复杂度的讨论,一个算法的空间复杂度(Space Complexity)定义为该算法所耗费的存储空间,它也是 问题规模 n 的函数。

  2. 空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。有的算法需要占用的 临时工作单元数与解决问题的规模 n 有关,它随着 n 的增大而增大,当 n 较大时,将占用较多的存储单元,例 如快速排序和归并排序算法, 基数排序就属于这种情况

  3. 在做算法分析时,主要讨论的是时间复杂度。从用户使用体验上看,更看重的程序执行的速度。一些缓存产品 (redis, memcache)和算法(基数排序)本质就是用空间换时间.

冒泡排序

基本介绍

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

优化:

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

我们举一个具体的案例来说明冒泡法。我们将五个无序的数:3, 9, -1, 10, -2 使用冒泡排序法将其排成一个从小 到大的有序数列

代码实现

package com.atguigu.sorted;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,-2};/* int[] arr = {3,9,10,20};bubblesort(arr);
*///测试冒泡排序的速度int[] arr = new int[80000];for (int i =0; i< 80000;i++){arr[i] =(int)( Math.random() * 80000);//[0-80000)的随机数}Date date1 = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String dateStr1 = simpleDateFormat.format(date1);System.out.println("排序前的时间是:" + dateStr1);bubblesort(arr);Date date2 = new Date();String dateSte2 = simpleDateFormat.format(date2);System.out.println("排序后的时间:" + dateSte2);//细化算法//int temp= 0;/*System.out.println("------第一趟---------");for(int j =0 ; j < arr.length-1;j++){if(arr[j]>arr[j+1]){temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}System.out.println(Arrays.toString(arr));System.out.println("------第二趟---------");for(int j =0 ; j < arr.length-2;j++){if(arr[j]>arr[j+1]){temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}System.out.println(Arrays.toString(arr));System.out.println("------第三趟---------");for(int j =0 ; j < arr.length-3;j++){if(arr[j]>arr[j+1]){temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}System.out.println(Arrays.toString(arr));System.out.println("------第四趟---------");for(int j =0 ; j < arr.length-4;j++){if(arr[j]>arr[j+1]){temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}System.out.println(Arrays.toString(arr));*///标识变量,表示是否进行过交换/* for (int i = 0; i< arr.length - 1 ;i++){boolean flag = false;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;}}System.out.println("第" + i + "次:" + Arrays.toString(arr));if (!flag) { // 在一趟排序中,一次交换都没有发生过break;}}*/}public static void bubblesort(int[] arr){int temp= 0;for (int i = 0; i< arr.length - 1 ;i++){boolean flag = false;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;}}//System.out.println("第" + (i+1) + "次:" + Arrays.toString(arr));if (!flag) { // 在一趟排序中,一次交换都没有发生过break;}}}
}

选择排序

基本介绍

选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的

基本思想

选择排序(select sorting)也是一种简单的排序方法。它的基本思想是:第一次从 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 次,得到一个按排序码从小到大排列的有序序列。

分析图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3L1AYUbD-1635986667283)(C:/Users/77/AppData/Roaming/Typora/typora-user-images/image-20210918163724818.png)]

实现代码

package com.atguigu.sorted;import java.text.SimpleDateFormat;
import java.util.Date;public class SelectedSort {public static void main(String[] args) {/*int[] arr = {100,34,119,1,22,98};selectsort(arr);*/int[] arr = new int[80000];for (int i = 0; i < 80000;i++){arr[i] = (int)(Math.random() * 80000);}Date date1 = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String s1 = simpleDateFormat.format(date1);System.out.println("排序前:" + s1);selectsort(arr);Date date2 = new Date();String s2 = simpleDateFormat.format(date2);System.out.println("排序后:" + s2);}//选择排序public static void selectsort(int[] arr){//使用逐步推导的方式来,讲解选择排序for (int i =0; i<arr.length - 1; i++){int minIndex= i;//假定最小数的索引int min = arr[minIndex];//假定最小数的值for ( int j = i+1 ; j<arr.length;j++){if(min > arr[j]){//说明这个最小值并不是最小min = arr[j];minIndex = j;}}//交换//多一个判断条件if(minIndex != i){arr[minIndex] = arr[i];arr[i] = min;}//System.out.println(Arrays.toString(arr));}}
}

插入排序

插入排序的介绍

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

插入排序的思想

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

代码实验

package com.atguigu.sorted;import java.util.Arrays;public class insertSort {public static void main(String[] args) {int[] arr = {100,34,119,1,22,98};insertsort(arr);}public static void insertsort(int[] arr){//使用逐步推到的方式//第一轮:int[] arr = {100,34,119,1,22,98};//定义待插入的数/*int insertVal = arr[1];int insertIndex = 1-1;//前一个数的下标//给insertVal插入一个位置//insertIndex < arr[insertIndex] 说明待插入的数还没有找到位置while(insertIndex >= 0 && insertVal < arr[insertIndex]){//为了保证不越界arr[insertIndex +1] = arr[insertIndex];insertIndex --;}//当退出while循环时,说明插入的位置找到,insertIndex +1;arr[insertIndex + 1] = insertVal;System.out.println(Arrays.toString(arr));//第二轮insertVal = arr[2];insertIndex = 2-1;//前一个数的下标while(insertIndex >= 0 && insertVal < arr[insertIndex]){//为了保证不越界arr[insertIndex +1] = arr[insertIndex];insertIndex --;}arr[insertIndex + 1] = insertVal;System.out.println(Arrays.toString(arr));*/for (int i = 1 ; i< arr.length ;i++){int insertVal = arr[i];int insertIndex = i-1;while(insertIndex >= 0 && insertVal < arr[insertIndex]){arr[insertIndex + 1] = arr[insertIndex];insertIndex --;}arr[insertIndex +1 ] = insertVal;System.out.println("第" + i + "次交换:" + Arrays.toString(arr));}}
}
package com.atguigu.sorted;import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;public class insertSort {public static void main(String[] args) {/*int[] arr = {100,34,119,1,22,98};insertsort(arr);*/int[] arr = new int[80000];for (int i = 0; i < 80000;i++){arr[i] = (int)(Math.random() * 80000);}Date date1 = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String s1 = simpleDateFormat.format(date1);System.out.println("排序前:" + s1);insertsort(arr);Date date2 = new Date();String s2 = simpleDateFormat.format(date2);System.out.println("排序后:" + s2);}public static void insertsort(int[] arr){//使用逐步推到的方式//第一轮:int[] arr = {100,34,119,1,22,98};//定义待插入的数/*int insertVal = arr[1];int insertIndex = 1-1;//前一个数的下标//给insertVal插入一个位置//insertIndex < arr[insertIndex] 说明待插入的数还没有找到位置while(insertIndex >= 0 && insertVal < arr[insertIndex]){//为了保证不越界arr[insertIndex +1] = arr[insertIndex];insertIndex --;}//当退出while循环时,说明插入的位置找到,insertIndex +1;arr[insertIndex + 1] = insertVal;System.out.println(Arrays.toString(arr));//第二轮insertVal = arr[2];insertIndex = 2-1;//前一个数的下标while(insertIndex >= 0 && insertVal < arr[insertIndex]){//为了保证不越界arr[insertIndex +1] = arr[insertIndex];insertIndex --;}arr[insertIndex + 1] = insertVal;System.out.println(Arrays.toString(arr));*/for (int i = 1 ; i< arr.length ;i++){int insertVal = arr[i];int insertIndex = i-1;while(insertIndex >= 0 && insertVal < arr[insertIndex]){arr[insertIndex + 1] = arr[insertIndex];insertIndex --;}//判断是否需要赋值//if(insertIndex + 1 != i)arr[insertIndex +1 ] = insertVal;//System.out.println("第" + i + "次交换:" + Arrays.toString(arr));}}
}

结论: 当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响.

希尔排序

介绍

希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。

基本思想

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

示意图

代码实现

交换法
package com.atguigu.sorted;import java.util.Arrays;public class ShellSort {public static void main(String[] args) {int[] arr = {8,9,1,7,2,3,5,4,6,0};shellsort(arr);}public static void shellsort(int [] arr){//使用 逐步推导的方式编写希尔排序//第一轮:希尔排序的第一轮排序/*for (int i = 5; i < arr.length;i++){//遍历各组中所有的元素(共5组,每组一共2个元素)for(int j = i-5; j >=0; j-=5){//如果当前元素大于加上步长后的的元素,说明交换if(arr[j] > arr[j+5]){int temp = arr[j];arr[j] = arr[j+5];arr[j+5] = temp;}}}System.out.println(Arrays.toString(arr));for (int i = 2; i < arr.length;i++){//遍历各组中所有的元素(共2组,每组一共5个元素)for(int j = i-2; j >=0; j-=2){//如果当前元素大于加上步长后的的元素,说明交换if(arr[j] > arr[j+2]){int temp = arr[j];arr[j] = arr[j+2];arr[j+2] = temp;}}}System.out.println(Arrays.toString(arr));for (int i = 1; i < arr.length;i++){//遍历各组中所有的元素(共1组,每组一共10个元素)for(int j = i-1; j >=0; j-=1){//如果当前元素大于加上步长后的的元素,说明交换if(arr[j] > arr[j+1]){int temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}}System.out.println(Arrays.toString(arr));*/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]){int temp = arr[j];arr[j] = arr[j+gap];arr[j+gap] = temp;}}}System.out.println(Arrays.toString(arr));}}
}
移动法
package com.atguigu.sorted;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};shellsort(arr);*/int[] arr = new int[80000];for (int i = 0; i < 80000;i++){arr[i] = (int)(Math.random() * 80000);}Date date1 = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");String s1 = simpleDateFormat.format(date1);System.out.println("排序前:" + s1);shellsort(arr);Date date2 = new Date();String s2 = simpleDateFormat.format(date2);System.out.println("排序后:" + s2);}/*public static void shellsort(int [] arr){//使用 逐步推导的方式编写希尔排序//第一轮:希尔排序的第一轮排序*//*for (int i = 5; i < arr.length;i++){//遍历各组中所有的元素(共5组,每组一共2个元素)for(int j = i-5; j >=0; j-=5){//如果当前元素大于加上步长后的的元素,说明交换if(arr[j] > arr[j+5]){int temp = arr[j];arr[j] = arr[j+5];arr[j+5] = temp;}}}System.out.println(Arrays.toString(arr));for (int i = 2; i < arr.length;i++){//遍历各组中所有的元素(共2组,每组一共5个元素)for(int j = i-2; j >=0; j-=2){//如果当前元素大于加上步长后的的元素,说明交换if(arr[j] > arr[j+2]){int temp = arr[j];arr[j] = arr[j+2];arr[j+2] = temp;}}}System.out.println(Arrays.toString(arr));for (int i = 1; i < arr.length;i++){//遍历各组中所有的元素(共1组,每组一共10个元素)for(int j = i-1; j >=0; j-=1){//如果当前元素大于加上步长后的的元素,说明交换if(arr[j] > arr[j+1]){int temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}}System.out.println(Arrays.toString(arr));*//*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]){int temp = arr[j];arr[j] = arr[j+gap];arr[j+gap] = temp;}}}// System.out.println(Arrays.toString(arr));}*/public static void shellsort(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;}//退出循环,找到位置arr[j] = temp;}}// System.out.println(Arrays.toString(arr));}}}

快速排序

介绍

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

快速排序示意图

快速排序法应用实例:

要求: 对 [-9,78,0,23,-567,70] 进行从小到大的排序,要求使用快速排序法。【测试8w和800w】
说明[验证分析]:
如果取消左右递归,结果是 -9 -567 0 23 78 70
如果取消右递归,结果是 -567 -9 0 23 78 70
如果取消左递归,结果是 -9 -567 0 23 70 78

归并排序

介绍

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

说明:
可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程。

归并排序思想示意图2-合并相邻有序子序列

再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤

代码实现

package com.atguigu.sorted;import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;public class MergeSort {static int count = 0;public static void main(String[] args) {/*  int[] arr= {8,4,5,7,1,3,6,2};int[] temp = new int[arr.length];mergesort(arr,0, arr.length-1,temp);System.out.println(Arrays.toString(arr));System.out.println(count);*/int[] arr = new int[800000];int[] temp = new int[arr.length];for (int i = 0; i < 800000;i++){arr[i] = (int)(Math.random() * 800000);}Date date1 = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");String s1 = simpleDateFormat.format(date1);System.out.println("排序前:" + s1);mergesort(arr,0, arr.length-1,temp);Date date2 = new Date();String s2 = simpleDateFormat.format(date2);System.out.println("排序后:" + s2);}public static void mergesort(int[] arr,int left,int right,int[] temp) {if(left<right){int mid = (left + right) /2;//向左递归进行分解mergesort(arr,left,mid,temp);//向右递归分解mergesort(arr,mid+1,right,temp);//每分解一次就合并一次merge(arr,left,mid,right,temp);}}//合并的方法/**** @param arr 原始数组* @param left 左边序列的初始索引* @param mid 中间索引* @param right 右边索引* @param temp 做中转的数组*/public static void merge(int[] arr ,int left , int mid,int right,int[] temp){int i = left;//初始化i,左边有序序列的初始索引int j = mid +1;//初始化j,右边有序序列的初始索引int index  = 0; //中间数组的索引count ++;//先把左右两边的数据,按规则拷贝到temp中//直到左右两边有一侧处理完毕为止while( i <= mid && j <= right){if(arr[i] <=  arr[j]){temp[index] = arr[i];index ++;i ++;} else {temp[index] = arr[j];index ++;j ++;}}//把有剩余数据的一边,剩余的数据一次放入temp中while(i <= mid){temp[index] = arr[i];index ++;i ++;}while( j <= right){temp[index] = arr[j];index ++;j++;}//将temp数组中的原数放入temp中index = 0; //将索引置0int tempLeft = left;while(tempLeft <= right){arr[tempLeft] = temp[index];index ++;tempLeft ++;}}
}

基数排序

基数排序(桶排序)介绍:

  1. 基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或 bin sort,顾 名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用

  2. 基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法

  3. 基数排序(Radix Sort)是桶排序的扩展

  4. 基数排序是 1887 年赫尔曼·何乐礼发明的。它是这样实现的**:将整数按位数切割成不同的数字,然后按每个位数分别比较**

基数排序的思想

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

  2. 这样说明,比较难理解,下面我们看一个图文解释,理解基数排序的步骤

代码实现:

package com.atguigu.sorted;import java.text.SimpleDateFormat;
import java.util.Date;public class RadixSrort {public static void main(String[] args) {/*int[] arr = {53,3,542,748,14,214};radixsort(arr);*/int[] arr = new int[800000];int[] temp = new int[arr.length];for (int i = 0; i < 800000;i++){arr[i] = (int)(Math.random() * 800000);}Date date1 = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");String s1 = simpleDateFormat.format(date1);System.out.println("排序前:" + s1);radixsort(arr);Date date2 = new Date();String s2 = simpleDateFormat.format(date2);System.out.println("排序后:" + s2);}public static void radixsort(int[] arr){//第一轮排序(针对个位进行处理)//定义一个二维数组代表10个桶,每一个桶就是一个一维数组int[][] bucket = new int[10][arr.length];//为了记录每一个桶中实际存放了多少个数据,我们定义一个一维数组来记录各个桶每次放入的数据个数int[] bucketElementCounts = new int[10];//记录每个桶中数据的数量int max = arr[0];for(int i = 0; i<arr.length;i++){if (arr[i]>max){max = arr[i];}}//System.out.println("Max为" + max);int maxLength = (max + "").length();//转换为字符串int n =1;for (int i = 0;i<maxLength;i++) {for (int j = 0; j < arr.length; j++) {//取出每个元素的个/十/百位int digitOfElement = arr[j] / n % 10;//放入到对应的桶中//数组中的第一个数代表这0.1.2...//数组中第二个数代表每一个桶中有多少个数//bucketElementCounts[digitOfElement] 代表着每一个桶中,元素的索引bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];bucketElementCounts[digitOfElement]++;}//放入原数组int index = 0;//遍历每一个桶,并将桶中的数据放入到原数组for (int k = 0; k < bucket.length; k++) {//如果桶中有数据,我们才放入到原数组if (bucketElementCounts[k] != 0) {//说明桶中有数据for (int l = 0; l < bucketElementCounts[k]; l++) {//取出元素放入到arrarr[index] = bucket[k][l];index++;}}bucketElementCounts[k] =0;//处理完一个桶之后需要将桶置为0}n *= 10;//System.out.println(Arrays.toString(arr));}}
}

排序算法对比

相关术语解释:

  1. 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
  2. 不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
  3. 内排序:所有排序操作都在内存中完成;
  4. 外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
  5. 时间复杂度: 一个算法执行所耗费的时间。
  6. 空间复杂度:运行完一个程序所需内存的大小
  7. n: 数据规模
  8. k: “桶”的个数
  9. In-place: 不占用额外内存
  10. Out-place: 占用额外内存

查找算法

  • 顺序(线性)
  • 查找二分查找/折半查找
  • 插值查找
  • 斐波那契查找

线性查找算法

有一个数列: {1,8, 10, 89, 1000, 1234} ,判断数列中是否包含此名称【顺序查找】

要求: 如果找到了,就提示找到,并给出下标值。

package com.atguigu.search;public class SeqSearch {public static void main(String[] args) {int arr[] = {1,9,11,-1,34,89};System.out.println(seqsearch(arr, 9) == -1? "没有查找到":"找到了,为" + seqsearch(arr,9));}public static int seqsearch(int[] arr,int values){//线性查找是逐一比对,发现有相同值,就返回下标//这里我们查找到一个就返回for (int i =0; i<arr.length;i++){if (arr[i] == values){return i;}}return -1;}
}

二分查找

请对一个有序数组进行二分查找 {1,8, 10, 89, 1000, 1234} ,输入一个数看看该数组是否存在此数,并且求出下标,如果没有就提示"没有这个数"。

二分查找的思路分析

1.首先确定该数组的中间的下标:mid = (left + right) / 2

2.然后让需要查找的数 findVal 和 arr[mid] 比较

  • findVal > arr[mid] , 说明你要查找的数在mid 的右边, 因此需要递归的向右查找
  • findVal < arr[mid], 说明你要查找的数在mid 的左边, 因此需要递归的向左查找
  • findVal == arr[mid] 说明找到,就返回

3.什么时候结束递归

  • 找到就结束递归
  • 递归完整个数组,仍然没有找到findVal ,也需要结束递归 当 left > right 就需要退出

代码实现

package com.atguigu.search;public class BinarySearch {public static void main(String[] args) {//使用二分查找的前提是,该数组是有序的int[] arr = {1,8,10,89,1000,1234};int resIndex = binarysearch(arr,arr.length-1,0,10);System.out.println(resIndex);}/**** @param arr 数组* @param right 右边的索引* @param left 左边的索引* @param findVal 要查找的值* @return*/public static int binarysearch(int[] arr,int right,int left,int findVal){//if (left > right) return -1;int mid = (left+right)/2;int midVal = arr[mid];//结束递归的条件while(left <= right) {if (findVal > midVal) {//向右边递归return binarysearch(arr, right, mid + 1, findVal);} else if (findVal < midVal) {return binarysearch(arr, mid - 1, left, findVal);} else {return mid;}}return -1;}
}

{1,8, 10, 89, 1000, 1000,1234} 当一个有序数组中,有多个相同的数值时,如何将所有的数值 都查找到,比如这里的 1000.

package com.atguigu.search;import java.util.List;public class BinarySearch2 {public static void main(String[] args) {int[] arr = {1,8,10,98,1000,1234};int resIndex = binarySearch(arr,0,arr.length-1,1000);//找1000这个数System.out.println(resIndex);}private static int binarySearch(int[] arr, int left, int right, int findVal) {//当left > right时,说明递归整个数组,但是没有找到if (left > right){return -1;}int mid = (left + right)/2;int midVal = arr[mid];if (findVal > midVal){//向右递归return  binarySearch(arr,mid+1,right,findVal);}else if(findVal < midVal){//向左递归return binarySearch(arr,left,mid-1,findVal);}else {return mid;}}
}
package com.atguigu.search;import java.util.ArrayList;
import java.util.List;public class BinarySearch3 {public static void main(String[] args) {int[] arr = {1,8,10,98,1000,1000,1234};List<Integer> integers = BinarySearch(arr, 0, arr.length - 1, 1000);for (Integer integer : integers) {System.out.print(integer + " ");}}public static List<Integer> BinarySearch(int[] arr,int left,int right,int findVal){if (left > right) return new ArrayList<Integer>();int mid = (left + right)/2;int midVal = arr[mid];if (findVal > midVal){return BinarySearch(arr,mid+1,right,findVal);}else if(findVal < midVal){return BinarySearch(arr,left,mid-1,findVal);}else {List<Integer> resIndexlist = new ArrayList<>();int temp = mid -1;//向mid左边开始继续搜索while(true){if (temp < 0|| arr[temp] != findVal){//退出break;}resIndexlist.add(temp);temp -= 1;}resIndexlist.add(mid);//向mid右边继续搜索temp = mid +1;while (true){if(temp > (arr.length-1) || arr[temp]!= findVal){break;}resIndexlist.add(temp);temp+=1;}return resIndexlist;}}
}

插值查找

  1. 插值查找算法类似于二分查找,不同的是插值查找每次从自适应 mid 处开始查找。
  1. 将折半查找中的求 mid 索引的公式 , low 表示左边索引 left, high 表示右边索引 right. key 就是前面我们讲的 findVal

t values){
//线性查找是逐一比对,发现有相同值,就返回下标
//这里我们查找到一个就返回
for (int i =0; i<arr.length;i++){
if (arr[i] == values){
return i;
}
}
return -1;
}
}


### 二分查找请对一个**有序数组**进行二分查找 {1,8, 10, 89, 1000, 1234} ,输入一个数看看该数组是否存在此数,并且求出下标,如果没有就提示"没有这个数"。#### 二分查找的思路分析1.首先确定该数组的中间的下标:mid = (left + right) / 22.然后让需要查找的数 findVal 和 arr[mid] 比较- findVal > arr[mid] ,  说明你要查找的数在mid 的右边, 因此需要递归的向右查找
- findVal < arr[mid], 说明你要查找的数在mid 的左边, 因此需要递归的向左查找
- findVal == arr[mid] 说明找到,就返回3.什么时候结束递归- 找到就结束递归
- 递归完整个数组,仍然没有找到findVal ,也需要结束递归  当 left > right 就需要退出#### 代码实现```java
package com.atguigu.search;public class BinarySearch {public static void main(String[] args) {//使用二分查找的前提是,该数组是有序的int[] arr = {1,8,10,89,1000,1234};int resIndex = binarysearch(arr,arr.length-1,0,10);System.out.println(resIndex);}/**** @param arr 数组* @param right 右边的索引* @param left 左边的索引* @param findVal 要查找的值* @return*/public static int binarysearch(int[] arr,int right,int left,int findVal){//if (left > right) return -1;int mid = (left+right)/2;int midVal = arr[mid];//结束递归的条件while(left <= right) {if (findVal > midVal) {//向右边递归return binarysearch(arr, right, mid + 1, findVal);} else if (findVal < midVal) {return binarysearch(arr, mid - 1, left, findVal);} else {return mid;}}return -1;}
}

{1,8, 10, 89, 1000, 1000,1234} 当一个有序数组中,有多个相同的数值时,如何将所有的数值 都查找到,比如这里的 1000.

package com.atguigu.search;import java.util.List;public class BinarySearch2 {public static void main(String[] args) {int[] arr = {1,8,10,98,1000,1234};int resIndex = binarySearch(arr,0,arr.length-1,1000);//找1000这个数System.out.println(resIndex);}private static int binarySearch(int[] arr, int left, int right, int findVal) {//当left > right时,说明递归整个数组,但是没有找到if (left > right){return -1;}int mid = (left + right)/2;int midVal = arr[mid];if (findVal > midVal){//向右递归return  binarySearch(arr,mid+1,right,findVal);}else if(findVal < midVal){//向左递归return binarySearch(arr,left,mid-1,findVal);}else {return mid;}}
}
package com.atguigu.search;import java.util.ArrayList;
import java.util.List;public class BinarySearch3 {public static void main(String[] args) {int[] arr = {1,8,10,98,1000,1000,1234};List<Integer> integers = BinarySearch(arr, 0, arr.length - 1, 1000);for (Integer integer : integers) {System.out.print(integer + " ");}}public static List<Integer> BinarySearch(int[] arr,int left,int right,int findVal){if (left > right) return new ArrayList<Integer>();int mid = (left + right)/2;int midVal = arr[mid];if (findVal > midVal){return BinarySearch(arr,mid+1,right,findVal);}else if(findVal < midVal){return BinarySearch(arr,left,mid-1,findVal);}else {List<Integer> resIndexlist = new ArrayList<>();int temp = mid -1;//向mid左边开始继续搜索while(true){if (temp < 0|| arr[temp] != findVal){//退出break;}resIndexlist.add(temp);temp -= 1;}resIndexlist.add(mid);//向mid右边继续搜索temp = mid +1;while (true){if(temp > (arr.length-1) || arr[temp]!= findVal){break;}resIndexlist.add(temp);temp+=1;}return resIndexlist;}}
}

尚硅谷【韩顺平】 | Java数据结构和算法【详细笔记】(持续更新)相关推荐

  1. 【尚硅谷|韩顺平】数据结构和算法

    文章目录 前言: 数据结构和算法 数据结构和算法的概述 数据结构和和算法的关系 数据结构 线性结构和非线性结构 非线性结构 稀疏 sparsearray 数组 基本介绍: 稀疏数组的处理方法是: 应用 ...

  2. 【数据结构与算法】尚硅谷韩顺平老师+含java代码(更新中)

    数据结构与算法 程序 = 数据结构 + 算法 数据结构:树.链表.图等 线性结构 数组.队列.链表和栈 非线性结构 二维数组,多维数组,广义表,树结构,图结构 稀疏数组 稀疏数组的好处时压缩数组 在这 ...

  3. Java 数据结构与算法 (尚硅谷Java数据结构与算法)笔记目录

    红色的表示重要,绿色的表示暂时还不懂而且很重要 线性结构和非线性结构 队列 顺序队列 循环队列 链表 链表(Linked List)介绍 链表是有序的列表,但是它在内存中是存储如下 小结: 1) 链表 ...

  4. 数据结构与算法复习(持续更新中)

    目录 数组 为什么很多编程语言中数组都从0开始编号? 如何实现随机访问 数组和链表的区别 链表 栈 函数调用栈来保存临时变量,为什么函数调用要用"栈"来保存临时变量呢?用其他数据结 ...

  5. 数据结构与算法-克鲁斯卡尔算法(Kruskal) | 尚硅谷韩顺平

    提出问题 基本介绍 克鲁斯卡尔(Kruskal)算法,求加权连通图最小生成树的算法 基本思想:按权值从小到大顺序选择n-1条边,保证n-1条边不够成回路 具体做法:先构造一个只有n顶点的森林,然后按权 ...

  6. 数据结构与算法-普利姆算法(Prim) | 尚硅谷韩顺平

    最小生成树 给定一个带权无向连通图,选取一棵树,让树所有边上权的总和最小,叫最小生成树 N个顶点,一定有N-1条边 包含全部顶点 N-1条边都要在图中 算法介绍 普里姆算法求最小生成树,也就是在包含n ...

  7. 【尚硅谷】Java数据结构与算法详细整理笔记(附代码)更新中…………

    目录 一.线性结构和非线性结构 线性结构 非线性结构 二.稀疏 sparsearray数组 1. 基本介绍 2. 稀疏数组的处理方法 3. 二维数组转稀疏数组的思路 4. 稀硫数组转原始的二维数组的思 ...

  8. Java开发入门教程!韩顺平java数据结构课堂笔记

    摘要 Apache Kafka是一个分布式消息发布订阅系统.它最初由LinkedIn公司基于独特的设计实现为一个分布式的提交日志系统( a distributed commit log),之后成为Ap ...

  9. linux一些常用指令(根据尚硅谷韩顺平老师视频所写,都是自己手打的)

    ` vim和vi的基本介绍 所有的 Linux 系统都会内建 vi 文本编辑器. Vim 具有程序编辑的能力,可以看做是Vi的增强版本,可以主动的以字体颜色辨别 语法的正确性,方便程序设计.代码补完. ...

  10. Linux入门笔记-尚硅谷韩顺平-基础篇实操篇

    文章目录 课程导论 基础篇 Linux入门 Linux介绍 Linux和Unix的关系 Linux和Windows比较 基础篇 Linux的目录结构 基本介绍 具体的目录结构 实操篇 vi和vim的使 ...

最新文章

  1. 13个JavaScript单行式代码
  2. IP协议详解之子网寻址、子网掩码、构造超网
  3. 到天宫做客(2017寒假培训测试压轴题)
  4. java8 入门脚本之家_Java 8中的Lambda表达式
  5. webpack配置路径问题
  6. 体重 年龄 性别 身高 预测鞋码_孩子身高低于同龄人就说明发育迟缓?这个简单公式可以算出来...
  7. [Java]toString的用法
  8. NSDictionary和NSMutableDictionary
  9. 山上有一口缸可以装50升水,现在有15升水。老和尚叫小和尚下山挑水,每次可以挑5升。问:小和尚要挑几次水才可以把水缸挑满?通过编程解决这个问题。
  10. 经典的面板数据集(R语言包plm)
  11. Dev-C++5.11游戏创作之简易游戏(之前的登录软件与跑酷程序的结合)
  12. 反冲物料_父母有更多的时间休息,然后反冲开始了
  13. NUC970 SD卡驱动(SDIO)
  14. ROS1云课→19仿真turtlebot(stage)
  15. 崩坏2服务器维护,崩坏学园2(日服)无法连接服务器是什么原因
  16. 华为鸿蒙福利群抢红包,成就红包第一抢!华为Mate S拒绝手慢无
  17. android Camera 设置焦距
  18. 吉林警察学院计算机录取分,2017年吉林警察学院录取分数线
  19. 使用MATLAB实现基于BP神经网络训练的手写字母识别程序
  20. Java中可变类型和不可变类型

热门文章

  1. 刘新华老师-沪师经纪
  2. android 禁止获得焦点,防止EditText自动获取焦点
  3. DDD如何区分实体和值对象
  4. 2020年度整理国内一线互联网公司内部Android面试题库,android网络文件下载
  5. 2020年度整理国内一线互联网公司内部Android面试题库
  6. 10月22日科技联播:饿了么与屈臣氏达成合作;马蜂窝回应数据造假
  7. ICASSP 2022 语音合成和语音识别简报
  8. CSS基础学习六:id选择器
  9. SQL 查询的分布式执行与调度
  10. 安捷伦万用表--Agilent34401A数字万用表串口发送数据只上位机使用说明