当前位置: 首页 > news >正文

Java基础——常用算法4

一、排序算法

1.1 插入算法

🌟 一、什么是插入排序?

插入排序(Insertion Sort) 是一种简单、直观且高效的比较类排序算法,其思想来源于我们打扑克牌时整理手牌的方式:

每次从未排序部分取出一个元素,插入到已排序部分的正确位置。

名字来源:像“插入”一张新牌到已有有序序列中。

🔁 二、插入排序的工作原理

如果数据比较乱,我们默认第一张或者第一个元素的数据就是有序的,其他的都看作无序的。

再从遍历无序的数组,一一跟有序的进行从后面依次进行的比较,然后插入其中。直接完成插入排序。

💻 三、Java 基础版插入排序代码实现

package com.lkbhua.Algorithm.sort;public class InsertDemo {public static void main(String[] args) {/*插入排序:将0索引元素到N索引的元素看做是有序的,把N+1索引的元素到最后一个当成是无序的。遍历无序的数据,将遍历到的元素插入有序序列中适当的位置,如遇到相同数据,插在后面。‘N的范围:0~最大索引*/int[] arr = {3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};// 1、规定无序的数据的范围从X开始int startIndex = -1 ;for(int i = 0; i < arr.length; i++){if(arr[i]> arr[i+1]){System.out.println(i); // 1// 这样就通过代码的方式找到了无序数据的开始索引startIndex = i + 1;break;// 这就能表示有序的那一组数组的最后一个索引}}// 剩下的数组就是无序的System.out.println("无序数据的开始索引:" + startIndex);// 2、遍历从startIndex开始无序的数组中的元素for(int i = startIndex; i < arr.length; i++){System.out.print(arr[i]+" ");}System.out.println();// 3、如何插入排序呢?for(int i = startIndex; i < arr.length; i++){// 记录当前要插入数据的索引 ,其实就是i// 用另外的变量记录一下int j = i;while( j > 0 && arr[j] < arr[j-1]){// 交换位置int temp = arr[j];arr[j] = arr[j-1];arr[j-1] = temp;j--;}// 修改这个索引}// 4、打印结果System.out.println("排序后:");for(int i = 0; i < arr.length; i++){System.out.print(arr[i]+" ");}}
}

📊 四、时间复杂度与空间复杂度分析

情况时间复杂度说明
最好情况O(n)数组已有序,内层循环不执行
平均情况O(n²)随机数据,平均移动 n/2 次
最坏情况O(n²)数组逆序(如 [5,4,3,2,1]),每次都要移动全部已排序元素

| 空间复杂度 | O(1) | 仅使用常量额外空间(原地排序) |

💡 插入排序是少数能在最好情况下达到 O(n) 的 O(n²) 排序算法!

🔍 五、稳定性分析

插入排序是 ✅ 稳定的排序算法。

为什么?

  • 在比较时使用 arr[j] > key(不是 >=
  • 相等元素不会发生交换或移动,相对顺序保持不变

✅ 六、插入排序的优点

  1. 实现简单:逻辑贴近人类直觉(如理牌)
  2. 原地排序:空间复杂度 O(1)
  3. 稳定:保持相等元素顺序
  4. 在线算法:可以边接收数据边排序
  5. 对小规模或近有序数据极高效
    • 最好情况 O(n)
    • 实际运行常数小,比快排在小数据上更快

🌟 最关键优势
Java 的 Arrays.sort() 在底层对小数组(长度 ≤ 47)会自动切换为插入排序!

🔗 为什么 Java 的 Arrays.sort() 会用插入排序?

这是工程实践中的经典优化

  • 快排、归并在小数组上递归开销大
  • 插入排序在 n ≤ 10~50 时实际运行更快

插入排序虽“简单”,但它是高性能排序库的重要组成部分

❌ 七、插入排序的缺点

  1. 时间复杂度高:O(n²),不适合大数据量
  2. 频繁移动元素:相比选择排序(只交换),插入排序需要多次赋值
  3. 缓存性能一般:内层循环反向遍历,局部性不如顺序访问

🔍 八、入排序 vs 冒泡排序 vs 选择排序

特性插入排序冒泡排序选择排序
时间复杂度(最好)O(n)O(n)(优化后)O(n²)
时间复杂度(平均)O(n²)O(n²)O(n²)
稳定性✅ 稳定✅ 稳定❌ 不稳定
交换/移动次数O(n²) 次移动O(n²) 次交换O(n) 次交换
适用场景小数据、近有序教学写操作昂贵
实际应用✅ 被 JDK 采用❌ 无❌ 无

💡 插入排序是三者中最实用的!

1.2 快速排序

🌟一、 递归思想


复习推文推荐:
https://blog.csdn.net/2401_84643729/article/details/154534984

🌟二、什么是快速排序?

快速排序(Quick Sort) 是由 Tony Hoare 在 1960 年提出的一种高效、原地、分治型比较排序算法
它是目前实际应用中最广泛使用的排序算法之一,也是 Java Arrays.sort() 对基本类型数组的底层实现基础。

核心思想
“分而治之” + “分区(Partition)”

  1. 选择一个元素作为 基准(pivot)
  2. 将数组划分为两部分:
    • 左边:≤ pivot 的元素
    • 右边:≥ pivot 的元素
  3. 递归地对左右两部分排序

🔁 三、快速排序的工作原理 

首先把0索引对应的元素6拿出来当作“基准数”,然后定义两个变量start和end用以记录头和尾。

随后移动end,让其与基准数进行比较,发现比基准数大,则不进行放置处理。让end进行--进行下一个数字判断,10也是,第三5比基准数小,end就停在这。接下来移动start,同理,一直找到第三个元素7,停止。最后拿着start指向的7和end指向的5进行交换,随后继续寻找start++,end--,4和9进行交换,直到start == end ;这个位置就是基准数存入的位置。拿着3和6进行交换,我们称之为基准数归位。至此第一轮结束。

💻 四、Java 基础版快速排序代码实现

package com.lkbhua.Algorithm.sort;public class QuickSortDemo2 {public static void main(String[] args) {/*快速排序:完整代码第一轮:以0索引的数字为基准数,确定基准数在数组中正确的位置比基准数小的全都在左边,比基准数大的全都在右边。后面依次类推*/int[] arr = {6,1,2,7,9,3,4,5,10,8};quickSort(arr, 0, arr.length - 1);for(int i = 0; i < arr.length; i++){System.out.print(arr[i]+" ");}}// 参数一:数组// 参数二:左边索引// 参数三:右边索引public static void quickSort(int[] arr, int left, int right){// 递归的出口if(left >= right){return;}// 定义两个变量记录左右索引int start = left;int end = right;// 记录基准数int baseNumber = arr[left];// 利用循环找到要交换的数字while(start != end){// 利用end索引向左移动,找到比baseNumber小的数字while (true){if(end <= start || arr[end] < baseNumber){break;}end--;}// 利用start索引向右移动,找到比baseNumber大的数字while( true){if(end <= start || arr[start] > baseNumber){break;}start++;}// 把end和start索引的数字交换int temp = arr[start];arr[start] = arr[end];arr[end] = temp;}// 当start和end相等的时候,交换start索引的数字和baseNumber// 表示基准数在数组中的正确位置// 基准数归位:int temp = arr[left];arr[left] = arr[start];arr[start] = temp;// 确定6左边的范围,重复刚刚的动作quickSort(arr, left, start - 1);// 确定6右边的范围,重复刚刚的动作quickSort(arr, start + 1, right);}
}

🔍五、稳定性与原地性

特性结论说明
原地排序✅ 是仅使用 O(1) 额外空间(不计递归栈)
稳定性❌ 不稳定分区过程中相等元素可能被交换顺序

1.3 面经

📘 面试加分问题

Q1:插入排序和冒泡排序哪个更快?

A:通常插入排序更快。因为:

  • 插入排序内层是移动(赋值),冒泡是交换(三次赋值)
  • 插入排序对近有序数据更敏感,实际比较次数更少

Q2:插入排序能用于链表吗?

A:✅ 非常适合!
链表插入只需修改指针,无需移动元素,时间复杂度仍为 O(n²),但常数更小。

Q3:为什么插入排序是稳定的?

A:因为比较条件是 > 而非 >=,相等元素不会被移动,相对顺序不变。

✅ 结语

插入排序看似简单,却是理论与实践结合的典范

  • 教学上:帮助理解“构建有序序列”的思想
  • 工程上:被 Java、Python 等主流语言的排序库采用

🌟 记住
“简单不等于无用,插入排序是高性能排序的基石之一。”

   

Q1:快排最坏时间复杂度是什么?如何避免?

A:最坏 O(n²),发生在每次 pivot 都是最值(如已排序数组)。
避免方法:随机化 pivot、三数取中、使用双轴快排。

Q2:快排是稳定的吗?能改成稳定吗?

A:默认不稳定。理论上可通过额外空间记录原始索引实现稳定,但会失去“原地”优势,一般不这么做

Q3:为什么 Java 对基本类型用快排,对对象用归并/Timsort?

A:因为对象排序要求稳定性,而快排不稳定;基本类型无“相等顺序”概念,可用更快的快排。

Q4:快排和归并排序怎么选?

  • 要求稳定 or 内存充足 → 归并
  • 内存受限 or 追求平均速度 → 快排

      

1.4、四大排序算法核心特性对比表📊

原地排序(In-place Sorting) 是指:
排序过程中只使用常量级(O(1))的额外存储空间,即不需要创建新的数组或大量辅助结构,所有操作都在原数组内部通过交换或移动完成

❌ 非原地排序(Out-of-place):

  • 需要额外的数组或数据结构来辅助排序
  • 空间复杂度通常为 O(n)

💡 举个例子:

1// 原地:直接在 arr 上操作
2bubbleSort(arr); // 排序后 arr 本身被修改
3
4// 非原地(伪代码):
5int[] sorted = mergeSort(arr); // 返回一个新数组,原 arr 不变

关键判断标准
除了输入数组外,算法是否只用了几个变量(如 temp, i, j)?如果是,就是原地排

特性冒泡排序(Bubble Sort)选择排序(Selection Sort)插入排序(Insertion Sort)快速排序(Quick Sort)
是否原地✅ 是✅ 是✅ 是✅ 是
空间复杂度O(1)O(1)O(1)O(log n) ~ O(n) ⚠️
时间复杂度(最好)O(n)(优化后)O(n²)O(n)O(n log n)
时间复杂度(平均)O(n²)O(n²)O(n²)O(n log n)
时间复杂度(最坏)O(n²)O(n²)O(n²)O(n²)(可优化避免)
稳定性✅ 稳定❌ 不稳定✅ 稳定❌ 不稳定
交换/移动次数最多 O(n²) 次交换最多 n-1 次交换最多 O(n²) 次移动O(n log n) 次交换(平均)
适用场景教学、极小数据写操作昂贵的小数据小数据、近有序数据大数据、通用高性能排序
Java 标准库是否使用❌ 否❌ 否✅ 是(小数组优化)✅ 是(基本类型主算法)

Q1:为什么快排空间复杂度是 O(log n) 而不是 O(1)?

  • 快排使用递归,每次递归调用都会在系统栈(call stack) 中占用空间
  • 平衡情况下,递归深度为 log n → 空间 O(log n)
  • 最坏情况下(如已排序数组),递归深度为 n → 空间 O(n)

✅ 但业界仍认为快排是“原地排序”,因为:

  • 额外空间来自递归栈,而非显式分配的数组
  • 没有使用与输入规模成正比的额外堆内存(heap)
  • 对比归并排序(明确需要 O(n) 的临时数组),快排更节省内存

Q2、总结:“原地”意味着什么?

算法是否原地为什么?
冒泡排序仅用几个变量交换相邻元素
选择排序每轮只做一次交换,无需额外数组
插入排序元素右移腾位,不依赖外部空间
快速排序✅(广义)无额外数组,但递归栈占用 O(log n) 空间

声明:

题目详细分析借鉴于通义AI

以上均来源于B站@ITheima的教学内容!!!

本人跟着视频内容学习,整理知识引用

http://www.dtcms.com/a/581292.html

相关文章:

  • SQL50+Hot100系列(11.7)
  • Python 第二十六节 多线程应用详细介绍及使用注意事项
  • 网站建设交接表wordpress编程视频教程
  • LeafView(轻量级电脑图片查看器) v3.8.1 中文绿色便携版
  • MySQL死锁问题分析与解决方案
  • shell中获取达梦信息方法示例
  • calibre QRC提取寄生参数
  • 【Hot100 |5-LeetCode 11. 盛最多水的容器】
  • 【MicroPython编程-ESP32篇】-DH11温度湿度传感器驱动
  • 字节deer-flow项目模块详解
  • 【Python】Python并发与并行编程图解
  • 清城网站seodiscuz自适应模板
  • 优秀网页设计网站是wordpress php开发
  • 内部网关协议——OSPF 协议(开放最短路径优先)(链路状态路由协议)
  • rman-08137:warning:archived log not deleted
  • 专业的开发网站建设价格虚拟云电脑
  • [Linux——Lesson21.进程信号:信号概念 信号的产生]
  • 浙江英文网站建设嘉兴高档网站建设
  • ERP与WMS一体化构建方案
  • python+django/flask的眼科患者随访管理系统 AI智能模型
  • 实战案例:用 Guava ImmutableList 优化缓存查询系统,解决多线程数据篡改与内存浪费问题
  • AR短视频SDK,打造差异化竞争壁垒
  • 什么是AR人脸特效sdk?
  • Angular由一个bug说起之二十:Table lazy load:防止重复渲染
  • 从0到1做一个“字母拼词”Unity小游戏(含源码/GIF)- 字母拼词正确错误判断
  • 网站建设自查情况报告做淘宝联盟网站要多少钱?
  • 重新思考 weapp-tailwindcss 的未来
  • RuoYi .net-实现商城秒杀下单(redis,rabbitmq)
  • Langchain 和LangGraph 为何是AI智能体开发的核心技术
  • C++与C#布尔类型深度解析:从语言设计到跨平台互操作