尚硅谷 java数据结构与算法 学习笔记(一)

这里写目录标题

  • 线性结构和 非线性结构
  • 稀疏数组SparseArray
    • 需求
    • 介绍
    • 实例
    • 代码实现
    • 代码执行结果
  • 队列
    • 介绍
    • 数组模拟队列思路
    • 代码实现
    • 问题
    • 数组模拟环形队列
    • 环形队列代码实现
  • 链表
    • 单链表
      • 不考虑排名
      • 考虑排名
      • 修改
      • 删除
      • 代码实现
    • 单链表面试题
      • 求单链表的有效节点的个数
        • 求单链表的倒数第K个节点
      • 单链表的反转 (有点难度)
      • 从未到头打印单列表
    • 双向链表
    • 单链表的问题
    • 代码实现
    • 单项环形链表
      • 约瑟夫问题
      • 单向环形链表介绍
      • 思路分析
    • 介绍
    • 数组模拟栈
      • 代码实现
    • 栈实现计算器
      • 思路分析(中缀表达式)
      • 代码实现(不考虑小括号)
    • 前缀 中缀 后缀(逆波兰)表达式
      • 前缀表达式(波兰表达式)
      • 中缀
      • 后缀(逆波兰表达式)
    • 逆波兰计算器
      • 代码实现
    • 中缀表达式转换为后缀表达式
      • 代码实现
  • 递归
    • 递归可以解决的问题
    • 递归的重要规则
    • 迷宫问题
      • 代码实现
      • 最短路径分析
    • 八皇后问题
      • 代码实现
  • 排序算法
    • 时间复杂度
      • 时间频度
      • 时间复杂度
      • 空间复杂度
    • 冒泡排序
      • 代码实现(步骤)
      • 整合后的代码
      • 时间复杂度 O(n^2)
      • 冒泡排序的优化
      • 韩老师的电脑大概20秒处理8万个随机数
    • 选择排序算法
      • 代码实现
      • 时间复杂度O(n^2)
      • 韩老师的电脑处理8万个随机数大概3秒
    • 插入排序法
      • 代码实现
      • 时间复杂度O(n^2)
      • 韩老师的电脑 8万个随机数排序 大致5秒
    • 哈希排序算法图解
      • 简单插入排序的问题
      • 希尔排序是插入排序的一种高效版本
      • 代码实现(交换法)效率并不是很高 韩老师的电脑对8万个随机数进行排序 花了17秒
      • 代码实现(移位法) 效率较高 韩老师的电脑对8万个随机数进行排序 花了1秒
      • 时间复杂度O(n^(1.3—2))
    • 快速排序算法
      • 代码实现
      • 韩老师的电脑对8万个随机数进行排序 花了不到一秒 80w数据也不到1秒 800w 2-3秒
      • 快排 时间复杂度O (nlogn) 但是空间复杂度较高
    • 归并排序
      • 代码实现
      • 时间复杂度O(nlogn) 韩老师的电脑对8万个随机数进行排序 花了不到0-1秒 80w 一秒 800w3-4秒
    • 基数排序 (桶排序)
      • 代码实现
      • 时间复杂度O(nlogn) 韩老师的电脑对8万个随机数进行排序 花了不到0-1秒 80w 1秒不到 800w1秒 8000w 80000000 *11 *4 /1024/1024/1024 = 3.3G的内存
      • 如果有负数就不使用基数排序
    • 八大排序算法的对比
  • 查找算法
    • 二分查找算法
      • d

线性结构和 非线性结构

尚硅谷 java数据结构与算法 学习笔记(一)_第1张图片

稀疏数组SparseArray

需求

记录棋盘的位置 可用用二位数组将它保存 ,但是会发现记录很多没有意义的数据很多空间浪费 可以用稀疏数组进行优化
尚硅谷 java数据结构与算法 学习笔记(一)_第2张图片

介绍

稀疏数组就是压缩缩多余的冗余数据
尚硅谷 java数据结构与算法 学习笔记(一)_第3张图片
第一行的记录原表的行列数和 非0值的个数
尚硅谷 java数据结构与算法 学习笔记(一)_第4张图片

实例

尚硅谷 java数据结构与算法 学习笔记(一)_第5张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第6张图片

代码实现

package com.luyi.DataStructures.sparse;

import java.util.Date;

/**
 * @author 卢意
 * @create 2020-12-01 9:26
 */
public class SparseArray {
	public static void main(String[] args) {
		// 创建一个原始的二维数组 11 * 11
		// 0 表示 没有棋子 1表示黑子 2表示白子
		int chessArray[][] = new int[11][11];
		chessArray[1][2] = 1;
		chessArray[2][3] = 2;
		// 输出原始的二维数组
		System.out.println("输出原始的二维数组");
		for(int row[] : chessArray){
			for (int data : row){
				System.out.print(data + "\t");
			}
			System.out.println();
		}

		// 将二维数组转为稀疏数组的思路
		// 1 先遍历 二维数组 得到 非0 数据的个数
		int sum = 0;
		for(int i = 0; i < chessArray.length; i++){
			for(int j = 0; j<chessArray.length; j++ ){
				if(chessArray[i][j] != 0){
					sum++;
				}
			}
		}
		System.out.println("非0数据的个数 sum="+ sum);
		// 2 创建对应的稀疏数组
		int sparseArray[][] = new int[sum+1][3];
		// 给稀疏数组赋值
		sparseArray[0][0] = 11;
		sparseArray[0][1] = 11;
		sparseArray[0][2] = sum;

		// 遍历二维数组 将非0的值存放到稀疏数组中
		int index = 1;
		for(int i = 0; i < chessArray.length; i++){
			for(int j = 0; j<chessArray.length; j++ ){
				if(chessArray[i][j] != 0){
					sparseArray[index][0] = i;
					sparseArray[index][1] = j;
					sparseArray[index][2] = chessArray[i][j];
					index++;
				}
			}
		}
		// 输出稀疏数组
		System.out.println("得到的稀疏数组为:");
		for (int i = 0;i<sum+1;i++){
			System.out.print(sparseArray[i][0] + "\t");
			System.out.print(sparseArray[i][1] + "\t");
			System.out.print(sparseArray[i][2] + "\t");
			System.out.println();
		}

		// 稀疏数组恢复成二维数组
		// 1 读取稀疏数组的第一行 根据第一行的数据,  创建原始的二维数组
		// 2 在读取稀疏数组的后几行 并赋值
		int row = sparseArray[0][0];
		int cos = sparseArray[0][1];
		int newChessArray[][] = new int[row][cos];
		for(int i = 0; i < row; i++){
			for(int j = 0; j < cos; j++ ){
				newChessArray[i][j] = 0;
			}
		}
		for (int i = 1; i < sparseArray.length;i++){
			newChessArray[sparseArray[i][0]][sparseArray[i][1]] = sparseArray[i][2];
		}

		System.out.println("输出恢复的二维数组");
		for(int rows[] : chessArray){
			for (int data : rows){
				System.out.print(data + "\t");
			}
			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 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 0
非0数据的个数 sum=2
得到的稀疏数组为:
11 11 2
1 2 1
2 3 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 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 0

队列

介绍

尚硅谷 java数据结构与算法 学习笔记(一)_第7张图片
上图的清晰图 rear待表队列的尾部 front代表数据的头部 存入数据时 front不变 rear变化 存数据时 rear位置不变 front 的位置在变化
尚硅谷 java数据结构与算法 学习笔记(一)_第8张图片

数组模拟队列思路

rear == maxSize-1 为满 front指向队列头部的头一个位置 rear指向队列尾部的具体位置
尚硅谷 java数据结构与算法 学习笔记(一)_第9张图片

代码实现

package com.luyi.DataStructures.queue;


import java.util.Scanner;

/**
 * @author 卢意
 * @create 2020-12-01 10:08
 * 数组模拟队列
 */
public class ArrayQueueDemo {
	public static void main(String[] args) {
		// 测试
		// 创建一个队列
		ArrayQueue arrayQueue = 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':
					arrayQueue.showQueue();
					break;
				case 'a':
					System.out.println("输入一个数");
					int value = scanner.nextInt();
					arrayQueue.addQueue(value);
					break;
				case 'g':
					try {
						System.out.println("取出的数据:"+arrayQueue.getQueue());
					} catch (Exception e){
						System.out.println(e.getMessage());
					}
					break;
				case 'h':
					try {
						System.out.println("队列头部的数据:"+arrayQueue.headQueue());
					} catch (Exception e){
						System.out.println(e.getMessage());
					}
					break;
				case 'e':
					scanner.close();
					loop = false;
					break;
				default :
					break;
			}
		}
		System.out.println("程序退出");

	}
}

// 使用数组模拟队列  编写一个ArrayQueue类
class ArrayQueue {
	private int maxSize; // 表示数组的最大容量
	private int front; // 队列头部
	private int rear; // 队列尾部
	private int[] arr; // 存放数据  模拟队列

	// 创建队列的构造器
	public ArrayQueue(int maxSize){
		this.maxSize = maxSize;
		arr= new int[maxSize];
		front = rear = -1; // 队列头部和尾部 front指向队列头部的头一个位置  rear指向队列尾部的具体位置
	}

	// 判断队列是否满
	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 getQueue(){
		// 判断对垒是否空
		if(isEmpty()){
			throw new RuntimeException("队列为空不能取数据");
		}
		return arr[++front];
	}

	// 显示对列的所有数据
	public void showQueue(){
		// 判断队列是否为空
		if(isEmpty()){
			System.out.println("队列为空");
			return;
		}
		for (int i = 0; i < arr.length; i++){
			System.out.println("arr["+i+"]="+arr[i]);
		}
	}

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

控制台结果

s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
s
队列为空
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a
输入一个数
10
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
s
arr[0]=10
arr[1]=0
arr[2]=0
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a
输入一个数
20
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a
输入一个数
30
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
s
arr[0]=10
arr[1]=20
arr[2]=30
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a
输入一个数
40
队列满,不能加入数据
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
h
队列头部的数据:10
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
g
取出的数据:10
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
h
队列头部的数据:20
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
g
取出的数据:20
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
g
取出的数据:30
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
g
队列为空不能取数据
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
h
队列为空,没有头数据
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据

问题

现在队列经历了加满 也全部取出 当前列表理论上为空
但是任然无法加入数据 这个数组无法复用 只能使用一次 没有到达复用的效果
需要将这个数组由算法改造成一个环形数组(取模)
尚硅谷 java数据结构与算法 学习笔记(一)_第10张图片

数组模拟环形队列

注意现在调整了front 和 rear 队列的
尚硅谷 java数据结构与算法 学习笔记(一)_第11张图片

环形队列代码实现

package com.luyi.DataStructures.queue;

import java.util.Scanner;

/**
 * 环形数组对列
 * @author 卢意
 * @create 2020-12-01 13:54
 */
public class CircleArrayQueue {
	public static void main(String[] args) {
		// 测试
		// 创建一个环形队列
		CircleArray arrayQueue = new CircleArray(4); // 设置为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':
					arrayQueue.showQueue();
					break;
				case 'a':
					System.out.println("输入一个数");
					int value = scanner.nextInt();
					arrayQueue.addQueue(value);
					break;
				case 'g':
					try {
						System.out.println("取出的数据:"+arrayQueue.getQueue());
					} catch (Exception e){
						System.out.println(e.getMessage());
					}
					break;
				case 'h':
					try {
						System.out.println("队列头部的数据:"+arrayQueue.headQueue());
					} catch (Exception e){
						System.out.println(e.getMessage());
					}
					break;
				case 'e':
					scanner.close();
					loop = false;
					break;
				default :
					break;
			}
		}
		System.out.println("程序退出");

	}
}


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

	// 创建队列的构造器
	public CircleArray(int maxSize){
		this.maxSize = maxSize;
		arr= new int[maxSize];
		//front = rear = 0 ; // 队列头部和尾部 front指向队列头部的头一个位置  rear指向队列尾部的具体位置
	}

	// 判断队列是否满
	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;
		}
		// rear指向对位的后一个位置 直接将数据加入  在后移rear指针
		arr[rear] = n;
		// 要考虑取模 如果rear已经在 数组的最后位置  需要进行取模到数组的开始部分
		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.println("arr["+ i % maxSize +"]="+arr[i % maxSize]);
		}
	}

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

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

运行结果

s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
s
队列为空
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a
输入一个数
10
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a 20
输入一个数
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
s
arr[0]=10
arr[1]=20
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a 
输入一个数
30
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
s
arr[0]=10
arr[1]=20
arr[2]=30
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a
输入一个数
40
队列满,不能加入数据
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据

g
取出的数据:10
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
s
arr[1]=20
arr[2]=30
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a 10
输入一个数
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
s
arr[1]=20
arr[2]=30
arr[3]=10
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a 70
输入一个数
队列满,不能加入数据
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
h 
队列头部的数据:20
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
g 
取出的数据:20
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
g
取出的数据:30
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
g
取出的数据:10
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
g
队列为空不能取数据
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a 50
输入一个数
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a

链表

尚硅谷 java数据结构与算法 学习笔记(一)_第12张图片

小结上图:

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

单链表

单链表看似连续 但是并不是 这只是逻辑结构
尚硅谷 java数据结构与算法 学习笔记(一)_第13张图片

不考虑排名

尚硅谷 java数据结构与算法 学习笔记(一)_第14张图片

考虑排名

尚硅谷 java数据结构与算法 学习笔记(一)_第15张图片

修改

思路(1) 先找到该节点,通过遍历,(2) temp.name = newHeroNode.name ; temp.nickname= newHeroNode.nickname

删除

尚硅谷 java数据结构与算法 学习笔记(一)_第16张图片

代码实现

package com.luyi.DataStructures.linkedlist;

/**
 * @author 卢意
 * @create 2020-12-01 21:10
 */
public class SingleLinkedListDemo {

	public static void main(String[] args) {
		// 先创建节点
		HeroNode heroNode1 = new HeroNode(1, "宋江", "及时雨");
		HeroNode heroNode2 = new HeroNode(2, "卢俊义", "玉麒麟");
		HeroNode heroNode3 = new HeroNode(3, "吴用", "智多星");
		HeroNode heroNode4 = new HeroNode(4, "林冲", "豹子头");

		// 创建一个链表
		SingleLinkedList singleLinkedList = new SingleLinkedList();
		// 加入
//		singleLinkedList.add(heroNode1);
//		singleLinkedList.add(heroNode2);
//		singleLinkedList.add(heroNode3);
//		singleLinkedList.add(heroNode4);
		/**
		 * 结果:
		 * HeroNode{no=1, name='宋江', nickName='及时雨'}
		 * HeroNode{no=2, name='卢俊义', nickName='玉麒麟'}
		 * HeroNode{no=3, name='吴用', nickName='智多星'}
		 * HeroNode{no=4, name='林冲', nickName='豹子头'}
		 */
		// 加入按照编号的顺序
		singleLinkedList.addByOrder(heroNode1);
		singleLinkedList.addByOrder(heroNode4);
		singleLinkedList.addByOrder(heroNode2);
		singleLinkedList.addByOrder(heroNode3);
		singleLinkedList.addByOrder(heroNode3);
		/**
		 * 结果
		 * 准备插入的英雄的编号[3]已存在,不能加入!
		 * HeroNode{no=1, name='宋江', nickName='及时雨'}
		 * HeroNode{no=2, name='卢俊义', nickName='玉麒麟'}
		 * HeroNode{no=3, name='吴用', nickName='智多星'}
		 * HeroNode{no=4, name='林冲', nickName='豹子头'}
		 */
		// 显示
		singleLinkedList.list();

		// 测试 修改节点
		HeroNode newHeroNode = new HeroNode(2, "小卢", "小麒麟");
		HeroNode newHeroNode1 = new HeroNode(5, "小卢", "小麒麟");
		singleLinkedList.update(newHeroNode );
		singleLinkedList.update(newHeroNode1 );
		/**
		 * 结果
		 * HeroNode{no=1, name='宋江', nickName='及时雨'}
		 * HeroNode{no=2, name='卢俊义', nickName='玉麒麟'}
		 * HeroNode{no=3, name='吴用', nickName='智多星'}
		 * HeroNode{no=4, name='林冲', nickName='豹子头'}
		 * 没有找到编号为[5]的节点
		 * 修改后的链表情况
		 *
		 * HeroNode{no=1, name='宋江', nickName='及时雨'}
		 * HeroNode{no=2, name='小卢', nickName='小麒麟'}
		 * HeroNode{no=3, name='吴用', nickName='智多星'}
		 * HeroNode{no=4, name='林冲', nickName='豹子头'}
		 */
		// 修改后的显示
		System.out.println("修改后的链表情况");
		System.out.println();
		singleLinkedList.list();

		System.out.println("删除一个节点");
		singleLinkedList.delete(1);
		singleLinkedList.delete(2);
		singleLinkedList.delete(3);
		singleLinkedList.delete(4);
		singleLinkedList.list();
		/**
		 * 结果
		 * 删除一个节点
		 * 链表为空
		 */

	}
}
// 定义一个单向链表 管理我们的英雄
class SingleLinkedList {
	// 初始化一下头结点  头结点不要动 不存放具体的数据
	private HeroNode head =new HeroNode(0,null,null);

	// 添加方法到单向链表
	// 思路 当不考虑编号顺序时
	// 1. 找到当前链表的最后节点
	// 2. 将最后的这个节点的next 指向 新节点
	public void add(HeroNode heroNode){
		// 因为 head节点不能动 我们需要一个辅助节点 temp
		HeroNode temp = head;
		// 遍历链表找到最后
		while (true){
			// 找到链表的最后
			if(temp.next == null){
				break;
			}
			// 如果没有找到 就将temp后移
			temp = temp.next;
		}
		// 当退出white循环时, temp 就指向了链表的最后
		temp.next = heroNode;
	}

	// 第二种添加英雄的方式, 根据排名将英雄插入到指定位置
	// 如果有这个排名, 则添加失败 并给出提示
	public void addByOrder(HeroNode heroNode){
		// 因为头结点不能动 因此我们任然通过一个辅助指针(变量) 来帮助找到添加的位置
		// 因为是单列表 temp是要找到添加节点的前一个节点, 否则插入不了
		HeroNode temp = head;
		boolean flag = false; // 标志添加的编号是否存在 默认为false
		while(true) {
			if(temp.next == null) { // 说明已经到了链表的最后了
				break;
			}
			if (temp.next.no > heroNode.no) { // 位置找到了 在temp和temp.next之间插入该节点
				break;
			}else if (temp.next.no == heroNode.no) { // 说明希望添加的heroNode的编号已经存在 不能添加
				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 list(){
		// 判断链表为空
		if(head.next == null){
			System.out.println("链表为空");
			return;
		}
		// 因为头结点不能动我们需要一个复制节点 temp
		HeroNode temp = head.next;
		while(true){
			// 输出这个节点的信息
			System.out.println(temp);
			if(temp.next == null){
				break;
			}
			// 将temp 后移 不然是一个死循环
			temp = temp.next;
		}

	}
	// 修改节点的信息 根据编号来修改 编号不能修改
	public void update (HeroNode newHeroNode){
		if(head.next == null){
			System.out.println("链表为空");
			return;
		}
		// 找到需要修改的节点 很具no查询
		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){
			System.out.println("没有找到编号为["+newHeroNode.no+ "]的节点");
		}else {
			temp.name = newHeroNode.name;
			temp.nickName = newHeroNode.nickName;
		}

	}

	// 删除节点
	//思路 1.head节点不能动 因此我们需要一个temp辅助节点找到删除节点的前一个节点
	//    2. 我们需要以temp节点的后一个节点的标号进行比较
	public void delete(int no){
		HeroNode temp = head;
		boolean flag = false; // 标志是否找到待删除节点的
		while (true) {
			if (temp.next == null){ // temp已经到链表的最后
				break;
			}
			if (temp.next.no == no){
				// 找到了待删除节点的前一个节点
				flag = true;
				break;
			}
			temp = temp.next;
		}
		if (flag){
			// 可以删除
			HeroNode t = temp.next;
			temp.next = t.next;
			t.next = null;
		}else {
			System.out.println("没有找到该no=["+no+"]节点,无法删除");
		}


	}

}

// 定义一个HeroNode , 每个HeroNode 对象就是一个节点
class HeroNode {
	public int no;
	public String name;
	public String nickName;
	public HeroNode next; // 指向下一个节点

	// 构造器
	public HeroNode(int hNo, String hName, String hNickName){
		this.no = hNo;
		this.name = hName;
		this.nickName = hNickName;
	}

	// 为了显示方便 重写toString方法


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


单链表面试题

尚硅谷 java数据结构与算法 学习笔记(一)_第17张图片

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


	/**
	 *
	 * @param head  链表的头节点
	 * @return 返回的是有效节点个数
	 */
	public Integer 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个节点

	// 求单链表的倒数第K个节点
	// 思路 1 编写一个方法接收head节点 接收index(倒数第index节点)
	//     2 先把链表变历 求出长度 length  去找到length-index的节点
	public HeroNode findNodeStartLast(HeroNode head ,int index){
		// 如果链表为空 返回null
		if (head.next == null){
			return null;
		}
		// 变历得到链表的长度
		int length = this.getLength(head);
		// 再一次遍历  返回 第length - index
		// 先做一个index的校验
		if(index <= 0 || index > length){
			return null;
		}
		// 定义一个辅助节点
		HeroNode temp = head.next;
		for(int i = 0 ;i < length-index ;i++ ){
			temp = temp.next;
		}
		return  temp;
	}

单链表的反转 (有点难度)

尚硅谷 java数据结构与算法 学习笔记(一)_第18张图片

	// 单链表的反转
	public HeroNode reverseNode(HeroNode head){
		if (head.next == null){
			return head;
		}
		HeroNode reverseHeroNode = new HeroNode(0, null, null);
		while (head.next != null){
			if (reverseHeroNode.next == null){
				reverseHeroNode.next = head.next;
				head.next = head.next.next;
				reverseHeroNode.next.next = null;
			}else {
				HeroNode temp = reverseHeroNode.next;
				reverseHeroNode.next = head.next;
				head.next = head.next.next;
				reverseHeroNode.next.next = temp;
			}
		}
		head.next = reverseHeroNode.next;
		return head;
	}

从未到头打印单列表

尚硅谷 java数据结构与算法 学习笔记(一)_第19张图片

	// 使用栈的方式 实现逆序打印单链表
	public static void reversePrint(HeroNode head){
		if(head.next == null) {
			System.out.println("队列为空,无法逆序打印单链表");
			return;
		}
			HeroNode temp = head;
			Stack<HeroNode> heroNodeStack = new Stack<>();
			while (temp.next != null){
				heroNodeStack.add(temp.next);
				temp = temp.next;
			}
			while (heroNodeStack.size() > 0){
				System.out.println(heroNodeStack.pop());
			}
	}

双向链表

单链表的问题

尚硅谷 java数据结构与算法 学习笔记(一)_第20张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第21张图片

代码实现

package com.luyi.DataStructures.linkedlist;

/**
 * @author 卢意
 * @create 2020-12-02 22:06
 */
public class DoubleLinkedListDemo {
	public static void main(String[] args) {
		System.out.println("双向链表的一个测试");
		// 先创建节点
		HeroNode2 heroNode1 = new HeroNode2(1, "宋江", "及时雨");
		HeroNode2 heroNode2 = new HeroNode2(2, "卢俊义", "玉麒麟");
		HeroNode2 heroNode3 = new HeroNode2(3, "吴用", "智多星");
		HeroNode2 heroNode4 = new HeroNode2(4, "林冲", "豹子头");

		//创建一个双向链表对象
		DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
		doubleLinkedList.add(heroNode1);
		doubleLinkedList.add(heroNode2);
		doubleLinkedList.add(heroNode3);
		doubleLinkedList.add(heroNode4);

		doubleLinkedList.list();

		// 修改测试
		System.out.println();
		System.out.println("修改测试");
		HeroNode2 newHeroNode = new HeroNode2(4, "公孙胜", "如云龙");
		doubleLinkedList.update(newHeroNode);
		doubleLinkedList.list();

		// 测试删除
		System.out.println("删除测试");
		doubleLinkedList.delete(3);
		doubleLinkedList.list();

		// 有序添加测试
		System.out.println();
		System.out.println("有序添加测试");
		HeroNode2 newHeroNode2 = new HeroNode2(0, "小工", "公公");
		doubleLinkedList.addOrder(newHeroNode2);
		doubleLinkedList.list();
	}
}

// 创建一个双向链表的类
class DoubleLinkedList {
	// 初始化一下头结点  头结点不要动 不存放具体的数据
	private HeroNode2 head =new HeroNode2(0,null,null);

	public HeroNode2 getHead() {
		return head;
	}

	public void setHead(HeroNode2 head) {
		this.head = head;
	}

	// 遍历双向链表的方法和单链表的方法一样
	// 显示链表 遍历
	public void list(){
		// 判断链表为空
		if(head.next == null){
			System.out.println("链表为空");
			return;
		}
		// 因为头结点不能动我们需要一个复制节点 temp
		HeroNode2 temp = head.next;
		while(true){
			// 输出这个节点的信息
			System.out.println(temp);
			if(temp.next == null){
				break;
			}
			// 将temp 后移 不然是一个死循环
			temp = temp.next;
		}
	}

	// 这个方法默认添加到双向链表的尾部
	public void add(HeroNode2 heroNode){
		// 因为 head节点不能动 我们需要一个辅助节点 temp
		HeroNode2 temp = head;
		// 遍历链表找到最后
		while (true){
			// 找到链表的最后
			if(temp.next == null){
				break;
			}
			// 如果没有找到 就将temp后移
			temp = temp.next;
		}
		// 当退出white循环时, temp 就指向了链表的最后
		// 形成一个双向链表
		temp.next=heroNode;
		heroNode.pre = temp;
	}

	// 按顺序添加双链表
	void addOrder(HeroNode2 heroNode) {
		HeroNode2 temp = head.next;
		boolean flag = false; // 标识是否可以插入
		if (head.next == null){
			heroNode.pre = head;
			head.next = heroNode;
		}else {
			while (true) {
				if (temp.no == heroNode.no){
					System.out.println("该编号已经存在 无法添加");
					flag = true;
					break;
				}else if (temp.no > heroNode.no){
					break;
				}
				if(temp.next == null){
					break;
				}
				temp = temp.next;
			}
			if (!flag){
				heroNode.next = temp;
				heroNode.pre = temp.pre;
				temp.pre.next=heroNode;
				temp.pre=heroNode;
			}
		}

	}

	// 修改一个双向链表的接单的内容  和单向链表一样 只是节点的类型发生了改变
	public void update (HeroNode2 newHeroNode){
		if(head.next == null){
			System.out.println("链表为空");
			return;
		}
		// 找到需要修改的节点 很具no查询
		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){
			System.out.println("没有找到编号为["+newHeroNode.no+ "]的节点");
		}else {
			temp.name = newHeroNode.name;
			temp.nickName = newHeroNode.nickName;
		}
	}

	// 从双向链表中删除一个节点
	// 对于双向链表可以直接找到要删除的节点 不用像单链表要找到要删除节点的前一个节点
	// 找到后 自我删除即可
	public void delete(int no){
		HeroNode2 temp = head.next;
		// 判断当前链表是否为空
		if (temp == null) {
			System.out.println("当前链表为空 无法删除");
		}
		boolean flag = false; // 标志是否找到待删除节点的
		while (true) {
			if (temp.no == no){
				// 找到了待删除节点的前一个节点
				flag = true;
				break;
			}
			temp = temp.next;
		}
		if (flag){
			// 可以删除
			// temp.next = temp.next.next; [单链表]
			temp.pre.next = temp.next;
			if (temp.next != null) {
				//  如果删除的不是最后一个节点
				temp.next.pre = temp.pre;
			}
		}else {
			System.out.println("没有找到该no=["+no+"]节点,无法删除");
		}
	}

}


// 定义一个HeroNode , 每个HeroNode 对象就是一个节点
class HeroNode2 {
	public int no;
	public String name;
	public String nickName;
	public HeroNode2 next; // 指向下一个节点 ,默认为null
	public HeroNode2 pre; // 指向上一个节点 ,默认为null

	// 构造器
	public HeroNode2(int hNo, String hName, String hNickName){
		this.no = hNo;
		this.name = hName;
		this.nickName = hNickName;
	}

	// 为了显示方便 重写toString方法


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

单项环形链表

约瑟夫问题

尚硅谷 java数据结构与算法 学习笔记(一)_第22张图片

单向环形链表介绍

尚硅谷 java数据结构与算法 学习笔记(一)_第23张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第24张图片

思路分析

尚硅谷 java数据结构与算法 学习笔记(一)_第25张图片

package com.luyi.DataStructures.linkedlist;

import com.sun.xml.internal.ws.wsdl.writer.document.soap.Body;
import jdk.nashorn.internal.ir.IfNode;

/**
 * 约瑟夫问题
 * @author 卢意
 * @create 2020-12-03 16:49
 */
public class Joseph {
	public static void main(String[] args) {
		CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
		circleSingleLinkedList.addBoy(25);
		circleSingleLinkedList.solveJoseph(1, 2, 25);
		//circleSingleLinkedList.list();
	}
}

// 创建一个环形的单项链表
class CircleSingleLinkedList {
	// 创建一个first节点, 当前没有编号 等等在设置编号
	private Boy first = new Boy(-1);

	// 添加小孩的节点 构建成一个环形链表
	public void addBoy(int nums) {
		// nums 做一个数据校验
		if (nums < 1 ) {
			System.out.println("nums的值不正确");
			return;
		}
		Boy currentBoy = null; // 辅助指针  帮助构建环形链表
		// 使用一个for循环创建环形链表
		for (int i =1; i <= nums; i++) {
			// 根据编号 创建小孩节点
			Boy newBoy = new Boy(i);
			// 如果是第一个小孩
			if (i == 1){
				first = newBoy;
				first.setNext(first); // 构成一个环
				currentBoy = first; // 让currentBoy指向第一个小孩  first节点不能动
			}else {
				currentBoy.setNext(newBoy);
				newBoy.setNext(first);
				currentBoy = newBoy;
			}
		}
	}

	// 遍历当前环形链表
	public void list(){
		Boy currentBoy = first;
		if (currentBoy.getNo() == -1){
			System.out.println("当前环形链表为空,无法遍历");
			return;
		}
		while (true) {
			System.out.println("小孩编号"+currentBoy.getNo());
			currentBoy = currentBoy.getNext();
			if (currentBoy == first){
				break;
			}
		}
	}

	// 根据用户的输入 计算小孩出圈的顺序

	/**
	 *
	 * @param startNo  表示从第几个小孩开始数数
	 * @param countNum 表示数几下
	 * @param num 表示最初有多少个小孩
	 * @return
	 */
	public void solveJoseph(int startNo, int countNum, int num){
		// 先对数据进行校验
		if (first.getNo() == -1 || startNo < 1 || startNo > num){
			System.out.println("参数输入有误,请重新输入");
			return;
		}
		// 创建辅助指针 helper  帮助完成小孩出圈
		Boy helper = first;
		// 先让helper 指向环形队列的最后一个节点
		while (true) {
			if (helper.getNext() == first) {
				break;
			}
			helper = helper.getNext();
		}
		//当小孩报数之前 先让first 和helper 移动 startNo-1次
		for (int j = 0; j < startNo-1; j++) {
			first = first.getNext();
			helper = helper.getNext();
		}
		// 当小孩报数时, 让first和helper 指针同时的移动 countNum - 1 次, 然后出圈
		// 这里是一个循环操作 , 直到圈里只有一个节点
		while (true) {
			if (helper == first) { // 当前圈中只有一个人
				System.out.print("最后出圈: "+first.getNo()+"");
				break;
			}
			for (int i = 0; i < countNum -1; i++ ) {
				first = first.getNext();
				helper = helper.getNext();
			}
			// 这是后first指向的小孩 出圈
			System.out.print(first.getNo()+" ");
			first = first.getNext();
			helper.setNext(first);

		}


	}

}


// 创建一个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 Boy getNext() {
		return next;
	}

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

尚硅谷 java数据结构与算法 学习笔记(一)_第26张图片

介绍

尚硅谷 java数据结构与算法 学习笔记(一)_第27张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第28张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第29张图片

数组模拟栈

尚硅谷 java数据结构与算法 学习笔记(一)_第30张图片

代码实现

package com.luyi.DataStructures.stack;

import java.lang.reflect.Array;
import java.util.LinkedList;
import java.util.Stack;

/**
 * @author 卢意
 * @create 2020-12-03 19:54
 */
public class ArrayStackDemo {
	public static void main(String[] args) {
		ArrayStack stack =new ArrayStack(4);
		stack.push(1);
		stack.push(2);
		stack.push(3);
		stack.push(4);
		System.out.println(stack.pop());
		System.out.println(stack.pop());

		System.out.println("遍历栈");
		stack.list();
	}

}

// 定义一个 ArrayStack 表示栈
class ArrayStack {
	private int maxSize; // 栈的大小
	private int[] stack; // 数组, 数组模拟栈 将栈的数据放入数组
	private int top = -1; //表示栈顶 初始化为-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;
	}

	// 入栈的操作
	public void push (int value) {
		// 判断是否满
		if (isFull()){
			System.out.println("栈已经满了,无法入栈");
			return;
		}
		top++;
		stack[top] = value;
	}

	// 出栈 将栈顶的数据返回
	public int pop () {
		// 先判断栈是否空
		if (isEmpty()){
			throw new RuntimeException("栈空,没有数据");
		}
		return  stack[top--];
	}

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


}

栈实现计算器

思路分析(中缀表达式)

尚硅谷 java数据结构与算法 学习笔记(一)_第31张图片

尚硅谷 java数据结构与算法 学习笔记(一)_第32张图片

代码实现(不考虑小括号)

package com.luyi.DataStructures.stack;

import java.awt.print.Pageable;
import java.util.Stack;

/**
 * @author 卢意
 * @create 2020-12-03 20:48
 */
public class Calculator {
	public static void main(String[] args) {
		// 根据前面的思路  完成 表达式的运算
		String expression = "7*2*2-5+1-5+3-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) {
			// 依次得到Expression 的每一个字符
			ch = expression.substring(index, index + 1).charAt(0);
			// 判断是否是数字和符号 做相应的处理
			if (operStack.isOper(ch)) { // 如果是运算符
				// 判断当前的符号栈 是否为空
				if (!operStack.isEmpty()) {
					// 如果符号栈有操作符 就进行比较 如果当前的操作符的优先级小于等于栈中的操作符,就需要从数栈中pop出两个数
					// 再从符号栈中pop出一个符号 ,进行运算,将得到的结果,入数栈 然后将 当前的操作符入符号栈
					if (operStack.priority(ch) <= operStack.priority(operStack.peak())) {
						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 { // 如果是数字 则直接入数栈 注意这是字符  不是真正的数字
				// numStack.push(ch - 48);  这样子有问题 13 + 1 => '1' '3' '+' '1'
				// 在处理多位数时, 不能发现是一个数就立即入栈 因为他可能是多位数
				// 在处理数,需要向expression的表达式的index 后再看一位 , 如果是数 就进行扫描, 如果是符号才入栈
				// 因此我们需要定义一个变量 keepNum字符串 ,用于拼接多位数

				//处理多为数
				keepNum += ch;

				// 如果ch是expression的最后一位 直接入栈
				if (index == expression.length()-1){
					numStack.push(ch - 48);
				}else {
					// 判断下一个字符 是不是数组 ,就继续扫描 ,如果是运算符 则入栈
					// 只看后一位 不是index++
					if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
						// 如果后一位是运算符 则数字入栈
						numStack.push(Integer.parseInt(keepNum));
						// 重要的!!!!!! 清空keepNum
						keepNum = "";
					}
				}
 			}
			// index +1 并判断是否扫描到最后
			index++;
			if (index >= expression.length()) {
				break;
			}
		}
		// 当表达式扫描完毕, 就顺序的从 数栈和符号栈中pop出相应的数和符号,并运行
		while (true) {
			// 如果符号栈为空,则计算到最后的结果 数字栈 只有一个结果
			if (operStack.isEmpty()) {
				break;
			}
			num1 = numStack.pop();
			num2 = numStack.pop();
			oper = operStack.pop();
			res = numStack.cal(num1, num2, oper);
			numStack.push(res);
		}
		System.out.println(expression+" = "+res);
	}
}

// 先创建一个栈 ,直接使用前面创建好的  需要扩展功能
// 定义一个 ArrayStack 表示栈
class ArrayStack2 {
	private int maxSize; // 栈的大小
	private int[] stack; // 数组, 数组模拟栈 将栈的数据放入数组
	private int top = -1; //表示栈顶 初始化为-1

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

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

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

	// 入栈的操作
	public void push (int value) {
		// 判断是否满
		if (isFull()){
			System.out.println("栈已经满了,无法入栈");
			return;
		}
		top++;
		stack[top] = value;
	}

	// 出栈 将栈顶的数据返回
	public int pop () {
		// 先判断栈是否空
		if (isEmpty()){
			throw new RuntimeException("栈空,没有数据");
		}
		return  stack[top--];
	}

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

	// 查看栈顶的值  不出栈
	public int peak() {
		if (top == -1) {
			System.out.println("为空栈");
			return -1;
		}
		return stack[top];
	}

	// 返回运算符的有限及 , 假定 返回值的数字越大 优先级越高
	public int priority(int oper){
		if (oper == '*' || oper == '/') { // java可以数字和字符比
			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 = num2 * num1;
				break;
			case '/' :
				res = num2 / num1; // 注意顺序
				break;
			default:break;
		}
		return res;
	}


}

前缀 中缀 后缀(逆波兰)表达式

前缀表达式(波兰表达式)

尚硅谷 java数据结构与算法 学习笔记(一)_第33张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第34张图片

中缀

尚硅谷 java数据结构与算法 学习笔记(一)_第35张图片

后缀(逆波兰表达式)

尚硅谷 java数据结构与算法 学习笔记(一)_第36张图片

逆波兰计算器

尚硅谷 java数据结构与算法 学习笔记(一)_第37张图片

代码实现

package com.luyi.DataStructures.stack;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;

/**
 * @author 卢意
 * @create 2020-12-07 8:45
 */
public class PolandNotation {

	public static void main(String[] args) {
		// 先定义一个 逆波兰表达式
		//(3+4)*5-6 => 3 4 + 5 * 6 -
		// 为了方便 中间用空格隔开
		String suffixExpression = "3 4 + 5 * 6 -";
		// 1 现将suffixExpression 放到一个ArrayList中
		// 2 将ArrayList 传递一个方法 遍历ArrayList 配合栈 完成计算
		List<String> rpnList=getListString(suffixExpression);
		System.out.println("rpnList:"+rpnList);
		System.out.println("结果为: "+calculator(rpnList) );

	}

	// 将一个逆波兰表达式 一次将数据和运算符放入一个 ArrayList中
	public static List<String> getListString(String suffixExpression) {
		// 将suffixExpression 按空格分隔
		String[] split = suffixExpression.split(" ");
		List<String> list = new ArrayList<String>(Arrays.asList(split));
		return list;
	}
    // 完成对逆波兰表达式的运算
	// 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,由此得出最终结果
	public static int calculator(List<String> ls) {
		// 创建一个栈 只需要一个
		Stack<String> stack = new Stack<>();
		// 遍历 ls
		for(String item : ls) {
			// 使用一个正则表达式取出数
			if(item.matches("\\d+")) { // 匹配的是多位数
				// 入栈
				stack.push(item);
			}else { // 如果是运算符
				// pop出两个数并运算
				int b = Integer.parseInt(stack.pop());
				int a = Integer.parseInt(stack.pop());
				int res = 0;
				switch (item) {
					case "+":
						res = a+b;
						break;
					case "-":
						res = a-b;
						break;
					case "*":
						res = a*b;
						break;
					case "/":
						res = a/b;
						break;
					default:
						throw new RuntimeException("符号有问题");
				}
				stack.push(res+"");
			}
		}
		return Integer.parseInt(stack.pop());
	}
}

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

尚硅谷 java数据结构与算法 学习笔记(一)_第38张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第39张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第40张图片

代码实现

package com.luyi.DataStructures.stack;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;

/**
 * @author 卢意
 * @create 2020-12-07 8:45
 */
public class PolandNotation {

	public static void main(String[] args) {

		// 完成将中缀表达式转成后缀代达式的功能
		// 1.  1+((2+3)*4)-5   =>     1 2 3 + 4 * + 5 -
		// 2.  因为直接对Str 进行操作不方便  因此 先将 1+((2+3)*4)-5 转成中缀表达式对应的List
		//     即  ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
		// 3.  将得到的中缀表达式的list => 后缀表达式的List
		// 4.  ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] =>  ArrayList [1,2,3,+,4,*,+,5]
		String expression = "1+((2+3)*4)-5";
		System.out.println("中缀表达式:"+ toInfixExpression(expression));
		List<String> parseSuffixExpressList = parseSuffixExpressionList(toInfixExpression(expression));
		System.out.println("逆波兰表达式:" +parseSuffixExpressList );
		//结果
		System.out.println("expression:"+expression+"="+ calculator(parseSuffixExpressList));


		// 先定义一个 逆波兰表达式
		//(3+4)*5-6 => 3 4 + 5 * 6 -
		//(30+4)*5-6 => 30 4 + 5 * 6 -
		//4*5-8+60+8/2 =>  4 5 * 8 - 60 + 8 2 / +
		// 为了方便 中间用空格隔开
		String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";
		// 1 现将suffixExpression 放到一个ArrayList中
		// 2 将ArrayList 传递一个方法 遍历ArrayList 配合栈 完成计算
		List<String> rpnList=getListString(suffixExpression);
		System.out.println("rpnList:"+rpnList);
		System.out.println("结果为: "+calculator(rpnList) );

	}
	// 将 中缀表达式转成对应的List
	public static List<String> toInfixExpression(String s) {
		// 定义一个ArrayList 存放中缀表达式的内容
		List<String> arrayList = new ArrayList<String>();
		int i = 0;
		String str; // 做对多位数的拼接
		char c; // 每遍历到一个字符放入c中
		do {
			// 如果c是一个非数字 直接加入到ls
			if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) < 57 ){
				arrayList.add(c + "");
				i++;
			}else { // 如果是一个数 需要考虑多位数的问题
				str = ""; // str 置空
				while (i < s.length() && (s.charAt(i) >= 48 && s.charAt(i) <= 57)) {
					str += c;
					i++;
				}
				arrayList.add(str);
			}
		}while (i < s.length());
		return arrayList;
	}

	// 将得到的中缀表达式的list => 后缀表达式的List ls=ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
	public static List<String> parseSuffixExpressionList(List<String> ls) {
		// 定义两个栈
		Stack<String> s1 = new Stack<String>(); // 符号栈
		// 第二个栈 在整个转换过程中 没有pop操作, 而且后面我们号需要逆序输出 还要逆序输出 我们同 List s2 代替
		// Stack s2 = new Stack();
		List<String> s2 = new ArrayList<String>();

		// 遍历ls
		for (String item : ls) {
			// 如果是一个数 加入s2
			if(item.matches("\\d+")) {
				s2.add(item);
			}else if (item.equals("(")) {
				s1.push(item);
			}else if (item.equals(")")) {
				//如果是右括号“)”,则依次弹出 s1 栈顶的运算符,并压入 s2,直到遇到左括号为止,此时将这一对括号丢弃
				while (!s1.peek().equals("(")){
					s2.add(s1.pop());
				}
				s1.pop(); // 弹出 (
			}else {
				// 当item的优先级 小于 s1栈顶的运算符的优先级 将 s1 栈顶的运算符弹出并加入到 s2 中,再次转到(4.1)与 s1 中新的栈顶运算符相比较
				while (s1.size() != 0 && (Operation.getValue(item) <= Operation.getValue(s1.peek()))) {
					s2.add(s1.pop());
				}
				// 还需要将Item入栈
				s1.push(item);
			}
		}
		// 将s1的剩余的运算符加入s2中
		while (s1.size() !=0){
			s2.add(s1.pop());
		}
		return s2; // 因为是存在list中  不用逆序 正常输出就是逆波兰表达式
	}
	// 将一个逆波兰表达式 一次将数据和运算符放入一个 ArrayList中
	public static List<String> getListString(String suffixExpression) {
		// 将suffixExpression 按空格分隔
		String[] split = suffixExpression.split(" ");
		List<String> list = new ArrayList<String>(Arrays.asList(split));
		return list;
	}

    // 完成对逆波兰表达式的运算
	// 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,由此得出最终结果
	public static int calculator(List<String> ls) {
		// 创建一个栈 只需要一个
		Stack<String> stack = new Stack<>();
		// 遍历 ls
		for(String item : ls) {
			// 使用一个正则表达式取出数
			if(item.matches("\\d+")) { // 匹配的是多位数
				// 入栈
				stack.push(item);
			}else { // 如果是运算符
				// pop出两个数并运算
				int b = Integer.parseInt(stack.pop());
				int a = Integer.parseInt(stack.pop());
				int res = 0;
				switch (item) {
					case "+":
						res = a+b;
						break;
					case "-":
						res = a-b;
						break;
					case "*":
						res = a*b;
						break;
					case "/":
						res = a/b;
						break;
					default:
						throw new RuntimeException("符号有问题");
				}
				stack.push(res+"");
			}
		}
		return Integer.parseInt(stack.pop());
	}
}
// 编写一个类 Operation 返回一个运算符 对应的优先级
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;
	}

}

递归

尚硅谷 java数据结构与算法 学习笔记(一)_第41张图片

递归可以解决的问题

尚硅谷 java数据结构与算法 学习笔记(一)_第42张图片

递归的重要规则

尚硅谷 java数据结构与算法 学习笔记(一)_第43张图片

迷宫问题

尚硅谷 java数据结构与算法 学习笔记(一)_第44张图片

代码实现

package com.luyi.DataStructures.recursion;

import com.sun.org.apache.xml.internal.resolver.helpers.PublicId;

/**
 * @author 卢意
 * @create 2020-12-07 16:01
 */
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;
		//map[1][2] = 1;
		//map[2][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();
		}
		// 使用递归回溯 找路 map是引用类型  可以动态改变值 等于公共变量
		if(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();
			}
		}else {
			System.out.println("无法找到");
		}


	}

	// 使用递归回溯给小球找路
	// 说明 map是地图
	//     i,j表示从哪个位置开始出发(i,j)
	//     如果小球到map[6][5] = 则说明通路找到
	//     约定 当map[i][j] 为0时 表示该点没有走过 1为墙 2为通路(小球行径路径) 3为该位置已经走过 但是走不通
	//     再走迷宫时 需要确定一个策略(行径方向 当走不通时往策略的下一个方向)  下->右->上->左 ,如果该点都走不通 在回溯
	/**
	 *
	 * @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 { // 肯能为 1是墙 2走过 3不能走
				 return false;
			}
		}
	}

}

最短路径分析

目前比较笨的方法
上下左右四个方向 设置所有的策略方法 (上下左右 、上下右左、上左下右 。。。24种) 都走一次 找到最短的那个策略

八皇后问题

尚硅谷 java数据结构与算法 学习笔记(一)_第45张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第46张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第47张图片

代码实现

package com.luyi.DataStructures.recursion;

/**
 * @author 卢意
 * @create 2020-12-07 18:25
 */
public class Queen {
	// 定义一个max表示有多少个皇后
	int max = 8;
	static int count = 0;
	// 定义一个数组 保存皇后放置位置的结果  arr[8] = {0 , 4, 7, 5, 2, 6, 1, 3}
	int[] arr = new int[max];
	public static void main(String[] args) {
		// 测试一把
		Queen queen = new Queen();
		queen.check(0); // 放到第一个位置


	}
	// 编写一个方法, 放置第n个皇后
	// 注意  进入check时 都会有一个for循环,因此会有回溯
	private void check(int n) {
		if(n == max){ // max个换后已经放好了
			print();
			return;
		}
		// 依次放皇后 判断是否冲突
		for (int i = 0; i < max; i++) {
			// 先把当前的皇后放到该行的第一列
			arr[n] = i;
			// 当放置第那个皇后到i列时 是否冲突
			if (judge(n)){ // 不冲突
				check(n + 1);
			}// 如果冲突则继续执行 arr[n] = i ; 本行后移一个位置
		}
	}
	/**
	 * 查看 当放置第N个皇后时 , 去检测该皇后是否和前面已经摆放的皇后冲突
	 * @param n 第n个皇后
	 * @return
	 */
	private boolean judge(int n) { // 表示放的第n个皇后
		for (int i = 0; i < n; i++){
			// arr[i] == arr[n] 判断第n个皇后和第i个皇后是否在同一直线上
			// Math.abs(n - i) == Math.abs(arr[n] - arr[i])判断第n个皇后和第i个皇后是否在同一斜线上(是因为当前是一维数组,二维数组需要
			//   用其他方法)
			// n = 1 代表第二个皇后 如果放在第二列的第二行 则 arr[1] = 1
			// Math.abs(1-0) == 1   Math.abs(1 - 0) == Math.abs(1 - 0) 成立
			// 不需要判断同一行 因为 n在递增
			if (arr[i] == arr[n] || Math.abs(n - i) == Math.abs(arr[n] - arr[i])){ // Math.abs 是绝对值
				return false;
			}
		}
		return true;
	}

	// 写一个方法 将皇后的位置打印
	private void print() {
		System.out.print("第"+(++count)+"种: ");
		for (int i = 0;i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
}

排序算法

尚硅谷 java数据结构与算法 学习笔记(一)_第48张图片

时间复杂度

尚硅谷 java数据结构与算法 学习笔记(一)_第49张图片

时间频度

尚硅谷 java数据结构与算法 学习笔记(一)_第50张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第51张图片

结论:

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

尚硅谷 java数据结构与算法 学习笔记(一)_第52张图片

结论:

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

忽略系数(3次方及以上不能忽略)
尚硅谷 java数据结构与算法 学习笔记(一)_第53张图片

时间复杂度

尚硅谷 java数据结构与算法 学习笔记(一)_第54张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第55张图片

尚硅谷 java数据结构与算法 学习笔记(一)_第56张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第57张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第58张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第59张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第60张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第61张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第62张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第63张图片

空间复杂度

尚硅谷 java数据结构与算法 学习笔记(一)_第64张图片

冒泡排序

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

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

尚硅谷 java数据结构与算法 学习笔记(一)_第65张图片

代码实现(步骤)

package com.luyi.sort;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * 冒泡排序
 * @author 卢意
 * @create 2020-12-08 13:46
 */
public class BubbleSort {
	public static void main(String[] args) {
		int arr[] = {3,9,-1,10,-2};

		// 展示冒泡排序的演变过程 比较arr.length-1 次 就可以得出结果
		/**
		 * 第一趟排序后的数组
		 * [3, -1, 9, -2, 10]
		 * 第二趟排序后的数组
		 * [-1, 3, -2, 9, 10]
		 * 第三趟排序后的数组
		 * [-1, -2, 3, 9, 10]
		 * 第四趟排序后的数组
		 * [-2, -1, 3, 9, 10]
		 */

		// 第一趟排序 就是将最大的数字放到最后
		int temp = 0; // 临时变量
		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("第一趟排序后的数组");
		System.out.println(Arrays.toString(arr));

		// 第二趟排序 , 把第二大的数放在倒数第二位  最后一个数字不参与排序
		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("第二趟排序后的数组");
		System.out.println(Arrays.toString(arr));

		// 第三趟排序 , 把第三大的数放在倒数第三位  最后两个数字不参与排序
		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("第三趟排序后的数组");
		System.out.println(Arrays.toString(arr));

		// 第四趟排序 , 把第四大的数放在倒数第四位  最后三个数字不参与排序
		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("第四趟排序后的数组");
		System.out.println(Arrays.toString(arr));
	}
}

整合后的代码

	int temp = 0; // 临时变量
		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]){
					temp = arr[j];
					arr[j] = arr[j+1];
					arr[j + 1] = temp;
				}
			}
		}
		System.out.println("冒泡排序后的数组");
		System.out.println(Arrays.toString(arr));

时间复杂度 O(n^2)

冒泡排序的优化

		int arr[] = {3,9,-1,10,20};
		int count = 0; // 标记跑了几趟
		int temp = 0; // 临时变量
		boolean flag = false; // 标识变量 表示是否进行交换
		for (int i = 0; i < arr.length -1; i++){
			count++;
			for (int j = 0; j< arr.length -1 -i;j++){
				if(arr[j] > arr[j + 1]){
					temp = arr[j];
					arr[j] = arr[j+1];
					arr[j + 1] = temp;
					flag = true;
				}
			}
			if (!flag){ // 一次交换都没有发生 代表已经排好序了  不用再去进行n-1趟
				break;
			}else {
				flag = false;
			}
		}
		System.out.println("跑了"+count+"趟");
		System.out.println("冒泡排序后的数组");
		System.out.println(Arrays.toString(arr));
		/**
		 * 跑了3趟
		 * 冒泡排序后的数组
		 * [-1, 3, 9, 10, 20]
		 */

韩老师的电脑大概20秒处理8万个随机数

选择排序算法

尚硅谷 java数据结构与算法 学习笔记(一)_第66张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第67张图片

代码实现

尚硅谷 java数据结构与算法 学习笔记(一)_第68张图片

package com.luyi.sort;

import com.sun.xml.internal.txw2.output.IndentingXMLFilter;

import java.util.Arrays;

/**
 * @author 卢意
 * @create 2020-12-08 14:27
 */
public class SelectSort {
	public static void main(String[] args) {
		int[] arr = {101,34,119,1};
		//selectSort(arr);  步骤分析
		selectSortAll(arr);
	}
	// 选择排序
	public static void selectSort(int[] arr){
		// 使用逐步推导的方式
		// 第一轮
		// 原始数组 101,34,119,1
		// 第一轮排序 1,34,119,101
		int minIndex = 0;
		int min = arr[0]; // 初始值
		for(int j = 1; j < arr.length;j++){
			if (min > arr[j]){
				minIndex = j;
				min = arr[j];
			}
		}
		int temp = arr[0];
		arr[0] = arr[minIndex];
		arr[minIndex] = temp;
		System.out.println("第一轮排序");
		System.out.println(Arrays.toString(arr));

		// 第二轮
		// 第一轮排序 1, 34, 119, 101
		// 第二轮排序 1, 34, 119, 101
		min = arr[1]; // 初始值
		minIndex = 1;
		for(int j = 1; j < arr.length;j++){
			if (min > arr[j]){
				minIndex = j;
				min = arr[j];
			}
		}
		temp = arr[1];
		arr[1] = arr[minIndex];
		arr[minIndex] = temp;
		System.out.println("第二轮排序");
		System.out.println(Arrays.toString(arr));

		// 第三轮
		// 第一轮排序 1, 34, 119, 101
		// 第二轮排序 1, 34, 119, 101
		// 第二轮排序 1, 34, 101, 119
		min = arr[2]; // 初始值
		minIndex = 2;
		for(int j = 2; j < arr.length;j++){
			if (min > arr[j]){
				minIndex = j;
				min = arr[j];
			}
		}
		temp = arr[2];
		arr[2] = arr[minIndex];
		arr[minIndex] = temp;
		System.out.println("第三轮排序");
		System.out.println(Arrays.toString(arr));

		/**
		 * 第一轮排序
		 * [1, 34, 119, 101]
		 * 第二轮排序
		 * [1, 34, 119, 101]
		 * 第三轮排序
		 * [1, 34, 101, 119]
		 */
	}
	// 完整代码
	public static void selectSortAll(int[] arr){

		for (int i = 0; i < arr.length -1; i++){
			int min = arr[i];
			int minIndex = i;
			for (int j = i+1; j < arr.length; j++ ){
				if (min > arr[j]){
					min = arr[j];
					minIndex = j;
				}
			}
			if (minIndex != i) {
				int temp = arr[i];
				arr[i] = min;
				arr[minIndex] = temp;
			}
		}
		System.out.println("选择排序");
		System.out.println(Arrays.toString(arr));
	}

}

时间复杂度O(n^2)

韩老师的电脑处理8万个随机数大概3秒

插入排序法

尚硅谷 java数据结构与算法 学习笔记(一)_第69张图片

尚硅谷 java数据结构与算法 学习笔记(一)_第70张图片

代码实现

有一群小牛, 考试成绩分别是 101, 34, 119, 1 请从小到大排序

package com.luyi.sort;

import java.util.Arrays;

/**
 * @author 卢意
 * @create 2020-12-08 15:09
 */
public class InsertSort {
	public static void main(String[] args) {
		int[] arr = {101,34,119,1};
		//insertSort(arr); 分步骤演示
		insertSortAll(arr);
	}
	// 插入排序
	public static void insertSort(int[] arr){
		// 使用逐步推导的方式来讲解 便于理解
		// 第一轮 101,34,119,1 => 34,101,119,1

		// 定义一个待插入的数
		int insertVal = arr[1]; // 初始化  因为101当做已经在一个有序数组里了  让待插入数
		int insertIndex = 1 -1 ; // 初始化  待插入的数的前一个数

		// 给insertVal 找到插入的位置
		// insertIndex >= 0  保证插入的位置不越界
		// insertVal < arr[insertIndex] 待插入数 还没有找到插入的位置 就需要 insertIndex 后移
		while (insertIndex >= 0 && insertVal < arr[insertIndex]){
			arr[insertIndex+1] = arr[insertIndex]; // 101 101 119 1
			insertIndex--; // insertIndex >= 0 小于零就终止了
		}
		// 退出while 循环时 插入位置找到 insertIndex + 1 (因为insertIndex初始化的时候是待插入数的前一个数)
		arr[insertIndex+1] = insertVal;
		System.out.println("第一轮后");
		System.out.println(Arrays.toString(arr));

		// 第二轮 34, 101, 119, 1 => 34, 101, 119, 1

		// 定义一个待插入的数
		insertVal = arr[2]; // 初始化  因为101当做已经在一个有序数组里了  让待插入数
		insertIndex = 2 -1 ; // 初始化  待插入的数的前一个数

		// 给insertVal 找到插入的位置
		// insertIndex >= 0  保证插入的位置不越界
		// insertVal < arr[insertIndex] 待插入数 还没有找到插入的位置 就需要 insertIndex 后移
		while (insertIndex >= 0 && insertVal < arr[insertIndex]){
			arr[insertIndex+1] = arr[insertIndex];
			insertIndex--; // insertIndex >= 0 小于零就终止了
		}
		// 退出while 循环时 插入位置找到 insertIndex + 1 (因为insertIndex初始化的时候是待插入数的前一个数)
		arr[insertIndex+1] = insertVal;
		System.out.println("第二轮后");
		System.out.println(Arrays.toString(arr));

		// 第三轮 34, 101, 119, 1 => 1, 34, 101, 119

		// 定义一个待插入的数
		insertVal = arr[3]; // 初始化  因为101当做已经在一个有序数组里了  让待插入数
		insertIndex = 3 -1 ; // 初始化  待插入的数的前一个数

		// 给insertVal 找到插入的位置
		// insertIndex >= 0  保证插入的位置不越界
		// insertVal < arr[insertIndex] 待插入数 还没有找到插入的位置 就需要 insertIndex 后移
		while (insertIndex >= 0 && insertVal < arr[insertIndex]){
			arr[insertIndex+1] = arr[insertIndex]; //
			insertIndex--; // insertIndex >= 0 小于零就终止了
		}
		// 退出while 循环时 插入位置找到 insertIndex + 1 (因为insertIndex初始化的时候是待插入数的前一个数)
		arr[insertIndex+1] = insertVal;
		System.out.println("第三轮后");
		System.out.println(Arrays.toString(arr));
		/**
		 * 第一轮后
		 * [34, 101, 119, 1]
		 * 第二轮后
		 * [34, 101, 119, 1]
		 * 第三轮后
		 * [1, 34, 101, 119]
		 */
	}

	// 总的插入排序
	public static void insertSortAll(int[] arr){
		int insertIndex = 0; // 待插入数据的前一个位置
		int insertValue = arr[0];
		for (int i = 1; i < arr.length; i++) {
			insertIndex = i - 1; // 待插入数据的前一个位置
			 insertValue = arr[i];
			while (insertIndex >=0 && insertValue < arr[insertIndex]){
				arr[insertIndex + 1] = arr[insertIndex];// 待插入数的前一个数后移
				insertIndex --; // 待插入位置
			}
			// 判断是否需要赋值
			if (insertIndex + 1 != i){
				arr[insertIndex + 1] = insertValue; // 退出while 循环时 插入位置找到 insertIndex + 1 (因为insertIndex初始化的时候是待插入数的前一个数)
			}
			}
		}
		System.out.println("插入排序");
		System.out.println(Arrays.toString(arr));
	}
}

时间复杂度O(n^2)

韩老师的电脑 8万个随机数排序 大致5秒

哈希排序算法图解

简单插入排序的问题

尚硅谷 java数据结构与算法 学习笔记(一)_第71张图片

希尔排序是插入排序的一种高效版本

尚硅谷 java数据结构与算法 学习笔记(一)_第72张图片

尚硅谷 java数据结构与算法 学习笔记(一)_第73张图片

代码实现(交换法)效率并不是很高 韩老师的电脑对8万个随机数进行排序 花了17秒

package com.luyi.sort;

import java.util.Arrays;

/**
 * @author 卢意
 * @create 2020-12-08 19:45
 */
public class HellSort {
	public static void main(String[] args) {
		int[] arr = {8,9,1,7,2,3,5,4,6,0};
		//分步骤
		//hellSort(arr);
		hellSortAll(arr);
	}
	// 使用逐步推到的方式编写希尔排序(交换法)
	public static void hellSort(int[] arr){
		// 希尔数组的第一轮排序
		// 因为第一轮排序 将10个数据分成10/2=5组
		int temp = 0;
		for (int i = 5; i < arr.length; i++){
			// 遍历各组中 所有的元素(共五组,每组2个元素) 步场为5 如第一个数字和第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("希尔数组的第一轮排序后");
		System.out.println(Arrays.toString(arr));

		// 希尔数组的第二轮排序
		// 因为第二轮排序 将10个数据分成5/2=2组
		for (int i = 2; i < arr.length; i++){
			// 遍历各组中 所有的元素(共五组,每组2个元素) 步场为5 如第一个数字和第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轮排序后");
		System.out.println(Arrays.toString(arr));

		// 希尔数组的第三轮排序
		// 因为第三轮排序 将10个数据分成2/2=1组
		for (int i = 1; i < arr.length; i++){
			// 遍历各组中 所有的元素(共五组,每组2个元素) 步场为5 如第一个数字和第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轮排序后");
		System.out.println(Arrays.toString(arr));
		/**
		 * 希尔数组的第一轮排序后
		 * [3, 5, 1, 6, 0, 8, 9, 4, 7, 2]
		 * 希尔数组的第2轮排序后
		 * [0, 2, 1, 4, 3, 5, 7, 6, 9, 8]
		 * 希尔数组的第3轮排序后
		 * [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
		 */
	}
	// 总的希尔排序(交换法)
	public static void hellSortAll(int[]arr){
		int temp = 0;
		int group = arr.length/2;
		while (group != 0) {
			for (int i = group; i < arr.length; i++) {
				for (int j = i - group; j >= 0; j -= group) {
					if (arr[j] > arr[j + 1]) {
						temp = arr[j];
						arr[j] = arr[j + 1];
						arr[j + 1] = temp;
					}
				}
			}
			group /= 2;
		}
		System.out.println("希尔排序结果");
		System.out.println(Arrays.toString(arr));
	}


}

代码实现(移位法) 效率较高 韩老师的电脑对8万个随机数进行排序 花了1秒

	// 对交换式的希尔排序法进行优化 -> 移位法
	public static void hellSortBetter(int[] arr){
		int group = input.length / 2;
        int insertIndex = 0;
        int insertValue = 0;
        while(group != 0){
            for(int i = group; i < input.length; i++){
                insertIndex = i - group;
                insertValue = input[i];
                while(insertIndex >= 0 && insertValue < input[insertIndex]){
                    input[insertIndex + group] = input[insertIndex];
                    insertIndex -= group;
                }
                input[insertIndex + group] = insertValue;
            }
            group /= 2;
        }
		System.out.println("希尔排序结果");
		System.out.println(Arrays.toString(arr));
	}

时间复杂度O(n^(1.3—2))

快速排序算法

尚硅谷 java数据结构与算法 学习笔记(一)_第74张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第75张图片

代码实现

package com.luyi.sort;

import java.util.Arrays;

/**
 * @author 卢意
 * @create 2020-12-08 21:06
 */
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));
	}

	/**
	 *
	 * @param arr
	 * @param left 左边的索引
	 * @param right 右边的索引
	 */
	public static void quickSort(int[] arr, int left, int right){
		int l = left; // 左下标
		int r = right; // 右下标
		int temp = 0; //临时变量
		// pivot 中轴
		int pivot = arr[(left + right) / 2];
		// 只要右边的索引大于左边的索引就继续循环  目的是 让 比pivot小的值放到左边 比 pivot 值大的放在右边
		while (l < r){
			// 在pivot的左边 一直找 找到大于等于pivot的值 ,才退出
			while (arr[l] < pivot){ //最多找到pivot本身
				l += 1;
			}
			// 在pivot的左边 一直找 找到小于等于pivot的值 ,才退出
			while (arr[r] > pivot){ //最多找到pivot本身
				r -= 1;
			}
			// 如果成立 说明 pivot 左边的值, 已经全部小于等于pivot的值, 右边, 已经全部大于等于pivot的值
			if(l >= r){
				break;
			}
			// 交换
			temp = arr[l];
			arr[l] = arr[r];
			arr[r] = temp;

			// 判断 如果交换完后 发现arr[l] = pivot r向前移动 l--
			if (arr[l] == pivot){
				r -= 1;
			}else if (arr[r] == pivot){// 判断 如果交换完后 发现arr[r] = pivot l向后移动 l++
				l++;
			}
		}
		// 如果 l == r 必须 l++ 和 r--, 否则会出现栈溢出
		if (l == r ){
			l++;
			r--;
		}
		// 向左递归
		if (left < r) {
			quickSort(arr,left,r);
		}
		// 向右递归
		if (right > l) {
			quickSort(arr, l, right);
		}
	}
}

韩老师的电脑对8万个随机数进行排序 花了不到一秒 80w数据也不到1秒 800w 2-3秒

快排 时间复杂度O (nlogn) 但是空间复杂度较高

归并排序

尚硅谷 java数据结构与算法 学习笔记(一)_第76张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第77张图片

代码实现

package com.luyi.sort;

import java.util.Arrays;
import java.util.TimerTask;

/**
 * 归并排序
 * @author 卢意
 * @create 2020-12-09 9:50
 */
public class MergeSort {
	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));
	}

	//分 + 合的方法
	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 t = 0; //初始化temp数组的索引

		// 先把左右两边(有序)的数据 按照规则填充到temp
		// 直到左右两边的有序序列有一方处理完毕
		while (i <= mid && j <= right){
			// 如果左边的有序序列的当前元素 <= 右边有序序列的当前元素 将左边的当前元素放到temp的t的位置
			if (arr[i] <= arr[j]){
				temp[t++] = arr[i++];// 放入后移
			}else {
				temp[t++] = arr[j++];
			}
		}
		// 将有剩余的数组的数据一次加入temp中的尾部
		while (i <= mid){ //左边的有序序列还有剩余的数据 将剩余的有序数据 依次加入temp数组
			temp[t++] = arr[i++];
		}
		while (j <=right){ //右边的有序序列还有剩余的数据 将剩余的有序数据 依次加入temp数组
			temp[t++] = arr[j++];
		}

		// 将temp数组重新放到arr
		// 注意 并不是每次拷贝所有
		t = 0;
		int tempLeft = left;// 第一次合并 tempLeft=0 right=1 //  第二次合并 tempLeft=2 right=3 //  第三次合并tempLeft=0 right=3
		//  最后一次合并  tempLeft=0 right=7
		while (tempLeft <= right){
			arr[tempLeft++] = temp[t++];
		}
	}

}

时间复杂度O(nlogn) 韩老师的电脑对8万个随机数进行排序 花了不到0-1秒 80w 一秒 800w3-4秒

基数排序 (桶排序)

尚硅谷 java数据结构与算法 学习笔记(一)_第78张图片
稳定排序 : 如果要排序的数组有两个相同的数字 如a1 =a2 =1 {a1,2,3,a2} 排序后 {a1,a2,2,3} a1还是在a2的前面

尚硅谷 java数据结构与算法 学习笔记(一)_第79张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第80张图片
尚硅谷 java数据结构与算法 学习笔记(一)_第81张图片

代码实现

package com.luyi.sort;

import java.util.Arrays;

/**
 * @author 卢意
 * @create 2020-12-09 13:43
 */
public class RadixSort {
	public static void main(String[] args) {
		int[] arr = {53,3,542,748,14,214};
		//radixSort(arr);
		radixSortAll(arr);
	}
	// 基数排序法
	public static void radixSort(int[] arr){
		// 模拟对一轮(针对每个元素的个位数进行排序处理)

		// 定义一个二维数组,表示一个二维数组, 每个桶就是一个一维数组
		// 说明 二维数组包含10个一维数组
		//     为了防止放入数的时候 数据溢出 ,则每一个一维数组的大小为array.length()
		//     很明显 空间换时间
		int[][] bucket = new int[10][arr.length];
		// 为了记录每个桶中 实际存放了多少个数据 我们定义一个一维数组来记录每个桶的个数
		// bucketElementCounts[0] 就是bucket[0] 的放入数据的个数
		int[] bucketElementCounts = new int[10];
		for (int j = 0; j < arr.length; j++){
			// 取数每个元素的个位
			int digitOfElement = arr[j] % 10;
			// 放入到对应的桶
			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 j = 0; j < bucketElementCounts[k]; j++){
					arr[index++] = bucket[k][j];
				}
			}
			// 第一轮需要对bucketElementCounts数组置零
			bucketElementCounts[k]= 0;
		}
		System.out.println("第一轮 对个位数进行处理后的数组:" + Arrays.toString(arr));

		// 模拟对二轮(针对每个元素的十位数进行排序处理)

		for (int j = 0; j < arr.length; j++){
			// 取数每个元素的十位
			int digitOfElement = arr[j] / 10 % 10;
			// 放入到对应的桶
			bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
			bucketElementCounts[digitOfElement]++;
		}
		// 将桶里的数据放入
		 index = 0;
		// 遍历每一个桶 将桶里的数据放入 原来的数组中
		for (int k = 0;k < bucket.length; k++){
			if (bucketElementCounts[k] != 0){
				for (int j = 0; j < bucketElementCounts[k]; j++){
					arr[index++] = bucket[k][j];
				}
			}
			bucketElementCounts[k]= 0;
		}
		System.out.println("第二轮 对十位数进行处理后的数组:" + Arrays.toString(arr));

		// 模拟对三轮(针对每个元素的百位数进行排序处理)

		for (int j = 0; j < arr.length; j++){
			// 取数每个元素的百位
			int digitOfElement = arr[j] /100 % 10;
			// 放入到对应的桶
			bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
			bucketElementCounts[digitOfElement]++;
		}
		// 将桶里的数据放入
		index = 0;
		// 遍历每一个桶 将桶里的数据放入 原来的数组中
		for (int k = 0;k < bucket.length; k++){
			if (bucketElementCounts[k] != 0){
				for (int j = 0; j < bucketElementCounts[k]; j++){
					arr[index++] = bucket[k][j];
				}
			}
			bucketElementCounts[k]= 0;
		}
		System.out.println("第三轮 对百位数进行处理后的数组:" + Arrays.toString(arr));

	}

	// 总的基数排序
	public static void radixSortAll(int[] arr){
		int[][] bucket = new int[10][arr.length];
		int[] bucketElementCounts = new int[10];
		int index;
		// 需要获取最大的数
		int max = arr[0];
		for (int i = 1; i < arr.length; i++){
			if (max < arr[i]){
				max = arr[i];
			}
		}
		int maxLength = (max+"").length();
 		for (int i = 0, n = 1; i < maxLength; i++, n *=10) {
			for (int j = 0; j < arr.length; j++){
				int digitOfElement = arr[j] / n % 10;
				// 放入到对应的桶
				bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
				bucketElementCounts[digitOfElement]++;
			}
			// 将桶里的数据放入
			index = 0;
			// 遍历每一个桶 将桶里的数据放入 原来的数组中
			for (int k = 0;k < bucket.length; k++){
				if (bucketElementCounts[k] != 0){
					for (int j = 0; j < bucketElementCounts[k]; j++){
						arr[index++] = bucket[k][j];
					}
				}
				bucketElementCounts[k]= 0;
			}

		}
		System.out.println("基数排序的结果" + Arrays.toString(arr));
	}


}

时间复杂度O(nlogn) 韩老师的电脑对8万个随机数进行排序 花了不到0-1秒 80w 1秒不到 800w1秒 8000w 80000000 *11 *4 /1024/1024/1024 = 3.3G的内存

在这里插入图片描述

如果有负数就不使用基数排序

八大排序算法的对比

尚硅谷 java数据结构与算法 学习笔记(一)_第82张图片

堆排序要学了二叉树才能理解

查找算法

尚硅谷 java数据结构与算法 学习笔记(一)_第83张图片

二分查找算法

尚硅谷 java数据结构与算法 学习笔记(一)_第84张图片

尚硅谷 java数据结构与算法 学习笔记(一)_第85张图片

d

你可能感兴趣的:(学习笔记,算法,数据结构,java)